DailyView/Carthage/Checkouts/facebook-ios-sdk/FBAEMKit/FBAEMKitTests/AEMInvocationTests.swift
2025-12-30 16:40:31 +08:00

1195 lines
39 KiB
Swift

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
@testable import FBAEMKit
import TestTools
import XCTest
final class AEMInvocationTests: XCTestCase {
enum Keys {
static let campaignID = "campaign_ids"
static let acsToken = "acs_token"
static let acsSharedSecret = "shared_secret"
static let acsConfigurationID = "acs_config_id"
static let advertiserID = "advertiser_id"
static let businessID = "advertiser_id"
static let catalogID = "catalog_id"
static let timestamp = "timestamp"
static let configurationMode = "config_mode"
static let configurationID = "config_id"
static let recordedEvents = "recorded_events"
static let recordedValues = "recorded_values"
static let conversionValues = "conversion_values"
static let priority = "priority"
static let conversionTimestamp = "conversion_timestamp"
static let isAggregated = "is_aggregated"
static let hasStoreKitAdNetwork = "has_skan"
static let defaultCurrency = "default_currency"
static let cutoffTime = "cutoff_time"
static let validFrom = "valid_from"
static let conversionValueRules = "conversion_value_rules"
static let conversionValue = "conversion_value"
static let events = "events"
static let eventName = "event_name"
static let values = "values"
static let currency = "currency"
static let amount = "amount"
static let paramRule = "param_rule"
static let content = "fb_content"
static let contentID = "fb_content_id"
static let contentType = "fb_content_type"
static let identity = "id"
static let itemPrice = "item_price"
static let quantity = "quantity"
}
enum Values {
static let purchase = "fb_mobile_purchase"
static let donate = "Donate"
static let unlock = "fb_unlock_level"
static let test = "fb_test_event"
static let defaultMode = "DEFAULT"
static let brandMode = "BRAND"
static let cpasMode = "CPAS"
static let USD = "USD"
}
let boostPriority = 32
var validInvocation = AEMInvocation(
campaignID: "test_campaign_1234",
acsToken: "test_token_12345",
acsSharedSecret: "test_shared_secret",
acsConfigurationID: "test_config_123",
businessID: "test_advertiserid_coffee",
catalogID: "test_catalog_123",
timestamp: Date(timeIntervalSince1970: 1618383600),
configurationMode: "DEFAULT",
configurationID: 10,
recordedEvents: nil,
recordedValues: nil,
conversionValue: -1,
priority: -1,
conversionTimestamp: Date(timeIntervalSince1970: 1618383700),
isAggregated: false,
isTestMode: false,
hasStoreKitAdNetwork: false,
isConversionFilteringEligible: true
)! // swiftlint:disable:this force_unwrapping
var configuration1 = AEMConfiguration(json: [
Keys.defaultCurrency: Values.USD,
Keys.cutoffTime: 1,
Keys.validFrom: 10000,
Keys.configurationMode: Values.defaultMode,
Keys.conversionValueRules: [
[
Keys.conversionValue: 2,
Keys.priority: 10,
Keys.events: [
[
Keys.eventName: Values.purchase,
],
[
Keys.eventName: Values.donate,
],
],
],
[
Keys.conversionValue: 1,
Keys.priority: 11,
Keys.events: [
[
Keys.eventName: Values.purchase,
Keys.values: [
[
Keys.currency: Values.USD,
Keys.amount: 100.0,
],
],
],
[
Keys.eventName: Values.unlock,
],
],
],
],
])! // swiftlint:disable:this force_unwrapping
var configuration2 = AEMConfiguration(json: [
Keys.defaultCurrency: Values.USD,
Keys.cutoffTime: 1,
Keys.validFrom: 20000,
Keys.configurationMode: Values.defaultMode,
Keys.conversionValueRules: [
[
Keys.conversionValue: 2,
Keys.priority: 10,
Keys.events: [
[
Keys.eventName: Values.purchase,
],
[
Keys.eventName: Values.donate,
],
],
],
],
])! // swiftlint:disable:this force_unwrapping
func testInvocationWithInvalidAppLinkData() {
var invalidData: [String: Any] = [:]
XCTAssertNil(AEMInvocation(appLinkData: nil))
invalidData = [
"acs_token": "test_token_12345",
]
XCTAssertNil(AEMInvocation(appLinkData: invalidData))
invalidData = [
"campaign_ids": "test_campaign_1234",
]
XCTAssertNil(AEMInvocation(appLinkData: invalidData))
invalidData = [
"advertiser_id": "test_advertiserid_coffee",
]
XCTAssertNil(AEMInvocation(appLinkData: invalidData))
invalidData = [
"acs_token": 123,
"campaign_ids": 123,
]
XCTAssertNil(AEMInvocation(appLinkData: invalidData))
}
func testInvocationWithValidAppLinkData() {
var validData: [String: Any] = [:]
var invocation: AEMInvocation?
validData = [
"acs_token": "test_token_12345",
"campaign_ids": "test_campaign_1234",
]
invocation = AEMInvocation(appLinkData: validData)
XCTAssertEqual(invocation?.acsToken, "test_token_12345")
XCTAssertEqual(invocation?.campaignID, "test_campaign_1234")
XCTAssertNil(invocation?.businessID)
validData = [
"acs_token": "test_token_12345",
"campaign_ids": "test_campaign_1234",
"advertiser_id": "test_advertiserid_coffee",
]
invocation = AEMInvocation(appLinkData: validData)
XCTAssertEqual(invocation?.acsToken, "test_token_12345")
XCTAssertEqual(invocation?.campaignID, "test_campaign_1234")
XCTAssertEqual(invocation?.businessID, "test_advertiserid_coffee")
}
func testInvocationWithCatalogID() {
let invocation = AEMInvocation(appLinkData: [
"acs_token": "test_token_12345",
"campaign_ids": "test_campaign_1234",
"advertiser_id": "test_advertiserid_coffee",
"catalog_id": "test_catalog_1234",
])
XCTAssertEqual(
invocation?.acsToken,
"test_token_12345",
"Invocation's ACS token is not expected"
)
XCTAssertEqual(
invocation?.campaignID,
"test_campaign_1234",
"Invocation's campaign ID is not expected"
)
XCTAssertEqual(
invocation?.businessID,
"test_advertiserid_coffee",
"Invocation's business ID is not expected"
)
XCTAssertEqual(
invocation?.catalogID,
"test_catalog_1234",
"Invocation's catalog ID is not expected"
)
}
func testInvocationWithoutCatalogID() {
let invocation = AEMInvocation(appLinkData: [
"acs_token": "test_token_12345",
"campaign_ids": "test_campaign_1234",
"advertiser_id": "test_advertiserid_coffee",
])
XCTAssertNotNil(
invocation,
"Invocation is not expected to be nil"
)
XCTAssertNil(
invocation?.catalogID,
"Invocation's catalog ID is expected to be nil"
)
}
func testInvocationWithDebuggingAppLinkData() throws {
let data = [
"acs_token": "debuggingtoken",
"campaign_ids": "test_campaign_1234",
"advertiser_id": "test_advertiserid_coffee",
"test_deeplink": 1,
] as [String: Any]
let invocation = try XCTUnwrap(AEMInvocation(appLinkData: data))
XCTAssertTrue(
invocation.isTestMode,
"Invocation is expected to be test mode when test_deeplink is true"
)
XCTAssertEqual(
invocation.acsToken,
"debuggingtoken",
"Invocations's acsToken is not expected"
)
XCTAssertEqual(
invocation.campaignID,
"test_campaign_1234",
"Invocations's campaignID is not expected"
)
}
func testInvocationWithSKANInfoAppLinkData() throws {
let data = [
"acs_token": "debuggingtoken",
"campaign_ids": "test_campaign_1234",
"advertiser_id": "test_advertiserid_coffee",
"has_skan": true,
] as [String: Any]
let invocation = try XCTUnwrap(AEMInvocation(appLinkData: data))
XCTAssertTrue(
invocation.hasStoreKitAdNetwork,
"Invocation's hasStoreKitAdNetwork is expected to be true when has_skan is true"
)
XCTAssertEqual(
invocation.acsToken,
"debuggingtoken",
"Invocations's acsToken is not expected"
)
XCTAssertEqual(
invocation.campaignID,
"test_campaign_1234",
"Invocations's campaignID is not expected"
)
}
func testProcessedParametersWithValidContentAndContentID() {
let invocation: AEMInvocation? = validInvocation
let content: [String: AnyHashable] = ["id": "123", "quantity": 5]
let contentIDs: [String] = ["id123", "id456"]
let parameters = invocation?.getProcessedParameters(
from: [
Keys.content: #"[{"id": "123", "quantity": 5}]"#,
Keys.contentID: #"["id123", "id456"]"#,
Keys.contentType: "product",
]
) as? [String: AnyHashable]
XCTAssertEqual(
parameters,
[
Keys.content: [content],
Keys.contentID: contentIDs,
Keys.contentType: "product",
],
"Processed parameters are not expected"
)
}
func testProcessedParametersWithValidContent() {
let invocation: AEMInvocation? = validInvocation
let content: [String: AnyHashable] = ["id": "123", "quantity": 5]
let parameters = invocation?.getProcessedParameters(
from: [
Keys.content: #"[{"id": "123", "quantity": 5}]"#,
Keys.contentID: "001",
Keys.contentType: "product",
]
) as? [String: AnyHashable]
XCTAssertEqual(
parameters,
[
Keys.content: [content],
Keys.contentID: "001",
Keys.contentType: "product",
],
"Processed parameters are not expected"
)
}
func testProcessedParametersWithInvalidContent() {
let invocation: AEMInvocation? = validInvocation
let parameters = invocation?.getProcessedParameters(
from: [
Keys.content: #"[{"id": ,"quantity": 5}]"#,
Keys.contentID: "001",
Keys.contentType: "product",
]
) as? [String: AnyHashable]
XCTAssertEqual(
parameters,
[
Keys.content: #"[{"id": ,"quantity": 5}]"#,
Keys.contentID: "001",
Keys.contentType: "product",
],
"Processed parameters are not expected"
)
}
func testFindConfiguration() {
var invocation: AEMInvocation? = validInvocation
invocation?.reset()
invocation?.configurationID = 10
XCTAssertNil(
invocation?.findConfiguration(in: [Values.defaultMode: [configuration1, configuration2]]),
"Should not find the configuration with unmatched configurationID"
)
invocation = AEMInvocation(
campaignID: "test_campaign_1234",
acsToken: "test_token_12345",
acsSharedSecret: nil,
acsConfigurationID: nil,
businessID: nil,
catalogID: nil,
isTestMode: false,
hasStoreKitAdNetwork: false,
isConversionFilteringEligible: true
)
let configuration = invocation?.findConfiguration(in: [Values.defaultMode: [configuration1, configuration2]])
XCTAssertEqual(invocation?.configurationID, 20000, "Should set the invocation with expected configurationID")
XCTAssertEqual(
invocation?.configurationMode,
Values.defaultMode,
"Should set the invocation with expected configurationMode"
)
XCTAssertEqual(configuration?.validFrom, configuration2.validFrom, "Should find the expected configuration")
XCTAssertEqual(
configuration?.mode,
configuration2.mode,
"Should find the expected configuration"
)
}
func testFindConfigWithBusinessID1() {
let configurationWithBusinessID = SampleAEMConfigurations.createConfigurationWithBusinessID()
let configurationWithoutBusinessID = SampleAEMConfigurations.createConfigurationWithoutBusinessID()
let invocation = validInvocation
invocation.reset()
invocation.configurationID = 10000
let configuration = invocation.findConfiguration(
in: [
Values.defaultMode: [configurationWithoutBusinessID],
Values.brandMode: [configurationWithBusinessID],
]
)
XCTAssertEqual(
configuration?.validFrom,
10000,
"Should have expected validFrom"
)
XCTAssertNil(
configuration?.businessID,
"Should not have unexpected advertiserID"
)
}
func testFindConfigWithBusinessID2() {
let configurationWithBusinessID = SampleAEMConfigurations.createConfigurationWithBusinessID()
let configurationWithoutBusinessID = SampleAEMConfigurations.createConfigurationWithoutBusinessID()
let invocation = validInvocation
invocation.reset()
invocation.configurationID = 10000
invocation.businessID = "test_advertiserid_123"
let configuration = invocation.findConfiguration(
in: [
Values.defaultMode: [configurationWithoutBusinessID],
Values.brandMode: [configurationWithBusinessID],
]
)
XCTAssertEqual(
configuration?.validFrom,
10000,
"Should have expected validFrom"
)
XCTAssertEqual(
configuration?.businessID,
"test_advertiserid_123",
"Should have expected advertiserID"
)
}
func testFindConfigWithBusinessID3() {
let configurationWithBusinessID = SampleAEMConfigurations.createConfigurationWithBusinessID()
let configurationWithoutBusinessID = SampleAEMConfigurations.createConfigurationWithoutBusinessID()
let invocation = AEMInvocation(
campaignID: "test_campaign_1234",
acsToken: "test_token_12345",
acsSharedSecret: nil,
acsConfigurationID: nil,
businessID: "test_advertiserid_123",
catalogID: nil,
isTestMode: false,
hasStoreKitAdNetwork: false,
isConversionFilteringEligible: true
)
let configuration = invocation?.findConfiguration(
in: [
Values.defaultMode: [configurationWithoutBusinessID],
Values.brandMode: [configurationWithBusinessID],
]
)
XCTAssertEqual(invocation?.configurationID, 10000, "Should set the invocation with expected configurationID")
XCTAssertEqual(
invocation?.configurationMode,
Values.defaultMode,
"Should set the invocation with expected configurationMode"
)
XCTAssertEqual(
configuration?.validFrom,
configurationWithBusinessID.validFrom,
"Should find the expected configuration"
)
XCTAssertEqual(
configuration?.mode,
configurationWithBusinessID.mode,
"Should find the expected configuration"
)
XCTAssertEqual(
configuration?.businessID,
configurationWithBusinessID.businessID,
"Should find the expected configuration"
)
}
func testFindConfigWithCpas() {
let cpasConfiguration = SampleAEMConfigurations.createCpasConfiguration()
let invocation = AEMInvocation(
campaignID: "test_campaign_1234",
acsToken: "test_token_12345",
acsSharedSecret: nil,
acsConfigurationID: nil,
businessID: "test_advertiserid_cpas",
catalogID: nil,
isTestMode: false,
hasStoreKitAdNetwork: false,
isConversionFilteringEligible: true
)
let configuration = invocation?.findConfiguration(
in: [
Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()],
Values.brandMode: [SampleAEMConfigurations.createConfigurationWithBusinessIDAndContentRule()],
Values.cpasMode: [cpasConfiguration],
]
)
XCTAssertEqual(invocation?.configurationID, 10000, "Should set the invocation with expected configurationID")
XCTAssertEqual(
invocation?.configurationMode,
Values.cpasMode,
"Should set the invocation with expected configurationMode"
)
XCTAssertEqual(
configuration?.validFrom,
cpasConfiguration.validFrom,
"Should find the expected configuration"
)
XCTAssertEqual(
configuration?.mode,
cpasConfiguration.mode,
"Should find the expected configuration"
)
XCTAssertEqual(
configuration?.businessID,
cpasConfiguration.businessID,
"Should find the expected configuration"
)
}
func testGetConfigurationList() {
let configurations = [
Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()],
Values.brandMode: [SampleAEMConfigurations.createConfigurationWithBusinessIDAndContentRule()],
Values.cpasMode: [SampleAEMConfigurations.createCpasConfiguration()],
]
let invocation = SampleAEMInvocations.createGeneralInvocation1()
var configurationList = invocation.getConfigurationList(mode: .default, configurations: configurations)
XCTAssertEqual(configurationList.count, 1, "Should only find the default configuration")
configurationList = invocation.getConfigurationList(mode: .brand, configurations: configurations)
XCTAssertEqual(configurationList.count, 2, "Should only find the brand or cpas configuration")
XCTAssertEqual(
configurationList.first?.mode,
Values.cpasMode,
"Should have the caps configuration first"
)
XCTAssertEqual(
configurationList.last?.mode,
Values.brandMode,
"Should have the brand configuration last"
)
}
func testAttributeEventWithValue() {
let invocation: AEMInvocation = validInvocation
invocation.reset()
invocation.setConfiguration(configuration1)
var isAttributed = invocation.attributeEvent(
Values.test,
currency: Values.USD,
value: 10,
parameters: nil,
configurations: [Values.defaultMode: [configuration1, configuration2]],
shouldUpdateCache: true,
isRuleMatchInServer: false
)
XCTAssertFalse(isAttributed, "Should not attribute unexpected event")
XCTAssertFalse(
invocation.recordedEvents.contains(Values.test),
"Should not add events that cannot be attributed to the invocation"
)
XCTAssertEqual(invocation.recordedValues.count, 0, "Should not attribute unexpected values")
isAttributed = invocation.attributeEvent(
Values.purchase,
currency: Values.USD,
value: 10,
parameters: nil,
configurations: [Values.defaultMode: [configuration1, configuration2]],
shouldUpdateCache: true,
isRuleMatchInServer: false
)
XCTAssertTrue(isAttributed, "Should attribute expected event")
XCTAssertTrue(
invocation.recordedEvents.contains(Values.purchase),
"Should add events that can be attributed to the invocation"
)
XCTAssertEqual(
invocation.recordedValues as? [String: [String: Int]],
[Values.purchase: [Values.USD: 10]],
"Should attribute unexpected values"
)
}
func testAttributeUnexpectedEventWithoutValue() {
let invocation: AEMInvocation = validInvocation
invocation.reset()
invocation.setConfiguration(configuration1)
let isAttributed = invocation.attributeEvent(
Values.test,
currency: nil,
value: nil,
parameters: nil,
configurations: [Values.defaultMode: [configuration1, configuration2]],
shouldUpdateCache: true,
isRuleMatchInServer: false
)
XCTAssertFalse(isAttributed, "Should not attribute unexpected event")
XCTAssertFalse(invocation.recordedEvents.contains(Values.test))
XCTAssertEqual(invocation.recordedValues.count, 0, "Should not attribute unexpected values")
}
func testAttributeExpectedEventWithoutValue() {
let invocation: AEMInvocation = validInvocation
invocation.reset()
invocation.setConfiguration(configuration1)
var isAttributed = invocation.attributeEvent(
Values.purchase,
currency: nil,
value: nil,
parameters: nil,
configurations: [Values.defaultMode: [configuration1, configuration2]],
shouldUpdateCache: true,
isRuleMatchInServer: false
)
XCTAssertTrue(isAttributed, "Should attribute the expected event")
XCTAssertTrue(invocation.recordedEvents.contains(Values.purchase))
XCTAssertEqual(invocation.recordedValues.count, 0, "Should not attribute unexpected values")
isAttributed = invocation.attributeEvent(
Values.donate,
currency: nil,
value: nil,
parameters: nil,
configurations: [Values.defaultMode: [configuration1, configuration2]],
shouldUpdateCache: true,
isRuleMatchInServer: false
)
XCTAssertTrue(isAttributed, "Should attribute the expected event")
XCTAssertTrue(invocation.recordedEvents.contains(Values.donate))
XCTAssertEqual(invocation.recordedValues.count, 0, "Should not attribute unexpected values")
}
func testAttributeEventWithExpectedContent() {
let configWithBusinessID = SampleAEMConfigurations.createConfigurationWithBusinessIDAndContentRule()
let configWithoutBusinessID = SampleAEMConfigurations.createConfigurationWithoutBusinessID()
let invocation = AEMInvocation(
campaignID: "test_campaign_1234",
acsToken: "test_token_12345",
acsSharedSecret: nil,
acsConfigurationID: nil,
businessID: "test_advertiserid_content_test",
catalogID: nil,
isTestMode: false,
hasStoreKitAdNetwork: false,
isConversionFilteringEligible: true
)! // swiftlint:disable:this force_unwrapping
let configurations = [
Values.defaultMode: [configWithoutBusinessID],
Values.brandMode: [configWithBusinessID],
]
let isAttributed = invocation.attributeEvent(
Values.purchase,
currency: Values.USD,
value: 0,
parameters: [
Keys.content: #"[{"id": "abc", "quantity": 5}]"#,
Keys.contentID: "001",
Keys.contentType: "product",
],
configurations: configurations,
shouldUpdateCache: true,
isRuleMatchInServer: false
)
XCTAssertTrue(isAttributed, "Should attribute the event with expected parameters")
}
func testAttributeEventWithUnexpectedContent() {
let configWithBusinessID = SampleAEMConfigurations.createConfigurationWithBusinessIDAndContentRule()
let configWithoutBusinessID = SampleAEMConfigurations.createConfigurationWithoutBusinessID()
let invocation = AEMInvocation(
campaignID: "test_campaign_1234",
acsToken: "test_token_12345",
acsSharedSecret: nil,
acsConfigurationID: nil,
businessID: "test_advertiserid_content_test",
catalogID: nil,
isTestMode: false,
hasStoreKitAdNetwork: false,
isConversionFilteringEligible: true
)! // swiftlint:disable:this force_unwrapping
let configurations = [
Values.defaultMode: [configWithoutBusinessID],
Values.brandMode: [configWithBusinessID],
]
let isAttributed = invocation.attributeEvent(
Values.purchase,
currency: Values.USD,
value: 0,
parameters: [
Keys.content: #"[{"id": "123", "quantity": 5}]"#,
Keys.contentID: "001",
Keys.contentType: "product",
],
configurations: configurations,
shouldUpdateCache: true,
isRuleMatchInServer: false
)
XCTAssertFalse(isAttributed, "Should attribute the event with expected parameters")
}
func testAttributeCpasEvent() {
let invocation = AEMInvocation(
campaignID: "test_campaign_1234",
acsToken: "test_token_12345",
acsSharedSecret: nil,
acsConfigurationID: nil,
businessID: "test_advertiserid_cpas",
catalogID: nil,
isTestMode: false,
hasStoreKitAdNetwork: false,
isConversionFilteringEligible: true
)! // swiftlint:disable:this force_unwrapping
let configurations = [
Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()],
Values.cpasMode: [SampleAEMConfigurations.createCpasConfiguration()],
]
let isAttributed = invocation.attributeEvent(
Values.purchase,
currency: Values.USD,
value: NSNumber(value: 5000),
parameters: [
Keys.content: [
[
Keys.identity: "abc",
Keys.itemPrice: NSNumber(value: 100),
Keys.quantity: NSNumber(value: 10),
],
[
Keys.identity: "test",
Keys.itemPrice: NSNumber(value: 200),
Keys.quantity: NSNumber(value: 20),
],
],
],
configurations: configurations,
shouldUpdateCache: true,
isRuleMatchInServer: false
)
XCTAssertTrue(
isAttributed,
"Should attribute the event"
)
XCTAssertEqual(
invocation.recordedEvents,
[Values.purchase],
"Should expect the event is updated in the cache"
)
XCTAssertEqual(
invocation.recordedValues as? [String: [String: Int]],
[Values.purchase: [Values.USD: 1000]],
"Should expect the total value is updated in the cache"
)
}
func testAttributeEventWithRuleMatchInServer() {
let invocation = AEMInvocation(
campaignID: "test_campaign_1234",
acsToken: "test_token_12345",
acsSharedSecret: nil,
acsConfigurationID: nil,
businessID: "test_advertiserid_cpas",
catalogID: nil,
isTestMode: false,
hasStoreKitAdNetwork: false,
isConversionFilteringEligible: true
)! // swiftlint:disable:this force_unwrapping
let configurations = [
Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()],
Values.cpasMode: [SampleAEMConfigurations.createCpasConfiguration()],
]
let isAttributed = invocation.attributeEvent(
Values.purchase,
currency: Values.USD,
value: NSNumber(value: 5000),
parameters: [
Keys.content: [
[
Keys.identity: "abc",
Keys.itemPrice: NSNumber(value: 100),
Keys.quantity: NSNumber(value: 10),
],
[
Keys.identity: "test",
Keys.itemPrice: NSNumber(value: 200),
Keys.quantity: NSNumber(value: 20),
],
],
],
configurations: configurations,
shouldUpdateCache: true,
isRuleMatchInServer: true
)
XCTAssertTrue(
isAttributed,
"Should attribute the event"
)
XCTAssertEqual(
invocation.recordedEvents,
[Values.purchase],
"Should expect the event is updated in the cache"
)
XCTAssertEqual(
invocation.recordedValues as? [String: [String: Int]],
[Values.purchase: [Values.USD: 5000]],
"Should expect the value is updated in the cache"
)
}
func testAttributeEventWithoutCache() {
let invocation: AEMInvocation = validInvocation
invocation.reset()
invocation.setConfiguration(configuration1)
let isAttributed = invocation.attributeEvent(
Values.purchase,
currency: nil,
value: nil,
parameters: nil,
configurations: [Values.defaultMode: [configuration1, configuration2]],
shouldUpdateCache: false,
isRuleMatchInServer: false
)
XCTAssertTrue(isAttributed, "Should attribute the expected event")
XCTAssertFalse(invocation.recordedEvents.contains(Values.purchase), "Should not update the event cache")
XCTAssertEqual(invocation.recordedValues.count, 0, "Should not update value cache")
}
func testAttributeEventAndValueWithoutCache() {
let invocation: AEMInvocation = validInvocation
invocation.reset()
invocation.setConfiguration(configuration1)
let isAttributed = invocation.attributeEvent(
Values.purchase,
currency: Values.USD,
value: 10,
parameters: nil,
configurations: [Values.defaultMode: [configuration1, configuration2]],
shouldUpdateCache: false,
isRuleMatchInServer: false
)
XCTAssertTrue(isAttributed, "Should attribute expected event")
XCTAssertFalse(
invocation.recordedEvents.contains(Values.purchase),
"Should add events that can be attributed to the invocation"
)
XCTAssertEqual(
invocation.recordedValues.count,
0,
"Should not update value cache"
)
}
func testUpdateConversionWithValue() {
let invocation: AEMInvocation = validInvocation
invocation.reset()
invocation.setConfiguration(configuration1)
invocation.recordedEvents = [Values.purchase, Values.unlock]
XCTAssertFalse(
invocation.updateConversionValue(
configurations: [Values.defaultMode: [configuration1, configuration2]],
event: Values.purchase,
shouldBoostPriority: false
),
"Should not update conversion value"
)
invocation.recordedEvents = [Values.purchase, Values.donate]
XCTAssertTrue(
invocation.updateConversionValue(
configurations: [Values.defaultMode: [configuration1, configuration2]],
event: Values.purchase,
shouldBoostPriority: false
),
"Should update conversion value"
)
XCTAssertEqual(
invocation.conversionValue,
2,
"Should update the expected conversion value"
)
invocation.recordedEvents = [Values.purchase, Values.unlock]
invocation.recordedValues = [Values.purchase: [Values.USD: 100.0]]
XCTAssertTrue(
invocation.updateConversionValue(
configurations: [Values.defaultMode: [configuration1, configuration2]],
event: Values.purchase,
shouldBoostPriority: false
),
"Should update conversion value"
)
XCTAssertEqual(
invocation.conversionValue,
1,
"Should update the expected conversion value"
)
invocation.reset()
invocation.priority = 100
invocation.recordedEvents = [Values.purchase, Values.unlock]
invocation.recordedValues = [Values.purchase: [Values.USD: 100]]
XCTAssertFalse(
invocation.updateConversionValue(
configurations: [Values.defaultMode: [configuration1, configuration2]],
event: Values.purchase,
shouldBoostPriority: false
),
"Should not update conversion value under priority"
)
}
func testUpdateConversionWithouValue() {
let invocation: AEMInvocation = validInvocation
invocation.reset()
invocation.setConfiguration(configuration2)
invocation.recordedEvents = [Values.purchase]
XCTAssertFalse(
invocation.updateConversionValue(
configurations: [Values.defaultMode: [configuration1, configuration2]],
event: Values.purchase,
shouldBoostPriority: false
),
"Should not update conversion value"
)
XCTAssertEqual(
invocation.conversionValue,
-1,
"Should not update the unexpected conversion value"
)
invocation.recordedEvents = [Values.purchase, Values.donate]
XCTAssertTrue(
invocation.updateConversionValue(
configurations: [Values.defaultMode: [configuration1, configuration2]],
event: Values.purchase,
shouldBoostPriority: false
),
"Should update conversion value"
)
XCTAssertEqual(
invocation.conversionValue,
2,
"Should update the expected conversion value"
)
}
func testUpdateConversionWithBoostPriority() {
let configuration = SampleAEMConfigurations.createWithMultipleRules()
let configurations = [Values.defaultMode: [configuration]]
let invocation = SampleAEMInvocations.createCatalogOptimizedInvocation()
// swiftlint:disable force_unwrapping
let lowestPriorityRule = configuration.conversionValueRules.last!
// Set the highest priority in the conversion rules
invocation.priority = configuration.conversionValueRules.first!.priority
// Add the lowest priority event
invocation.recordedEvents = [lowestPriorityRule.events.first!.eventName]
// swiftlint:enable force_unwrapping
XCTAssertTrue(
invocation.updateConversionValue(configurations: configurations, event: Values.donate, shouldBoostPriority: true),
"Should expect to update the conversion value"
)
XCTAssertEqual(
invocation.priority,
lowestPriorityRule.priority + boostPriority,
"Should expect the updated priority to be boosted"
)
XCTAssertEqual(
invocation.conversionValue,
lowestPriorityRule.conversionValue,
"Should expect the conversion value is updated for the optimized event"
)
}
// Test conversion value updating when optimized event has multiple conversion values
func testUpdateConversionWithHigherBoostPriority() {
let configuration = SampleAEMConfigurations.createWithMultipleRules()
let configurations = [Values.defaultMode: [configuration]]
let invocation = SampleAEMInvocations.createCatalogOptimizedInvocation()
invocation.campaignID = "83" // The campaign id's modulo is matched to purchase's conversion value
let highestPriorityRule = configuration.conversionValueRules.first! // swiftlint:disable:this force_unwrapping
invocation.priority = 42 // Set the second highest priority with boost priority in the conversion rules
invocation.recordedEvents = [Values.purchase] // Add the optimzied event
invocation.recordedValues = [Values.purchase: [Values.USD: 100.0]]
XCTAssertTrue(
invocation.updateConversionValue(
configurations: configurations,
event: Values.purchase,
shouldBoostPriority: true
),
"Should expect to update the conversion value"
)
XCTAssertEqual(
invocation.priority,
highestPriorityRule.priority + boostPriority,
"Should expect the updated priority to be boosted"
)
XCTAssertEqual(
invocation.conversionValue,
highestPriorityRule.conversionValue,
"Should expect the conversion value is updated for the optimized event"
)
}
func testUpdateConversionWithBoostPriorityAndNonOptimziedEvent() {
let configuration = SampleAEMConfigurations.createWithMultipleRules()
let configurations = [Values.defaultMode: [configuration]]
let invocation = SampleAEMInvocations.createCatalogOptimizedInvocation()
let lowestPriorityRule = configuration.conversionValueRules.last! // swiftlint:disable:this force_unwrapping
// Set the highest priority in the conversion rules
invocation.priority = configuration.conversionValueRules.first!.priority // swiftlint:disable:this force_unwrapping
// Add the lowest priority event
invocation.recordedEvents = [
Values.purchase,
lowestPriorityRule.events.first!.eventName, // swiftlint:disable:this force_unwrapping
]
XCTAssertFalse(
invocation.updateConversionValue(
configurations: configurations,
event: Values.purchase,
shouldBoostPriority: true
),
"Should expect not to update the conversion value"
)
}
func testDecodeBase64UrlSafeString() {
let decodedString = validInvocation
.decodeBase64URLSafeString(
"E_dwjTaF9-SHijRKoD5jrgJoi9pgObKEqrkxgl3iE9-mxpDn-wpseBmtlNFN2HTI5OzzTVqhBwNi2zrwt-TxCw"
)
XCTAssertEqual(
decodedString?.base64EncodedString(),
"E/dwjTaF9+SHijRKoD5jrgJoi9pgObKEqrkxgl3iE9+mxpDn+wpseBmtlNFN2HTI5OzzTVqhBwNi2zrwt+TxCw==",
"Should decode the base64 url safe string correctly"
)
}
func testDecodeBase64UrlSafeStringWithEmptyString() {
let decodedString = validInvocation.decodeBase64URLSafeString("")
XCTAssertNil(
decodedString?.base64EncodedString(),
"Should decode the base64 url safe string as nil with empty string"
)
}
func testGetHmacWithoutACSSecret() {
let invocation = SampleAEMInvocations.createGeneralInvocation1()
invocation.acsSharedSecret = nil
XCTAssertNil(
invocation.getHMAC(delay: 10),
"HMAC should be nil when ACS Shared Secret is nil"
)
}
func testGetHmacWithEmptyACSSecret() {
let invocation = SampleAEMInvocations.createGeneralInvocation1()
invocation.acsSharedSecret = ""
XCTAssertNil(
invocation.getHMAC(delay: 10),
"HMAC should be nil when ACS Shared Secret is an empty string"
)
}
func testGetHmacWithoutACSConfigurationID() {
let invocation = SampleAEMInvocations.createGeneralInvocation1()
invocation.acsConfigurationID = nil
XCTAssertNil(
invocation.getHMAC(delay: 10),
"HMAC should be nil when ACS configuration ID is nil"
)
}
func testGetHmacWithACSSecretAndACSConfigurationID() {
let invocation = SampleAEMInvocations.createGeneralInvocation1()
invocation.campaignID = "aaa"
invocation.acsConfigurationID = "abc"
invocation.acsSharedSecret =
"E_dwjTaF9-SHijRKoD5jrgJoi9pgObKEqrkxgl3iE9-mxpDn-wpseBmtlNFN2HTI5OzzTVqhBwNi2zrwt-TxCw"
invocation.conversionValue = 6
XCTAssertEqual(
invocation.getHMAC(delay: 31),
"Z65Xxo-IevEwpLYNES9QmWRlx-zPH8zxfIJPw6ofQtpDJvKWuNI93SBHlUapS1_DIVl9Ovwoa5Xo7v63zQ5_HA",
"Should generate the expected HMAC"
)
}
func testIsOptimizedEventWithoutCatalogID() {
let invocation = SampleAEMInvocations.createGeneralInvocation1()
let configurations = [
Values.defaultMode: [configuration1],
]
XCTAssertFalse(
invocation.isOptimizedEvent(Values.purchase, configurations: configurations),
"Invocation without catalog ID doesn't have optimized event"
)
}
func testIsOptimizedEventWithoutExpectedEvent() {
let invocation = SampleAEMInvocations.createCatalogOptimizedInvocation()
let configurations = [
Values.defaultMode: [configuration1],
]
XCTAssertFalse(
invocation.isOptimizedEvent(Values.donate, configurations: configurations),
"Event is not expected to be optimized"
)
}
func testIsOptimizedEventWithExpectedEvent() {
let invocation = SampleAEMInvocations.createCatalogOptimizedInvocation()
let configurations = [
Values.defaultMode: [configuration1],
]
XCTAssertTrue(
invocation.isOptimizedEvent(Values.purchase, configurations: configurations),
"Event is expected to be optimized"
)
}
func testSecureCoding() {
XCTAssertTrue(
AEMInvocation.supportsSecureCoding,
"AEM Invocation should support secure coding"
)
}
func testEncodingAndDecoding() throws {
// Encode and Decode
let object = validInvocation
let decodedObject = try CodabilityTesting.encodeAndDecode(object)
// Test Objects
// XCTAssertEqual(decodedObject, invocation, .isCodable) // Doesn't work since isEqual not implemented
XCTAssertNotIdentical(decodedObject, object, .isCodable)
// Test Properties
XCTAssertEqual(decodedObject.campaignID, object.campaignID, .isCodable)
XCTAssertEqual(decodedObject.acsToken, object.acsToken, .isCodable)
XCTAssertEqual(decodedObject.acsSharedSecret, object.acsSharedSecret, .isCodable)
XCTAssertEqual(decodedObject.acsConfigurationID, object.acsConfigurationID, .isCodable)
XCTAssertEqual(decodedObject.businessID, object.businessID, .isCodable)
XCTAssertEqual(decodedObject.catalogID, object.catalogID, .isCodable)
XCTAssertEqual(decodedObject.timestamp, object.timestamp, .isCodable)
XCTAssertEqual(decodedObject.configurationMode, object.configurationMode, .isCodable)
XCTAssertEqual(decodedObject.configurationID, object.configurationID, .isCodable)
XCTAssertEqual(decodedObject.recordedEvents, object.recordedEvents, .isCodable)
XCTAssertTrue(decodedObject.recordedValues.isEmpty, .isCodable)
XCTAssertEqual(decodedObject.conversionValue, object.conversionValue, .isCodable)
XCTAssertEqual(decodedObject.priority, object.priority, .isCodable)
XCTAssertEqual(decodedObject.conversionTimestamp, object.conversionTimestamp, .isCodable)
XCTAssertEqual(decodedObject.isAggregated, object.isAggregated, .isCodable)
XCTAssertEqual(decodedObject.hasStoreKitAdNetwork, object.hasStoreKitAdNetwork, .isCodable)
XCTAssertEqual(decodedObject.isConversionFilteringEligible, object.isConversionFilteringEligible, .isCodable)
}
}
// swiftformat:disable extensionaccesscontrol
// MARK: - Assumptions
fileprivate extension String {
static let isCodable = "AEMInvocation should be encodable and decodable"
}