FBAEMKit/FBAEMKitTests/AEMInvocationTests.swift (1,102 lines of code) (raw):
/*
* 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 XCTest
final class AEMInvocationTests: XCTestCase {
enum Keys {
static let campaignID = "campaign_ids"
static let ACSToken = "acs_token"
static let ACSSharedSecret = "shared_secret"
static let ACSConfigID = "acs_config_id"
static let advertiserID = "advertiser_id"
static let businessID = "advertiser_id"
static let catalogID = "catalog_id"
static let timestamp = "timestamp"
static let configMode = "config_mode"
static let configID = "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 hasSKAN = "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",
acsConfigID: "test_config_123",
businessID: "test_advertiserid_coffee",
catalogID: "test_catalog_123",
timestamp: Date(timeIntervalSince1970: 1618383600),
configMode: "DEFAULT",
configID: 10,
recordedEvents: nil,
recordedValues: nil,
conversionValue: -1,
priority: -1,
conversionTimestamp: Date(timeIntervalSince1970: 1618383700),
isAggregated: false,
isTestMode: false,
hasSKAN: false,
isConversionFilteringEligible: true
)! // swiftlint:disable:this force_unwrapping
var config1 = _AEMConfiguration(json: [
Keys.defaultCurrency: Values.USD,
Keys.cutoffTime: 1,
Keys.validFrom: 10000,
Keys.configMode: 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,
],
],
],
[
Keys.eventName: Values.unlock,
],
],
],
],
])! // swiftlint:disable:this force_unwrapping
var config2 = _AEMConfiguration(json: [
Keys.defaultCurrency: Values.USD,
Keys.cutoffTime: 1,
Keys.validFrom: 20000,
Keys.configMode: 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.hasSKAN,
"Invocation's hasSKAN 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?.processedParameters([
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?.processedParameters([
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?.processedParameters([
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 testFindConfig() {
var invocation: _AEMInvocation? = validInvocation
invocation?.reset()
invocation?.setConfigID(10)
XCTAssertNil(
invocation?._findConfig([Values.defaultMode: [config1, config2]]),
"Should not find the config with unmatched configID"
)
invocation = _AEMInvocation(
campaignID: "test_campaign_1234",
acsToken: "test_token_12345",
acsSharedSecret: nil,
acsConfigID: nil,
businessID: nil,
catalogID: nil,
isTestMode: false,
hasSKAN: false,
isConversionFilteringEligible: true
)
let config = invocation?._findConfig([Values.defaultMode: [config1, config2]])
XCTAssertEqual(invocation?.configID, 20000, "Should set the invocation with expected configID")
XCTAssertEqual(invocation?.configMode, Values.defaultMode, "Should set the invocation with expected configMode")
XCTAssertEqual(config?.validFrom, config2.validFrom, "Should find the expected config")
XCTAssertEqual(config?.configMode, config2.configMode, "Should find the expected config")
}
func testFindConfigWithBusinessID1() {
let configWithBusinessID = SampleAEMConfigurations.createConfigWithBusinessID()
let configWithoutBusinessID = SampleAEMConfigurations.createConfigWithoutBusinessID()
let invocation = validInvocation
invocation.reset()
invocation.setConfigID(10000)
let config = invocation._findConfig([
Values.defaultMode: [configWithoutBusinessID],
Values.brandMode: [configWithBusinessID],
])
XCTAssertEqual(
config?.validFrom,
10000,
"Should have expected validFrom"
)
XCTAssertNil(
config?.businessID,
"Should not have unexpected advertiserID"
)
}
func testFindConfigWithBusinessID2() {
let configWithBusinessID = SampleAEMConfigurations.createConfigWithBusinessID()
let configWithoutBusinessID = SampleAEMConfigurations.createConfigWithoutBusinessID()
let invocation = validInvocation
invocation.reset()
invocation.setConfigID(10000)
invocation.setBusinessID("test_advertiserid_123")
let config = invocation._findConfig([
Values.defaultMode: [configWithoutBusinessID],
Values.brandMode: [configWithBusinessID],
])
XCTAssertEqual(
config?.validFrom,
10000,
"Should have expected validFrom"
)
XCTAssertEqual(
config?.businessID,
"test_advertiserid_123",
"Should have expected advertiserID"
)
}
func testFindConfigWithBusinessID3() {
let configWithBusinessID = SampleAEMConfigurations.createConfigWithBusinessID()
let configWithoutBusinessID = SampleAEMConfigurations.createConfigWithoutBusinessID()
let invocation = _AEMInvocation(
campaignID: "test_campaign_1234",
acsToken: "test_token_12345",
acsSharedSecret: nil,
acsConfigID: nil,
businessID: "test_advertiserid_123",
catalogID: nil,
isTestMode: false,
hasSKAN: false,
isConversionFilteringEligible: true
)
let config = invocation?._findConfig([
Values.defaultMode: [configWithoutBusinessID],
Values.brandMode: [configWithBusinessID],
])
XCTAssertEqual(invocation?.configID, 10000, "Should set the invocation with expected configID")
XCTAssertEqual(invocation?.configMode, Values.defaultMode, "Should set the invocation with expected configMode")
XCTAssertEqual(
config?.validFrom,
configWithBusinessID.validFrom,
"Should find the expected config"
)
XCTAssertEqual(
config?.configMode,
configWithBusinessID.configMode,
"Should find the expected config"
)
XCTAssertEqual(
config?.businessID,
configWithBusinessID.businessID,
"Should find the expected config"
)
}
func testFindConfigWithCpas() {
let cpasConfig = SampleAEMConfigurations.createCpasConfig()
let invocation = _AEMInvocation(
campaignID: "test_campaign_1234",
acsToken: "test_token_12345",
acsSharedSecret: nil,
acsConfigID: nil,
businessID: "test_advertiserid_cpas",
catalogID: nil,
isTestMode: false,
hasSKAN: false,
isConversionFilteringEligible: true
)
let config = invocation?._findConfig([
Values.defaultMode: [SampleAEMConfigurations.createConfigWithoutBusinessID()],
Values.brandMode: [SampleAEMConfigurations.createConfigWithBusinessIDAndContentRule()],
Values.cpasMode: [cpasConfig],
])
XCTAssertEqual(invocation?.configID, 10000, "Should set the invocation with expected configID")
XCTAssertEqual(invocation?.configMode, Values.cpasMode, "Should set the invocation with expected configMode")
XCTAssertEqual(
config?.validFrom,
cpasConfig.validFrom,
"Should find the expected config"
)
XCTAssertEqual(
config?.configMode,
cpasConfig.configMode,
"Should find the expected config"
)
XCTAssertEqual(
config?.businessID,
cpasConfig.businessID,
"Should find the expected config"
)
}
func testGetConfigList() {
let configs = [
Values.defaultMode: [SampleAEMConfigurations.createConfigWithoutBusinessID()],
Values.brandMode: [SampleAEMConfigurations.createConfigWithBusinessIDAndContentRule()],
Values.cpasMode: [SampleAEMConfigurations.createCpasConfig()],
]
let invocation = SampleAEMInvocations.createGeneralInvocation1()
var configList = invocation._getConfigList(Values.defaultMode, configs: configs)
XCTAssertEqual(configList.count, 1, "Should only find the default config")
configList = invocation._getConfigList(Values.brandMode, configs: configs)
XCTAssertEqual(configList.count, 2, "Should only find the brand or cpas config")
XCTAssertEqual(configList.first?.configMode, Values.cpasMode, "Should have the caps config first")
XCTAssertEqual(configList.last?.configMode, Values.brandMode, "Should have the brand config last")
}
func testAttributeEventWithValue() {
let invocation: _AEMInvocation = validInvocation
invocation.reset()
invocation._setConfig(config1)
var isAttributed = invocation.attributeEvent(
Values.test,
currency: Values.USD,
value: 10,
parameters: nil,
configs: [Values.defaultMode: [config1, config2]],
shouldUpdateCache: true
)
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,
configs: [Values.defaultMode: [config1, config2]],
shouldUpdateCache: true
)
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, [Values.purchase: [Values.USD: 10]], "Should attribute unexpected values")
}
func testAttributeUnexpectedEventWithoutValue() {
let invocation: _AEMInvocation = validInvocation
invocation.reset()
invocation._setConfig(config1)
let isAttributed = invocation.attributeEvent(
Values.test,
currency: nil,
value: nil,
parameters: nil,
configs: [Values.defaultMode: [config1, config2]],
shouldUpdateCache: true
)
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._setConfig(config1)
var isAttributed = invocation.attributeEvent(
Values.purchase,
currency: nil,
value: nil,
parameters: nil,
configs: [Values.defaultMode: [config1, config2]],
shouldUpdateCache: true
)
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,
configs: [Values.defaultMode: [config1, config2]],
shouldUpdateCache: true
)
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.createConfigWithBusinessIDAndContentRule()
let configWithoutBusinessID = SampleAEMConfigurations.createConfigWithoutBusinessID()
let invocation = _AEMInvocation(
campaignID: "test_campaign_1234",
acsToken: "test_token_12345",
acsSharedSecret: nil,
acsConfigID: nil,
businessID: "test_advertiserid_content_test",
catalogID: nil,
isTestMode: false,
hasSKAN: false,
isConversionFilteringEligible: true
)! // swiftlint:disable:this force_unwrapping
let configs = [
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",
],
configs: configs,
shouldUpdateCache: true
)
XCTAssertTrue(isAttributed, "Should attribute the event with expected parameters")
}
func testAttributeEventWithUnexpectedContent() {
let configWithBusinessID = SampleAEMConfigurations.createConfigWithBusinessIDAndContentRule()
let configWithoutBusinessID = SampleAEMConfigurations.createConfigWithoutBusinessID()
let invocation = _AEMInvocation(
campaignID: "test_campaign_1234",
acsToken: "test_token_12345",
acsSharedSecret: nil,
acsConfigID: nil,
businessID: "test_advertiserid_content_test",
catalogID: nil,
isTestMode: false,
hasSKAN: false,
isConversionFilteringEligible: true
)! // swiftlint:disable:this force_unwrapping
let configs = [
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",
],
configs: configs,
shouldUpdateCache: true
)
XCTAssertFalse(isAttributed, "Should attribute the event with expected parameters")
}
func testAttributeCpasEvent() {
let invocation = _AEMInvocation(
campaignID: "test_campaign_1234",
acsToken: "test_token_12345",
acsSharedSecret: nil,
acsConfigID: nil,
businessID: "test_advertiserid_cpas",
catalogID: nil,
isTestMode: false,
hasSKAN: false,
isConversionFilteringEligible: true
)! // swiftlint:disable:this force_unwrapping
let configs = [
Values.defaultMode: [SampleAEMConfigurations.createConfigWithoutBusinessID()],
Values.cpasMode: [SampleAEMConfigurations.createCpasConfig()],
]
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),
],
],
],
configs: configs,
shouldUpdateCache: true
)
XCTAssertTrue(
isAttributed,
"Should attribute the event"
)
XCTAssertEqual(
invocation.recordedEvents,
[Values.purchase],
"Should expect the event is updated in the cache"
)
XCTAssertEqual(
invocation.recordedValues,
[Values.purchase: [Values.USD: 1000]],
"Should expect the value is updated in the cache"
)
}
func testAttributeEventWithoutCache() {
let invocation: _AEMInvocation = validInvocation
invocation.reset()
invocation._setConfig(config1)
let isAttributed = invocation.attributeEvent(
Values.purchase,
currency: nil,
value: nil,
parameters: nil,
configs: [Values.defaultMode: [config1, config2]],
shouldUpdateCache: 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._setConfig(config1)
let isAttributed = invocation.attributeEvent(
Values.purchase,
currency: Values.USD,
value: 10,
parameters: nil,
configs: [Values.defaultMode: [config1, config2]],
shouldUpdateCache: 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._setConfig(config1)
invocation.setRecordedEvents(NSMutableSet(array: [Values.purchase, Values.unlock]))
XCTAssertFalse(
invocation.updateConversionValue(
withConfigs: [Values.defaultMode: [config1, config2]],
event: Values.purchase,
shouldBoostPriority: false
),
"Should not update conversion value"
)
invocation.setRecordedEvents(NSMutableSet(array: [Values.purchase, Values.donate]))
XCTAssertTrue(
invocation.updateConversionValue(
withConfigs: [Values.defaultMode: [config1, config2]],
event: Values.purchase,
shouldBoostPriority: false
),
"Should update conversion value"
)
XCTAssertEqual(
invocation.conversionValue,
2,
"Should update the expected conversion value"
)
invocation.setRecordedEvents(NSMutableSet(array: [Values.purchase, Values.unlock]))
invocation.setRecordedValues(NSMutableDictionary(dictionary: [Values.purchase: [Values.USD: 100]]))
XCTAssertTrue(
invocation.updateConversionValue(
withConfigs: [Values.defaultMode: [config1, config2]],
event: Values.purchase,
shouldBoostPriority: false
),
"Should update conversion value"
)
XCTAssertEqual(
invocation.conversionValue,
1,
"Should update the expected conversion value"
)
invocation.reset()
invocation.setPriority(100)
invocation.setRecordedEvents(NSMutableSet(array: [Values.purchase, Values.unlock]))
invocation.setRecordedValues(NSMutableDictionary(dictionary: [Values.purchase: [Values.USD: 100]]))
XCTAssertFalse(
invocation.updateConversionValue(
withConfigs: [Values.defaultMode: [config1, config2]],
event: Values.purchase,
shouldBoostPriority: false
),
"Should not update conversion value under priority"
)
}
func testUpdateConversionWithouValue() {
let invocation: _AEMInvocation = validInvocation
invocation.reset()
invocation._setConfig(config2)
invocation.setRecordedEvents(NSMutableSet(array: [Values.purchase]))
XCTAssertFalse(
invocation.updateConversionValue(
withConfigs: [Values.defaultMode: [config1, config2]],
event: Values.purchase,
shouldBoostPriority: false
),
"Should not update conversion value"
)
XCTAssertEqual(
invocation.conversionValue,
-1,
"Should not update the unexpected conversion value"
)
invocation.setRecordedEvents(NSMutableSet(array: [Values.purchase, Values.donate]))
XCTAssertTrue(
invocation.updateConversionValue(
withConfigs: [Values.defaultMode: [config1, config2]],
event: Values.purchase,
shouldBoostPriority: false
),
"Should update conversion value"
)
XCTAssertEqual(
invocation.conversionValue,
2,
"Should update the expected conversion value"
)
}
func testUpdateConversionWithBoostPriority() {
let config = SampleAEMConfigurations.createWithMultipleRules()
let configs = [Values.defaultMode: [config]]
let invocation = SampleAEMInvocations.createCatalogOptimizedInvocation()
let lowestPriorityRule = config.conversionValueRules.last! // swiftlint:disable:this force_unwrapping
// Set the highest priority in the conversion rules
invocation.setPriority(config.conversionValueRules.first!.priority) // swiftlint:disable:this force_unwrapping
// Add the lowest priority event
invocation.setRecordedEvents(
[lowestPriorityRule.events.first!.eventName] // swiftlint:disable:this force_unwrapping
)
XCTAssertTrue(
invocation.updateConversionValue(withConfigs: configs, 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 config = SampleAEMConfigurations.createWithMultipleRules()
let configs = [Values.defaultMode: [config]]
let invocation = SampleAEMInvocations.createCatalogOptimizedInvocation()
invocation.campaignID = "83" // The campaign id's modulo is matched to purchase's conversion value
let highestPriorityRule = config.conversionValueRules.first! // swiftlint:disable:this force_unwrapping
invocation.setPriority(42) // Set the second highest priority with boost priority in the conversion rules
invocation.setRecordedEvents([Values.purchase]) // Add the optimzied event
invocation.setRecordedValues([
Values.purchase: [Values.USD: 100],
])
XCTAssertTrue(
invocation.updateConversionValue(withConfigs: configs, 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 config = SampleAEMConfigurations.createWithMultipleRules()
let configs = [Values.defaultMode: [config]]
let invocation = SampleAEMInvocations.createCatalogOptimizedInvocation()
let lowestPriorityRule = config.conversionValueRules.last! // swiftlint:disable:this force_unwrapping
// Set the highest priority in the conversion rules
invocation.setPriority(config.conversionValueRules.first!.priority) // swiftlint:disable:this force_unwrapping
// Add the lowest priority event
invocation.setRecordedEvents(
[
Values.purchase,
lowestPriorityRule.events.first!.eventName, // swiftlint:disable:this force_unwrapping
]
)
XCTAssertFalse(
invocation.updateConversionValue(withConfigs: configs, 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(10),
"HMAC should be nil when ACS Shared Secret is nil"
)
}
func testGetHmacWithEmptyACSSecret() {
let invocation = SampleAEMInvocations.createGeneralInvocation1()
invocation.acsSharedSecret = ""
XCTAssertNil(
invocation.getHMAC(10),
"HMAC should be nil when ACS Shared Secret is an empty string"
)
}
func testGetHmacWithoutACSConfigID() {
let invocation = SampleAEMInvocations.createGeneralInvocation1()
invocation.acsConfigID = nil
XCTAssertNil(
invocation.getHMAC(10),
"HMAC should be nil when ACS config ID is nil"
)
}
func testGetHmacWithACSSecretAndACSConfigID() {
let invocation = SampleAEMInvocations.createGeneralInvocation1()
invocation.campaignID = "aaa"
invocation.acsConfigID = "abc"
invocation.acsSharedSecret =
"E_dwjTaF9-SHijRKoD5jrgJoi9pgObKEqrkxgl3iE9-mxpDn-wpseBmtlNFN2HTI5OzzTVqhBwNi2zrwt-TxCw"
invocation.conversionValue = 6
XCTAssertEqual(
invocation.getHMAC(31),
"Z65Xxo-IevEwpLYNES9QmWRlx-zPH8zxfIJPw6ofQtpDJvKWuNI93SBHlUapS1_DIVl9Ovwoa5Xo7v63zQ5_HA",
"Should generate the expected HMAC"
)
}
func testIsOptimizedEventWithoutCatalogID() {
let invocation = SampleAEMInvocations.createGeneralInvocation1()
let configs = [
Values.defaultMode: [config1],
]
XCTAssertFalse(
invocation.isOptimizedEvent(Values.purchase, configs: configs),
"Invocation without catalog ID doesn't have optimized event"
)
}
func testIsOptimizedEventWithoutExpectedEvent() {
let invocation = SampleAEMInvocations.createCatalogOptimizedInvocation()
let configs = [
Values.defaultMode: [config1],
]
XCTAssertFalse(
invocation.isOptimizedEvent(Values.donate, configs: configs),
"Event is not expected to be optimized"
)
}
func testIsOptimizedEventWithExpectedEvent() {
let invocation = SampleAEMInvocations.createCatalogOptimizedInvocation()
let configs = [
Values.defaultMode: [config1],
]
XCTAssertTrue(
invocation.isOptimizedEvent(Values.purchase, configs: configs),
"Event is expected to be optimized"
)
}
func testSecureCoding() {
XCTAssertTrue(
_AEMInvocation.supportsSecureCoding,
"AEM Invocation should support secure coding"
)
}
func testEncoding() {
let coder = TestCoder()
let invocation: _AEMInvocation = validInvocation
invocation.encode(with: coder)
XCTAssertEqual(
coder.encodedObject[Keys.campaignID] as? String,
invocation.campaignID,
"Should encode the expected campaignID with the correct key"
)
XCTAssertEqual(
coder.encodedObject[Keys.ACSToken] as? String,
invocation.acsToken,
"Should encode the expected acsToken with the correct key"
)
XCTAssertEqual(
coder.encodedObject[Keys.ACSSharedSecret] as? String,
invocation.acsSharedSecret,
"Should encode the expected ACSSharedSecret with the correct key"
)
XCTAssertEqual(
coder.encodedObject[Keys.ACSConfigID] as? String,
invocation.acsConfigID,
"Should encode the expected acsConfigID with the correct key"
)
XCTAssertEqual(
coder.encodedObject[Keys.catalogID] as? String,
invocation.catalogID,
"Should encode the expected catalogID with the correct key"
)
XCTAssertEqual(
coder.encodedObject[Keys.timestamp] as? Date,
invocation.timestamp,
"Should encode the expected timestamp with the correct key"
)
XCTAssertEqual(
coder.encodedObject[Keys.configMode] as? String,
invocation.configMode,
"Should encode the expected configMode with the correct key"
)
let configID = coder.encodedObject[Keys.configID] as? NSNumber
XCTAssertEqual(
configID?.intValue,
invocation.configID,
"Should encode the expected configID with the correct key"
)
XCTAssertEqual(
coder.encodedObject[Keys.recordedEvents] as? NSSet,
invocation.recordedEvents,
"Should encode the expected recordedEvents with the correct key"
)
XCTAssertEqual(
coder.encodedObject[Keys.recordedValues] as? NSDictionary,
invocation.recordedValues,
"Should encode the expected recordedValues with the correct key"
)
let conversionValue = coder.encodedObject[Keys.conversionValue] as? NSNumber
XCTAssertEqual(
conversionValue?.intValue,
invocation.conversionValue,
"Should encode the expected conversionValue with the correct key"
)
let priority = coder.encodedObject[Keys.priority] as? NSNumber
XCTAssertEqual(
priority?.intValue,
invocation.priority,
"Should encode the expected priority with the correct key"
)
XCTAssertEqual(
coder.encodedObject[Keys.conversionTimestamp] as? Date,
invocation.conversionTimestamp,
"Should encode the expected conversionTimestamp with the correct key"
)
let isAggregated = coder.encodedObject[Keys.isAggregated] as? NSNumber
XCTAssertEqual(
isAggregated?.boolValue,
invocation.isAggregated,
"Should encode the expected isAggregated with the correct key"
)
let hasSKAN = coder.encodedObject[Keys.hasSKAN] as? NSNumber
XCTAssertEqual(
hasSKAN?.boolValue,
invocation.hasSKAN,
"Should encode the expected hasSKAN with the correct key"
)
}
func testDecoding() {
let decoder = TestCoder()
_ = _AEMInvocation(coder: decoder)
XCTAssertTrue(
decoder.decodedObject[Keys.campaignID] is NSString.Type,
"Should decode the expected type for the campaign_id key"
)
XCTAssertTrue(
decoder.decodedObject[Keys.ACSToken] is NSString.Type,
"Should decode the expected type for the acs_token key"
)
XCTAssertTrue(
decoder.decodedObject[Keys.ACSSharedSecret] is NSString.Type,
"Should decode the expected type for the shared_secret key"
)
XCTAssertTrue(
decoder.decodedObject[Keys.ACSConfigID] is NSString.Type,
"Should decode the expected type for the acs_config_id key"
)
XCTAssertTrue(
decoder.decodedObject[Keys.businessID] is NSString.Type,
"Should decode the expected type for the advertiser_id key"
)
XCTAssertTrue(
decoder.decodedObject[Keys.catalogID] is NSString.Type,
"Should decode the expected type for the catalog_id key"
)
XCTAssertTrue(
decoder.decodedObject[Keys.timestamp] is NSDate.Type,
"Should decode the expected type for the timestamp key"
)
XCTAssertTrue(
decoder.decodedObject[Keys.configMode] is NSString.Type,
"Should decode the expected type for the config_mode key"
)
XCTAssertEqual(
decoder.decodedObject[Keys.configID] as? String,
"decodeIntegerForKey",
"Should decode the expected type for the config_id key"
)
XCTAssertTrue(
decoder.decodedObject[Keys.recordedEvents] is NSSet.Type,
"Should decode the expected type for the recorded_events key"
)
XCTAssertTrue(
decoder.decodedObject[Keys.recordedValues] is NSDictionary.Type,
"Should decode the expected type for the recorded_values key"
)
XCTAssertEqual(
decoder.decodedObject[Keys.conversionValue] as? String,
"decodeIntegerForKey",
"Should decode the expected type for the conversion_value key"
)
XCTAssertEqual(
decoder.decodedObject[Keys.priority] as? String,
"decodeIntegerForKey",
"Should decode the expected type for the priority key"
)
XCTAssertTrue(
decoder.decodedObject[Keys.conversionTimestamp] is NSDate.Type,
"Should decode the expected type for the conversion_timestamp key"
)
XCTAssertEqual(
decoder.decodedObject[Keys.isAggregated] as? String,
"decodeBoolForKey",
"Should decode the expected type for the is_aggregated key"
)
XCTAssertEqual(
decoder.decodedObject[Keys.hasSKAN] as? String,
"decodeBoolForKey",
"Should decode the expected type for the has_skan key"
)
}
}