-
Notifications
You must be signed in to change notification settings - Fork 54
fix: audience occurrence interval limits bypassed under memory pressure #455
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -650,6 +650,77 @@ class CoreDataManagerTests: XCTestCase { | |
| XCTAssertEqual(userEventCount, 0, "paywall_decline should be deleted after deleteAllEntities") | ||
| } | ||
|
|
||
| // MARK: - Save and Count on Same Context | ||
|
|
||
| func test_saveAndCountAudienceOccurrence_sameContext() async { | ||
| let key = "same_context_test" | ||
| let maxCount = 1 | ||
| let interval: TriggerAudienceOccurrence.Interval = .minutes(60) | ||
| let occurrence = TriggerAudienceOccurrence( | ||
| key: key, | ||
| maxCount: maxCount, | ||
| interval: interval | ||
| ) | ||
|
|
||
| // Save via CoreDataManager (uses its own backgroundContext) | ||
| let saveExpectation = expectation(description: "Saved occurrence") | ||
| coreDataManager.save(triggerAudienceOccurrence: occurrence) { _ in | ||
| saveExpectation.fulfill() | ||
| } | ||
| await fulfillment(of: [saveExpectation], timeout: 2.0) | ||
|
|
||
| // Count should find the saved occurrence on the same context | ||
| let count = await coreDataManager.countAudienceOccurrences(for: occurrence) | ||
| XCTAssertEqual(count, 1, "Count should see the occurrence saved on the same context") | ||
| } | ||
|
|
||
| func test_saveAndCountAudienceOccurrence_infinity() async { | ||
| let key = "infinity_context_test" | ||
| let maxCount = 1 | ||
| let interval: TriggerAudienceOccurrence.Interval = .infinity | ||
| let occurrence = TriggerAudienceOccurrence( | ||
| key: key, | ||
| maxCount: maxCount, | ||
| interval: interval | ||
| ) | ||
|
|
||
| // Save via CoreDataManager | ||
| let saveExpectation = expectation(description: "Saved occurrence") | ||
| coreDataManager.save(triggerAudienceOccurrence: occurrence) { _ in | ||
| saveExpectation.fulfill() | ||
| } | ||
| await fulfillment(of: [saveExpectation], timeout: 2.0) | ||
|
|
||
| // Count should find it immediately | ||
| let count = await coreDataManager.countAudienceOccurrences(for: occurrence) | ||
| XCTAssertEqual(count, 1, "Count should see the occurrence saved on the same context") | ||
| } | ||
|
|
||
| func test_saveMultipleAndCountAudienceOccurrences() async { | ||
| let key = "multi_save_test" | ||
| let maxCount = 5 | ||
| let interval: TriggerAudienceOccurrence.Interval = .minutes(60) | ||
| let occurrence = TriggerAudienceOccurrence( | ||
| key: key, | ||
| maxCount: maxCount, | ||
| interval: interval | ||
| ) | ||
|
|
||
| // Save 3 occurrences via CoreDataManager | ||
| let saveExpectation = expectation(description: "Saved occurrences") | ||
| saveExpectation.expectedFulfillmentCount = 3 | ||
| for _ in 0..<3 { | ||
| coreDataManager.save(triggerAudienceOccurrence: occurrence) { _ in | ||
| saveExpectation.fulfill() | ||
| } | ||
| } | ||
| await fulfillment(of: [saveExpectation], timeout: 2.0) | ||
|
|
||
| // Count should find all 3 | ||
| let count = await coreDataManager.countAudienceOccurrences(for: occurrence) | ||
| XCTAssertEqual(count, 3, "Count should see all occurrences saved on the same context") | ||
| } | ||
|
Comment on lines
+699
to
+722
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In The test only validates the "below limit" case, so it doesn't exercise the cap behaviour. Consider adding a complementary case where Prompt To Fix With AIThis is a comment left during a code review.
Path: Tests/SuperwallKitTests/Storage/Core Data/CoreDataManagerTests.swift
Line: 699-722
Comment:
**`maxCount` capped by `fetchLimit` hides the real count**
In `countAudienceOccurrences`, `fetchRequest.fetchLimit` is set to `audienceOccurrence.maxCount` (which is `5` in this test), and `NSManagedObjectContext.count(for:)` respects `fetchLimit`. This means the test with `maxCount = 5` and 3 saved records will return `3` correctly, but if you had saved 6 records, the count would be capped at `5` (the fetchLimit), not `6`.
The test only validates the "below limit" case, so it doesn't exercise the cap behaviour. Consider adding a complementary case where `maxCount < numberOfSaves` to verify the limit is properly enforced (e.g., save 5 occurrences with `maxCount = 3` and assert `count == 3`).
How can I resolve this? If you propose a fix, please make it concise. |
||
|
|
||
| func test_countPlacement_concurrent_access() async { | ||
|
|
||
| let placementName = "concurrent_test" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CLAUDE.mdexplicitly states:All three new tests (
test_saveAndCountAudienceOccurrence_sameContext,test_saveAndCountAudienceOccurrence_infinity,test_saveMultipleAndCountAudienceOccurrences) useXCTestCaseAPIs (expectation(description:),await fulfillment(of:),XCTAssertEqual). They should be ported to Swift Testing (@Test,#expect,withKnownIssue, etc.) to stay consistent with the project's testing conventions.Note: This applies even though the surrounding file already uses XCTest — per the guide, new tests should use Swift Testing.
Rule Used: CLAUDE.md (source)
Prompt To Fix With AI
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!