test: enforce 90 percent SDK coverage - #308
Conversation
Add blocking per-component Codecov and local LCOV gates for React Native, Expo, Flutter, and IAPKit. Expand behavioral regression tests, fix Flutter purchase-error handler mapping, and remove unreachable internal header compatibility branches.
|
Warning Review limit reached
Next review available in: 29 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (9)
📝 WalkthroughWalkthroughThe PR adds 90% LCOV enforcement across CI workflows, expands Codecov configuration for IAP packages and the server, and adds regression coverage for Expo, React Native, Flutter, kit APIs, and IAPKit server behavior. ChangesCoverage and regression validation
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant CIWorkflow
participant CoverageTests
participant LCOVValidator
participant Codecov
CIWorkflow->>CoverageTests: run coverage-enabled tests
CoverageTests-->>CIWorkflow: write coverage/lcov.info
CIWorkflow->>LCOVValidator: assert 90% line coverage
LCOVValidator-->>CIWorkflow: pass or fail verification
CIWorkflow->>Codecov: upload coverage/lcov.info
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #308 +/- ##
===========================================
+ Coverage 72.14% 84.11% +11.97%
===========================================
Files 35 55 +20
Lines 5870 7317 +1447
Branches 1269 1701 +432
===========================================
+ Hits 4235 6155 +1920
+ Misses 1254 714 -540
- Partials 381 448 +67
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (6)
libraries/react-native-iap/src/__tests__/utils/errorMapping.test.ts (1)
172-182: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winThe
undefinedrow testsnull, not an undefined code.Line 181 converts the
undefinedrow intoisNetworkError(null). The row label reports an undefined error code, but the input never contains{code: undefined}. That branch stays uncovered.♻️ Suggested table change
- [undefined, false], - ])('classifies network error %s', (code, expected) => { - expect(isNetworkError(code ? {code} : null)).toBe(expected); - }); + [undefined, false], + ])('classifies network error %s', (code, expected) => { + expect(isNetworkError({code} as any)).toBe(expected); + }); + + test('treats a missing error object as a non-network error', () => { + expect(isNetworkError(null)).toBe(false); + });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/react-native-iap/src/__tests__/utils/errorMapping.test.ts` around lines 172 - 182, Update the isNetworkError parameterized test so the undefined case passes an error object with code: undefined instead of converting it to null. Preserve the existing expected false result and all other test cases.libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts (1)
573-580: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHoist the console spy setup.
The
jest.spyOn(console, ...)plustry/finallyrestore pattern repeats at lines 182-208, 256-265 and 573-580. Create the spies in abeforeEachand calljest.restoreAllMocks()in anafterEachfor this file. That removes the boilerplate and keeps each case focused on the assertion.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts` around lines 573 - 580, In the test file, move the repeated console.error spy setup into a beforeEach and clean up all mocks with jest.restoreAllMocks() in an afterEach. Remove the local spy and try/finally restoration blocks from the affected tests around validateNitroPurchase and the other repeated cases, keeping each test focused on its assertions.libraries/react-native-iap/src/__tests__/index.test.ts (2)
1647-1670: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAssert the native removal call.
The test name states that it removes listeners. Lines 1662-1663 call
first.remove()andsecond.remove(), but no assertion coversmockIap.removeUserChoiceBillingListenerAndroid. The removal path runs without verification, so a regression that skips the native removal still passes.💚 Suggested assertion
expect(mockIap.addUserChoiceBillingListenerAndroid).toHaveBeenCalledTimes( 1, ); + expect( + mockIap.removeUserChoiceBillingListenerAndroid, + ).toHaveBeenCalledTimes(1); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/react-native-iap/src/__tests__/index.test.ts` around lines 1647 - 1670, Extend the test around userChoiceBillingListenerAndroid so it asserts mockIap.removeUserChoiceBillingListenerAndroid is called after first.remove() and second.remove(). Keep the existing fan-out, callback isolation, and single native-add assertions unchanged.
3200-3263: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRestore shared mock implementations after the failure cases.
jest.clearAllMocks()does not restore methods reassigned onmockIap. These rejecting implementations persist into later tests. Restore each previous implementation or usejest.spyOn(...).mockRestore()inafterEach. The existingbeforeEachalready resetsPlatform.OS.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/react-native-iap/src/__tests__/index.test.ts` around lines 3200 - 3263, Restore the original mockIap method implementations after these failure tests, since jest.clearAllMocks() does not undo direct reassignments in the iosFailureCases, connection lifecycle, and Android failure cases. Add appropriate cleanup using saved implementations or jest.spyOn(...).mockRestore(), while preserving the existing Platform.OS reset in beforeEach.libraries/expo-iap/src/__tests__/index.kepler.test.ts (1)
113-126: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert listener removal and avoid hardcoded mock call indices.
Two points in this segment:
- Line 116 calls
Kepler.emitter.removeListener, but the test does not assert thatmodule.removeListenerreceived the event and callback. The removal path is executed without verification.- Lines 124-125 index
module.addListener.mock.calls[1]and[2]. These indices depend on the order of the precedingemitter.addListenercall. Ifemitter.addListenerstops delegating tomodule.addListener, the indices shift and the test can assert against the wrong handler.Capture the handlers from the return values of the specific registration calls instead.
♻️ Suggested hardening
Kepler.emitter.removeListener( Kepler.OpenIapEvent.PurchaseUpdated, listener, ); + expect(module.removeListener).toHaveBeenCalledWith( + Kepler.OpenIapEvent.PurchaseUpdated, + listener, + ); + module.addListener.mockClear(); const purchaseSub = Kepler.purchaseUpdatedListener(listener); const errorSub = Kepler.purchaseErrorListener(listener); expect(purchaseSub).toBe(subscription); expect(errorSub).toBe(subscription); - module.addListener.mock.calls[1][1]({productId: 'premium'}); - module.addListener.mock.calls[2][1]({code: 'network-error'}); + module.addListener.mock.calls[0][1]({productId: 'premium'}); + module.addListener.mock.calls[1][1]({code: 'network-error'}); expect(listener).toHaveBeenCalledTimes(2);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/expo-iap/src/__tests__/index.kepler.test.ts` around lines 113 - 126, Update the listener test around Kepler.emitter.removeListener, purchaseUpdatedListener, and purchaseErrorListener to assert module.removeListener received the PurchaseUpdated event and listener callback. Capture each registration’s handler from its specific module.addListener return or call result, then invoke those handlers directly instead of relying on hardcoded mock call indices, preserving the two listener invocation assertions.libraries/react-native-iap/src/__tests__/index.kepler.test.ts (1)
14-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRename the shared mock and reset its implementations between tests.
Two points:
- The name
moduleshadows the CommonJSmodulebinding inside thedescribescope. Rename it tovegaModuleto prevent confusion and accidental shadowing.jest.clearAllMocks()inbeforeEachclears calls, but it does not reset mock implementations. Line 164 usesfetchProductsNative.mockResolvedValue([...])instead ofmockResolvedValueOnce, so that resolved value persists for every test that runs after it. Any futurefetchProductstest added below line 190 will receive the subscription fixture. UsemockResolvedValueOncethere, or calljest.resetAllMocks()and re-apply the default implementations inbeforeEach.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/react-native-iap/src/__tests__/index.kepler.test.ts` around lines 14 - 32, Rename the shared mock object from module to vegaModule and update all references within the test setup. In the fetchProducts test around fetchProductsNative.mockResolvedValue, use mockResolvedValueOnce so the subscription fixture is consumed by only that test while preserving the existing beforeEach mock reset behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/kit/server/api/v1/webhooks.test.ts`:
- Around line 12-15: Update the test cleanup and missing-audience case around
afterEach and the test at lines 262–279 to preserve each test’s original
GOOGLE_PUBSUB_PUSH_AUDIENCE and KIT_ALLOW_UNAUTHENTICATED_PUBSUB values,
restoring them afterward rather than unconditionally deleting them. Explicitly
delete GOOGLE_PUBSUB_PUSH_AUDIENCE within the missing-audience test before
making the request.
In `@packages/kit/server/server.test.ts`:
- Around line 103-116: Reset the serverState.readFile mock at the start of the
“caches a missing SPA shell failure” test before configuring its rejection, then
update the final assertion to expect one call so the test remains independent of
prior tests.
In `@scripts/audit-non-godot-parity.mjs`:
- Line 869: Update the workflow validation around the assertion-command check to
locate both the lcov assertion step and the codecov/codecov-action@v7 upload
step, then require the assertion index to precede the upload index. Preserve the
existing assertion-presence validation while failing audits where coverage is
uploaded before the local threshold check.
---
Nitpick comments:
In `@libraries/expo-iap/src/__tests__/index.kepler.test.ts`:
- Around line 113-126: Update the listener test around
Kepler.emitter.removeListener, purchaseUpdatedListener, and
purchaseErrorListener to assert module.removeListener received the
PurchaseUpdated event and listener callback. Capture each registration’s handler
from its specific module.addListener return or call result, then invoke those
handlers directly instead of relying on hardcoded mock call indices, preserving
the two listener invocation assertions.
In `@libraries/react-native-iap/src/__tests__/index.kepler.test.ts`:
- Around line 14-32: Rename the shared mock object from module to vegaModule and
update all references within the test setup. In the fetchProducts test around
fetchProductsNative.mockResolvedValue, use mockResolvedValueOnce so the
subscription fixture is consumed by only that test while preserving the existing
beforeEach mock reset behavior.
In `@libraries/react-native-iap/src/__tests__/index.test.ts`:
- Around line 1647-1670: Extend the test around userChoiceBillingListenerAndroid
so it asserts mockIap.removeUserChoiceBillingListenerAndroid is called after
first.remove() and second.remove(). Keep the existing fan-out, callback
isolation, and single native-add assertions unchanged.
- Around line 3200-3263: Restore the original mockIap method implementations
after these failure tests, since jest.clearAllMocks() does not undo direct
reassignments in the iosFailureCases, connection lifecycle, and Android failure
cases. Add appropriate cleanup using saved implementations or
jest.spyOn(...).mockRestore(), while preserving the existing Platform.OS reset
in beforeEach.
In `@libraries/react-native-iap/src/__tests__/utils/errorMapping.test.ts`:
- Around line 172-182: Update the isNetworkError parameterized test so the
undefined case passes an error object with code: undefined instead of converting
it to null. Preserve the existing expected false result and all other test
cases.
In `@libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts`:
- Around line 573-580: In the test file, move the repeated console.error spy
setup into a beforeEach and clean up all mocks with jest.restoreAllMocks() in an
afterEach. Remove the local spy and try/finally restoration blocks from the
affected tests around validateNitroPurchase and the other repeated cases,
keeping each test focused on its assertions.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 10ce68c6-b29f-46b8-9c67-622647716aa1
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (27)
.github/workflows/ci-expo-iap.yml.github/workflows/ci-flutter-inapp-purchase.yml.github/workflows/ci-react-native-iap.yml.github/workflows/deploy-kit.ymlcodecov.ymllibraries/expo-iap/src/__tests__/index.kepler.test.tslibraries/expo-iap/src/__tests__/kit-api.test.tslibraries/expo-iap/src/kit-api.tslibraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dartlibraries/flutter_inapp_purchase/test/coverage_regression_test.dartlibraries/flutter_inapp_purchase/test/fetch_products_all_test.dartlibraries/flutter_inapp_purchase/test/helpers_coverage_test.dartlibraries/react-native-iap/src/__tests__/index.kepler.test.tslibraries/react-native-iap/src/__tests__/index.test.tslibraries/react-native-iap/src/__tests__/kit-api.test.tslibraries/react-native-iap/src/__tests__/utils/coverage-regression.test.tslibraries/react-native-iap/src/__tests__/utils/errorMapping.test.tslibraries/react-native-iap/src/__tests__/utils/type-bridge.test.tslibraries/react-native-iap/src/kit-api.tspackages/gql/src/kit-api.tspackages/kit/README.mdpackages/kit/package.jsonpackages/kit/server/api/v1/webhooks.test.tspackages/kit/server/server.test.tsscripts/assert-lcov-coverage.mjsscripts/assert-lcov-coverage.test.mjsscripts/audit-non-godot-parity.mjs
Summary
Verification
Preview: not applicable; this is a CI, test, and internal bridge correctness change.
Summary by CodeRabbit
Bug Fixes
Quality Improvements