diff --git a/swift-sdk/Constants.swift b/swift-sdk/Constants.swift index f71ae9484..d621fa77c 100644 --- a/swift-sdk/Constants.swift +++ b/swift-sdk/Constants.swift @@ -96,6 +96,7 @@ enum JsonKey { static let currentEmail = "currentEmail" static let currentUserId = "currentUserId" static let newEmail = "newEmail" + static let merge = "merge" static let emailListIds = "emailListIds" static let unsubscribedChannelIds = "unsubscribedChannelIds" static let unsubscribedMessageTypeIds = "unsubscribedMessageTypeIds" diff --git a/swift-sdk/Internal/ApiClient.swift b/swift-sdk/Internal/ApiClient.swift index 104aac9bb..85cec8707 100644 --- a/swift-sdk/Internal/ApiClient.swift +++ b/swift-sdk/Internal/ApiClient.swift @@ -108,8 +108,8 @@ extension ApiClient: ApiClientProtocol { return send(iterableRequestResult: result) } - func updateEmail(newEmail: String) -> Pending { - let result = createRequestCreator().flatMap { $0.createUpdateEmailRequest(newEmail: newEmail) } + func updateEmail(newEmail: String, merge: Bool?) -> Pending { + let result = createRequestCreator().flatMap { $0.createUpdateEmailRequest(newEmail: newEmail, merge: merge) } return send(iterableRequestResult: result) } diff --git a/swift-sdk/Internal/ApiClientProtocol.swift b/swift-sdk/Internal/ApiClientProtocol.swift index 86a57812e..7149d7df0 100644 --- a/swift-sdk/Internal/ApiClientProtocol.swift +++ b/swift-sdk/Internal/ApiClientProtocol.swift @@ -9,7 +9,7 @@ protocol ApiClientProtocol: AnyObject { func updateUser(_ dataFields: [AnyHashable: Any], mergeNestedObjects: Bool) -> Pending - func updateEmail(newEmail: String) -> Pending + func updateEmail(newEmail: String, merge: Bool?) -> Pending func updateCart(items: [CommerceItem]) -> Pending diff --git a/swift-sdk/Internal/InternalIterableAPI.swift b/swift-sdk/Internal/InternalIterableAPI.swift index 181db8054..1aeb56273 100644 --- a/swift-sdk/Internal/InternalIterableAPI.swift +++ b/swift-sdk/Internal/InternalIterableAPI.swift @@ -241,10 +241,12 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider { @discardableResult func updateEmail(_ newEmail: String, + merge: Bool? = nil, withToken token: String? = nil, onSuccess: OnSuccessHandler? = nil, onFailure: OnFailureHandler? = nil) -> Pending { requestHandler.updateEmail(newEmail, + merge: merge, onSuccess: nil, onFailure: nil).onSuccess { json in if self.email != nil { diff --git a/swift-sdk/Internal/OnlineRequestProcessor.swift b/swift-sdk/Internal/OnlineRequestProcessor.swift index 0a51524d5..f98ccd66d 100644 --- a/swift-sdk/Internal/OnlineRequestProcessor.swift +++ b/swift-sdk/Internal/OnlineRequestProcessor.swift @@ -67,9 +67,10 @@ struct OnlineRequestProcessor: RequestProcessorProtocol { @discardableResult func updateEmail(_ newEmail: String, + merge: Bool? = nil, onSuccess: OnSuccessHandler? = nil, onFailure: OnFailureHandler? = nil) -> Pending { - sendRequest(requestProvider: { apiClient.updateEmail(newEmail: newEmail) }, + sendRequest(requestProvider: { apiClient.updateEmail(newEmail: newEmail, merge: merge) }, successHandler: onSuccess, failureHandler: onFailure, requestIdentifier: "updateEmail") diff --git a/swift-sdk/Internal/RequestCreator.swift b/swift-sdk/Internal/RequestCreator.swift index c80f4a6fc..b4a639c15 100644 --- a/swift-sdk/Internal/RequestCreator.swift +++ b/swift-sdk/Internal/RequestCreator.swift @@ -14,7 +14,7 @@ struct RequestCreator { // MARK: - API REQUEST CALLS - func createUpdateEmailRequest(newEmail: String) -> Result { + func createUpdateEmailRequest(newEmail: String, merge: Bool?) -> Result { if case .none = auth.emailOrUserId { ITBError(Self.authMissingMessage) return .failure(IterableError.general(description: Self.authMissingMessage)) @@ -27,7 +27,9 @@ struct RequestCreator { } else if let userId = auth.userId { body[JsonKey.currentUserId] = userId } - + if let accountMerge = merge { + body[JsonKey.merge] = accountMerge + } body[JsonKey.newEmail] = newEmail return .success(.post(createPostRequest(path: Const.Path.updateEmail, body: body))) diff --git a/swift-sdk/Internal/RequestHandler.swift b/swift-sdk/Internal/RequestHandler.swift index b56a2f18d..5828e029d 100644 --- a/swift-sdk/Internal/RequestHandler.swift +++ b/swift-sdk/Internal/RequestHandler.swift @@ -78,9 +78,11 @@ class RequestHandler: RequestHandlerProtocol { @discardableResult func updateEmail(_ newEmail: String, + merge: Bool?, onSuccess: OnSuccessHandler?, onFailure: OnFailureHandler?) -> Pending { onlineProcessor.updateEmail(newEmail, + merge: merge, onSuccess: onSuccess, onFailure: onFailure) } diff --git a/swift-sdk/Internal/RequestHandlerProtocol.swift b/swift-sdk/Internal/RequestHandlerProtocol.swift index a3b370833..6967a3e24 100644 --- a/swift-sdk/Internal/RequestHandlerProtocol.swift +++ b/swift-sdk/Internal/RequestHandlerProtocol.swift @@ -35,6 +35,7 @@ protocol RequestHandlerProtocol: AnyObject { @discardableResult func updateEmail(_ newEmail: String, + merge: Bool?, onSuccess: OnSuccessHandler?, onFailure: OnFailureHandler?) -> Pending diff --git a/swift-sdk/IterableAPI.swift b/swift-sdk/IterableAPI.swift index 9c57fd4b3..9e50a9069 100644 --- a/swift-sdk/IterableAPI.swift +++ b/swift-sdk/IterableAPI.swift @@ -316,7 +316,7 @@ import UIKit /// - SeeAlso: OnSuccessHandler, OnFailureHandler @objc(updateEmail:onSuccess:onFailure:) public static func updateEmail(_ newEmail: String, onSuccess: OnSuccessHandler?, onFailure: OnFailureHandler?) { - implementation?.updateEmail(newEmail, onSuccess: onSuccess, onFailure: onFailure) + implementation?.updateEmail(newEmail, merge: nil, onSuccess: onSuccess, onFailure: onFailure) } /// Updates the current user's email, and set the new authentication token @@ -335,9 +335,46 @@ import UIKit withToken token: String, onSuccess: OnSuccessHandler?, onFailure: OnFailureHandler?) { - implementation?.updateEmail(newEmail, withToken: token, onSuccess: onSuccess, onFailure: onFailure) + implementation?.updateEmail(newEmail, merge: nil, withToken: token, onSuccess: onSuccess, onFailure: onFailure) } - + + /// Updates the current user's email, allowing for account merging + /// + /// - Parameters: + /// - newEmail: The new email address + /// - merge: whether or not to merge the current account into the new account + /// - onSuccess: `OnSuccessHandler` to invoke if update is successful + /// - onFailure: `OnFailureHandler` to invoke if update fails + /// + /// - Remark: Also updates the current email in this IterableAPIImplementation instance if the API call was successful. + /// + /// - SeeAlso: OnSuccessHandler, OnFailureHandler + @objc(updateEmail:merge:onSuccess:onFailure:) + public static func updateEmail(_ newEmail: String, merge: Bool, onSuccess: OnSuccessHandler?, onFailure: OnFailureHandler?) { + implementation?.updateEmail(newEmail, merge: merge, onSuccess: onSuccess, onFailure: onFailure) + } + + /// Updates the current user's email, and set the new authentication token + /// + /// - Parameters: + /// - newEmail: The new email of this user + /// - merge: whether or not to merge the current account into the new account + /// - token: The new authentication token for this user, if left out, the SDK will not update the token in any way + /// - onSuccess: `OnSuccessHandler` to invoke if update is successful + /// - onFailure: `OnFailureHandler` to invoke if update fails + /// + /// - Remark: Also updates the current email in this internal instance if the API call was successful. + /// + /// - SeeAlso: OnSuccessHandler, OnFailureHandler + @objc(updateEmail:merge:withToken:onSuccess:onFailure:) + public static func updateEmail(_ newEmail: String, + merge: Bool, + withToken token: String, + onSuccess: OnSuccessHandler?, + onFailure: OnFailureHandler?) { + implementation?.updateEmail(newEmail, merge: merge, withToken: token, onSuccess: onSuccess, onFailure: onFailure) + } + /// Tracks what's in the shopping cart (or equivalent) at this point in time /// /// - Parameters: diff --git a/tests/offline-events-tests/RequestHandlerTests.swift b/tests/offline-events-tests/RequestHandlerTests.swift index f9b3637cc..f2428bed3 100644 --- a/tests/offline-events-tests/RequestHandlerTests.swift +++ b/tests/offline-events-tests/RequestHandlerTests.swift @@ -156,6 +156,7 @@ class RequestHandlerTests: XCTestCase { let requestGenerator = { (requestHandler: RequestHandlerProtocol) in requestHandler.updateEmail("new_user@example.com", + merge: nil, onSuccess: expectations.onSuccess, onFailure: expectations.onFailure) } @@ -166,7 +167,30 @@ class RequestHandlerTests: XCTestCase { wait(for: [expectations.successExpectation, expectations.failureExpectation], timeout: testExpectationTimeout) } - + + func testUpdateEmailWithMerge() throws { + let bodyDict: [String: Any] = [ + "currentEmail": "user@example.com", + "newEmail": "new_user@example.com", + "merge": true + ] + + let expectations = createExpectations(description: #function) + + let requestGenerator = { (requestHandler: RequestHandlerProtocol) in + requestHandler.updateEmail("new_user@example.com", + merge: true, + onSuccess: expectations.onSuccess, + onFailure: expectations.onFailure) + } + + try handleRequestWithSuccessAndFailure(requestGenerator: requestGenerator, + path: Const.Path.updateEmail, + bodyDict: bodyDict) + + wait(for: [expectations.successExpectation, expectations.failureExpectation], timeout: testExpectationTimeout) + } + func testTrackPurchase() throws { let total = NSNumber(value: 15.32) let items = [CommerceItem(id: "id1", name: "myCommerceItem", price: 5.1, quantity: 2)] diff --git a/tests/unit-tests/AuthTests.swift b/tests/unit-tests/AuthTests.swift index eac7136ca..d48b36bf6 100644 --- a/tests/unit-tests/AuthTests.swift +++ b/tests/unit-tests/AuthTests.swift @@ -202,6 +202,7 @@ class AuthTests: XCTestCase { XCTAssertEqual(API.auth.authToken, originalToken) API.updateEmail(updatedEmail, + merge: true, onSuccess: { data in XCTAssertEqual(API.email, updatedEmail) XCTAssertNil(API.userId) @@ -244,7 +245,7 @@ class AuthTests: XCTestCase { XCTAssertNil(API.userId) XCTAssertEqual(API.auth.authToken, originalToken) - API.updateEmail(updatedEmail, withToken: updatedToken) { data in + API.updateEmail(updatedEmail, merge: true, withToken: updatedToken) { data in XCTAssertEqual(API.email, updatedEmail) XCTAssertNil(API.userId) XCTAssertEqual(API.auth.authToken, updatedToken) diff --git a/tests/unit-tests/BlankApiClient.swift b/tests/unit-tests/BlankApiClient.swift index 43571ae10..4961827d8 100644 --- a/tests/unit-tests/BlankApiClient.swift +++ b/tests/unit-tests/BlankApiClient.swift @@ -15,7 +15,7 @@ class BlankApiClient: ApiClientProtocol { Pending() } - func updateEmail(newEmail: String) -> Pending { + func updateEmail(newEmail: String, merge: Bool?) -> Pending { Pending() } diff --git a/tests/unit-tests/IterableAPITests.swift b/tests/unit-tests/IterableAPITests.swift index 02177a04b..2741a11ce 100644 --- a/tests/unit-tests/IterableAPITests.swift +++ b/tests/unit-tests/IterableAPITests.swift @@ -354,6 +354,7 @@ class IterableAPITests: XCTestCase { let internalAPI = InternalIterableAPI.initializeForTesting(apiKey: IterableAPITests.apiKey, networkSession: networkSession) internalAPI.email = IterableAPITests.email internalAPI.updateEmail(newEmail, + merge: true, onSuccess: { _ in guard let request = networkSession.getRequest(withEndPoint: Const.Path.updateEmail) else { return @@ -392,6 +393,7 @@ class IterableAPITests: XCTestCase { let internalAPI = InternalIterableAPI.initializeForTesting(apiKey: IterableAPITests.apiKey, networkSession: networkSession) internalAPI.userId = currentUserId internalAPI.updateEmail(newEmail, + merge: true, onSuccess: { _ in guard let request = networkSession.getRequest(withEndPoint: Const.Path.updateEmail) else { return @@ -421,7 +423,85 @@ class IterableAPITests: XCTestCase { wait(for: [expectation], timeout: testExpectationTimeout) } - + + func testUpdateEmailWithEmailWithoutMerge() { + let expectation = XCTestExpectation(description: "testUpdateEmailWIthEmail") + + let newEmail = "new_user@example.com" + let networkSession = MockNetworkSession(statusCode: 200) + let internalAPI = InternalIterableAPI.initializeForTesting(apiKey: IterableAPITests.apiKey, networkSession: networkSession) + internalAPI.email = IterableAPITests.email + internalAPI.updateEmail(newEmail, + merge: nil, + onSuccess: { _ in + guard let request = networkSession.getRequest(withEndPoint: Const.Path.updateEmail) else { + return + } + guard let body = TestUtils.getRequestBody(request: request) else { + return + } + TestUtils.validate(request: request, + requestType: .post, + apiEndPoint: Endpoint.api, + path: Const.Path.updateEmail, + queryParams: []) + TestUtils.validateElementPresent(withName: JsonKey.newEmail, andValue: newEmail, inDictionary: body) + TestUtils.validateElementPresent(withName: JsonKey.currentEmail, andValue: IterableAPITests.email, inDictionary: body) + XCTAssertEqual(internalAPI.email, newEmail) + expectation.fulfill() + }, + onFailure: { reason, _ in + expectation.fulfill() + if let reason = reason { + XCTFail("encountered error: \(reason)") + } else { + XCTFail("encountered error") + } + }) + + wait(for: [expectation], timeout: testExpectationTimeout) + } + + func testUpdateEmailWithUserIdWithoutMerge() { + let expectation = XCTestExpectation(description: "testUpdateEmailWithUserId") + + let currentUserId = IterableUtil.generateUUID() + let newEmail = "new_user@example.com" + let networkSession = MockNetworkSession(statusCode: 200) + let internalAPI = InternalIterableAPI.initializeForTesting(apiKey: IterableAPITests.apiKey, networkSession: networkSession) + internalAPI.userId = currentUserId + internalAPI.updateEmail(newEmail, + merge: nil, + onSuccess: { _ in + guard let request = networkSession.getRequest(withEndPoint: Const.Path.updateEmail) else { + return + } + guard let body = TestUtils.getRequestBody(request: request) else { + return + } + TestUtils.validate(request: request, + requestType: .post, + apiEndPoint: Endpoint.api, + path: Const.Path.updateEmail, + queryParams: []) + TestUtils.validateElementPresent(withName: JsonKey.newEmail, andValue: newEmail, inDictionary: body) + TestUtils.validateElementPresent(withName: JsonKey.currentUserId, andValue: currentUserId, inDictionary: body) + XCTAssertEqual(internalAPI.userId, currentUserId) + XCTAssertNil(internalAPI.email) + expectation.fulfill() + }, + onFailure: { reason, _ in + expectation.fulfill() + if let reason = reason { + XCTFail("encountered error: \(reason)") + } else { + XCTFail("encountered error") + } + }) + + wait(for: [expectation], timeout: testExpectationTimeout) + } + func testRegisterTokenNilAppName() { let expectation = XCTestExpectation(description: "testRegisterToken")