Skip to content

Commit d9b1cde

Browse files
nan-licursoragent
andcommitted
test: cover tag merge and restoration after a concurrent FetchUser
Add a unit test for mergeConfirmedTags and a regression test asserting tags cleared by a concurrent fetch are restored once the update is confirmed. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent c6f63c1 commit d9b1cde

1 file changed

Lines changed: 59 additions & 0 deletions

File tree

iOS_SDK/OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,63 @@ final class OneSignalUserTests: XCTestCase {
189189
contains: expectedPayload)
190190
)
191191
}
192+
193+
/**
194+
Unit test for the tag-merge primitive.
195+
196+
`mergeConfirmedTags` must overlay only the provided keys onto the local model: update existing
197+
values, remove keys whose value is `""`, add new keys, and leave all other (e.g. backend-managed)
198+
tags untouched. It must never wholesale-replace the tag dictionary.
199+
*/
200+
func testMergeConfirmedTags_mergesAndRemovesWithoutTouchingOtherTags() throws {
201+
/* Setup */
202+
let model = OSPropertiesModel(changeNotifier: OSEventProducer())
203+
model.hydrate(["tags": ["keep": "1", "update": "old", "remove": "2"]])
204+
205+
/* When */
206+
// "update" changes, "remove" is deleted (""), "add" is new; "keep" must be left untouched
207+
model.mergeConfirmedTags(["update": "new", "remove": "", "add": "3"])
208+
209+
/* Then */
210+
XCTAssertEqual(model.tags, ["keep": "1", "update": "new", "add": "3"])
211+
}
212+
213+
/**
214+
Regression test: `getTags()` returns `{}` after a concurrent, stale `FetchUser` overwrites the
215+
local tag model.
216+
217+
A concurrent `FetchUser` whose response is missing a recently written tag clears the local tag
218+
cache (`clearUserData()`) and hydrates without it. The `OSRequestUpdateProperties` 202 response
219+
echoes the tags the server just confirmed, so merging those back into the local model must restore
220+
the tags that the stale fetch cleared.
221+
*/
222+
func testUpdatePropertiesResponse_restoresTagsClearedByConcurrentFetch() throws {
223+
/* Setup */
224+
let client = MockOneSignalClient()
225+
MockUserRequests.setDefaultCreateAnonUserResponses(with: client)
226+
let tags = ["tag_a": "value_a", "tag_b": "value_b"]
227+
MockUserRequests.setAddTagsResponse(with: client, tags: tags)
228+
OneSignalCoreImpl.setSharedClient(client)
229+
230+
OneSignalUserManagerImpl.sharedInstance.start()
231+
232+
// Let the anonymous user be created so it has a OneSignal ID for the update request
233+
OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5)
234+
235+
/* When */
236+
// Tags are applied optimistically to the local model and queued as an update request
237+
OneSignalUserManagerImpl.sharedInstance.addTags(tags)
238+
239+
// Simulate a concurrent, stale FetchUser clearing the local tag cache before the
240+
// UpdateProperties 202 response is processed
241+
OneSignalUserManagerImpl.sharedInstance.user.propertiesModel.clearData()
242+
XCTAssertTrue(OneSignalUserManagerImpl.sharedInstance.getTags().isEmpty)
243+
244+
// Let the queued UpdateProperties request flush and its 202 echo be processed
245+
OneSignalCoreMocks.waitForBackgroundThreads(seconds: 1)
246+
247+
/* Then */
248+
// The confirmed tags from the 202 response are merged back into the local model
249+
XCTAssertEqual(OneSignalUserManagerImpl.sharedInstance.getTags(), tags)
250+
}
192251
}

0 commit comments

Comments
 (0)