Skip to content
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

fix(Analytics): Making PinpointEndpointProfile thread safe. #3457

Merged
merged 3 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ extension AWSPinpointAnalyticsPlugin {
}

Task {
let currentEndpointProfile = await pinpoint.currentEndpointProfile()
var currentEndpointProfile = await pinpoint.currentEndpointProfile()
currentEndpointProfile.addUserId(userId)
if let userProfile = userProfile {
currentEndpointProfile.addUserProfile(userProfile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class AWSPinpointAnalyticsPluginClientBehaviorTests: AWSPinpointAnalyticsPluginT
plan: testPlan,
location: testLocation,
properties: testProperties)
let expectedEndpointProfile = PinpointEndpointProfile(applicationId: "appId",
var expectedEndpointProfile = PinpointEndpointProfile(applicationId: "appId",
endpointId: "endpointId")
expectedEndpointProfile.addUserId(testIdentityId)
expectedEndpointProfile.addUserProfile(userProfile)
Expand Down Expand Up @@ -108,7 +108,7 @@ class AWSPinpointAnalyticsPluginClientBehaviorTests: AWSPinpointAnalyticsPluginT
plan: testPlan,
location: testLocation,
properties: testProperties)
let expectedEndpointProfile = PinpointEndpointProfile(applicationId: "appId",
var expectedEndpointProfile = PinpointEndpointProfile(applicationId: "appId",
endpointId: "endpointId")
expectedEndpointProfile.addUserId(testIdentityId)
expectedEndpointProfile.addUserProfile(userProfile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ actor EndpointClient: EndpointClientBehaviour {
}

private func configure(endpointProfile: PinpointEndpointProfile) async -> PinpointEndpointProfile {
var endpointProfile = endpointProfile
var deviceToken: PinpointEndpointProfile.DeviceToken?
if let tokenData = Self.getStoredData(from: keychain, forKey: Constants.deviceTokenKey, fallbackTo: userDefaults) {
deviceToken = tokenData.asHexString()
Expand All @@ -109,6 +110,7 @@ actor EndpointClient: EndpointClientBehaviour {
}

private func updateEndpoint(with endpointProfile: PinpointEndpointProfile) async throws {
var endpointProfile = endpointProfile
endpointProfile.effectiveDate = Date()
let input = createUpdateInput(from: endpointProfile)
log.verbose("UpdateEndpointInput: \(input)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import AWSPinpoint
import Foundation

@_spi(InternalAWSPinpoint)
public class PinpointEndpointProfile: Codable {
public struct PinpointEndpointProfile: Codable, Equatable {
typealias DeviceToken = String

var applicationId: String
Expand Down Expand Up @@ -45,11 +45,11 @@ public class PinpointEndpointProfile: Codable {
self.user = user
}

public func addUserId(_ userId: String) {
public mutating func addUserId(_ userId: String) {
user.userId = userId
}

public func addUserProfile(_ userProfile: UserProfile) {
public mutating func addUserProfile(_ userProfile: UserProfile) {
if let email = userProfile.email {
setCustomProperty(email, forKey: Constants.AttributeKeys.email)
}
Expand All @@ -75,18 +75,18 @@ public class PinpointEndpointProfile: Codable {
}
}

public func setAPNsToken(_ apnsToken: Data) {
public mutating func setAPNsToken(_ apnsToken: Data) {
deviceToken = apnsToken.asHexString()
}

private func addCustomProperties(_ properties: [String: UserProfilePropertyValue]?) {
private mutating func addCustomProperties(_ properties: [String: UserProfilePropertyValue]?) {
guard let properties = properties else { return }
for (key, value) in properties {
setCustomProperty(value, forKey: key)
}
}

private func addUserAttributes(_ attributes: [String: [String]]?) {
private mutating func addUserAttributes(_ attributes: [String: [String]]?) {
guard let attributes = attributes else { return }
let userAttributes = user.userAttributes ?? [:]
user.userAttributes = userAttributes.merging(
Expand All @@ -95,7 +95,7 @@ public class PinpointEndpointProfile: Codable {
)
}

private func setCustomProperty(_ value: UserProfilePropertyValue,
private mutating func setCustomProperty(_ value: UserProfilePropertyValue,
forKey key: String) {
if let value = value as? String {
attributes[key] = [value]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class EndpointClientTests: XCTestCase {
XCTAssertNotNil(keychain.dataValues[EndpointClient.Constants.endpointProfileKey])
XCTAssertNotNil(keychain.dataValues[EndpointClient.Constants.deviceTokenKey])
XCTAssertEqual(archiver.decodeCount, 1)
XCTAssertTrue(endpointProfile === storedEndpointProfile, "Expected stored PinpointEndpointProfile object")
XCTAssertNotEqual(endpointProfile, storedEndpointProfile, "Expected updated PinpointEndpointProfile object")
XCTAssertEqual(endpointProfile.applicationId, currentApplicationId)
XCTAssertEqual(endpointProfile.endpointId, currentEndpointId)
XCTAssertEqual(endpointProfile.deviceToken, newToken.asHexString())
Expand Down Expand Up @@ -104,7 +104,7 @@ class EndpointClientTests: XCTestCase {
XCTAssertEqual(keychain.dataForKeyCountMap[EndpointClient.Constants.endpointProfileKey], 1)
XCTAssertEqual(keychain.dataForKeyCountMap[EndpointClient.Constants.deviceTokenKey], 1)
XCTAssertEqual(archiver.decodeCount, 0)
XCTAssertFalse(endpointProfile === storedEndpointProfile, "Expected new PinpointEndpointProfile object")
XCTAssertNotEqual(endpointProfile, storedEndpointProfile, "Expected new PinpointEndpointProfile object")
XCTAssertEqual(endpointProfile.applicationId, currentApplicationId)
XCTAssertEqual(endpointProfile.endpointId, currentEndpointId)
XCTAssertEqual(endpointProfile.deviceToken, newToken?.asHexString())
Expand All @@ -128,7 +128,7 @@ class EndpointClientTests: XCTestCase {
func testUpdateEndpointProfile_withAPNsToken_withoutStoredToken_shouldSaveToken() async {
keychain.resetCounters()

let endpoint = PinpointEndpointProfile(applicationId: "applicationId",
var endpoint = PinpointEndpointProfile(applicationId: "applicationId",
endpointId: "endpointId")
endpoint.setAPNsToken(Data(hexString: newTokenHex)!)
try? await endpointClient.updateEndpointProfile(with: endpoint)
Expand All @@ -142,7 +142,7 @@ class EndpointClientTests: XCTestCase {
storeToken("oldToken")
keychain.resetCounters()

let endpoint = PinpointEndpointProfile(applicationId: "applicationId",
var endpoint = PinpointEndpointProfile(applicationId: "applicationId",
endpointId: "endpointId")
endpoint.setAPNsToken(Data(hexString: newTokenHex)!)
try? await endpointClient.updateEndpointProfile(with: endpoint)
Expand All @@ -156,7 +156,7 @@ class EndpointClientTests: XCTestCase {
storeToken(newTokenHex)
keychain.resetCounters()

let endpoint = PinpointEndpointProfile(applicationId: "applicationId",
var endpoint = PinpointEndpointProfile(applicationId: "applicationId",
endpointId: "endpointId")
endpoint.setAPNsToken(Data(hexString: newTokenHex)!)
try? await endpointClient.updateEndpointProfile(with: endpoint)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import AppKit

extension AWSPinpointPushNotificationsPlugin {
public func identifyUser(userId: String, userProfile: UserProfile?) async throws {
let currentEndpointProfile = await pinpoint.currentEndpointProfile()
var currentEndpointProfile = await pinpoint.currentEndpointProfile()
currentEndpointProfile.addUserId(userId)
if let userProfile = userProfile {
currentEndpointProfile.addUserProfile(userProfile)
Expand All @@ -30,7 +30,7 @@ extension AWSPinpointPushNotificationsPlugin {
}

public func registerDevice(apnsToken: Data) async throws {
let currentEndpointProfile = await pinpoint.currentEndpointProfile()
var currentEndpointProfile = await pinpoint.currentEndpointProfile()
currentEndpointProfile.setAPNsToken(apnsToken)
do {
try await pinpoint.updateEndpoint(with: currentEndpointProfile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class AWSPinpointPushNotificationsPluginClientBehaviourTests: AWSPinpointPushNot

XCTAssertEqual(mockPinpoint.currentEndpointProfileCount, 1)
XCTAssertEqual(mockPinpoint.updateEndpointCount, 1)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.user.userId, "newUserId")
let updatedEndpoint = try XCTUnwrap(mockPinpoint.updatedPinpointEndpointProfile)
XCTAssertEqual(updatedEndpoint.user.userId, "newUserId")
}

func testIdentifyUser_withProfile_shouldUpdateUserProfile() async throws {
Expand All @@ -38,13 +39,14 @@ class AWSPinpointPushNotificationsPluginClientBehaviourTests: AWSPinpointPushNot

XCTAssertEqual(mockPinpoint.currentEndpointProfileCount, 1)
XCTAssertEqual(mockPinpoint.updateEndpointCount, 1)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.user.userId, "newUserId")
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.attributes.count, 3)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.attributes["name"]?.first, "Name")
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.attributes["email"]?.first, "Email")
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.attributes["plan"]?.first, "Plan")
XCTAssertTrue(mockPinpoint.mockedPinpointEndpointProfile.metrics.isEmpty)
XCTAssertNil(mockPinpoint.mockedPinpointEndpointProfile.user.userAttributes)
let updatedEndpoint = try XCTUnwrap(mockPinpoint.updatedPinpointEndpointProfile)
XCTAssertEqual(updatedEndpoint.user.userId, "newUserId")
XCTAssertEqual(updatedEndpoint.attributes.count, 3)
XCTAssertEqual(updatedEndpoint.attributes["name"]?.first, "Name")
XCTAssertEqual(updatedEndpoint.attributes["email"]?.first, "Email")
XCTAssertEqual(updatedEndpoint.attributes["plan"]?.first, "Plan")
XCTAssertTrue(updatedEndpoint.metrics.isEmpty)
XCTAssertNil(updatedEndpoint.user.userAttributes)
}

func testIdentifyUser_withAnalyticsProfile_shouldUpdateUserProfile() async throws {
Expand All @@ -60,14 +62,15 @@ class AWSPinpointPushNotificationsPluginClientBehaviourTests: AWSPinpointPushNot

XCTAssertEqual(mockPinpoint.currentEndpointProfileCount, 1)
XCTAssertEqual(mockPinpoint.updateEndpointCount, 1)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.user.userId, "newUserId")
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.attributes.count, 2)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.attributes["attribute"]?.first, "string")
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.attributes["boolAttribute"]?.first, "true")
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.metrics["metric"], 2.0)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.metrics["intMetric"], 1)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.metrics.count, 2)
XCTAssertNil(mockPinpoint.mockedPinpointEndpointProfile.user.userAttributes)
let updatedEndpoint = try XCTUnwrap(mockPinpoint.updatedPinpointEndpointProfile)
XCTAssertEqual(updatedEndpoint.user.userId, "newUserId")
XCTAssertEqual(updatedEndpoint.attributes.count, 2)
XCTAssertEqual(updatedEndpoint.attributes["attribute"]?.first, "string")
XCTAssertEqual(updatedEndpoint.attributes["boolAttribute"]?.first, "true")
XCTAssertEqual(updatedEndpoint.metrics["metric"], 2.0)
XCTAssertEqual(updatedEndpoint.metrics["intMetric"], 1)
XCTAssertEqual(updatedEndpoint.metrics.count, 2)
XCTAssertNil(updatedEndpoint.user.userAttributes)
}

func testIdentifyUser_withBasicProfile_shouldUpdateUserProfile() async throws {
Expand All @@ -83,14 +86,15 @@ class AWSPinpointPushNotificationsPluginClientBehaviourTests: AWSPinpointPushNot

XCTAssertEqual(mockPinpoint.currentEndpointProfileCount, 1)
XCTAssertEqual(mockPinpoint.updateEndpointCount, 1)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.user.userId, "newUserId")
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.attributes.count, 2)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.attributes["attribute"]?.first, "string")
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.attributes["boolAttribute"]?.first, "true")
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.metrics["metric"], 2.0)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.metrics["intMetric"], 1)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.metrics.count, 2)
XCTAssertNil(mockPinpoint.mockedPinpointEndpointProfile.user.userAttributes)
let updatedEndpoint = try XCTUnwrap(mockPinpoint.updatedPinpointEndpointProfile)
XCTAssertEqual(updatedEndpoint.user.userId, "newUserId")
XCTAssertEqual(updatedEndpoint.attributes.count, 2)
XCTAssertEqual(updatedEndpoint.attributes["attribute"]?.first, "string")
XCTAssertEqual(updatedEndpoint.attributes["boolAttribute"]?.first, "true")
XCTAssertEqual(updatedEndpoint.metrics["metric"], 2.0)
XCTAssertEqual(updatedEndpoint.metrics["intMetric"], 1)
XCTAssertEqual(updatedEndpoint.metrics.count, 2)
XCTAssertNil(updatedEndpoint.user.userAttributes)
}

func testIdentifyUser_withPinpointProfile_shouldUpdateUserProfile() async throws {
Expand All @@ -110,26 +114,29 @@ class AWSPinpointPushNotificationsPluginClientBehaviourTests: AWSPinpointPushNot

XCTAssertEqual(mockPinpoint.currentEndpointProfileCount, 1)
XCTAssertEqual(mockPinpoint.updateEndpointCount, 1)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.attributes["attribute"]?.first, "string")
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.attributes["attributes"]?.count, 2)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.attributes["attributes"]?.first, "string")
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.attributes["boolAttribute"]?.first, "true")
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.metrics["metric"], 2)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.metrics["intMetric"], 1)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.user.userId, "newUserId")
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.user.userAttributes?["roles"]?.count, 2)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.user.userAttributes?["roles"]?.first, "Test")
let updatedEndpoint = try XCTUnwrap(mockPinpoint.updatedPinpointEndpointProfile)
XCTAssertEqual(updatedEndpoint.attributes["attribute"]?.first, "string")
XCTAssertEqual(updatedEndpoint.attributes["attributes"]?.count, 2)
XCTAssertEqual(updatedEndpoint.attributes["attributes"]?.first, "string")
XCTAssertEqual(updatedEndpoint.attributes["boolAttribute"]?.first, "true")
XCTAssertEqual(updatedEndpoint.metrics["metric"], 2)
XCTAssertEqual(updatedEndpoint.metrics["intMetric"], 1)
XCTAssertEqual(updatedEndpoint.user.userId, "newUserId")
XCTAssertEqual(updatedEndpoint.user.userAttributes?["roles"]?.count, 2)
XCTAssertEqual(updatedEndpoint.user.userAttributes?["roles"]?.first, "Test")
}

func testIdentifyUser_withPinpointProfileOptedOutOfMessages_shouldUpdateUserProfileOptOutValue() async throws {
try await plugin.identifyUser(userId: "newUserId", userProfile: nil)
let updatedEndpoint = try XCTUnwrap(mockPinpoint.updatedPinpointEndpointProfile)
var updatedEndpoint = try XCTUnwrap(mockPinpoint.updatedPinpointEndpointProfile)
XCTAssertFalse(updatedEndpoint.isOptOut)

try await plugin.identifyUser(userId: "newUserId", userProfile: PinpointUserProfile(optedOutOfMessages: true))
updatedEndpoint = try XCTUnwrap(mockPinpoint.updatedPinpointEndpointProfile)
XCTAssertTrue(updatedEndpoint.isOptOut)

try await plugin.identifyUser(userId: "newUserId", userProfile: PinpointUserProfile(name: "User"))
updatedEndpoint = try XCTUnwrap(mockPinpoint.updatedPinpointEndpointProfile)
XCTAssertTrue(updatedEndpoint.isOptOut)
}

Expand All @@ -140,7 +147,8 @@ class AWSPinpointPushNotificationsPluginClientBehaviourTests: AWSPinpointPushNot

XCTAssertEqual(mockPinpoint.currentEndpointProfileCount, 1)
XCTAssertEqual(mockPinpoint.updateEndpointCount, 1)
XCTAssertEqual(mockPinpoint.mockedPinpointEndpointProfile.deviceToken, apnsToken.asHexString())
let updatedEndpoint = try XCTUnwrap(mockPinpoint.updatedPinpointEndpointProfile)
XCTAssertEqual(updatedEndpoint.deviceToken, apnsToken.asHexString())
}

// MARK: - Record Notification received tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class MockAWSPinpoint: AWSPinpointBehavior {
)
func currentEndpointProfile() async -> PinpointEndpointProfile {
currentEndpointProfileCount += 1
return mockedPinpointEndpointProfile
return updatedPinpointEndpointProfile ?? mockedPinpointEndpointProfile
}

var updateEndpointCount = 0
Expand Down
Loading