diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index ab7b61cf..5e81312d 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -37,7 +37,7 @@ jobs: - uses: actions/checkout@v3 - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: 12.4 + xcode-version: 14.1.0 - env: SRCCLR_API_TOKEN: ${{ secrets.SRCCLR_API_TOKEN }} run: | @@ -56,7 +56,7 @@ jobs: - uses: actions/checkout@v3 - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: 12.4 + xcode-version: 14.1.0 - id: prepare_for_release name: Prepare for release env: @@ -79,7 +79,7 @@ jobs: - uses: actions/checkout@v3 - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: 12.4 + xcode-version: 14.1.0 - name: Push to cocoapods.org env: GITHUB_TOKEN: ${{ secrets.CI_USER_TOKEN }} diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 222e0da1..ef4d27a2 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -50,7 +50,7 @@ jobs: - uses: actions/checkout@v3 - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: 12.4 + xcode-version: 14.1.0 - name: set SDK Branch if PR if: ${{ github.event_name == 'pull_request' }} run: | diff --git a/Sources/ODP/OdpEventManager.swift b/Sources/ODP/OdpEventManager.swift index 57d98994..3656c426 100644 --- a/Sources/ODP/OdpEventManager.swift +++ b/Sources/ODP/OdpEventManager.swift @@ -51,13 +51,15 @@ class OdpEventManager { data: [:]) } - func identifyUser(vuid: String, userId: String) { + func identifyUser(vuid: String, userId: String?) { + var identifiers = [Constants.ODP.keyForVuid: vuid] + if let userId = userId { + identifiers[Constants.ODP.keyForUserId] = userId + } + sendEvent(type: Constants.ODP.eventType, action: "identified", - identifiers: [ - Constants.ODP.keyForVuid: vuid, - Constants.ODP.keyForUserId: userId - ], + identifiers: identifiers, data: [:]) } diff --git a/Sources/ODP/OdpManager.swift b/Sources/ODP/OdpManager.swift index 28db371a..54efec95 100644 --- a/Sources/ODP/OdpManager.swift +++ b/Sources/ODP/OdpManager.swift @@ -74,7 +74,7 @@ class OdpManager { return } - let userKey = vuidManager.isVuid(visitorId: userId) ? Constants.ODP.keyForVuid : Constants.ODP.keyForUserId + let userKey = OdpVuidManager.isVuid(userId) ? Constants.ODP.keyForVuid : Constants.ODP.keyForUserId let userValue = userId segmentManager.fetchQualifiedSegments(userKey: userKey, @@ -94,7 +94,15 @@ class OdpManager { return } - eventManager.identifyUser(vuid: vuidManager.vuid, userId: userId) + var vuid = vuidManager.vuid + var fsUserId: String? = userId + if OdpVuidManager.isVuid(userId) { + // overwrite if userId is vuid (when userContext is created with vuid) + vuid = userId + fsUserId = nil + } + + eventManager.identifyUser(vuid: vuid, userId: fsUserId) } /// Send an event to the ODP server. diff --git a/Sources/ODP/OdpVuidManager.swift b/Sources/ODP/OdpVuidManager.swift index 6c882041..f1112148 100644 --- a/Sources/ODP/OdpVuidManager.swift +++ b/Sources/ODP/OdpVuidManager.swift @@ -27,7 +27,7 @@ class OdpVuidManager { self.vuid = load() } - func makeVuid() -> String { + static var newVuid: String { let maxLength = 32 // required by ODP server // make sure UUIDv4 is used (not UUIDv1 or UUIDv6) since the trailing 5 chars will be truncated. See TDD for details. @@ -36,9 +36,10 @@ class OdpVuidManager { return vuid } - func isVuid(visitorId: String) -> Bool { + static func isVuid(_ visitorId: String) -> Bool { return visitorId.starts(with: "vuid_") } + } // MARK: - VUID Store @@ -54,7 +55,7 @@ extension OdpVuidManager { return oldVuid } - let vuid = makeVuid() + let vuid = OdpVuidManager.newVuid save(vuid: vuid) return vuid } diff --git a/Tests/OptimizelyTests-Common/OdpEventManagerTests.swift b/Tests/OptimizelyTests-Common/OdpEventManagerTests.swift index 1776945d..d9511fab 100644 --- a/Tests/OptimizelyTests-Common/OdpEventManagerTests.swift +++ b/Tests/OptimizelyTests-Common/OdpEventManagerTests.swift @@ -110,6 +110,17 @@ class OdpEventManagerTests: XCTestCase { validateData(evt.data, customData: [:]) } + func testIdentifyUser_noApiKey_nilUserId() { + manager.identifyUser(vuid: "v1", userId: nil) + + XCTAssertEqual(1, manager.eventQueue.count) + let evt = manager.eventQueue.getFirstItem()! + XCTAssertEqual("fullstack", evt.type) + XCTAssertEqual("identified", evt.action) + XCTAssertEqual(["vuid": "v1"], evt.identifiers) + validateData(evt.data, customData: [:]) + } + func testSendEvent_apiKey() { odpConfig = OdpConfig() _ = odpConfig.update(apiKey: "valid", apiHost: "host", segmentsToCheck: []) diff --git a/Tests/OptimizelyTests-Common/OdpManagerTests.swift b/Tests/OptimizelyTests-Common/OdpManagerTests.swift index 1d74e7fe..5eb16aac 100644 --- a/Tests/OptimizelyTests-Common/OdpManagerTests.swift +++ b/Tests/OptimizelyTests-Common/OdpManagerTests.swift @@ -103,7 +103,7 @@ class OdpManagerTests: XCTestCase { // MARK: - registerVuid func testRegisterVUIDCalledAutomatically() { - XCTAssertEqual(eventManager.receivedVuid, manager.vuid, "registerVUID is implicitly called on OdpManager init") + XCTAssertEqual(eventManager.receivedRegisterVuid, manager.vuid, "registerVUID is implicitly called on OdpManager init") } func testRegisterVUIDCalledAutomatically_odpDisabled() { @@ -116,7 +116,7 @@ class OdpManagerTests: XCTestCase { segmentManager: segmentManager, eventManager: newEventManager) - XCTAssertNil(newEventManager.receivedVuid, "registerVUID should not implicitly called when ODP disabled") + XCTAssertNil(newEventManager.receivedRegisterVuid, "registerVUID should not implicitly called when ODP disabled") } // MARK: - identifyUser @@ -124,28 +124,39 @@ class OdpManagerTests: XCTestCase { func testIdentifyUser_datafileNotReady() { manager.identifyUser(userId: "user-1") - XCTAssertEqual(eventManager.receivedUserId, "user-1") + XCTAssertEqual(eventManager.receivedIdentifyUserId, "user-1") } func testIdentifyUser_odpIntegrated() { manager.updateOdpConfig(apiKey: "key-1", apiHost: "host-1", segmentsToCheck: []) manager.identifyUser(userId: "user-1") - XCTAssertEqual(eventManager.receivedUserId, "user-1") + XCTAssert(OdpVuidManager.isVuid(eventManager.receivedIdentifyVuid)) + XCTAssertEqual(eventManager.receivedIdentifyUserId, "user-1") + } + + func testIdentifyUser_odpIntegrated_vuidAsUserId() { + manager.updateOdpConfig(apiKey: "key-1", apiHost: "host-1", segmentsToCheck: []) + + let vuidAsUserId = OdpVuidManager.newVuid + manager.identifyUser(userId: vuidAsUserId) + + XCTAssertEqual(eventManager.receivedIdentifyVuid, vuidAsUserId) + XCTAssertNil(eventManager.receivedIdentifyUserId) } func testIdentifyUser_odpNotIntegrated() { manager.updateOdpConfig(apiKey: nil, apiHost: nil, segmentsToCheck: []) manager.identifyUser(userId: "user-1") - XCTAssertNil(eventManager.receivedUserId, "identifyUser event requeut should be discarded if ODP not integrated.") + XCTAssertNil(eventManager.receivedIdentifyUserId, "identifyUser event requeut should be discarded if ODP not integrated.") } func testIdentifyUser_odpDisabled() { manager.enabled = false manager.identifyUser(userId: "user-1") - XCTAssertNil(eventManager.receivedUserId, "identifyUser event requeut should be discarded if ODP disabled.") + XCTAssertNil(eventManager.receivedIdentifyUserId, "identifyUser event requeut should be discarded if ODP disabled.") } // MARK: - sendEvent @@ -315,9 +326,11 @@ class OdpManagerTests: XCTestCase { // MARK: - Helpers class MockOdpEventManager: OdpEventManager { - var receivedVuid: String! - var receivedUserId: String! + var receivedRegisterVuid: String! + var receivedIdentifyVuid: String! + var receivedIdentifyUserId: String? + var receivedType: String! var receivedAction: String! var receivedIdentifiers: [String: String]! @@ -328,12 +341,12 @@ class OdpManagerTests: XCTestCase { var resetCalled = false override func registerVUID(vuid: String) { - self.receivedVuid = vuid + self.receivedRegisterVuid = vuid } - override func identifyUser(vuid: String, userId: String) { - self.receivedVuid = vuid - self.receivedUserId = userId + override func identifyUser(vuid: String, userId: String?) { + self.receivedIdentifyVuid = vuid + self.receivedIdentifyUserId = userId } override func sendEvent(type: String, action: String, identifiers: [String: String], data: [String: Any?]) { diff --git a/Tests/OptimizelyTests-Common/OdpVuidManagerTests.swift b/Tests/OptimizelyTests-Common/OdpVuidManagerTests.swift index ddf52e84..261c0544 100644 --- a/Tests/OptimizelyTests-Common/OdpVuidManagerTests.swift +++ b/Tests/OptimizelyTests-Common/OdpVuidManagerTests.swift @@ -19,17 +19,17 @@ import XCTest class OdpVuidManagerTests: XCTestCase { var manager = OdpVuidManager() - func testMakeVuid() { - let vuid = manager.makeVuid() + func testNewVuid() { + let vuid = OdpVuidManager.newVuid XCTAssertTrue(vuid.starts(with: "vuid_")) XCTAssertEqual(vuid.count, 32) } func testIsVuid() { - XCTAssertTrue(manager.isVuid(visitorId: "vuid_123")) - XCTAssertFalse(manager.isVuid(visitorId: "vuid-123")) - XCTAssertFalse(manager.isVuid(visitorId: "123")) + XCTAssertTrue(OdpVuidManager.isVuid("vuid_123")) + XCTAssertFalse(OdpVuidManager.isVuid("vuid-123")) + XCTAssertFalse(OdpVuidManager.isVuid("123")) } func testAutoSaveAndLoad() { @@ -42,8 +42,8 @@ class OdpVuidManagerTests: XCTestCase { let vuid2 = manager.vuid XCTAssertTrue(vuid1 == vuid2) - XCTAssert(manager.isVuid(visitorId: vuid1)) - XCTAssert(manager.isVuid(visitorId: vuid2)) + XCTAssert(OdpVuidManager.isVuid(vuid1)) + XCTAssert(OdpVuidManager.isVuid(vuid2)) UserDefaults.standard.removeObject(forKey: "optimizely-vuid")