diff --git a/DevCycle/DevCycleClient.swift b/DevCycle/DevCycleClient.swift index af3d9ee..c2fb8d2 100644 --- a/DevCycle/DevCycleClient.swift +++ b/DevCycle/DevCycleClient.swift @@ -423,13 +423,11 @@ public class DevCycleClient { } } - public func identifyUser(user: DevCycleUser, callback: IdentifyCompletedHandler? = nil) throws { - guard let currentUser = self.user, !currentUser.userId.isEmpty, - !user.userId.isEmpty - else { - throw ClientError.InvalidUser - } + private func _identifyUser( + user: DevCycleUser, currentUser: DevCycleUser, callback: IdentifyCompletedHandler? = nil + ) { self.flushEvents() + var updateUser: DevCycleUser = currentUser if currentUser.userId == user.userId { updateUser.update(with: user) @@ -481,22 +479,46 @@ public class DevCycleClient { }) } + @available(*, deprecated, message: "Use the non-throwing or async identifyUser method instead") + public func identifyUser(user: DevCycleUser, callback: IdentifyCompletedHandler? = nil) throws { + guard let currentUser = self.user, !currentUser.userId.isEmpty, + !user.userId.isEmpty + else { + throw ClientError.InvalidUser + } + + self._identifyUser(user: user, currentUser: currentUser, callback: callback) + } + + public func identifyUser(user: DevCycleUser, completion: IdentifyCompletedHandler? = nil) { + guard let currentUser = self.user, !currentUser.userId.isEmpty, + !user.userId.isEmpty + else { + completion?(ClientError.InvalidUser, nil) + return + } + + self._identifyUser(user: user, currentUser: currentUser, callback: completion) + } + @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public func identifyUser(user: DevCycleUser) async throws -> [String: Variable]? { + guard let currentUser = self.user, !currentUser.userId.isEmpty, + !user.userId.isEmpty + else { + throw ClientError.InvalidUser + } + return try await withCheckedThrowingContinuation { continuation in - do { - try self.identifyUser(user: user) { error, variables in - if let error = error { - continuation.resume(throwing: error) - } else { - continuation.resume(returning: variables) - } + self._identifyUser(user: user, currentUser: currentUser) { error, variables in + if let error = error { + Log.error( + "Error calling identifyUser for user_id \(String(describing: user.userId))", + tags: ["identify"]) + continuation.resume(throwing: error) + } else { + continuation.resume(returning: variables) } - } catch { - Log.error( - "Error calling identifyUser for user_id \(String(describing: user.userId))", - tags: ["identify"]) - continuation.resume(throwing: error) } } } diff --git a/DevCycleTests/Models/DevCycleClientTests.swift b/DevCycleTests/Models/DevCycleClientTests.swift index 8e77f09..1a50a17 100644 --- a/DevCycleTests/Models/DevCycleClientTests.swift +++ b/DevCycleTests/Models/DevCycleClientTests.swift @@ -431,13 +431,13 @@ class DevCycleClientTest: XCTestCase { XCTAssertEqual(service.numberOfConfigCalls, 2) let user2 = try! DevCycleUser.builder().userId("user2").build() - try! client.identifyUser(user: user2) + try! client.identifyUser(user: user2, callback: nil) XCTAssertEqual(client.lastIdentifiedUser?.userId, user2.userId) client.refetchConfig(sse: true, lastModified: 456, etag: "etag") XCTAssertEqual(service.numberOfConfigCalls, 4) let user3 = try! DevCycleUser.builder().userId("user3").build() - try! client.identifyUser(user: user3) + try! client.identifyUser(user: user3, callback: nil) XCTAssertEqual(client.lastIdentifiedUser?.userId, user3.userId) client.refetchConfig(sse: true, lastModified: 789, etag: "etag") XCTAssertEqual(service.numberOfConfigCalls, 6) @@ -698,7 +698,7 @@ class DevCycleClientTest: XCTestCase { do { let user = try DevCycleUser.builder().userId("user1").build() - try client.identifyUser(user: user) + try client.identifyUser(user: user, callback: nil) try client.resetUser() client.track( @@ -768,6 +768,89 @@ class DevCycleClientTest: XCTestCase { wait(for: [expectation], timeout: 100.0) client.close(callback: nil) } + + func testIdentifyUserWithInvalidUserCallsCallbackWithError() { + let expectation = XCTestExpectation( + description: "identifyUser with invalid user should call callback with error") + + let client = try! self.builder.user(self.user).sdkKey("my_sdk_key").build( + onInitialized: nil) + client.config?.userConfig = self.userConfig + + // Test with empty user ID + let emptyUser = DevCycleUser(userId: "", isAnonymous: false) + + client.identifyUser( + user: emptyUser, + callback: { error, variables in + XCTAssertNotNil(error, "identifyUser should return error for empty user ID") + XCTAssertNil(variables, "identifyUser should return nil variables for invalid user") + + if let clientError = error as? ClientError { + XCTAssertEqual( + clientError, ClientError.InvalidUser, "Error should be InvalidUser") + } else { + XCTFail("Error should be of type ClientError.InvalidUser") + } + + expectation.fulfill() + }) + + wait(for: [expectation], timeout: 1.0) + client.close(callback: nil) + } + + func testIdentifyUserWithCallbackSuccess() { + let expectation = XCTestExpectation( + description: "identifyUser with callback should succeed") + + let client = try! self.builder.user(self.user).sdkKey("my_sdk_key").build( + onInitialized: nil) + client.config?.userConfig = self.userConfig + + let newUser = try! DevCycleUser.builder().userId("new_user").build() + + client.identifyUser( + user: newUser, + callback: { error, variables in + XCTAssertNil(error, "identifyUser should not return error for valid user") + // Variables can be nil or non-nil depending on the config + // The important thing is that no error occurred + expectation.fulfill() + }) + + wait(for: [expectation], timeout: 1.0) + client.close(callback: nil) + } + + @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) + func testAsyncIdentifyUserWithInvalidUserCallsCallbackWithError() async throws { + let expectation = XCTestExpectation( + description: "identifyUser with invalid user should call callback with error") + + let client = try! self.builder.user(self.user).sdkKey("my_sdk_key").build( + onInitialized: nil) + client.config?.userConfig = self.userConfig + + // Test with empty user ID + let emptyUser = DevCycleUser(userId: "", isAnonymous: false) + + do { + let _ = try await client.identifyUser(user: emptyUser) + XCTFail("identifyUser should throw error for empty user ID") + } catch { + XCTAssertNotNil(error, "identifyUser should return error for empty user ID") + if let clientError = error as? ClientError { + XCTAssertEqual( + clientError, ClientError.InvalidUser, "Error should be InvalidUser") + } else { + XCTFail("Error should be of type ClientError.InvalidUser") + } + + expectation.fulfill() + client.close(callback: nil) + } + } } extension DevCycleClientTest {