From b7e2dab1f9587257bbdf6cf7ae8893b94616790b Mon Sep 17 00:00:00 2001 From: dirw-a11 <220655622+dirw-a11@users.noreply.github.com> Date: Wed, 23 Jul 2025 00:48:12 +0800 Subject: [PATCH] Delete FBAEMKit directory updated --- .../Configurations/FBAEMKit-Dynamic.xcconfig | 16 - .../Configurations/FBAEMKit-Static.xcconfig | 16 - .../Configurations/FBAEMKitTests.xcconfig | 24 - FBAEMKit/Configurations/Shared | 1 - .../AEMAdvertiserMultiEntryRule.swift | 68 - .../FBAEMKit/AEMAdvertiserRuleFactory.swift | 177 --- .../FBAEMKit/AEMAdvertiserRuleMatching.swift | 13 - .../FBAEMKit/AEMAdvertiserRuleOperator.swift | 34 - .../FBAEMKit/AEMAdvertiserRuleProviding.swift | 19 - .../AEMAdvertiserSingleEntryRule.swift | 335 ----- FBAEMKit/FBAEMKit/AEMConfiguration.swift | 183 --- FBAEMKit/FBAEMKit/AEMEvent.swift | 85 -- FBAEMKit/FBAEMKit/AEMInvocation.swift | 510 ------- FBAEMKit/FBAEMKit/AEMNetworker.swift | 198 --- FBAEMKit/FBAEMKit/AEMNetworking.swift | 23 - FBAEMKit/FBAEMKit/AEMReporter.swift | 982 -------------- FBAEMKit/FBAEMKit/AEMRequestBody.swift | 99 -- FBAEMKit/FBAEMKit/AEMRule.swift | 147 -- FBAEMKit/FBAEMKit/AEMSettings.swift | 26 - FBAEMKit/FBAEMKit/AEMUtility.swift | 120 -- FBAEMKit/FBAEMKit/DependentAsType.swift | 45 - FBAEMKit/FBAEMKit/Info.plist | 26 - .../FBAEMKit/MissingDependenciesError.swift | 19 - FBAEMKit/FBAEMKit/PrivacyInfo.xcprivacy | 21 - FBAEMKit/FBAEMKit/SKAdNetworkReporting.swift | 21 - .../AEMAdvertiserMultiEntryRuleTests.swift | 202 --- .../AEMAdvertiserRuleFactoryTests.swift | 311 ----- .../AEMAdvertiserSingleEntryRuleTests.swift | 488 ------- .../FBAEMKitTests/AEMConfigurationTests.swift | 355 ----- FBAEMKit/FBAEMKitTests/AEMEventTests.swift | 185 --- .../FBAEMKitTests/AEMInvocationTests.swift | 1194 ----------------- FBAEMKit/FBAEMKitTests/AEMReporterTests.swift | 1132 ---------------- .../FBAEMKitTests/AEMRequestBodyTests.swift | 93 -- FBAEMKit/FBAEMKitTests/AEMRuleTests.swift | 358 ----- FBAEMKit/FBAEMKitTests/AEMSettingsTests.swift | 57 - FBAEMKit/FBAEMKitTests/AEMUtilityTests.swift | 190 --- .../FBAEMKitTests/DependentAsTypeTests.swift | 165 --- .../Helpers/SampleAEMConfigurations.swift | 217 --- .../FBAEMKitTests/Helpers/SampleAEMData.swift | 181 --- .../Helpers/SampleAEMError.swift | 9 - .../Helpers/SampleAEMInvocations.swift | 96 -- .../Helpers/SampleAEMMultiEntryRules.swift | 18 - .../Helpers/SampleAEMSingleEntryRules.swift | 69 - .../FBAEMKitTests/Helpers/TestBundle.swift | 19 - .../Helpers/TestInvocation.swift | 82 -- FBAEMKit/FBAEMKitTests/Info.plist | 31 - .../FBAEMKitTests/PrivacyManifestTests.swift | 78 -- FBAEMKit/project.yml | 78 -- 48 files changed, 8816 deletions(-) delete mode 100644 FBAEMKit/Configurations/FBAEMKit-Dynamic.xcconfig delete mode 100644 FBAEMKit/Configurations/FBAEMKit-Static.xcconfig delete mode 100644 FBAEMKit/Configurations/FBAEMKitTests.xcconfig delete mode 120000 FBAEMKit/Configurations/Shared delete mode 100644 FBAEMKit/FBAEMKit/AEMAdvertiserMultiEntryRule.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMAdvertiserRuleFactory.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMAdvertiserRuleMatching.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMAdvertiserRuleOperator.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMAdvertiserRuleProviding.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMAdvertiserSingleEntryRule.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMConfiguration.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMEvent.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMInvocation.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMNetworker.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMNetworking.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMReporter.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMRequestBody.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMRule.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMSettings.swift delete mode 100644 FBAEMKit/FBAEMKit/AEMUtility.swift delete mode 100644 FBAEMKit/FBAEMKit/DependentAsType.swift delete mode 100644 FBAEMKit/FBAEMKit/Info.plist delete mode 100644 FBAEMKit/FBAEMKit/MissingDependenciesError.swift delete mode 100644 FBAEMKit/FBAEMKit/PrivacyInfo.xcprivacy delete mode 100644 FBAEMKit/FBAEMKit/SKAdNetworkReporting.swift delete mode 100644 FBAEMKit/FBAEMKitTests/AEMAdvertiserMultiEntryRuleTests.swift delete mode 100644 FBAEMKit/FBAEMKitTests/AEMAdvertiserRuleFactoryTests.swift delete mode 100644 FBAEMKit/FBAEMKitTests/AEMAdvertiserSingleEntryRuleTests.swift delete mode 100644 FBAEMKit/FBAEMKitTests/AEMConfigurationTests.swift delete mode 100644 FBAEMKit/FBAEMKitTests/AEMEventTests.swift delete mode 100644 FBAEMKit/FBAEMKitTests/AEMInvocationTests.swift delete mode 100644 FBAEMKit/FBAEMKitTests/AEMReporterTests.swift delete mode 100644 FBAEMKit/FBAEMKitTests/AEMRequestBodyTests.swift delete mode 100644 FBAEMKit/FBAEMKitTests/AEMRuleTests.swift delete mode 100644 FBAEMKit/FBAEMKitTests/AEMSettingsTests.swift delete mode 100644 FBAEMKit/FBAEMKitTests/AEMUtilityTests.swift delete mode 100644 FBAEMKit/FBAEMKitTests/DependentAsTypeTests.swift delete mode 100644 FBAEMKit/FBAEMKitTests/Helpers/SampleAEMConfigurations.swift delete mode 100644 FBAEMKit/FBAEMKitTests/Helpers/SampleAEMData.swift delete mode 100644 FBAEMKit/FBAEMKitTests/Helpers/SampleAEMError.swift delete mode 100644 FBAEMKit/FBAEMKitTests/Helpers/SampleAEMInvocations.swift delete mode 100644 FBAEMKit/FBAEMKitTests/Helpers/SampleAEMMultiEntryRules.swift delete mode 100644 FBAEMKit/FBAEMKitTests/Helpers/SampleAEMSingleEntryRules.swift delete mode 100644 FBAEMKit/FBAEMKitTests/Helpers/TestBundle.swift delete mode 100644 FBAEMKit/FBAEMKitTests/Helpers/TestInvocation.swift delete mode 100644 FBAEMKit/FBAEMKitTests/Info.plist delete mode 100644 FBAEMKit/FBAEMKitTests/PrivacyManifestTests.swift delete mode 100644 FBAEMKit/project.yml diff --git a/FBAEMKit/Configurations/FBAEMKit-Dynamic.xcconfig b/FBAEMKit/Configurations/FBAEMKit-Dynamic.xcconfig deleted file mode 100644 index 8669643618..0000000000 --- a/FBAEMKit/Configurations/FBAEMKit-Dynamic.xcconfig +++ /dev/null @@ -1,16 +0,0 @@ -// 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. - -#include "Shared/Platform/iOS.xcconfig" -#include "Shared/Target/DynamicFramework.xcconfig" -#include "Shared/Version.xcconfig" - -PRODUCT_NAME = FBAEMKit -PRODUCT_BUNDLE_IDENTIFIER = com.facebook.sdk.FBAEMKit - -CURRENT_PROJECT_VERSION = $(FBSDK_PROJECT_VERSION) - -INFOPLIST_FILE = $(SRCROOT)/FBAEMKit/Info.plist diff --git a/FBAEMKit/Configurations/FBAEMKit-Static.xcconfig b/FBAEMKit/Configurations/FBAEMKit-Static.xcconfig deleted file mode 100644 index fe58c56f2e..0000000000 --- a/FBAEMKit/Configurations/FBAEMKit-Static.xcconfig +++ /dev/null @@ -1,16 +0,0 @@ -// 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. - -#include "Shared/Platform/iOS.xcconfig" -#include "Shared/Target/StaticFramework.xcconfig" -#include "Shared/Version.xcconfig" - -PRODUCT_NAME = FBAEMKit -PRODUCT_BUNDLE_IDENTIFIER = com.facebook.sdk.FBAEMKit - -CURRENT_PROJECT_VERSION = $(FBSDK_PROJECT_VERSION) - -INFOPLIST_FILE = $(SRCROOT)/FBAEMKit/Info.plist diff --git a/FBAEMKit/Configurations/FBAEMKitTests.xcconfig b/FBAEMKit/Configurations/FBAEMKitTests.xcconfig deleted file mode 100644 index c265f1f405..0000000000 --- a/FBAEMKit/Configurations/FBAEMKitTests.xcconfig +++ /dev/null @@ -1,24 +0,0 @@ -// 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. - -#include "Shared/Platform/iOS.xcconfig" -#include "Shared/Target/LogicTests.xcconfig" - -PRODUCT_NAME = FBAEMKitTests -PRODUCT_BUNDLE_IDENTIFIER = com.facebook.sdk.FBAEMKitTests - -INFOPLIST_FILE = $(SRCROOT)/FBAEMKitTests/Info.plist - -HEADER_SEARCH_PATHS = $(inherited) $(BUILT_PRODUCTS_DIR) -LIBRARY_SEARCH_PATHS = $(inherited) $(BUILT_PRODUCTS_DIR) - -// These were overridden at the project level. Keeping them -// here until we know if we need them specified this way. -CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES -ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES -LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks -CLANG_ENABLE_MODULES = YES -SWIFT_OPTIMIZATION_LEVEL = -Onone diff --git a/FBAEMKit/Configurations/Shared b/FBAEMKit/Configurations/Shared deleted file mode 120000 index 703e2bdeb2..0000000000 --- a/FBAEMKit/Configurations/Shared +++ /dev/null @@ -1 +0,0 @@ -../../Configurations \ No newline at end of file diff --git a/FBAEMKit/FBAEMKit/AEMAdvertiserMultiEntryRule.swift b/FBAEMKit/FBAEMKit/AEMAdvertiserMultiEntryRule.swift deleted file mode 100644 index 1ba997c629..0000000000 --- a/FBAEMKit/FBAEMKit/AEMAdvertiserMultiEntryRule.swift +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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. - */ - -import Foundation - -final class AEMAdvertiserMultiEntryRule: NSObject, AEMAdvertiserRuleMatching, NSSecureCoding { - - enum CodingKeys: String, CodingKey { - case `operator` - case rules - } - - let `operator`: AEMAdvertiserRuleOperator - let rules: [AEMAdvertiserRuleMatching] - - init(with operator: AEMAdvertiserRuleOperator, rules: [AEMAdvertiserRuleMatching]) { - self.operator = `operator` - self.rules = rules - } - - // MARK: - AEMAdvertiserRuleMatching - - func isMatchedEventParameters(_ eventParams: [String: Any]?) -> Bool { - var isMatched = `operator` != .or - for rule in rules { - let doesSubruleMatch = rule.isMatchedEventParameters(eventParams) - if `operator` == .and { - isMatched = isMatched && doesSubruleMatch - } - if `operator` == .or { - isMatched = isMatched || doesSubruleMatch - } - if `operator` == .not { - isMatched = isMatched && !doesSubruleMatch - } - } - return isMatched - } - - // MARK: - NSCoding - - static var supportsSecureCoding: Bool { true } - - convenience init?(coder: NSCoder) { - let `operator` = AEMAdvertiserRuleOperator(rawValue: coder.decodeInteger(forKey: CodingKeys.operator.rawValue)) - let classes = [ - NSArray.self, - AEMAdvertiserMultiEntryRule.self, - AEMAdvertiserSingleEntryRule.self, - ] - let rules = coder.decodeObject(of: classes, forKey: CodingKeys.rules.rawValue) as? [AEMAdvertiserRuleMatching] - guard let `operator` = `operator`, - let rules = rules else { - return nil - } - self.init(with: `operator`, rules: rules) - } - - func encode(with coder: NSCoder) { - coder.encode(`operator`.rawValue, forKey: CodingKeys.operator.rawValue) - coder.encode(rules, forKey: CodingKeys.rules.rawValue) - } -} diff --git a/FBAEMKit/FBAEMKit/AEMAdvertiserRuleFactory.swift b/FBAEMKit/FBAEMKit/AEMAdvertiserRuleFactory.swift deleted file mode 100644 index a9f6915a75..0000000000 --- a/FBAEMKit/FBAEMKit/AEMAdvertiserRuleFactory.swift +++ /dev/null @@ -1,177 +0,0 @@ -/* - * 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. - */ - -import FBSDKCoreKit_Basics -import Foundation - -final class AEMAdvertiserRuleFactory: AEMAdvertiserRuleProviding { - - // MARK: - AEMAdvertiserRuleProviding - - func createRule(json: String?) -> AEMAdvertiserRuleMatching? { - guard let json = json, - let data = json.data(using: .utf8) - else { - return nil - } - - do { - let rule = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] ?? [:] - return createRule(dictionary: rule) - } catch { - print("Fail to parse Advertiser Rules with JSON") - return nil - } - } - - func createRule(dictionary: [String: Any]) -> AEMAdvertiserRuleMatching? { - let `operator` = getOperator(from: dictionary) - - if isOperatorForMultiEntryRule(`operator`) { - return createMultiEntryRule(from: dictionary) - } else { - return createSingleEntryRule(from: dictionary) - } - } - - // MARK: - Internal - - func createMultiEntryRule(from dictionary: [String: Any]) -> AEMAdvertiserMultiEntryRule? { - guard !dictionary.isEmpty, - let opString = primaryKey(for: dictionary) - else { - return nil - } - - let `operator` = getOperator(from: dictionary) - - if !isOperatorForMultiEntryRule(`operator`) { - return nil - } - - let subrules: [[String: Any]] = dictionary[opString] as? [[String: Any]] ?? [] - var rules: [AEMAdvertiserRuleMatching] = [] - - for subrule in subrules { - guard let entryRule = createRule(dictionary: subrule) else { - return nil - } - rules.append(entryRule) - } - - guard !rules.isEmpty else { - return nil - } - - return AEMAdvertiserMultiEntryRule(with: `operator`, rules: rules) - } - - func createSingleEntryRule(from dictionary: [String: Any]) -> AEMAdvertiserSingleEntryRule? { - guard let paramKey = primaryKey(for: dictionary) else { - return nil - } - - let rawRule: [String: Any] = dictionary[paramKey] as? [String: Any] ?? [:] - - guard let encodedOperator = primaryKey(for: rawRule) else { - return nil - } - - let `operator`: AEMAdvertiserRuleOperator = getOperator(from: rawRule) - - var linguisticCondition: String? - var numericalCondition: NSNumber? - var arrayCondition: [String]? - - switch `operator` { - case .contains, - .notContains, - .startsWith, - .caseInsensitiveContains, - .caseInsensitiveNotContains, - .caseInsensitiveStartsWith, - .regexMatch, - .equal, - .notEqual: - linguisticCondition = rawRule[encodedOperator] as? String - - case .lessThan, - .lessThanOrEqual, - .greaterThan, - .greaterThanOrEqual: - numericalCondition = rawRule[encodedOperator] as? NSNumber - - case .caseInsensitiveIsAny, .caseInsensitiveIsNotAny, .isAny, .isNotAny: - arrayCondition = rawRule[encodedOperator] as? [String] - - case .unknown: - return nil - default: - return nil - } - - if linguisticCondition != nil || numericalCondition != nil || arrayCondition?.isEmpty == false { - return AEMAdvertiserSingleEntryRule( - with: `operator`, - paramKey: paramKey, - linguisticCondition: linguisticCondition, - numericalCondition: numericalCondition, - arrayCondition: arrayCondition - ) - } else { - return nil - } - } - - func primaryKey(for rule: [String: Any]) -> String? { - rule.keys.first - } - - func getOperator(from rule: [String: Any]) -> AEMAdvertiserRuleOperator { - guard let key = primaryKey(for: rule) else { - return .unknown - } - - let operatorKeys: [String] = [ - // UNCRUSTIFY_FORMAT_OFF - "unknown", // FBAEMAdvertiserRuleOperatorUnknown - "and", // FBAEMAdvertiserRuleOperatorAnd - "or", // FBAEMAdvertiserRuleOperatorOr - "not", // FBAEMAdvertiserRuleOperatorNot - "contains", // FBAEMAdvertiserRuleOperatorContains - "not_contains", // FBAEMAdvertiserRuleOperatorNotContains - "starts_with", // FBAEMAdvertiserRuleOperatorStartsWith - "i_contains", // FBAEMAdvertiserRuleOperatorCaseInsensitiveContains - "i_not_contains", // FBAEMAdvertiserRuleOperatorCaseInsensitiveNotContains - "i_starts_with", // FBAEMAdvertiserRuleOperatorCaseInsensitiveStartsWith - "regex_match", // FBAEMAdvertiserRuleOperatorRegexMatch - "eq", // FBAEMAdvertiserRuleOperatorEqual - "neq", // FBAEMAdvertiserRuleOperatorNotEqual - "lt", // FBAEMAdvertiserRuleOperatorLessThan - "lte", // FBAEMAdvertiserRuleOperatorLessThanOrEqual - "gt", // FBAEMAdvertiserRuleOperatorGreaterThan - "gte", // FBAEMAdvertiserRuleOperatorGreaterThanOrEqual - "i_is_any", // FBAEMAdvertiserRuleOperatorCaseInsensitiveIsAny - "i_is_not_any", // FBAEMAdvertiserRuleOperatorCaseInsensitiveIsNotAny - "is_any", // FBAEMAdvertiserRuleOperatorIsAny - "is_not_any", // FBAEMAdvertiserRuleOperatorIsNotAny - // UNCRUSTIFY_FORMAT_ON - ] - - guard let index = operatorKeys.firstIndex(of: key.lowercased()) else { - return .unknown - } - - return AEMAdvertiserRuleOperator(rawValue: index) ?? .unknown - } - - func isOperatorForMultiEntryRule(_ operator: AEMAdvertiserRuleOperator) -> Bool { - let operators: [AEMAdvertiserRuleOperator] = [.and, .or, .not] - return operators.contains(`operator`) - } -} diff --git a/FBAEMKit/FBAEMKit/AEMAdvertiserRuleMatching.swift b/FBAEMKit/FBAEMKit/AEMAdvertiserRuleMatching.swift deleted file mode 100644 index 6c4c0f7531..0000000000 --- a/FBAEMKit/FBAEMKit/AEMAdvertiserRuleMatching.swift +++ /dev/null @@ -1,13 +0,0 @@ -/* - * 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. - */ - -import Foundation - -protocol AEMAdvertiserRuleMatching { - func isMatchedEventParameters(_ eventParams: [String: Any]?) -> Bool -} diff --git a/FBAEMKit/FBAEMKit/AEMAdvertiserRuleOperator.swift b/FBAEMKit/FBAEMKit/AEMAdvertiserRuleOperator.swift deleted file mode 100644 index fa8944841a..0000000000 --- a/FBAEMKit/FBAEMKit/AEMAdvertiserRuleOperator.swift +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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. - */ - -enum AEMAdvertiserRuleOperator: Int { - case unknown = 0 - // Multi Entry Rule Operator - case and - // swiftlint:disable:next identifier_name - case or - case not - // Single Entry Rule Operator - case contains - case notContains - case startsWith - case caseInsensitiveContains - case caseInsensitiveNotContains - case caseInsensitiveStartsWith - case regexMatch - case equal - case notEqual - case lessThan - case lessThanOrEqual - case greaterThan - case greaterThanOrEqual - case caseInsensitiveIsAny - case caseInsensitiveIsNotAny - case isAny - case isNotAny -} diff --git a/FBAEMKit/FBAEMKit/AEMAdvertiserRuleProviding.swift b/FBAEMKit/FBAEMKit/AEMAdvertiserRuleProviding.swift deleted file mode 100644 index 8afea2d29a..0000000000 --- a/FBAEMKit/FBAEMKit/AEMAdvertiserRuleProviding.swift +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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. - */ - -import Foundation - -/** - Describes anything that can provide instances of `AEMAdvertiserRuleMatching` - */ -protocol AEMAdvertiserRuleProviding { - - func createRule(json: String?) -> AEMAdvertiserRuleMatching? - - func createRule(dictionary: [String: Any]) -> AEMAdvertiserRuleMatching? -} diff --git a/FBAEMKit/FBAEMKit/AEMAdvertiserSingleEntryRule.swift b/FBAEMKit/FBAEMKit/AEMAdvertiserSingleEntryRule.swift deleted file mode 100644 index 590865af3a..0000000000 --- a/FBAEMKit/FBAEMKit/AEMAdvertiserSingleEntryRule.swift +++ /dev/null @@ -1,335 +0,0 @@ -/* - * 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. - */ - -import FBSDKCoreKit_Basics -import Foundation - -final class AEMAdvertiserSingleEntryRule: NSObject, NSSecureCoding, AEMAdvertiserRuleMatching { - - var `operator`: AEMAdvertiserRuleOperator - let paramKey: String - let linguisticCondition: String? - let numericalCondition: Double? - let arrayCondition: [String]? - - private enum Keys { - static let `operator` = "operator" - static let param = "param_key" - static let stringValue = "string_value" - static let numberValue = "number_value" - static let arrayValue = "array_value" - } - - private enum Delimeter { - static let param = "." - static let asterisk = "[*]" - } - - // MRAK: - Init - - init( - operator: AEMAdvertiserRuleOperator, - paramKey: String, - linguisticCondition: String?, - numericalCondition: Double?, - arrayCondition: [String]? - ) { - self.operator = `operator` - self.paramKey = paramKey - self.linguisticCondition = linguisticCondition - self.numericalCondition = numericalCondition - self.arrayCondition = arrayCondition - super.init() - } - - convenience init( - with operator: AEMAdvertiserRuleOperator, - paramKey: String, - linguisticCondition: String?, - numericalCondition: NSNumber?, - arrayCondition: [String]? - ) { - self.init( - operator: `operator`, - paramKey: paramKey, - linguisticCondition: linguisticCondition, - numericalCondition: numericalCondition?.doubleValue, - arrayCondition: arrayCondition - ) - } - - // MARK: - AEMAdvertiserRuleMatching - - func isMatchedEventParameters(_ eventParams: [String: Any]?) -> Bool { - let paramPath = paramKey.components(separatedBy: Delimeter.param) - return isMatchedEventParameters(eventParams: eventParams, paramPath: paramPath) - } - - func isMatchedEventParameters(eventParams: [String: Any]?, paramPath: [String]) -> Bool { - guard let eventParams = eventParams, !eventParams.isEmpty else { - return false - } - let param = paramPath.first - if let param = param, - param.hasSuffix(Delimeter.asterisk) == true { - return isMatched(withAsteriskParam: param, eventParameters: eventParams, paramPath: paramPath) - } - - // if data does not contain the key, we should return false directly. - guard let param = param, - eventParams.keys.contains(param) else { - return false - } - - // Apply operator rule if the last param is reached - if paramPath.count == 1 { - var stringValue: String? - var numericalValue: Double? - switch `operator` { - case .contains, - .notContains, - .startsWith, - .caseInsensitiveContains, - .caseInsensitiveNotContains, - .caseInsensitiveStartsWith, - .regexMatch, - .equal, - .notEqual, - .caseInsensitiveIsAny, - .caseInsensitiveIsNotAny, - .isAny, - .isNotAny: - stringValue = eventParams[param] as? String - - case .lessThan, - .lessThanOrEqual, - .greaterThan, - .greaterThanOrEqual: - numericalValue = eventParams[param] as? Double - - default: - break - } - - return isMatched(withStringValue: stringValue, numericalValue: numericalValue) - } - - let subParams = eventParams[param] as? [String: Any] - let subParamPath = Array(paramPath.dropFirst()) - return isMatchedEventParameters(eventParams: subParams, paramPath: subParamPath) - } - - func isMatched(withAsteriskParam param: String, eventParameters: [String: Any], paramPath: [String]) -> Bool { - let length = param.count - Delimeter.asterisk.count - let paramSubstring = String(param[param.startIndex ..< param.index(param.startIndex, offsetBy: length)]) - let items = eventParameters[paramSubstring] as? [Any] ?? [] - if items.isEmpty || paramPath.count < 2 { - return false - } - - var isMatched = false - let subParamPath = Array(paramPath.dropFirst()) - for item in items { - isMatched = isMatchedEventParameters(eventParams: item as? [String: Any], paramPath: subParamPath) - if isMatched { - break - } - } - return isMatched - } - - // swiftlint:disable:next cyclomatic_complexity - func isMatched(withStringValue stringValue: String?, numericalValue: Double?) -> Bool { - var isMatched = false - switch `operator` { - case .contains: - if let stringValue = stringValue, - let linguisticCondition = linguisticCondition, - stringValue.lowercased().contains(linguisticCondition.lowercased()) { - isMatched = true - } - - case .notContains: - if let stringValue = stringValue, - let linguisticCondition = linguisticCondition { - isMatched = !stringValue.lowercased().contains(linguisticCondition.lowercased()) - } - - case .startsWith: - if let stringValue = stringValue, - let linguisticCondition = linguisticCondition { - isMatched = stringValue.lowercased().hasPrefix(linguisticCondition.lowercased()) - } - - case .caseInsensitiveContains: - if let stringValue = stringValue, - let linguisticCondition = linguisticCondition, - stringValue.lowercased().contains(linguisticCondition.lowercased()) { - isMatched = true - } - - case .caseInsensitiveNotContains: - if let stringValue = stringValue, - let linguisticCondition = linguisticCondition { - isMatched = !stringValue.lowercased().contains(linguisticCondition.lowercased()) - } - - case .caseInsensitiveStartsWith: - if let stringValue = stringValue, - let linguisticCondition = linguisticCondition, - stringValue.lowercased().hasPrefix(linguisticCondition.lowercased()) { - isMatched = true - } - - case .regexMatch: - if let stringValue = stringValue { - isMatched = isRegexMatch(stringValue) - } - - case .equal: - if let stringValue = stringValue, - let linguisticCondition = linguisticCondition, - stringValue.lowercased() == linguisticCondition.lowercased() { - isMatched = true - } - - case .notEqual: - if let stringValue = stringValue, - let linguisticCondition = linguisticCondition { - isMatched = stringValue.lowercased() != linguisticCondition.lowercased() - } - - case .caseInsensitiveIsAny: - if let stringValue = stringValue { - isMatched = isAny(of: arrayCondition ?? [], stringValue: stringValue, ignoreCase: true) - } - - case .caseInsensitiveIsNotAny: - if let stringValue = stringValue { - return !isAny(of: arrayCondition ?? [], stringValue: stringValue, ignoreCase: true) - } - - case .isAny: - if let stringValue = stringValue { - return isAny(of: arrayCondition ?? [], stringValue: stringValue, ignoreCase: false) - } - - case .isNotAny: - if let stringValue = stringValue, - !isAny(of: arrayCondition ?? [], stringValue: stringValue, ignoreCase: false) { - isMatched = true - } - - case .lessThan: - if let numericalValue = numericalValue, - let numericalCondition = numericalCondition { - isMatched = numericalValue < numericalCondition - } - - case .lessThanOrEqual: - if let numericalValue = numericalValue, - let numericalCondition = numericalCondition, - numericalValue <= numericalCondition { - isMatched = true - } - - case .greaterThan: - if let numericalValue = numericalValue, - let numericalCondition = numericalCondition, - numericalValue > numericalCondition { - isMatched = true - } - - case .greaterThanOrEqual: - if let numericalValue = numericalValue, - let condition = numericalCondition, - numericalValue >= condition { - isMatched = true - } - default: - break - } - return isMatched - } - - func isRegexMatch(_ stringValue: String) -> Bool { - guard let linguisticCondition = linguisticCondition, !linguisticCondition.isEmpty else { - return false - } - do { - let regex = try NSRegularExpression(pattern: linguisticCondition, options: .allowCommentsAndWhitespace) - let range = NSRange(location: 0, length: stringValue.count) - let matches = regex.matches(in: stringValue, options: .anchored, range: range) - return !matches.isEmpty - } catch { - return false - } - } - - func isAny(of arrayCondition: [String], stringValue: String, ignoreCase: Bool) -> Bool { - var set = Set() - for item in arrayCondition { - if ignoreCase { - set.insert(item.lowercased()) - } else { - set.insert(item) - } - } - return set.contains(ignoreCase ? stringValue.lowercased() : stringValue) - } - - // MARK: - NSCoding - - static var supportsSecureCoding = true - - init?(coder: NSCoder) { - let operatorValue = coder.decodeInteger(forKey: Keys.operator) - guard let `operator` = AEMAdvertiserRuleOperator(rawValue: operatorValue), - let paramKey = coder.decodeObject(of: NSString.self, forKey: Keys.param), - let linguisticCondition = coder.decodeObject(of: NSString.self, forKey: Keys.stringValue), - let numericalCondition = coder.decodeObject(of: NSNumber.self, forKey: Keys.numberValue) else { - return nil - } - let arrayCondition = coder.decodeObject(of: [NSArray.self, NSString.self], forKey: Keys.arrayValue) as? [String] - - self.operator = `operator` - self.paramKey = paramKey as String - self.linguisticCondition = linguisticCondition as String - self.numericalCondition = numericalCondition.doubleValue - self.arrayCondition = arrayCondition - super.init() - } - - func encode(with coder: NSCoder) { - coder.encode(`operator`.rawValue, forKey: Keys.operator) - coder.encode(paramKey, forKey: Keys.param) - coder.encode(linguisticCondition, forKey: Keys.stringValue) - coder.encode(numericalCondition, forKey: Keys.numberValue) - coder.encode(arrayCondition, forKey: Keys.arrayValue) - } - - override func isEqual(_ object: Any?) -> Bool { - if let rule = object as? AEMAdvertiserSingleEntryRule { - let isOpEqual = self.operator == rule.operator - let isParamKeyEqual = paramKey == rule.paramKey - let isLinguisticConditionEqual = linguisticCondition == rule.linguisticCondition - var isArrayConditionEqual = false - if let array1 = arrayCondition { - let array2 = rule.arrayCondition - isArrayConditionEqual = array1 == array2 - } else { - isArrayConditionEqual = rule.arrayCondition == nil - } - let isNumericConditionEqual = ((numericalCondition == nil && rule.numericalCondition == nil) - || (numericalCondition == rule.numericalCondition) == true) - return isOpEqual && isParamKeyEqual && isLinguisticConditionEqual - && isArrayConditionEqual && isNumericConditionEqual - } - return false - } -} diff --git a/FBAEMKit/FBAEMKit/AEMConfiguration.swift b/FBAEMKit/FBAEMKit/AEMConfiguration.swift deleted file mode 100644 index d3af07fc82..0000000000 --- a/FBAEMKit/FBAEMKit/AEMConfiguration.swift +++ /dev/null @@ -1,183 +0,0 @@ -/* - * 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. - */ - -import Foundation - -final class AEMConfiguration: NSObject, NSSecureCoding { - - enum CodingKeys: String, CodingKey { - case defaultCurrency = "default_currency" - case cutoffTime = "cutoff_time" - case conversionValueRules = "conversion_value_rules" - case validFrom = "valid_from" - case mode = "config_mode" - case advertiserID = "advertiser_id" - case businessID = "business_id" - case paramRule = "param_rule" - } - - private(set) var cutoffTime: Int - - /// The UNIX timestamp of configuration's valid date and works as a unqiue identifier of the configuration - private(set) var validFrom: Int - private(set) var defaultCurrency: String - private(set) var mode: String - private(set) var businessID: String? - private(set) var matchingRule: AEMAdvertiserRuleMatching? - private(set) var conversionValueRules: [AEMRule] - private(set) var eventSet: Set - private(set) var currencySet: Set - - private(set) static var ruleProvider: AEMAdvertiserRuleProviding? - - static func configure(withRuleProvider ruleProvider: AEMAdvertiserRuleProviding) { - self.ruleProvider = ruleProvider - } - - init?(json dict: [String: Any]?) { - guard let dict = dict else { return nil } - - guard let defaultCurrency = dict[CodingKeys.defaultCurrency.rawValue] as? String, - let cutoffTime = dict[CodingKeys.cutoffTime.rawValue] as? Int, - let validFrom = dict[CodingKeys.validFrom.rawValue] as? Int, - let mode = dict[CodingKeys.mode.rawValue] as? String - else { return nil } - - let businessID = dict[CodingKeys.advertiserID.rawValue] as? String - let paramRuleJson = dict[CodingKeys.paramRule.rawValue] as? String - let matchingRule = AEMConfiguration.ruleProvider?.createRule(json: paramRuleJson) - guard let rules = AEMConfiguration.parseRules(dict[CodingKeys.conversionValueRules.rawValue] as? [[String: Any]]), - !rules.isEmpty, - businessID == nil || matchingRule != nil else { return nil } - - self.validFrom = validFrom - self.cutoffTime = cutoffTime - self.validFrom = validFrom - self.businessID = businessID - self.matchingRule = matchingRule - self.defaultCurrency = defaultCurrency - self.mode = mode - conversionValueRules = rules - eventSet = AEMConfiguration.getEventSet(from: conversionValueRules) - currencySet = AEMConfiguration.getCurrencySet(from: conversionValueRules) - } - - private init( - defaultCurrency: String, - cutoffTime: Int, - validFrom: Int, - mode: String, - businessID: String?, - matchingRule: AEMAdvertiserRuleMatching?, - conversionValueRules: [AEMRule] - ) { - self.defaultCurrency = defaultCurrency - self.cutoffTime = cutoffTime - self.validFrom = validFrom - self.mode = mode - self.businessID = businessID - self.matchingRule = matchingRule - self.conversionValueRules = conversionValueRules - eventSet = AEMConfiguration.getEventSet(from: self.conversionValueRules) - currencySet = AEMConfiguration.getCurrencySet(from: self.conversionValueRules) - } - - static func parseRules(_ rules: [[String: Any]]?) -> [AEMRule]? { - guard let rules = rules, - !rules.isEmpty else { return nil } - - var parsedRules: [AEMRule] = [] - for ruleEntry in rules { - guard let rule = AEMRule(json: ruleEntry) else { return nil } - - parsedRules.append(rule) - } - // Sort the rules in descending priority order - parsedRules.sort { obj1, obj2 in - obj1.priority > obj2.priority - } - return parsedRules - } - - static func getEventSet(from rules: [AEMRule]) -> Set { - var eventSet: Set = [] - for rule in rules { - for event in rule.events { - eventSet.insert(event.eventName) - } - } - return eventSet - } - - static func getCurrencySet(from rules: [AEMRule]) -> Set { - var currencySet: Set = [] - for rule in rules { - for event in rule.events { - if let currencyValueDict = event.values { - for currency in currencyValueDict.keys { - currencySet.insert(currency.uppercased()) - } - } - } - } - return currencySet - } - - func isSame(validFrom: Int, businessID: String?) -> Bool { - (validFrom == self.validFrom) && isSameBusinessID(businessID) - } - - func isSameBusinessID(_ businessID: String?) -> Bool { - businessID == self.businessID - } - - // MARK: NSSecureCoding - - func encode(with coder: NSCoder) { - coder.encode(defaultCurrency, forKey: CodingKeys.defaultCurrency.rawValue) - coder.encode(cutoffTime, forKey: CodingKeys.cutoffTime.rawValue) - coder.encode(validFrom, forKey: CodingKeys.validFrom.rawValue) - coder.encode(mode, forKey: CodingKeys.mode.rawValue) - coder.encode(businessID, forKey: CodingKeys.businessID.rawValue) - coder.encode(matchingRule, forKey: CodingKeys.paramRule.rawValue) - coder.encode(conversionValueRules, forKey: CodingKeys.conversionValueRules.rawValue) - } - - convenience init?(coder: NSCoder) { - let defaultCurrency = coder.decodeObject( - of: NSString.self, forKey: CodingKeys.defaultCurrency.rawValue - ) as String? ?? "" - let cutoffTime = coder.decodeInteger(forKey: CodingKeys.cutoffTime.rawValue) - let validFrom = coder.decodeInteger(forKey: CodingKeys.validFrom.rawValue) - let mode = coder.decodeObject( - of: NSString.self, - forKey: CodingKeys.mode.rawValue - ) as String? ?? "" - let businessID = coder.decodeObject(of: NSString.self, forKey: CodingKeys.businessID.rawValue) as String? - let matchingRule = coder.decodeObject( - of: [NSArray.self, AEMAdvertiserMultiEntryRule.self, AEMAdvertiserSingleEntryRule.self], - forKey: CodingKeys.paramRule.rawValue - ) as? AEMAdvertiserRuleMatching - guard let rules = coder.decodeObject( - of: [NSArray.self, AEMRule.self, AEMEvent.self], - forKey: CodingKeys.conversionValueRules.rawValue - ) as? [AEMRule] else { return nil } - - self.init( - defaultCurrency: defaultCurrency, - cutoffTime: cutoffTime, - validFrom: validFrom, - mode: mode, - businessID: businessID, - matchingRule: matchingRule, - conversionValueRules: rules - ) - } - - static var supportsSecureCoding: Bool { true } -} diff --git a/FBAEMKit/FBAEMKit/AEMEvent.swift b/FBAEMKit/FBAEMKit/AEMEvent.swift deleted file mode 100644 index 827d4f44ff..0000000000 --- a/FBAEMKit/FBAEMKit/AEMEvent.swift +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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. - */ - -import Foundation - -final class AEMEvent: NSObject, NSSecureCoding { - enum CodingKeys: String, CodingKey { - case eventName = "event_name" - case values - case currency - case amount - } - - private(set) var eventName: String - private(set) var values: [String: Double]? - - init?(dict: [String: Any]?) { - guard let dict = dict else { return nil } - - // Event name is a required field - guard let eventName = dict[CodingKeys.eventName.rawValue] as? String else { - return nil - } - self.eventName = eventName - - // Values is an optional field, so don't return nil - guard let valueEntries = dict[CodingKeys.values.rawValue] as? [[String: Any]] else { return } - - if !valueEntries.isEmpty { - var valuesDict: [String: Double] = [:] - - for valueEntry in valueEntries { - guard let currency = valueEntry[CodingKeys.currency.rawValue] as? String, - let amount = valueEntry[CodingKeys.amount.rawValue] as? Double, - !currency.isEmpty else { - return nil - } - valuesDict[currency.uppercased()] = amount - } - - values = valuesDict - } - } - - private init(eventName: String, values: [String: Double]?) { - self.eventName = eventName - self.values = values - } - - // MARK: NSSecureCoding - - static var supportsSecureCoding: Bool { - true - } - - convenience init?(coder: NSCoder) { - let decodedEventName = coder.decodeObject(of: NSString.self, forKey: CodingKeys.eventName.rawValue) as String? ?? "" - let decodedValues = coder.decodeObject( - of: [NSDictionary.self, NSNumber.self, NSString.self], - forKey: CodingKeys.values.rawValue - ) as? [String: Double] - self.init(eventName: decodedEventName, values: decodedValues) - } - - func encode(with coder: NSCoder) { - coder.encode(eventName, forKey: CodingKeys.eventName.rawValue) - if values != nil { - coder.encode(values, forKey: CodingKeys.values.rawValue) - } - } - - // MARK: - NSObject - - override func isEqual(_ object: Any?) -> Bool { - guard let other = object as? AEMEvent else { return false } - - return eventName == other.eventName - && values == other.values - } -} diff --git a/FBAEMKit/FBAEMKit/AEMInvocation.swift b/FBAEMKit/FBAEMKit/AEMInvocation.swift deleted file mode 100644 index 8fa698b2ce..0000000000 --- a/FBAEMKit/FBAEMKit/AEMInvocation.swift +++ /dev/null @@ -1,510 +0,0 @@ -/* - * 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. - */ - -import CommonCrypto.CommonHMAC -import FBSDKCoreKit_Basics -import Foundation - -class AEMInvocation: NSObject, NSSecureCoding { // swiftlint:disable:this prefer_final_classes - - var campaignID: String - let acsToken: String - var acsSharedSecret: String? - var acsConfigurationID: String? - var businessID: String? - var catalogID: String? - let isTestMode: Bool - var hasStoreKitAdNetwork: Bool - var isConversionFilteringEligible: Bool - private(set) var timestamp: Date - private(set) var configurationMode: String - /// The unique identifier of the configuration, it's the same as configuration's validFrom - var configurationID: Int - var recordedEvents: Set - var recordedValues: [String: [String: Any]] - var conversionValue: Int - var priority: Int - var conversionTimestamp: Date? - var isAggregated: Bool - - private static let secondsInDay = 24 /* hours */ * 60 /* minutes */ * 60 /* seconds */ - private static let catalogOptimizationModulus = 8 - private static let topOutPriority = 32 - - private enum Key: String { - case campaignIdentifier = "campaign_ids" - case acsToken = "acs_token" - case acsSharedSecret = "shared_secret" - case acsConfigurationIdentifier = "acs_config_id" - case businessIdentifier = "advertiser_id" - case catalogIdentifier = "catalog_id" - case testDeepLink = "test_deeplink" - case timestamp - case configurationMode = "config_mode" - case configurationIdentifier = "config_id" - case recordedEvents = "recorded_events" - case recordedValues = "recorded_values" - case conversionValue = "conversion_value" - case priority - case conversionTimestamp = "conversion_timestamp" - case isAggregated = "is_aggregated" - case hasStoreKitAdNetwork = "has_skan" - case isConversionFilteringEligible = "is_conversion_filtering_eligible" - case facebookContent = "fb_content" - case facebookContentIdentifier = "fb_content_id" - } - - enum ConfigurationMode: String { - case `default` = "DEFAULT" - case brand = "BRAND" - case cpas = "CPAS" - } - - convenience init?(appLinkData: [AnyHashable: Any]?) { - guard - let appLinkData = appLinkData, - let campaignID = appLinkData[Key.campaignIdentifier.rawValue] as? String, - let acsToken = appLinkData[Key.acsToken.rawValue] as? String - else { return nil } - - let acsSharedSecret = appLinkData[Key.acsSharedSecret.rawValue] as? String - let acsConfigurationID = appLinkData[Key.configurationIdentifier.rawValue] as? String - let businessID = appLinkData[Key.businessIdentifier.rawValue] as? String - let catalogID = appLinkData[Key.catalogIdentifier.rawValue] as? String - let isTestMode = (appLinkData[Key.testDeepLink.rawValue] as? NSNumber)?.boolValue ?? false - let hasStoreKitAdNetwork = (appLinkData[Key.hasStoreKitAdNetwork.rawValue] as? NSNumber)?.boolValue ?? false - - self.init( - campaignID: campaignID, - acsToken: acsToken, - acsSharedSecret: acsSharedSecret, - acsConfigurationID: acsConfigurationID, - businessID: businessID, - catalogID: catalogID, - isTestMode: isTestMode, - hasStoreKitAdNetwork: hasStoreKitAdNetwork, - isConversionFilteringEligible: true - ) - } - - convenience init?( - campaignID: String, - acsToken: String, - acsSharedSecret: String?, - acsConfigurationID: String?, - businessID: String?, - catalogID: String?, - isTestMode: Bool, - hasStoreKitAdNetwork: Bool, - isConversionFilteringEligible: Bool - ) { - self.init( - campaignID: campaignID, - acsToken: acsToken, - acsSharedSecret: acsSharedSecret, - acsConfigurationID: acsConfigurationID, - businessID: businessID, - catalogID: catalogID, - timestamp: nil, - configurationMode: "DEFAULT", - configurationID: -1, - recordedEvents: nil, - recordedValues: nil, - conversionValue: -1, - priority: -1, - conversionTimestamp: nil, - isAggregated: true, - isTestMode: isTestMode, - hasStoreKitAdNetwork: hasStoreKitAdNetwork, - isConversionFilteringEligible: isConversionFilteringEligible - ) - } - - init?( - campaignID: String, - acsToken: String, - acsSharedSecret: String?, - acsConfigurationID: String?, - businessID: String?, - catalogID: String?, - timestamp: Date?, - configurationMode: String, - configurationID: Int, - recordedEvents: Set?, - recordedValues: [String: [String: Any]]?, - conversionValue: Int, - priority: Int, - conversionTimestamp: Date?, - isAggregated: Bool, - isTestMode: Bool, - hasStoreKitAdNetwork: Bool, - isConversionFilteringEligible: Bool - ) { - self.campaignID = campaignID - self.acsToken = acsToken - self.acsSharedSecret = acsSharedSecret - self.acsConfigurationID = acsConfigurationID - self.businessID = businessID - self.catalogID = catalogID - self.timestamp = timestamp ?? Date() - self.configurationMode = configurationMode - self.configurationID = configurationID - self.recordedEvents = recordedEvents ?? [] - self.recordedValues = recordedValues ?? [:] - self.conversionValue = conversionValue - self.priority = priority - self.conversionTimestamp = conversionTimestamp - self.isAggregated = isAggregated - self.isTestMode = isTestMode - self.hasStoreKitAdNetwork = hasStoreKitAdNetwork - self.isConversionFilteringEligible = isConversionFilteringEligible - - super.init() - } - - @discardableResult - // swiftlint:disable:next function_parameter_count - func attributeEvent( - _ event: String, - currency potentialValueCurrency: String?, - value potentialValue: NSNumber?, - parameters: [String: Any]?, - configurations: [String: [AEMConfiguration]]?, - shouldUpdateCache: Bool, - isRuleMatchInServer: Bool - ) -> Bool { - guard - let configuration = findConfiguration(in: configurations), - !isOutOfWindow(configuration: configuration), - configuration.eventSet.contains(event) - else { return false } - - var processedParameters: [String: Any]? - if !isRuleMatchInServer { - // Check advertiser rule matching - processedParameters = getProcessedParameters(from: parameters) - if let matchingRule = configuration.matchingRule, - !matchingRule.isMatchedEventParameters(processedParameters) { - return false - } - } - - var isAttributed = false - - if !recordedEvents.contains(event) { - if shouldUpdateCache { - recordedEvents.insert(event) - } - - isAttributed = true - } - - // Change currency to default currency if currency is not found in currencySet - var valueCurrency = configuration.defaultCurrency - if let currency = potentialValueCurrency?.uppercased(), - configuration.currencySet.contains(currency) { - valueCurrency = currency - } - - var value = potentialValue - if !isRuleMatchInServer { - // Use in-segment value for CPAS - if configuration.mode == ConfigurationMode.cpas.rawValue { - value = AEMUtility.shared.getInSegmentValue(processedParameters, matchingRule: configuration.matchingRule) - } - } - - if let value = value { - var mapping = recordedValues[event] ?? [:] - let valueInMapping = (mapping[valueCurrency] as? NSNumber)?.doubleValue ?? 0.0 - - // Overwrite values when the incoming event's value is greater than the cached one - if value.doubleValue > valueInMapping { - if shouldUpdateCache { - mapping[valueCurrency] = value - recordedValues[event] = mapping - } - - isAttributed = true - } - } - - return isAttributed - } - - func updateConversionValue( - configurations: [String: [AEMConfiguration]]?, - event: String, - shouldBoostPriority: Bool - ) -> Bool { - guard let configuration = findConfiguration(in: configurations) - else { return false } - - var isConversionValueUpdated = false - - // Update conversion value if a rule is matched - for rule in configuration.conversionValueRules { - var rulePriority = rule.priority - - if isConversionFilteringEligible, - shouldBoostPriority, - rule.containsEvent(event), - isOptimizedEvent(event, configuration: configuration) { - rulePriority += Self.topOutPriority - } - - guard rulePriority > priority else { continue } - - if rule.isMatched(withRecordedEvents: recordedEvents, recordedValues: recordedValues) { - conversionValue = rule.conversionValue - priority = rulePriority - conversionTimestamp = Date() - isAggregated = false - isConversionValueUpdated = true - } - } - - return isConversionValueUpdated - } - - func isOptimizedEvent(_ event: String, configurations: [String: [AEMConfiguration]]?) -> Bool { - guard - catalogID != nil, - let configuration = findConfiguration(in: configurations) - else { return false } - - return isOptimizedEvent(event, configuration: configuration) - } - - private func isOptimizedEvent(_ event: String, configuration: AEMConfiguration) -> Bool { - // Look up conversion bit mapping to check if an event is optimized - configuration.conversionValueRules.contains { rule in - guard - let campaign = Int(campaignID), - (campaign % Self.catalogOptimizationModulus) == (rule.conversionValue % Self.catalogOptimizationModulus) - else { return false } - - return rule.events.contains { $0.eventName == event } - } - } - - func isOutOfWindow(configurations: [String: [AEMConfiguration]]?) -> Bool { - isOutOfWindow(configuration: findConfiguration(in: configurations)) - } - - // Second attempt - - func getHMAC(delay: Int) -> String? { - guard - acsConfigurationID != nil, - let secretKey = acsSharedSecret, - let secretKeyData = decodeBase64URLSafeString(secretKey), - let hmac = NSMutableData(length: Int(CC_SHA512_DIGEST_LENGTH)) - else { return nil } - - let message = "\(campaignID)|\(conversionValue)|\(delay)|server" - guard let messageData = message.data(using: .utf8) else { return nil } - - let secretKeyNSData = NSData(data: secretKeyData) - let messageNSData = NSData(data: messageData) - - CCHmac( - CCHmacAlgorithm(kCCHmacAlgSHA512), - secretKeyNSData.bytes, - secretKeyNSData.length, - messageNSData.bytes, - messageNSData.length, - hmac.mutableBytes - ) - - return hmac - .base64EncodedString() - .replacingOccurrences(of: "/", with: "_") - .replacingOccurrences(of: "+", with: "-") - .replacingOccurrences(of: "=", with: "") - } - - func decodeBase64URLSafeString(_ string: String) -> Data? { - guard !string.isEmpty else { return nil } - - let length = string.count - let paddedLength = length + (4 - (length % 4)) - let decoded = string - .replacingOccurrences(of: "-", with: "+") - .replacingOccurrences(of: "_", with: "/") - .padding(toLength: paddedLength, withPad: "=", startingAt: 0) - - return Data(base64Encoded: decoded) - } - - func getProcessedParameters(from parameters: [String: Any]?) -> [String: Any]? { - guard var processed = parameters else { return nil } - - if let content = processed[Key.facebookContent.rawValue] as? String, - let contentData = content.data(using: .utf8), - let jsonObject = try? JSONSerialization.jsonObject(with: contentData) { - processed[Key.facebookContent.rawValue] = jsonObject - } - - if let contentID = processed[Key.facebookContentIdentifier.rawValue] as? String, - let stringData = contentID.data(using: .utf8), - let data = try? JSONSerialization.jsonObject(with: stringData) { - processed[Key.facebookContentIdentifier.rawValue] = data - } - - return processed - } - - private func isOutOfWindow(configuration: AEMConfiguration?) -> Bool { - guard let configuration = configuration else { return true } - - let cutoff = TimeInterval(configuration.cutoffTime * Self.secondsInDay) - let isCutoff = Date().timeIntervalSince(timestamp) > cutoff - - var isOverLastConversionWindow = false - if let conversionTimestamp = conversionTimestamp { - let oneDay = TimeInterval(Self.secondsInDay) - isOverLastConversionWindow = Date().timeIntervalSince(conversionTimestamp) > oneDay - } - - return isCutoff || isOverLastConversionWindow - } - - func findConfiguration(in configurations: [String: [AEMConfiguration]]?) -> AEMConfiguration? { - let configurationMode = (businessID != nil) ? ConfigurationMode.brand : .default - let configurationList = getConfigurationList(mode: configurationMode, configurations: configurations) - - guard !configurationList.isEmpty else { return nil } - - if configurationID > 0 { - return configurationList.first { - $0.isSame(validFrom: configurationID, businessID: businessID) - } - } else { - let configuration = configurationList.reversed().first { - TimeInterval($0.validFrom) <= timestamp.timeIntervalSince1970 - && $0.isSameBusinessID(businessID) - } - - if let configuration = configuration { - setConfiguration(configuration) - } - - return configuration - } - } - - func getConfigurationList( - mode: ConfigurationMode, - configurations: [String: [AEMConfiguration]]? - ) -> [AEMConfiguration] { - guard let configurations = configurations else { return [] } - - if mode == .brand { - return (configurations[ConfigurationMode.cpas.rawValue] ?? []) - + (configurations[ConfigurationMode.brand.rawValue] ?? []) - } else { - return configurations[mode.rawValue] ?? [] - } - } - - func setConfiguration(_ configuration: AEMConfiguration) { - configurationID = configuration.validFrom - configurationMode = configuration.mode - } - - // MARK: - NSCoding - - static var supportsSecureCoding: Bool { true } - - required init?(coder decoder: NSCoder) { - guard - let campaignID = decoder.decodeObject(of: NSString.self, forKey: Key.campaignIdentifier.rawValue), - let acsToken = decoder.decodeObject(of: NSString.self, forKey: Key.acsToken.rawValue), - let configurationMode = decoder.decodeObject(of: NSString.self, forKey: Key.configurationMode.rawValue) - else { return nil } - - let acsSharedSecret = decoder.decodeObject(of: NSString.self, forKey: Key.acsSharedSecret.rawValue) - let acsConfigurationID = decoder.decodeObject(of: NSString.self, forKey: Key.acsConfigurationIdentifier.rawValue) - let businessID = decoder.decodeObject(of: NSString.self, forKey: Key.businessIdentifier.rawValue) - let catalogID = decoder.decodeObject(of: NSString.self, forKey: Key.catalogIdentifier.rawValue) - let timestamp = decoder.decodeObject(of: NSDate.self, forKey: Key.timestamp.rawValue) ?? NSDate() - let configurationID = decoder.decodeInteger(forKey: Key.configurationIdentifier.rawValue) - let recordedEvents = decoder.decodeObject( - of: [NSSet.self, NSString.self], - forKey: Key.recordedEvents.rawValue - ) as? NSSet - let recordedValues = decoder.decodeObject( - of: [NSDictionary.self, NSString.self, NSNumber.self], - forKey: Key.recordedValues.rawValue - ) as? [String: [String: Any]] - let conversionValue = decoder.decodeInteger(forKey: Key.conversionValue.rawValue) - let priority = decoder.decodeInteger(forKey: Key.priority.rawValue) - let conversionTimestamp = decoder.decodeObject(of: NSDate.self, forKey: Key.conversionTimestamp.rawValue) - let isAggregated = decoder.decodeBool(forKey: Key.isAggregated.rawValue) - let hasStoreKitAdNetwork = decoder.decodeBool(forKey: Key.hasStoreKitAdNetwork.rawValue) - let isConversionFilteringEligible = decoder.decodeBool(forKey: Key.isConversionFilteringEligible.rawValue) - - self.campaignID = campaignID as String - self.acsToken = acsToken as String - self.acsSharedSecret = acsSharedSecret as String? - self.acsConfigurationID = acsConfigurationID as String? - self.businessID = businessID as String? - self.catalogID = catalogID as String? - self.timestamp = timestamp as Date - self.configurationMode = configurationMode as String - self.configurationID = configurationID - self.recordedEvents = (recordedEvents as? Set) ?? [] - self.recordedValues = recordedValues ?? [:] - self.conversionValue = conversionValue - self.priority = priority - self.conversionTimestamp = conversionTimestamp as Date? - self.isAggregated = isAggregated - isTestMode = false - self.hasStoreKitAdNetwork = hasStoreKitAdNetwork - self.isConversionFilteringEligible = isConversionFilteringEligible - } - - func encode(with encoder: NSCoder) { - encoder.encode(campaignID, forKey: Key.campaignIdentifier.rawValue) - encoder.encode(acsToken, forKey: Key.acsToken.rawValue) - encoder.encode(acsSharedSecret, forKey: Key.acsSharedSecret.rawValue) - encoder.encode(acsConfigurationID, forKey: Key.acsConfigurationIdentifier.rawValue) - encoder.encode(businessID, forKey: Key.businessIdentifier.rawValue) - encoder.encode(catalogID, forKey: Key.catalogIdentifier.rawValue) - encoder.encode(timestamp, forKey: Key.timestamp.rawValue) - encoder.encode(configurationMode, forKey: Key.configurationMode.rawValue) - encoder.encode(configurationID, forKey: Key.configurationIdentifier.rawValue) - encoder.encode(recordedEvents, forKey: Key.recordedEvents.rawValue) - encoder.encode(recordedValues, forKey: Key.recordedValues.rawValue) - encoder.encode(conversionValue, forKey: Key.conversionValue.rawValue) - encoder.encode(priority, forKey: Key.priority.rawValue) - encoder.encode(conversionTimestamp, forKey: Key.conversionTimestamp.rawValue) - encoder.encode(isAggregated, forKey: Key.isAggregated.rawValue) - encoder.encode(hasStoreKitAdNetwork, forKey: Key.hasStoreKitAdNetwork.rawValue) - encoder.encode(isConversionFilteringEligible, forKey: Key.isConversionFilteringEligible.rawValue) - } - - #if DEBUG - func reset() { - timestamp = Date() - configurationMode = "DEFAULT" - configurationID = -1 - businessID = nil - catalogID = nil - recordedEvents = [] - recordedValues = [:] - conversionValue = -1 - priority = -1 - conversionTimestamp = Date() - isAggregated = true - hasStoreKitAdNetwork = false - isConversionFilteringEligible = true - } - #endif -} diff --git a/FBAEMKit/FBAEMKit/AEMNetworker.swift b/FBAEMKit/FBAEMKit/AEMNetworker.swift deleted file mode 100644 index 98c2b267a6..0000000000 --- a/FBAEMKit/FBAEMKit/AEMNetworker.swift +++ /dev/null @@ -1,198 +0,0 @@ -/* - * 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. - */ - -import FBSDKCoreKit_Basics -import Foundation - -final class AEMNetworker: NSObject, AEMNetworking, URLSessionDataDelegate { - - enum Error: Swift.Error { - case missingOperationQueue - case failedToCreateURL - case failedToParseJSON - } - - private enum Values { - static let newline = "\r\n" - static let versionString = "18.0.0" - static let defaultGraphAPIVersion = "v17.0" - static let SDK = "ios" - static let userAgentBase = "FBiOSAEM" - static let graphAPIEndpoint = "https://graph.facebook.com/v17.0/" - static let graphAPIContentType = "application/json" - static let errorDomain = "com.facebook.aemkit" - static let agent = "\(Values.userAgentBase).\(Values.versionString)" - } - - var userAgentSuffix: String? - - lazy var userAgent: String = { - var agentWithSuffix = Values.agent - if let userAgentSuffix = userAgentSuffix, !userAgentSuffix.isEmpty { - agentWithSuffix += "/\(userAgentSuffix)" - } - - if #available(iOS 13.0, *), - ProcessInfo.processInfo.isMacCatalystApp { - return agentWithSuffix + "/macOS" - } - - return agentWithSuffix - }() - - // MARK: - AEMNetworking - - func startGraphRequest( - withGraphPath graphPath: String, - parameters: [String: Any], - tokenString: String?, - httpMethod method: String?, - completion: @escaping FBGraphRequestCompletion - ) { - guard let url = URL(string: Values.graphAPIEndpoint + graphPath) else { - completion(nil, Error.failedToCreateURL) - return - } - - var request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 60.0) - request.httpMethod = method - request.setValue(userAgent, forHTTPHeaderField: "User-Agent") - request.setValue(Values.graphAPIContentType, forHTTPHeaderField: "Content-Type") - request.httpShouldHandleCookies = false - - // add parameters to body - let body = AEMRequestBody() - - var params = parameters - params["format"] = "json" - params["sdk"] = Values.SDK - params["include_headers"] = "false" - - appendAttachments(attachments: params, toBody: body, addFormData: method == "POST") - - if request.httpMethod == "POST" { - request.httpBody = body.compressedData() - request.setValue("gzip", forHTTPHeaderField: "Content-Encoding") - } else { - request.httpBody = body.data - } - - guard let queue = OperationQueue.current else { - completion(nil, Error.missingOperationQueue) - return - } - - let session = FBSDKURLSession(delegate: self, delegateQueue: queue) - session.execute(request) { [weak self] responseData, response, error in - guard let httpResponse = response as? HTTPURLResponse else { - completion(nil, URLError(.badServerResponse)) - return - } - - if let error = error { - completion(nil, error) - return - } - - var parseError: Swift.Error? - let result = self?.parseJSONResponse( - data: responseData, - error: &parseError, - statusCode: httpResponse.statusCode - ) - - if let error = parseError { - completion(nil, error) - return - } - - guard let result = result else { - completion(nil, Error.failedToParseJSON) - return - } - - completion(result, error) - } - } - - func parseJSONResponse( - data: Data?, - error: inout Swift.Error?, - statusCode: Int - ) -> [String: Any] { - guard let data = data else { - return [:] - } - - let rawResponse = String(data: data, encoding: .utf8) - let response = parseJSONOrOtherwise(unsafeString: rawResponse, error: &error) - var result = [String: Any]() - - if rawResponse == nil { - let base64Data = !data.isEmpty ? data.base64EncodedString(options: .lineLength64Characters) : "" - if !base64Data.isEmpty { - print("fb_response_invalid_utf8") - } - } - - if response == nil, - error == nil { - error = NSError(domain: Values.errorDomain, code: statusCode, userInfo: nil) - } else if error != nil, response != nil { - return [:] - } else if let response = response as? [String: Any] { - result = response - } - - return result - } - - func parseJSONOrOtherwise( - unsafeString: String?, - error: inout Swift.Error? - ) -> Any? { - var parsed: Any? - if let utf8 = unsafeString, error == nil { - guard let data = utf8.data(using: .utf8) else { - return nil - } - do { - parsed = try JSONSerialization.jsonObject(with: data, options: []) - // swiftlint:disable:next untyped_error_in_catch - } catch let serializationError { - error = serializationError - return nil - } - } - return parsed - } - - func appendAttachments( - attachments: [String: Any], - toBody body: AEMRequestBody, - addFormData: Bool - ) { - for (key, value) in attachments { - var typedvalue: String? - if let integer = value as? Int { - typedvalue = "\(integer)" - } else if let number = value as? NSNumber { - typedvalue = number.stringValue - } else if let url = value as? URL { - typedvalue = url.absoluteString - } - - if let string = typedvalue, - addFormData { - body.append(withKey: key, formValue: string) - } else { - print("Unsupported attachment:\(String(describing: value)), skipping.") - } - } - } -} diff --git a/FBAEMKit/FBAEMKit/AEMNetworking.swift b/FBAEMKit/FBAEMKit/AEMNetworking.swift deleted file mode 100644 index 60df7ac218..0000000000 --- a/FBAEMKit/FBAEMKit/AEMNetworking.swift +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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. - */ - -import Foundation - -public typealias FBGraphRequestCompletion = (Any?, Error?) -> Void - -@objc(FBAEMNetworking) -public protocol AEMNetworking { - @objc(startGraphRequestWithGraphPath:parameters:tokenString:HTTPMethod:completion:) - func startGraphRequest( - withGraphPath graphPath: String, - parameters: [String: Any], - tokenString: String?, - httpMethod method: String?, - completion: @escaping FBGraphRequestCompletion - ) -} diff --git a/FBAEMKit/FBAEMKit/AEMReporter.swift b/FBAEMKit/FBAEMKit/AEMReporter.swift deleted file mode 100644 index dddce81d07..0000000000 --- a/FBAEMKit/FBAEMKit/AEMReporter.swift +++ /dev/null @@ -1,982 +0,0 @@ -/* - * 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. - */ - -import FBSDKCoreKit_Basics -import Foundation - -typealias FBAEMReporterBlock = (NSError?) -> Void - -@objcMembers -@objc(FBAEMReporter) -public final class AEMReporter: NSObject { - private enum Keys { - static let businessID = "advertiser_id" - static let businessIDs = "advertiser_ids" - static let fbContentData = "fb_content_data" - static let alApplinkData = "al_applink_data" - static let campaignId = "campaign_id" - static let conversionData = "conversion_data" - static let consumptionHour = "consumption_hour" - static let token = "token" - static let hmac = "hmac" - static let configId = "config_id" - static let delayFlow = "delay_flow" - static let isConversionFiltering = "is_conversion_filtering" - static let fbContentIds = "fb_content_ids" - static let catalogId = "catalog_id" - static let minAggregationRequestTimestamp = "com.facebook.sdk:FBAEMMinAggregationRequestTimestamp" - static let fields = "fields" - static let data = "data" - static let aemConversions = "aem_conversions" - static let success = "success" - static let isValidMatch = "is_valid_match" - static let matchedAdvertiserId = "matched_advertiser_id" - static let inSegmentValue = "in_segment_value" - static let contentIdBelongsToCatalogId = "content_id_belongs_to_catalog_id" - } - - private enum DispatchQueueLabels { - static let appEvents = "com.facebook.appevents.AEM.FBAEMReporter" - } - - private enum TimeIntervals { - static let aemConfigTimeout: TimeInterval = 86400 - static let aemDelay: TimeInterval = 3 - } - - private enum FileNames { - static let aemReporter = "FBSDKAEMReportData.report" - static let aemConfig = "FBSDKAEMReportData.config" - } - - private enum Paths { - static let aemConversionConfigs = "aem_conversion_configs" - static let aemConversionFilter = "aem_conversion_filter" - static let aemAttribution = "aem_attribution" - } - - private enum HTTPMethods { - static let GET = "GET" - static let POST = "POST" - } - - static var networker: AEMNetworking? - static var appID: String? - static let nullAppID = "(null)" // Objective-C uses "(null)" if there are nil objects in an interpolated string - static var analyticsAppID: String? - static var reporter: SKAdNetworkReporting? - static var dataStore: DataPersisting? - - static var isAEMReportEnabled = false - static var isLoadingConfiguration = false - static var isConversionFilteringEnabled = false - static var isCatalogMatchingEnabled = false - static var isAdvertiserRuleMatchInServerEnabled = false - static var serialQueue = DispatchQueue(label: DispatchQueueLabels.appEvents) - static var reportFile: String? - private static var configFile: String? - static var configurations: [String: [AEMConfiguration]] = [:] - static var invocations: [AEMInvocation] = [] - static var configRefreshTimestamp: Date? - static var minAggregationRequestTimestamp: Date? - static var completionBlocks: [FBAEMReporterBlock] = [] - - public static func configure( - networker: AEMNetworking?, - appID: String?, - reporter: SKAdNetworkReporting? - ) { - configure( - networker: networker, - appID: appID, - reporter: reporter, - analyticsAppID: nil - ) - } - - private static func configure( - networker: AEMNetworking?, - appID: String?, - reporter: SKAdNetworkReporting?, - analyticsAppID: String? - ) { - configure( - networker: networker, - appID: appID, - reporter: reporter, - analyticsAppID: analyticsAppID, - store: UserDefaults.standard - ) - } - - public static func configure( - networker: AEMNetworking?, - appID: String?, - reporter: SKAdNetworkReporting?, - analyticsAppID: String?, - store: DataPersisting? - ) { - Self.networker = networker - Self.appID = appID - Self.reporter = reporter - Self.analyticsAppID = analyticsAppID - Self.dataStore = store - } - - /** - Enable AEM reporting. This function won't work and AEM APIs will early return. - - This function should be called in application(_:open:options:) from ApplicationDelegate. - */ - public static func enable() { - // AEMKit is disabled and public APIs will always early return - isAEMReportEnabled = false - } - - /** - Control whether to enable conversion filtering - - This function should be called in `application(_:open:options:)` from ApplicationDelegate - */ - public static func setConversionFilteringEnabled(_ enabled: Bool) { - isConversionFilteringEnabled = enabled - } - - /** - Control whether to enable catalog matching - - This function should be called in `application(_:open:options:)` from ApplicationDelegate - */ - public static func setCatalogMatchingEnabled(_ enabled: Bool) { - isCatalogMatchingEnabled = enabled - } - - /** - Control whether to enable advertiser rule match enabled in server side. This is expected - to be called internally by FB SDK and will be removed in the future - - This function should be called in `application(_:open:options:)` from ApplicationDelegate - */ - public static func setAdvertiserRuleMatchInServerEnabled(_ enabled: Bool) { - isAdvertiserRuleMatchInServerEnabled = enabled - } - - /** - Handle deeplink - - This function should be called in `application(_:open:options:) `from ApplicationDelegate - */ - public static func handle(_ url: URL?) { - guard - isAEMReportEnabled, - let invocation = parseURL(url) - else { - return - } - - guard !invocation.isTestMode else { - sendDebuggingRequest(invocation) - return - } - - loadConfiguration(withRefreshForced: true, block: nil) - appendAndSaveInvocation(invocation) - } - - static func parseURL(_ url: URL?) -> AEMInvocation? { - guard let url = url else { - return nil - } - - let params = BasicUtility.dictionary(withQueryString: url.query ?? "") - guard let applinkDataString = params[Keys.alApplinkData] else { - return nil - } - - guard let applinkData = try? BasicUtility.object(forJSONString: applinkDataString) as? [AnyHashable: Any] else { - return nil - } - - return AEMInvocation(appLinkData: applinkData) - } - - /** - Calculate the conversion value for the app event based on the AEM configuration - - This function should be called when you log any in-app events - */ - @objc(recordAndUpdateEvent:currency:value:parameters:) - public static func recordAndUpdate( - event: String, - currency: String?, - value: NSNumber?, - parameters: [String: Any]? - ) { - guard #available(iOS 14.0, *) else { - return - } - - if !isAEMReportEnabled || event.isEmpty { - return - } - - loadConfiguration(withRefreshForced: false) { _ in - if configurations.isEmpty || invocations.isEmpty { - return - } - - let businessIDs = AEMUtility.shared.getBusinessIDsInOrder(invocations) - if isAdvertiserRuleMatchInServerEnabled, - let businessID = businessIDs.first, - !businessID.isEmpty { - self.loadRuleMatch(businessIDs, event: event, currency: currency, value: value, parameters: parameters) - } else { - self.attributionV1WithEvent(event, currency: currency, value: value, parameters: parameters) - } - } - } - - private static func attributionV1WithEvent( - _ event: String, - currency: String?, - value: NSNumber?, - parameters: [String: Any]? - ) { - guard let attributedInvocation = attributedInvocation( - invocations, - event: event, - currency: currency, - value: value, - parameters: parameters, - configurations: configurations - ) else { - return - } - - attributionWithInvocation( - attributedInvocation, - event: event, - currency: currency, - value: value, - parameters: parameters, - isRuleMatchInServer: false - ) - } - - // swiftlint:disable:next function_parameter_count - private static func attributionWithInvocation( - _ invocation: AEMInvocation, - event: String, - currency: String?, - value: NSNumber?, - parameters: [String: Any]?, - isRuleMatchInServer: Bool - ) { - // We will report conversion in catalog level if - // 1. conversion filtering and catalog matching are enabled - // 2. invocation has catalog Any - // 3. event is optimized - // 4. event's content Any belongs to the catalog - if shouldReportConversion(inCatalogLevel: invocation, event: event) { - let contentID = AEMUtility.shared.getContentID(parameters) - loadCatalogOptimization(with: invocation, contentID: contentID) { - self.updateAttributedInvocation( - invocation, - event: event, - currency: currency, - value: value, - parameters: parameters, - shouldBoostPriority: true, - isRuleMatchInServer: isRuleMatchInServer - ) - } - } else { - updateAttributedInvocation( - invocation, - event: event, - currency: currency, - value: value, - parameters: parameters, - shouldBoostPriority: isConversionFilteringEnabled, - isRuleMatchInServer: isRuleMatchInServer - ) - } - } - - // swiftlint:disable:next function_parameter_count - private static func updateAttributedInvocation( - _ invocation: AEMInvocation, - event: String, - currency: String?, - value: NSNumber?, - parameters: [String: Any]?, - shouldBoostPriority: Bool, - isRuleMatchInServer: Bool - ) { - invocation.attributeEvent( - event, - currency: currency, - value: value, - parameters: parameters, - configurations: configurations, - shouldUpdateCache: true, - isRuleMatchInServer: isRuleMatchInServer - ) - - if invocation.updateConversionValue( - configurations: configurations, - event: event, - shouldBoostPriority: shouldBoostPriority - ) { - sendAggregationRequest() - } - - saveReportData() - } - - // swiftlint:disable:next function_parameter_count - static func attributedInvocation( - _ invocations: [AEMInvocation], - event: String, - currency: String?, - value: NSNumber?, - parameters: [String: Any]?, - configurations: [String: [AEMConfiguration]] - ) -> AEMInvocation? { - var isGeneralInvocationVisited = false - var attributedInvocation: AEMInvocation? - for invocation in invocations.reversed() { - if isDoubleCounting(invocation, event: event) { - break - } - - if invocation.businessID == nil, - isGeneralInvocationVisited { - continue - } - - if invocation.attributeEvent( - event, - currency: currency, - value: value, - parameters: parameters, - configurations: configurations, - shouldUpdateCache: false, - isRuleMatchInServer: false - ) { - attributedInvocation = invocation - break - } - - if invocation.businessID == nil { - isGeneralInvocationVisited = true - } - } - return attributedInvocation - } - - static func isDoubleCounting( - _ invocation: AEMInvocation, - event: String - ) -> Bool { - // We consider it as Double counting if following conditions meet simultaneously - // 1. The field hasStoreKitAdNetwork is true - // 2. The conversion happens before SKAdNetwork cutoff - // 3. The event is also being reported by SKAdNetwork - return invocation.hasStoreKitAdNetwork - && reporter?.shouldCutoff() == false - && reporter?.isReportingEvent(event) == true - } - - private static func appendAndSaveInvocation(_ invocation: AEMInvocation) { - dispatchOnQueue(serialQueue) { - invocations.append(invocation) - saveReportData() - } - } - - static func loadConfiguration(withRefreshForced forced: Bool, block: FBAEMReporterBlock?) { - dispatchOnQueue(serialQueue) { - if let block = block { - completionBlocks.append(block) - } - - // Executes blocks if there is cache - if !shouldRefresh(withIsForced: forced) { - for executionBlock in completionBlocks { - executionBlock(nil) - } - completionBlocks.removeAll() - return - } - - if isLoadingConfiguration { - return - } - - isLoadingConfiguration = true - - networker?.startGraphRequest( - withGraphPath: "\(appID ?? nullAppID)/\(Paths.aemConversionConfigs)", - parameters: requestParameters(), - tokenString: nil, - httpMethod: HTTPMethods.GET - ) { result, error in - dispatchOnQueue(serialQueue) { - if let error = error { - for executionBlock in completionBlocks { - executionBlock(error as NSError) - } - completionBlocks.removeAll() - isLoadingConfiguration = false - return - } - - if let json = result as? [String: Any] { - configRefreshTimestamp = Date() - if let configurations = json[Keys.data] as? [[String: Any]] { - self.addConfigurations(configurations) - } - - for executionBlock in completionBlocks { - executionBlock(nil) - } - completionBlocks.removeAll() - } else { - print("Received invalid AEM configuration") - } - - isLoadingConfiguration = false - } - } - } - } - - static func loadCatalogOptimization( - with invocation: AEMInvocation, - contentID: String?, - block: @escaping () -> Void - ) { - networker?.startGraphRequest( - withGraphPath: "\(appID ?? nullAppID)/\(Paths.aemConversionFilter)", - parameters: catalogRequestParameters(invocation.catalogID, contentID: contentID), - tokenString: nil, - httpMethod: HTTPMethods.GET - ) { result, error in - dispatchOnQueue(serialQueue) { - guard error == nil else { - return - } - - if self.isContentOptimized(result) { - block() - } - } - } - } - - static func loadRuleMatch( - _ businessIDs: [String], - event: String, - currency potentialCurrency: String?, - value potentialValue: NSNumber?, - parameters: [String: Any]? - ) { - let content = AEMUtility.shared.getContent(parameters) - networker?.startGraphRequest( - withGraphPath: "\(appID ?? nullAppID)/\(Paths.aemAttribution)", - parameters: ruleMatchRequestParameters(businessIDs, content: content), - tokenString: nil, - httpMethod: HTTPMethods.GET - ) { result, error in - if error != nil || result == nil { - return - } - - guard - let result = result as? [String: Any], - let data: [Any] = result[Keys.data] as? [Any], - let json = data.first as? [String: Any] - else { - return - } - - guard let success = json[Keys.success] as? NSNumber else { return } - - if success.boolValue { - guard let isValidMatch = json[Keys.isValidMatch] as? NSNumber else { - return - } - - let matchedBusinessID = json[Keys.matchedAdvertiserId] as? String - let inSegmentValue = json[Keys.inSegmentValue] as? NSNumber - let matchedInvocation = AEMUtility.shared.getMatchedInvocation(invocations, businessID: matchedBusinessID) - // Drop the conversion if not a valid match or no matched invocation - if !isValidMatch.boolValue || matchedInvocation == nil { - return - } - - guard let matchedInvocation = matchedInvocation else { return } - - dispatchOnQueue(serialQueue) { - var currency = potentialCurrency - var value = potentialValue - if matchedInvocation.businessID != nil { - currency = "USD" - value = inSegmentValue - } - - self.attributionWithInvocation( - matchedInvocation, - event: event, - currency: currency, - value: value, - parameters: parameters, - isRuleMatchInServer: true - ) - } - } else { - // Fall back to attribution v1 if fails - dispatchOnQueue(serialQueue) { - self.attributionV1WithEvent( - event, - currency: potentialCurrency, - value: potentialValue, - parameters: parameters - ) - } - } - } - } - - static func shouldReportConversion( - inCatalogLevel invocation: AEMInvocation, - event: String - ) -> Bool { - isConversionFilteringEnabled - && isCatalogMatchingEnabled - && invocation.catalogID != nil - && invocation.isOptimizedEvent(event, configurations: configurations) - } - - static func isContentOptimized(_ result: Any?) -> Bool { - let json = result as? [String: Any] - let data = json?[Keys.data] as? [Any] - let catalogData = data?.first as? [String: Any] - let isOptimized = catalogData?[Keys.contentIdBelongsToCatalogId] as? NSNumber ?? false - return isOptimized.boolValue - } - - static func requestParameters() -> [String: Any] { - var params: [String: Any] = [:] - // append business ids to the request params - var businessIDs: [String] = [] - for invocation in invocations { - if let businessID = invocation.businessID { - businessIDs.append(businessID) - } - } - - let businessIDsString = try? BasicUtility.jsonString(for: businessIDs) - params[Keys.businessIDs] = businessIDsString - params[Keys.fields] = "" - return params - } - - static func catalogRequestParameters( - _ catalogID: String?, - contentID: String? - ) -> [String: Any] { - [ - Keys.fbContentIds: contentID, - Keys.catalogId: catalogID, - ].compactMapValues { $0 } - } - - static func ruleMatchRequestParameters( - _ businessIDs: [String], - content: String? - ) -> [String: Any] { - let businessIDsString = try? BasicUtility.jsonString(for: businessIDs) - - return [ - Keys.businessIDs: businessIDsString, - Keys.fbContentData: content, - ].compactMapValues { $0 } - } - - static func isConfigRefreshTimestampValid() -> Bool { - guard let timestamp = configRefreshTimestamp else { - return false - } - - return Date().timeIntervalSince(timestamp) < TimeIntervals.aemConfigTimeout - } - - static func shouldRefresh(withIsForced isForced: Bool) -> Bool { - if isForced { - return true - } - - // Refresh if there exists invocation which has business ID - for invocation in invocations where invocation.businessID != nil { - return true - } - - // Refresh if timestamp is expired or cached configuration is empty - return !isConfigRefreshTimestampValid() || configurations.isEmpty - } - - static func shouldDelayAggregationRequest() -> Bool { - guard let timestamp = minAggregationRequestTimestamp else { - return false - } - return Date().timeIntervalSince(timestamp) < 0 - } - - // MARK: - Deeplink debugging methods - - static func sendDebuggingRequest(_ invocation: AEMInvocation) { - var params: [[String: Any]] = [] - params.append(debuggingRequestParameters(invocation)) - if params.isEmpty { - return - } - - guard - let jsonData = try? TypeUtility.data(withJSONObject: params), - let reports = String(data: jsonData, encoding: .utf8) - else { - return - } - - networker?.startGraphRequest( - withGraphPath: "\(appID ?? nullAppID)/\(Keys.aemConversions)", - parameters: [Keys.aemConversions: reports], - tokenString: nil, - httpMethod: HTTPMethods.POST - ) { _, error in - if let error = error { - print("Fail to send AEM debugging request with error: \(error)") - } - } - } - - static func debuggingRequestParameters(_ invocation: AEMInvocation) -> [String: Any] { - [ - Keys.campaignId: invocation.campaignID, - Keys.conversionData: 0, - Keys.consumptionHour: 0, - Keys.token: invocation.acsToken, - Keys.delayFlow: "server", - ] - } - - // MARK: - Background methods - - static func loadMinAggregationRequestTimestamp() -> Date? { - dataStore?.fb_object(forKey: Keys.minAggregationRequestTimestamp) as? Date - } - - static func updateAggregationRequestTimestamp(_ timeInterval: TimeInterval) { - let newTimestamp = Date(timeIntervalSince1970: timeInterval) - minAggregationRequestTimestamp = newTimestamp - dataStore?.fb_setObject(newTimestamp, forKey: Keys.minAggregationRequestTimestamp) - } - - static func loadConfigurations() -> [String: [AEMConfiguration]] { - guard let configFile = configFile else { return [:] } - - if let cachedConfiguration = try? NSData(contentsOfFile: configFile, options: .mappedIfSafe) { - let cache = try? NSKeyedUnarchiver.unarchivedObject( - ofClasses: [ - NSDictionary.self, - NSArray.self, - NSString.self, - AEMConfiguration.self, - AEMRule.self, - AEMEvent.self, - ], - from: cachedConfiguration as Data - ) as? [String: [AEMConfiguration]] - - if let cache = cache { - return cache - } - } - - return [:] - } - - private static func saveConfigurations() { - let cache = try? NSKeyedArchiver.archivedData(withRootObject: configurations, requiringSecureCoding: false) - guard - let cache = cache, - let configFile = configFile - else { - return - } - - (cache as NSData).write(toFile: configFile, atomically: true) - } - - static func addConfigurations(_ configurations: [[String: Any]]) { - if configurations.isEmpty { - return - } - - for configuration in configurations { - addConfiguration(AEMConfiguration(json: configuration)) - } - saveConfigurations() - } - - private static func addConfiguration(_ configuration: AEMConfiguration?) { - guard let configuration = configuration else { return } - - let configurationsForMode = configurations[configuration.mode] ?? [] - // Remove the configuration in the array that has the same "validFrom" and "businessID" as the added configuration - var newConfigurations: [AEMConfiguration] = [] - for candidateConfiguration in configurationsForMode { - if configuration.isSame( - validFrom: candidateConfiguration.validFrom, - businessID: candidateConfiguration.businessID - ) { - continue - } - - newConfigurations.append(candidateConfiguration) - } - newConfigurations.append(configuration) - // Sort the configurations via "validFrom" - - if #available(iOS 15.0, *) { - newConfigurations.sort(using: KeyPathComparator(\.validFrom)) - } else { - newConfigurations.sort { $0.validFrom < $1.validFrom } - } - configurations[configuration.mode] = newConfigurations - } - - static func loadReportData() -> [AEMInvocation] { - guard let reportFile = reportFile else { return [] } - if let cachedReportData = try? NSData(contentsOfFile: reportFile, options: .mappedIfSafe) { - let cache = try? NSKeyedUnarchiver.unarchivedObject( - ofClasses: [NSArray.self, AEMInvocation.self], - from: cachedReportData as Data - ) as? [AEMInvocation] - - if let cache = cache { - return cache - } - } - return [] - } - - static func saveReportData() { - let cache = try? NSKeyedArchiver.archivedData(withRootObject: invocations, requiringSecureCoding: false) - if let cache = cache, - let reportFile = reportFile { - (cache as NSData).write(toFile: reportFile, atomically: true) - } - } - - static func sendAggregationRequest() { - var params: [[String: Any]] = [] - var aggregatedInvocations: [AEMInvocation] = [] - for invocation in invocations { - if !invocation.isAggregated { - params.append(aggregationRequestParameters(invocation)) - aggregatedInvocations.append(invocation) - } - } - - if params.isEmpty { - return - } - - let block = { - guard - let jsonData = try? TypeUtility.data(withJSONObject: params), - let reports = String(data: jsonData, encoding: .utf8) - else { - return - } - - networker?.startGraphRequest( - withGraphPath: "\(appID ?? nullAppID)/\(Keys.aemConversions)", - parameters: [Keys.aemConversions: reports], - tokenString: nil, - httpMethod: HTTPMethods.POST - ) { _, error in - if error != nil { - return - } - - dispatchOnQueue(serialQueue) { - for invocation in aggregatedInvocations { - invocation.isAggregated = true - } - saveReportData() - } - } - } - - if shouldDelayAggregationRequest() { - var timestampDelay: TimeInterval = 0 - if let timestamp = minAggregationRequestTimestamp { - timestampDelay = timestamp.timeIntervalSince1970 - Date().timeIntervalSince1970 - } - - let delay = max(TimeIntervals.aemDelay, timestampDelay) - dispatchOnQueue(serialQueue, delay: delay, block: block) - } else { - block() - } - - let minAggregationRequestTimestampDelay = minAggregationRequestTimestamp?.timeIntervalSince1970 ?? 0 - updateAggregationRequestTimestamp( - max( - Date().timeIntervalSince1970 + TimeIntervals.aemDelay, - minAggregationRequestTimestampDelay + TimeIntervals.aemDelay - ) - ) - } - - static func aggregationRequestParameters(_ invocation: AEMInvocation) -> [String: Any] { - let delay = 24 + arc4random_uniform(24) - let enableConversionFiltering = invocation.isConversionFilteringEligible && isConversionFilteringEnabled - return [ - Keys.campaignId: invocation.campaignID, - Keys.conversionData: invocation.conversionValue, - Keys.consumptionHour: delay, - Keys.token: invocation.acsToken, - Keys.delayFlow: "server", - Keys.configId: invocation.acsConfigurationID, - Keys.hmac: invocation.getHMAC(delay: Int(delay)), - Keys.businessID: invocation.businessID, - Keys.isConversionFiltering: enableConversionFiltering, - ].compactMapValues { $0 } - } - - private static func dispatchOnQueue( - _ queue: DispatchQueue, - delay: TimeInterval? = nil, - block: (() -> Void)? - ) { - guard let block = block else { return } - - if queue.label == DispatchQueueLabels.appEvents { - if let delay = delay { - queue.asyncAfter(deadline: .now() + delay, execute: block) - } else { - queue.async(execute: block) - } - } else { // For tests the queue's label is different - if delay == nil { - block() - } - } - } - - static func clearCache() { - // step 1: clear aggregated invocations that are outside attribution window - clearInvocations() - // step 2: clear old configurations that are not used anymore and keep the most recent configuration - clearConfigurations() - } - - static func clearConfigurations() { - var shouldSaveCache = false - if !configurations.isEmpty { - var newConfigurationsDict: [String: [AEMConfiguration]] = [:] - for (key, value) in configurations { - var oldConfigurations: [AEMConfiguration] = value - var newConfigurations: [AEMConfiguration] = [] - - // Removes the last of the old default mode configurations and stores it so it can be - // added to the array-to-save - var lastConfiguration: AEMConfiguration? - if key == "DEFAULT" { - lastConfiguration = oldConfigurations.last - oldConfigurations.removeLast() - } - - for oldConfiguration in oldConfigurations { - if !isUsingConfiguration(oldConfiguration, forInvocations: invocations) { - shouldSaveCache = true - continue - } - newConfigurations.append(oldConfiguration) - } - - if let lastConfiguration = lastConfiguration { - newConfigurations.append(lastConfiguration) - } - newConfigurationsDict[key] = newConfigurations - } - configurations = newConfigurationsDict - } - if shouldSaveCache { - saveConfigurations() - } - } - - private static func clearInvocations() { - var isInvocationCacheUpdated = false - if !invocations.isEmpty { - var newInvocations: [AEMInvocation] = [] - for invocation in invocations { - if invocation.isOutOfWindow(configurations: configurations), invocation.isAggregated { - isInvocationCacheUpdated = true - continue - } - newInvocations.append(invocation) - } - invocations = newInvocations - } - - if isInvocationCacheUpdated { - saveReportData() - } - } - - private static func isUsingConfiguration( - _ configuration: AEMConfiguration, - forInvocations invocations: [AEMInvocation] - ) -> Bool { - for invocation in invocations { - if configuration.isSame(validFrom: invocation.configurationID, businessID: invocation.businessID) { - return true - } - } - return false - } - - // MARK: - Testability - - #if DEBUG - - public static func reset() { - isAEMReportEnabled = false - isLoadingConfiguration = false - isConversionFilteringEnabled = false - isCatalogMatchingEnabled = false - isAdvertiserRuleMatchInServerEnabled = false - completionBlocks = [] - configurations = [:] - minAggregationRequestTimestamp = nil - networker = nil - appID = nil - reporter = nil - dataStore = nil - clearCache() - } - - #endif -} diff --git a/FBAEMKit/FBAEMKit/AEMRequestBody.swift b/FBAEMKit/FBAEMKit/AEMRequestBody.swift deleted file mode 100644 index 8701380e28..0000000000 --- a/FBAEMKit/FBAEMKit/AEMRequestBody.swift +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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. - */ - -import FBSDKCoreKit_Basics -import Foundation - -final class AEMRequestBody { - - /// Compressed version of `data` - func compressedData() -> Data? { - if data.isEmpty { - return nil - } - return BasicUtility.gzip(data) - } - - #if DEBUG - var multipartData: Data { - _data - } - #endif - - /// Requests Constants - private enum Constants { - static let kNewline = "\r\n" - } - - /// Callback alias - typealias AEMCodeBlock = () -> Void - - var data: Data { - var jsonData = Data() - if !json.keys.isEmpty, - let data = try? TypeUtility.data(withJSONObject: json, options: .sortedKeys) { - jsonData = data - } - return jsonData - } - - private var _data = Data() - - /// JSON Dictionary - private var json = [String: Any]() - - func append(withKey key: String?, formValue value: String?) { - _append(with: key, filename: nil, contentType: nil) { [weak self] in - guard let value = value else { - return - } - self?.append(utf8: value) - } - - if let key = key, - let value = value { - json[key] = value - } - } - - private func append(utf8: String) { - if _data.isEmpty { - let headerUTF8 = String(format: "--%@", Constants.kNewline) - let headerData = headerUTF8.data(using: .utf8) ?? Data() - _data.append(headerData) - } - guard let data = utf8.data(using: .utf8) else { - return - } - _data.append(data) - } - - private func _append( - with key: String?, - filename: String?, - contentType: String?, - contentBlock: AEMCodeBlock? - ) { - var disposition = [String]() - disposition.append("Content-Disposition: form-data") - if let key = key { - disposition.append("name=\"\(key)\"") - } - - if let filename = filename { - disposition.append("filename=\"\(filename)\"") - } - append(utf8: "\(disposition.joined(separator: "; "))\(Constants.kNewline)") - if let contentType = contentType { - append(utf8: "Content-Type: \(contentType)\(Constants.kNewline)") - } - append(utf8: Constants.kNewline) - contentBlock?() - append(utf8: Constants.kNewline) - } -} diff --git a/FBAEMKit/FBAEMKit/AEMRule.swift b/FBAEMKit/FBAEMKit/AEMRule.swift deleted file mode 100644 index 0b357d2758..0000000000 --- a/FBAEMKit/FBAEMKit/AEMRule.swift +++ /dev/null @@ -1,147 +0,0 @@ -/* - * 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. - */ - -import FBSDKCoreKit_Basics -import Foundation - -final class AEMRule: NSObject, NSSecureCoding { - let conversionValue: Int - let priority: Int - let events: [AEMEvent] - - private enum Keys { - static let conversionValueKey = "conversion_value" - static let priorityKey = "priority" - static let eventsKey = "events" - } - - init?(json dict: [String: Any]) { - let conversionValue = dict[Keys.conversionValueKey] as? NSNumber - let priority = dict[Keys.priorityKey] as? NSNumber - let events = AEMRule.parse(events: dict[Keys.eventsKey] as? [[String: Any]] ?? []) ?? [] - - guard let conversionValue = conversionValue, - let priority = priority, - !events.isEmpty - else { - return nil - } - - self.conversionValue = conversionValue.intValue - self.priority = priority.intValue - self.events = events - - super.init() - } - - /// Check if event contains target event with name - /// - Parameter event: Event name to check for - /// - Returns: Boolean - func containsEvent(_ event: String) -> Bool { - events.contains { $0.eventName == event } - } - - /// Check if recorded events and values match `events` - /// - Parameters: - /// - recordedEvents: Recorded events to check - /// - recordedValues: Recorded values to check - /// - Returns: Boolean - func isMatched( - withRecordedEvents recordedEvents: Set?, - recordedValues: [String: [String: Any]]? - ) -> Bool { - guard let recordedEvents = recordedEvents else { - return false - } - - for event in events { - // Check if event name matches - if !recordedEvents.contains(event.eventName) { - return false - } - // Check if event value matches when values is not nil - if let values = event.values, - !values.isEmpty { - let recordedEventValues = recordedValues?[event.eventName] as? [String: Double] - if !isMatched(values: values, recordedEventValues: recordedEventValues) { - return false - } - } - } - - return true - } - - /// Attempts value matching for event values - /// - Parameters: - /// - values: Event values - /// - recordedEventValues: Recorded event values to check (nullable) - /// - Returns: Boolean - private func isMatched(values: [String: Double], recordedEventValues: [String: Double]?) -> Bool { - for (currency, valueInMapping) in values { - let value = recordedEventValues?[currency] ?? 0 - if value >= valueInMapping { - return true - } - } - - return false - } - - /// Parse json dictionary to collection of `AEMEvent` - /// - Parameter events: Collection of dictionaries to parse - /// - Returns: Collection of `AEMEvent` objects - private static func parse( - events: [[String: Any]] - ) -> [AEMEvent]? { - guard !events.isEmpty else { - return nil - } - - return events.compactMap(AEMEvent.init(dict:)) - } - - // MARK: - NSCoding - - static var supportsSecureCoding: Bool { - true - } - - init?(coder: NSCoder) { - let conversionValue = coder.decodeInteger(forKey: Keys.conversionValueKey) - let priority = coder.decodeInteger(forKey: Keys.priorityKey) - let events: [AEMEvent] = coder.decodeObject( - of: [NSArray.classForCoder(), AEMEvent.classForCoder()], - forKey: Keys.eventsKey - ) as? [AEMEvent] ?? [] - - self.conversionValue = conversionValue - self.priority = priority - self.events = events - } - - func encode(with coder: NSCoder) { - coder.encode(conversionValue, forKey: Keys.conversionValueKey) - coder.encode(priority, forKey: Keys.priorityKey) - coder.encode(events, forKey: Keys.eventsKey) - } - - override func isEqual(_ object: Any?) -> Bool { - guard let rule = object as? AEMRule else { - return false - } - - if self === rule { - return true - } - - return conversionValue == rule.conversionValue - && priority == rule.priority - && events == rule.events - } -} diff --git a/FBAEMKit/FBAEMKit/AEMSettings.swift b/FBAEMKit/FBAEMKit/AEMSettings.swift deleted file mode 100644 index 484203be87..0000000000 --- a/FBAEMKit/FBAEMKit/AEMSettings.swift +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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. - */ - -import Foundation - -enum AEMSettings { - static func appID() -> String? { - // swiftformat:disable:next redundantSelf - self.bundle?.object(forInfoDictionaryKey: "FacebookAppID") as? String - } -} - -extension AEMSettings: DependentAsType { - struct TypeDependencies { - var bundle: Bundle - } - - static var configuredDependencies: TypeDependencies? - - static var defaultDependencies: TypeDependencies? = TypeDependencies(bundle: .main) -} diff --git a/FBAEMKit/FBAEMKit/AEMUtility.swift b/FBAEMKit/FBAEMKit/AEMUtility.swift deleted file mode 100644 index a98b1b761c..0000000000 --- a/FBAEMKit/FBAEMKit/AEMUtility.swift +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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. - */ - -import FBSDKCoreKit_Basics -import Foundation - -final class AEMUtility { - private enum Keys { - static let content = "fb_content" - static let contentID = "fb_content_id" - static let itemPrice = "item_price" - static let identity = "id" - static let quantity = "quantity" - } - - static let shared = AEMUtility() - - func getMatchedInvocation(_ invocations: [AEMInvocation], businessID: String?) -> AEMInvocation? { - guard let businessID = businessID else { - for invocation in invocations.reversed() where invocation.businessID == nil { - return invocation - } - return nil - } - - for invocation in invocations.reversed() { - if let thisID = invocation.businessID, thisID == businessID { - return invocation - } - } - return nil - } - - func getInSegmentValue( - _ parameters: [String: Any]?, - matchingRule: AEMAdvertiserRuleMatching? - ) -> NSNumber { - guard let parameters = parameters, - let contentsData = parameters[Keys.content] as? [[String: Any]] else { - return 0 - } - - let value = contentsData.reduce(0.0) { value, entry in - if let matchingRule = matchingRule, - matchingRule.isMatchedEventParameters([Keys.content: [entry]]) { - let itemPrice = entry[Keys.itemPrice] as? NSNumber ?? 0 - let quantity = entry[Keys.quantity] as? NSNumber ?? 1 - - return value + itemPrice.doubleValue * quantity.doubleValue - } else { - return value - } - } - - return NSNumber(value: value) - } - - func getContent(_ parameters: [String: Any]?) -> String? { - guard let parameters = parameters else { - return nil - } - - return parameters[Keys.content] as? String - } - - func getContentID(_ parameters: [String: Any]?) -> String? { - guard let parameters = parameters else { - return nil - } - - let content = parameters[Keys.content] as? String - - return content.flatMap { content in - do { - return try getContentIDs(content) - } catch { - NSLog("Fail to parse AEM fb_content") - return nil - } - } ?? (parameters[Keys.contentID] as? String) - } - - func getBusinessIDsInOrder(_ invocations: [AEMInvocation]) -> [String] { - var res: [String] = [] - - for invocation in invocations.reversed() { - res.append(invocation.businessID ?? "") - } - - return res - } - - private func getContentIDs(_ content: String) throws -> String { - guard let json = try TypeUtility.jsonObject( - with: Data(content.utf8), - options: .mutableContainers - ) as? [[String: Any]] else { - throw Error.invalidContentIDsJSONObject - } - - let contentIDs = json.reduce(into: [String]()) { result, entry in - if let contentID = entry[Keys.identity] as? NSNumber { - result.append(contentID.stringValue) - } else if let contentID = entry[Keys.identity] as? String { - result.append(contentID) - } - } - - return try BasicUtility.jsonString(for: contentIDs, invalidObjectHandler: nil) - } - - private enum Error: Swift.Error { - case invalidContentIDsJSONObject - } -} diff --git a/FBAEMKit/FBAEMKit/DependentAsType.swift b/FBAEMKit/FBAEMKit/DependentAsType.swift deleted file mode 100644 index 90a200ee5c..0000000000 --- a/FBAEMKit/FBAEMKit/DependentAsType.swift +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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. - */ - -@dynamicMemberLookup -protocol DependentAsType { - associatedtype TypeDependencies - - static var configuredDependencies: TypeDependencies? { get set } - static var defaultDependencies: TypeDependencies? { get } - - static func setDependencies(_ dependencies: TypeDependencies) - - #if DEBUG - static func resetDependencies() - #endif -} - -extension DependentAsType { - static func setDependencies(_ dependencies: TypeDependencies) { - configuredDependencies = dependencies - } - - #if DEBUG - static func resetDependencies() { - configuredDependencies = nil - } - #endif - - static func getDependencies() throws -> TypeDependencies { - guard let dependencies = configuredDependencies ?? defaultDependencies else { - throw MissingDependenciesError(for: Self.self) - } - - return dependencies - } - - static subscript(dynamicMember keyPath: KeyPath) -> Dependency? { - try? getDependencies()[keyPath: keyPath] - } -} diff --git a/FBAEMKit/FBAEMKit/Info.plist b/FBAEMKit/FBAEMKit/Info.plist deleted file mode 100644 index d3de8eefb6..0000000000 --- a/FBAEMKit/FBAEMKit/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/FBAEMKit/FBAEMKit/MissingDependenciesError.swift b/FBAEMKit/FBAEMKit/MissingDependenciesError.swift deleted file mode 100644 index 4c122126a6..0000000000 --- a/FBAEMKit/FBAEMKit/MissingDependenciesError.swift +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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. - */ - -struct MissingDependenciesError: Error, CustomStringConvertible { - private let dependentType: Dependent.Type - - init(for dependentType: Dependent.Type) { - self.dependentType = dependentType - } - - var description: String { - "The dependencies for the type '\(dependentType)' or an instance of it have not been set" - } -} diff --git a/FBAEMKit/FBAEMKit/PrivacyInfo.xcprivacy b/FBAEMKit/FBAEMKit/PrivacyInfo.xcprivacy deleted file mode 100644 index 93cac16c23..0000000000 --- a/FBAEMKit/FBAEMKit/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,21 +0,0 @@ - - - - - NSPrivacyTracking - - NSPrivacyCollectedDataTypes - - NSPrivacyAccessedAPITypes - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryUserDefaults - NSPrivacyAccessedAPITypeReasons - - CA92.1 - - - - - diff --git a/FBAEMKit/FBAEMKit/SKAdNetworkReporting.swift b/FBAEMKit/FBAEMKit/SKAdNetworkReporting.swift deleted file mode 100644 index a2109c9dec..0000000000 --- a/FBAEMKit/FBAEMKit/SKAdNetworkReporting.swift +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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. - */ - -import Foundation - -@objc(FBSKAdNetworkReporting) -public protocol SKAdNetworkReporting { - @objc - func shouldCutoff() -> Bool - - @objc(isReportingEvent:) - func isReportingEvent(_ event: String) -> Bool - - @objc - func checkAndRevokeTimer() -} diff --git a/FBAEMKit/FBAEMKitTests/AEMAdvertiserMultiEntryRuleTests.swift b/FBAEMKit/FBAEMKitTests/AEMAdvertiserMultiEntryRuleTests.swift deleted file mode 100644 index b5cdd2032b..0000000000 --- a/FBAEMKit/FBAEMKitTests/AEMAdvertiserMultiEntryRuleTests.swift +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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 AEMAdvertiserMultiEntryRuleTests: XCTestCase { - - enum Keys { - static let ruleOperator = "operator" - static let rules = "rules" - } - - func testIsMatchedEventParametersForAnd() { - let rule = AEMAdvertiserMultiEntryRule( - with: .and, - rules: [SampleAEMSingleEntryRules.cardTypeRule1, SampleAEMSingleEntryRules.valueRule] - ) - XCTAssertTrue( - rule.isMatchedEventParameters( - [ - "card_type": "platium", - "amount": NSNumber(value: 100), - ] - ), - "Should expect the parameter matched with the rule" - ) - XCTAssertFalse( - rule.isMatchedEventParameters( - [ - "card_type": "platium", - "amount": NSNumber(value: 1), - ] - ), - "Should not expect the parameter matched with the rule if the amount is low" - ) - XCTAssertFalse( - rule.isMatchedEventParameters( - [ - "card_type": "gold", - "amount": NSNumber(value: 100), - ] - ), - "Should not expect the parameter matched with the rule if the card type is wrong" - ) - } - - func testIsMatchedEventParametersForOr() { - let rule = AEMAdvertiserMultiEntryRule( - with: .or, - rules: [SampleAEMSingleEntryRules.cardTypeRule1, SampleAEMSingleEntryRules.valueRule] - ) - XCTAssertFalse( - rule.isMatchedEventParameters( - [ - "card_type": "gold", - "amount": NSNumber(value: 1), - ] - ), - "Should not expect the parameter matched with the rule" - ) - XCTAssertTrue( - rule.isMatchedEventParameters( - [ - "card_type": "platium", - "amount": NSNumber(value: 1), - ] - ), - "Should expect the parameter matched with the rule if the card type is the same" - ) - XCTAssertTrue( - rule.isMatchedEventParameters( - [ - "card_type": "gold", - "amount": NSNumber(value: 100), - ] - ), - "Should expect the parameter matched with the rule if amount is high" - ) - } - - func testIsMatchedEventParametersForNot() { - let rule = AEMAdvertiserMultiEntryRule( - with: .not, - rules: [SampleAEMSingleEntryRules.cardTypeRule1, SampleAEMSingleEntryRules.valueRule] - ) - XCTAssertTrue( - rule.isMatchedEventParameters( - [ - "card_type": "gold", - "amount": NSNumber(value: 1), - ] - ), - "Should expect the parameter matched with the rule" - ) - XCTAssertFalse( - rule.isMatchedEventParameters( - [ - "card_type": "platium", - "amount": NSNumber(value: 1), - ] - ), - "Should not expect the parameter matched with the rule if the card type is the same" - ) - XCTAssertFalse( - rule.isMatchedEventParameters( - [ - "card_type": "gold", - "amount": NSNumber(value: 100), - ] - ), - "Should not expect the parameter matched with the rule if amount is high" - ) - } - - func testIsMatchedEventParametersForNestedRules() { - let andRule = AEMAdvertiserMultiEntryRule( - with: .and, - rules: [SampleAEMSingleEntryRules.cardTypeRule2, SampleAEMSingleEntryRules.valueRule] - ) - let orRule = AEMAdvertiserMultiEntryRule( - with: .or, - rules: [SampleAEMSingleEntryRules.contentNameRule, SampleAEMSingleEntryRules.contentCategoryRule] - ) - let nestedRule = AEMAdvertiserMultiEntryRule( - with: .and, - rules: [andRule, orRule, SampleAEMSingleEntryRules.urlRule] - ) - XCTAssertTrue( - nestedRule.isMatchedEventParameters( - [ - "URL": "thankyou.do.com", - "content_category": "demand", - "card_type": "blue_credit", - "amount": NSNumber(value: 100), - ] - ), - "Shoule expect the rule is matched" - ) - XCTAssertFalse( - nestedRule.isMatchedEventParameters( - [ - "URL": "thankyou.com", - "content_category": "demand", - "card_type": "blue_credit", - "amount": NSNumber(value: 100), - ] - ), - "Shoule not expect the rule is matched with wrong URL" - ) - XCTAssertFalse( - nestedRule.isMatchedEventParameters( - [ - "URL": "thankyou.do.com", - "content_category": "required", - "card_type": "blue_credit", - "amount": NSNumber(value: 100), - ] - ), - "Shoule not expect the rule is matched with wrong content_category" - ) - } - - func testSecureCoding() { - XCTAssertTrue( - AEMAdvertiserMultiEntryRule.supportsSecureCoding, - "AEM Advertiser Multi Entry Rule should support secure coding" - ) - } - - func testEncodingAndDecoding() throws { - let entryRule = SampleAEMData.validAdvertiserMultiEntryRule - let decodedObject = try CodabilityTesting.encodeAndDecode(entryRule) - - // Test Objects - XCTAssertNotIdentical(decodedObject, entryRule, .isCodable) - XCTAssertNotEqual(decodedObject, entryRule, .isCodable) // isEqual Method hasn't been implemented - - // Test Properties - XCTAssertEqual(decodedObject.operator.rawValue, entryRule.operator.rawValue) - let rules = try XCTUnwrap( - entryRule.rules as? [AEMAdvertiserSingleEntryRule] - ) - let decodedRules = try XCTUnwrap( - decodedObject.rules as? [AEMAdvertiserSingleEntryRule] - ) - XCTAssertEqual(decodedRules, rules) - } -} - -// MARK: - Assumptions - -extension String { - fileprivate static let isCodable = "AEMAdvertiserMultiEntryRule should be encodable and decodable" -} diff --git a/FBAEMKit/FBAEMKitTests/AEMAdvertiserRuleFactoryTests.swift b/FBAEMKit/FBAEMKitTests/AEMAdvertiserRuleFactoryTests.swift deleted file mode 100644 index c306a46406..0000000000 --- a/FBAEMKit/FBAEMKitTests/AEMAdvertiserRuleFactoryTests.swift +++ /dev/null @@ -1,311 +0,0 @@ -/* - * 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 AEMAdvertiserRuleFactoryTests: XCTestCase { - - let factory = AEMAdvertiserRuleFactory() - - func testCreateRuleWithJson() { - XCTAssertNil( - factory.createRule(json: nil), - "Should not create valid Single Entry Rule with nil" - ) - XCTAssertNil( - factory.createRule(json: ""), - "Should not create valid Single Entry Rule with empty string" - ) - XCTAssertNil( - factory.createRule(json: nil), - "Should not create valid Single Entry Rule with nil" - ) - XCTAssertNotNil( - factory.createRule(json: #"{"and": [{"value": {"contains": "abc"}}]}"#), - "Should create expected Multi Entry Rule" - ) - XCTAssertNotNil( - factory.createRule(json: #"{"value": {"contains": "abc"}}"#), - "Should create expected Single Entry Rule" - ) - XCTAssertNotNil( - factory.createRule(json: #"{"and": [{"event": {"eq": "Lead"}}, {"or": [{"URL": {"contains": "achievetestprep.com"}}]}]}"#), // swiftlint:disable:this line_length - "Should create expected nested Multi Entry Rule" - ) - } - - func testCreateRuleWithDict() { - XCTAssertNil( - factory.createRule(dictionary: [:]), - "Should not create valid Single Entry Rule with emtpy dictionary" - ) - XCTAssertNil( - factory.createRule(dictionary: ["value": ["contains": 10]]), - "Should not create valid Single Entry Rule with invalid dictionary" - ) - let multiEntryRule = factory.createRule(dictionary: ["and": [SampleAEMData.validAdvertiserSingleEntryRuleJson1]]) as? AEMAdvertiserMultiEntryRule // swiftlint:disable:this line_length - XCTAssertNotNil( - multiEntryRule, - "Should create expected Multi Entry Rule" - ) - let singleEntryRule = factory.createRule(dictionary: ["value": ["contains": "abc"]]) as? AEMAdvertiserSingleEntryRule // swiftlint:disable:this line_length - XCTAssertNotNil( - singleEntryRule, - "Should create expected Single Entry Rule" - ) - } - - func testCreateSingleEntryRuleWithValidDict() { - var rule: AEMAdvertiserSingleEntryRule? - rule = factory.createSingleEntryRule(from: SampleAEMData.validAdvertiserSingleEntryRuleJson1) - XCTAssertTrue( - SampleAEMData.advertiserSingleEntryRule1.isEqual(rule), - "Should create the expected Single Entry Rule" - ) - rule = factory.createSingleEntryRule(from: SampleAEMData.validAdvertiserSingleEntryRuleJson2) - XCTAssertTrue( - SampleAEMData.advertiserSingleEntryRule2.isEqual(rule), - "Should create the expected Single Entry Rule" - ) - rule = factory.createSingleEntryRule(from: SampleAEMData.validAdvertiserSingleEntryRuleJson3) - XCTAssertTrue( - SampleAEMData.advertiserSingleEntryRule3.isEqual(rule), - "Should create the expected Single Entry Rule" - ) - } - - func testCreateSingleEntryRuleWithInvalidDict() { - XCTAssertNil( - factory.createSingleEntryRule(from: [:]), - "Should not create valid Single Entry Rule with empty dictionary" - ) - XCTAssertNil( - factory.createSingleEntryRule(from: ["contains": []]), - "Should not create valid Single Entry Rule with invalid dictionary" - ) - XCTAssertNil( - factory.createSingleEntryRule(from: ["value": ["contains": 10]]), - "Should not create valid Single Entry Rule with invalid dictionary" - ) - XCTAssertNil( - factory.createSingleEntryRule(from: ["value": ["lt": "abc"]]), - "Should not create valid Single Entry Rule with invalid dictionary" - ) - } - - func testCreateMultiEntryRuleWithValidDict() { - zip( - ["and", "or", "not"], - [ - .and, - .or, - .not, - ] as [AEMAdvertiserRuleOperator] - ).forEach { opString, expectedOperator in - let rule: AEMAdvertiserMultiEntryRule? = factory.createMultiEntryRule( - from: [opString: [SampleAEMData.validAdvertiserSingleEntryRuleJson1, SampleAEMData.validAdvertiserSingleEntryRuleJson2]]) // swiftlint:disable:this line_length - XCTAssertNotNil( - rule, - "Should create Multi Entry Rule with valid dictionary" - ) - XCTAssertEqual( - expectedOperator, - rule?.operator, - "Multi Entry Rule should have the expected operator" - ) - XCTAssertEqual( - 2, - rule?.rules.count, - "Multi Entry Rule should have the expected number of subrules" - ) - } // swiftlint:disable:this closure_end_indentation - } - - func testCreateMultiEntryRuleWithInvalidDict() { - XCTAssertNil( - factory.createMultiEntryRule(from: [:]), - "Should not create valid Multi Entry Rule with empty dictionary" - ) - XCTAssertNil( - factory.createMultiEntryRule(from: ["contains": [["content": ["starts_with": "abc"]]]]), - "Should not create valid Multi Entry Rule with invlaid operator" - ) - XCTAssertNil( - factory.createMultiEntryRule(from: ["and": []]), - "Should not create valid Multi Entry Rule with empty subrules" - ) - XCTAssertNil( - factory.createMultiEntryRule(from: ["and": ["value": ["contains": 10]]]), - "Should not create valid Multi Entry Rule with invlaid subrule" - ) - } - - func testGetPrimaryKey() { - XCTAssertEqual( - factory.primaryKey(for: ["test_key": "abc"]), - "test_key", - "Should get the expected key of the dictionary" - ) - XCTAssertNil( - factory.primaryKey(for: [:]), - "Should not get the unexpected key while the dictionay is empty" - ) - } - - func testGetOperator() { - XCTAssertEqual( - factory.getOperator(from: ["test_key": "abc"]), - .unknown, - "Should get the expected Unknown operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["And": "abc"]), - .and, - "Should get the expected AND operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["and": "abc"]), - .and, - "Should get the expected AND operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["or": "abc"]), - .or, - "Should get the expected OR operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["not": "abc"]), - .not, - "Should get the expected NOT operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["contains": "abc"]), - .contains, - "Should get the expected Contains operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["not_contains": "abc"]), - .notContains, - "Should get the expected NotContains operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["starts_with": "abc"]), - .startsWith, - "Should get the expected StartsWith operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["i_contains": "abc"]), - .caseInsensitiveContains, - "Should get the expected i_contains operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["i_not_contains": "abc"]), - .caseInsensitiveNotContains, - "Should get the expected I_NotContains operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["i_starts_with": "abc"]), - .caseInsensitiveStartsWith, - "Should get the expected I_StartsWith operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["regex_match": "abc"]), - .regexMatch, - "Should get the expected REGEX_MATCH operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["eq": "abc"]), - .equal, - "Should get the expected EQ operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["neq": "abc"]), - .notEqual, - "Should get the expected NEQ operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["lt": 10]), - .lessThan, - "Should get the expected LT operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["lte": 10]), - .lessThanOrEqual, - "Should get the expected LTE operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["gt": 10]), - .greaterThan, - "Should get the expected GT operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["gte": 10]), - .greaterThanOrEqual, - "Should get the expected GTE operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["i_is_any": ["abc"]]), - .caseInsensitiveIsAny, - "Should get the expected I_IsAny operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["i_is_not_any": ["abc"]]), - .caseInsensitiveIsNotAny, - "Should get the expected I_IsNotAny operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["is_any": ["abc"]]), - .isAny, - "Should get the expected IsAny operator of the dictionary" - ) - XCTAssertEqual( - factory.getOperator(from: ["is_not_any": ["abc"]]), - .isNotAny, - "Should get the expected IsNotAny operator of the dictionary" - ) - } - - func testIsOperatorForMultiEntryRule() { - for ruleOperator in [ - .and, - .or, - .not, - ] as [AEMAdvertiserRuleOperator] { - XCTAssertTrue( - factory.isOperatorForMultiEntryRule(ruleOperator), - "Should expect the operator for multi entry rule" - ) - } - for ruleOperator in [ - .contains, - .notContains, - .startsWith, - .caseInsensitiveContains, - .caseInsensitiveNotContains, - .caseInsensitiveStartsWith, - .regexMatch, - .equal, - .notEqual, - .lessThan, - .lessThanOrEqual, - .greaterThan, - .greaterThanOrEqual, - .caseInsensitiveIsAny, - .caseInsensitiveIsNotAny, - .isAny, - .isNotAny, - ] as [AEMAdvertiserRuleOperator] { - XCTAssertFalse( - factory.isOperatorForMultiEntryRule(ruleOperator), - "Should expect the operator not for multi entry rule" - ) - } - } -} diff --git a/FBAEMKit/FBAEMKitTests/AEMAdvertiserSingleEntryRuleTests.swift b/FBAEMKit/FBAEMKitTests/AEMAdvertiserSingleEntryRuleTests.swift deleted file mode 100644 index 9e8b49b24c..0000000000 --- a/FBAEMKit/FBAEMKitTests/AEMAdvertiserSingleEntryRuleTests.swift +++ /dev/null @@ -1,488 +0,0 @@ -/* - * 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 AEMAdvertiserSingleEntryRuleTests: XCTestCase { - - enum Keys { - static let ruleOperator = "operator" - static let ruleParamKey = "param_key" - static let ruleStringValue = "string_value" - static let ruleNumberValue = "number_value" - static let ruleArrayValue = "array_value" - } - - func testIsMatchedWithEventParameters() { - var rule = AEMAdvertiserSingleEntryRule( - operator: .contains, - paramKey: "fb_content.title", - linguisticCondition: "hello", - numericalCondition: nil, - arrayCondition: nil - ) - XCTAssertTrue( - rule.isMatchedEventParameters(["fb_content": ["title": "helloworld"]]), - "Should expect the event parameter matched with the rule" - ) - XCTAssertTrue( - rule.isMatchedEventParameters(["fb_content": ["title": "HelloWorld"]]), - "Should expect the event parameter matched with the rule" - ) - XCTAssertFalse( - rule.isMatchedEventParameters(["fb_content": ["tt": "helloworld"]]), - "Should not expect the event parameter matched with the rule" - ) - XCTAssertFalse( - rule.isMatchedEventParameters(["fb_content": ["title": 100]]), - "Should not expect the event parameter matched with the rule" - ) - XCTAssertFalse( - rule.isMatchedEventParameters(["quantitly": ["title": "helloworld"]]), - "Should not expect the event parameter matched with the rule" - ) - - rule.operator = .notEqual - XCTAssertTrue( - rule.isMatchedEventParameters(["fb_content": ["title": "helloworld"]]), - "Should expect the event parameter matched with the rule" - ) - XCTAssertFalse( - rule.isMatchedEventParameters(["fb_content": ["tt": "helloworld"]]), - "Should not expect the event parameter matched with the rule" - ) - - rule = AEMAdvertiserSingleEntryRule( - operator: .greaterThan, - paramKey: "fb_content.product1.quantity", - linguisticCondition: nil, - numericalCondition: 10, - arrayCondition: nil - ) - XCTAssertTrue( - rule.isMatchedEventParameters(["fb_content": ["product1": ["quantity": 100.0]]]), - "Should expect the event parameter matched with the rule" - ) - XCTAssertFalse( - rule.isMatchedEventParameters(["fb_content": ["product1": ["quantity": 1]]]), - "Should expect the event parameter matched with the rule" - ) - } - - func testIsMatchedWithEventParametersForAsteriskOperator() { - let rule = AEMAdvertiserSingleEntryRule( - operator: .contains, - paramKey: "fb_content[*].id", - linguisticCondition: "coffee", - numericalCondition: nil, - arrayCondition: nil - ) - - XCTAssertTrue( - rule.isMatchedEventParameters(["fb_content": [["id": "shop"], ["id": "coffeeshop"]]]), - "Should expect the event parameter matched with the rule" - ) - XCTAssertFalse( - rule.isMatchedEventParameters(["fb_content": ["id": "coffeeshop"]]), - "Should not expect the event parameter matched with the rule without expected item" - ) - XCTAssertFalse( - rule.isMatchedEventParameters(["fb_content": [["id": "shop"]]]), - "Should not expect the event parameter matched with the rule without expected id" - ) - } - - func testIsMatchedWithEventParametersAndAsterisk() { - let rule = AEMAdvertiserSingleEntryRule( - operator: .contains, - paramKey: "fb_content[*].title", - linguisticCondition: "hello", - numericalCondition: nil, - arrayCondition: nil - ) - XCTAssertTrue( - rule.isMatched( - withAsteriskParam: "fb_content[*]", - eventParameters: ["fb_content": [["title": "hello"], ["title", "world"]]], - paramPath: ["fb_content[*]", "title"] - ), - "Should expect the event parameter matched with the rule" - ) - XCTAssertFalse( - rule.isMatched( - withAsteriskParam: "fb_content[*]", - eventParameters: ["fb_content": [["title": "aaaa"], ["title", "world"]]], - paramPath: ["fb_content[*]", "title"] - ), - "Should not expect the event parameter matched with the rule" - ) - } - - func testIsMatchedWithAsteriskParam() { - let rule = AEMAdvertiserSingleEntryRule( - operator: .contains, - paramKey: "fb_content[*].title", - linguisticCondition: "hello", - numericalCondition: nil, - arrayCondition: nil - ) - - XCTAssertTrue( - rule.isMatched( - withAsteriskParam: "fb_content[*]", - eventParameters: ["fb_content": [["title": "hello"], ["title", "world"]]], - paramPath: ["fb_content[*]", "title"] - ), - "Should expect the event parameter matched with the rule" - ) - XCTAssertFalse( - rule.isMatched( - withAsteriskParam: "fb_content[*]", - eventParameters: ["fb_content": [["title": "aaaa"], ["title", "world"]]], - paramPath: ["fb_content[*]", "title"] - ), - "Should not expect the event parameter matched with the rule" - ) - XCTAssertFalse( - rule.isMatched( - withAsteriskParam: "fb_content[*]", - eventParameters: ["fb_content_aaa": [["title": "aaaa"], ["title", "world"]]], - paramPath: ["fb_content[*]", "title"] - ), - "Should not expect the event parameter matched with the rule" - ) - XCTAssertFalse( - rule.isMatched( - withAsteriskParam: "fb_content[*]", - eventParameters: ["fb_content_aaa": ["title": "aaaa"]], - paramPath: ["fb_content[*]", "title"] - ), - "Should not expect the event parameter matched with the rule" - ) - } - - func testIsMatchedWithStringComparision() { - let rule = AEMAdvertiserSingleEntryRule( - operator: .contains, - paramKey: "fb_content.title", - linguisticCondition: "hello", - numericalCondition: nil, - arrayCondition: nil - ) - XCTAssertTrue( - rule.isMatched(withStringValue: "worldhelloworld", numericalValue: nil), - "Shoule expect parameter matched with the value" - ) - XCTAssertFalse( - rule.isMatched(withStringValue: "worldhellworld", numericalValue: nil), - "Shoule not expect parameter matched with the value" - ) - - rule.operator = .notContains - XCTAssertFalse( - rule.isMatched(withStringValue: "worldhelloworld", numericalValue: nil), - "Shoule not expect parameter matched with the value" - ) - XCTAssertFalse( - rule.isMatched(withStringValue: "WorldHelloWorld", numericalValue: nil), - "Shoule not expect parameter matched with the value" - ) - XCTAssertTrue( - rule.isMatched(withStringValue: "worldhellworld", numericalValue: nil), - "Shoule expect parameter matched with the value" - ) - - rule.operator = .startsWith - XCTAssertTrue( - rule.isMatched(withStringValue: "helloworld", numericalValue: nil), - "Shoule expect parameter matched with the value" - ) - XCTAssertTrue( - rule.isMatched(withStringValue: "HelloWorld", numericalValue: nil), - "Shoule expect parameter matched with the value" - ) - XCTAssertFalse( - rule.isMatched(withStringValue: "worldhelloworld", numericalValue: nil), - "Shoule not expect parameter matched with the value" - ) - - rule.operator = .caseInsensitiveContains - XCTAssertTrue( - rule.isMatched(withStringValue: "worldHELLOworld", numericalValue: nil), - "Shoule expect parameter matched with the value" - ) - XCTAssertFalse( - rule.isMatched(withStringValue: "worldhellworld", numericalValue: nil), - "Shoule not expect parameter matched with the value" - ) - - rule.operator = .caseInsensitiveNotContains - XCTAssertFalse( - rule.isMatched(withStringValue: "worldHELLOworld", numericalValue: nil), - "Shoule not expect parameter matched with the value" - ) - XCTAssertTrue( - rule.isMatched(withStringValue: "worldHELLworld", numericalValue: nil), - "Shoule expect parameter matched with the value" - ) - - rule.operator = .caseInsensitiveStartsWith - XCTAssertTrue( - rule.isMatched(withStringValue: "HELLOworld", numericalValue: nil), - "Shoule expect parameter matched with the value" - ) - XCTAssertFalse( - rule.isMatched(withStringValue: "worldHELLOworld", numericalValue: nil), - "Shoule not expect parameter matched with the value" - ) - - rule.operator = .equal - XCTAssertTrue( - rule.isMatched(withStringValue: "hello", numericalValue: nil), - "Shoule expect parameter matched with the value" - ) - XCTAssertTrue( - rule.isMatched(withStringValue: "Hello", numericalValue: nil), - "Shoule expect parameter matched with the value" - ) - XCTAssertFalse( - rule.isMatched(withStringValue: "hellw", numericalValue: nil), - "Shoule not expect parameter matched with the value" - ) - - rule.operator = .notEqual - XCTAssertFalse( - rule.isMatched(withStringValue: "hello", numericalValue: nil), - "Shoule not expect parameter matched with the value" - ) - XCTAssertFalse( - rule.isMatched(withStringValue: "Hello", numericalValue: nil), - "Shoule not expect parameter matched with the value" - ) - XCTAssertTrue( - rule.isMatched(withStringValue: "hellw", numericalValue: nil), - "Shoule expect parameter matched with the value" - ) - } - - func testIsMatchedWithNumberComparision() { - let rule = AEMAdvertiserSingleEntryRule( - operator: .lessThan, - paramKey: "fb_content.title", - linguisticCondition: nil, - numericalCondition: 100, - arrayCondition: nil - ) - XCTAssertTrue( - rule.isMatched(withStringValue: nil, numericalValue: 90), - "Shoule expect parameter matched with value" - ) - XCTAssertFalse( - rule.isMatched(withStringValue: nil, numericalValue: 100), - "Shoule not expect parameter matched with value" - ) - XCTAssertFalse( - rule.isMatched(withStringValue: nil, numericalValue: 101), - "Shoule not expect parameter matched with value" - ) - - rule.operator = .lessThanOrEqual - XCTAssertTrue( - rule.isMatched(withStringValue: nil, numericalValue: 99), - "Shoule expect parameter matched with value" - ) - XCTAssertTrue( - rule.isMatched(withStringValue: nil, numericalValue: 100), - "Shoule expect parameter matched with value" - ) - XCTAssertFalse( - rule.isMatched(withStringValue: nil, numericalValue: 100.1), - "Shoule not expect parameter matched with value" - ) - - rule.operator = .greaterThan - XCTAssertTrue( - rule.isMatched(withStringValue: nil, numericalValue: 101.5), - "Shoule expect parameter matched with value" - ) - XCTAssertFalse( - rule.isMatched(withStringValue: nil, numericalValue: 100), - "Shoule not expect parameter matched with value" - ) - XCTAssertFalse( - rule.isMatched(withStringValue: nil, numericalValue: 99), - "Shoule not expect parameter matched with value" - ) - - rule.operator = .greaterThanOrEqual - XCTAssertTrue( - rule.isMatched(withStringValue: nil, numericalValue: 101.5), - "Shoule expect parameter matched with value" - ) - XCTAssertTrue( - rule.isMatched(withStringValue: nil, numericalValue: 100), - "Shoule expect parameter matched with value" - ) - XCTAssertFalse( - rule.isMatched(withStringValue: nil, numericalValue: 99), - "Shoule not expect parameter matched with value" - ) - } - - func testIsMatchedWithArrayComparision() { - let rule = AEMAdvertiserSingleEntryRule( - operator: .isAny, - paramKey: "fb_content.title", - linguisticCondition: nil, - numericalCondition: nil, - arrayCondition: ["Abc", "aaa", "ABC", "XXXX"] - ) - XCTAssertTrue( - rule.isMatched(withStringValue: "aaa", numericalValue: nil), - "Shoule expect parameter matched with item in the array" - ) - XCTAssertFalse( - rule.isMatched(withStringValue: "bbb", numericalValue: nil), - "Shoule not expect parameter matched with item in the array" - ) - - rule.operator = .caseInsensitiveIsAny - XCTAssertTrue( - rule.isMatched(withStringValue: "abc", numericalValue: nil), - "Shoule expect parameter matched with item in the array" - ) - - rule.operator = .isNotAny - XCTAssertTrue( - rule.isMatched(withStringValue: "xxxx", numericalValue: nil), - "Shoule expect parameter matched with item in the array" - ) - - rule.operator = .caseInsensitiveIsNotAny - XCTAssertTrue( - rule.isMatched(withStringValue: "ab", numericalValue: nil), - "Shoule expect parameter matched with item in the array" - ) - } - - func testIsRegexMatch() { - let rule = AEMAdvertiserSingleEntryRule( - operator: .isAny, - paramKey: "fb_content.title", - linguisticCondition: "eylea.us/support/?$|eylea.us/support/?", - numericalCondition: nil, - arrayCondition: nil - ) - XCTAssertTrue( - rule.isRegexMatch("eylea.us/support"), - "Should expect parameter matched with regex" - ) - XCTAssertFalse( - rule.isRegexMatch("eylea.us.support"), - "Should not expect parameter matched with regex" - ) - } - - func testIsRegexMatchWithEmtpyString() { - let rule = AEMAdvertiserSingleEntryRule( - operator: .isAny, - paramKey: "fb_content.title", - linguisticCondition: "", - numericalCondition: nil, - arrayCondition: nil - ) - XCTAssertFalse( - rule.isRegexMatch("eylea.us.support"), - "Should not expect parameter matched with regex" - ) - } - - func testIsRegexMatchWithNullableString() { - let rule = AEMAdvertiserSingleEntryRule( - operator: .isAny, - paramKey: "fb_content.title", - linguisticCondition: nil, - numericalCondition: nil, - arrayCondition: nil - ) - XCTAssertFalse( - rule.isRegexMatch("eylea.us.support"), - "Should not expect parameter matched with regex" - ) - } - - func testIsAnyOf() { - let rule = AEMAdvertiserSingleEntryRule( - operator: .isAny, - paramKey: "fb_content.title", - linguisticCondition: nil, - numericalCondition: nil, - arrayCondition: ["abc", "AAA", "Abc"] - ) - XCTAssertTrue( - rule.isAny(of: ["abc", "AAA", "Abc"], stringValue: "AAA", ignoreCase: false), - "Should expect parameter matched" - ) - XCTAssertTrue( - rule.isAny(of: ["abc", "AAA", "Abc"], stringValue: "aaa", ignoreCase: true), - "Should expect parameter matched" - ) - XCTAssertTrue( - rule.isAny(of: ["abc", "AAA", "Abc"], stringValue: "ABC", ignoreCase: true), - "Should expect parameter matched" - ) - XCTAssertFalse( - rule.isAny(of: ["abc", "AAA", "Abc"], stringValue: "aaa", ignoreCase: false), - "Should not expect parameter matched" - ) - XCTAssertFalse( - rule.isAny(of: ["abc", "AAA", "Abc"], stringValue: "ab", ignoreCase: false), - "Should not expect parameter matched" - ) - } - - func testSecureCoding() { - XCTAssertTrue( - AEMAdvertiserSingleEntryRule.supportsSecureCoding, - "AEM Advertiser Single Entry Rule should support secure coding" - ) - } - - func testEncodingAndDecoding() throws { - let entryRule = SampleAEMData.validAdvertiserSingleEntryRule - let decodedObject = try CodabilityTesting.encodeAndDecode(entryRule) - - // Test Objects - XCTAssertNotIdentical(entryRule, decodedObject, .isCodable) - XCTAssertEqual(entryRule, decodedObject, .isCodable) - - // Test Properties - XCTAssertEqual(entryRule.operator.rawValue, decodedObject.operator.rawValue, .isCodable) - XCTAssertEqual(entryRule.paramKey, decodedObject.paramKey, .isCodable) - XCTAssertEqual( - entryRule.linguisticCondition, - decodedObject.linguisticCondition, - .isCodable - ) - XCTAssertEqual( - entryRule.numericalCondition, - decodedObject.numericalCondition, - .isCodable - ) - XCTAssertEqual(entryRule.arrayCondition, decodedObject.arrayCondition, .isCodable) - } -} - -// MARK: - Assumptions - -extension String { - fileprivate static let isCodable = "AEMAdvertiserSingleEntryRule should be encodable and decodable" -} diff --git a/FBAEMKit/FBAEMKitTests/AEMConfigurationTests.swift b/FBAEMKit/FBAEMKitTests/AEMConfigurationTests.swift deleted file mode 100644 index 58dd70f00b..0000000000 --- a/FBAEMKit/FBAEMKitTests/AEMConfigurationTests.swift +++ /dev/null @@ -1,355 +0,0 @@ -/* - * 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 AEMConfigurationTests: XCTestCase { - - enum Keys { - static let defaultCurrency = "default_currency" - static let cutoffTime = "cutoff_time" - static let validFrom = "valid_from" - static let mode = "config_mode" - static let advertiserID = "advertiser_id" - static let businessID = "business_id" - static let paramRule = "param_rule" - static let conversionValueRules = "conversion_value_rules" - static let conversionValue = "conversion_value" - static let priority = "priority" - static let events = "events" - static let eventName = "event_name" - static let values = "values" - static let currency = "currency" - static let amount = "amount" - } - - enum Values { - static let coffeeBrand = "coffeebrand" - static let paramRule = #"{"and": [{"fb_content[*].brand": {"eq": "CoffeeShop"}}]}"# - static let purchase = "fb_mobile_purchase" - static let donate = "Donate" - static let defaultMode = "default" - static let USD = "USD" - static let JPY = "JPY" - } - - var sampleData: [String: Any] = [ - Keys.defaultCurrency: Values.USD, - Keys.cutoffTime: 1, - Keys.validFrom: 10000, - Keys.mode: Values.defaultMode, - Keys.advertiserID: Values.coffeeBrand, - Keys.paramRule: Values.paramRule, - Keys.conversionValueRules: [ - [ - Keys.conversionValue: 2, - Keys.priority: 10, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - ], - [ - Keys.eventName: Values.donate, - ], - ], - ], - ], - ] - - var rulesData = [ - [ - Keys.conversionValue: 9, - Keys.priority: 10, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - Keys.values: [ - [ - Keys.currency: Values.USD, - Keys.amount: 100.0, - ], - [ - Keys.currency: Values.JPY, - Keys.amount: 1000.0, - ], - ], - ], - [ - Keys.eventName: Values.donate, - ], - ], - ], - [ - Keys.conversionValue: 15, - Keys.priority: 7, - Keys.events: [ - [ - Keys.eventName: Values.donate, - ], - ], - ], - [ - Keys.conversionValue: 20, - Keys.priority: 15, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - ], - ], - ], - ] - - let advertiserRuleFactory = AEMAdvertiserRuleFactory() - - override func setUp() { - super.setUp() - - AEMConfiguration.configure(withRuleProvider: advertiserRuleFactory) - } - - func testConfiguration() { - XCTAssertIdentical( - AEMConfiguration.ruleProvider as AnyObject, - advertiserRuleFactory, - "Should configure the AEMConfiguration correctly" - ) - } - - func testValidCases() { - let configuration = AEMConfiguration(json: sampleData) - - XCTAssertEqual( - configuration?.defaultCurrency, - Values.USD, - "Should parse the expected default_currency with the correct value" - ) - XCTAssertEqual( - configuration?.cutoffTime, - 1, - "Should parse the expected cutoff_time with the correct value" - ) - XCTAssertEqual( - configuration?.validFrom, - 10000, - "Should parse the expected valid_from with the correct value" - ) - XCTAssertEqual( - configuration?.mode, - Values.defaultMode, - "Should parse the expected config_mode with the correct value" - ) - XCTAssertEqual( - configuration?.businessID, - Values.coffeeBrand, - "Should parse the expected business_id with the correct value" - ) - XCTAssertEqual( - configuration?.conversionValueRules.count, - 1, - "Should parse the expected conversion_value_rules with the correct value" - ) - } - - func testInvalidCases() { - var invalidData: [String: Any] = [:] - XCTAssertNil(AEMConfiguration(json: invalidData)) - invalidData = [ - Keys.defaultCurrency: 100, - Keys.cutoffTime: 1, - Keys.validFrom: 10000, - Keys.mode: Values.defaultMode, - Keys.conversionValueRules: [ - [ - Keys.conversionValue: 2, - Keys.priority: 10, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - ], - [ - Keys.eventName: Values.donate, - ], - ], - ], - ], - ] - XCTAssertNil( - AEMConfiguration(json: invalidData), - "Should not consider the configuration json valid with unexpected type for default_currency" - ) - invalidData = [ - Keys.defaultCurrency: Values.USD, - Keys.cutoffTime: 1, - Keys.validFrom: 10000, - Keys.mode: Values.defaultMode, - ] - XCTAssertNil( - AEMConfiguration(json: invalidData), - "Should not consider the configuration json valid without any conversion value rules" - ) - invalidData = [ - Keys.defaultCurrency: Values.USD, - Keys.cutoffTime: 1, - Keys.validFrom: 10000, - Keys.mode: Values.defaultMode, - Keys.conversionValueRules: [ - [ - Keys.conversionValue: "2", - Keys.priority: 10, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - ], - [ - Keys.eventName: Values.donate, - ], - ], - ], - ], - ] - XCTAssertNil( - AEMConfiguration(json: invalidData), - "Should not consider the configuration json valid with invalid conversion value rule" - ) - } - - func testGetEventSet() { - guard let parsedRules: [AEMRule] = AEMConfiguration.parseRules(rulesData) - else { return XCTFail("Unwrapping Error") } - let eventSet = AEMConfiguration.getEventSet(from: parsedRules) - XCTAssertEqual(eventSet, [Values.purchase, Values.donate], "Should get the expected event set") - } - - func testGetCurrencySet() { - guard let parsedRules: [AEMRule] = AEMConfiguration.parseRules(rulesData) - else { return XCTFail("Unwrapping Error") } - let eventSet = AEMConfiguration.getCurrencySet(from: parsedRules) - XCTAssertEqual(eventSet, [Values.USD, Values.JPY], "Should get the expected event set") - } - - func testParseRules() { - let parsedRules: [AEMRule]? = AEMConfiguration.parseRules(rulesData) - XCTAssertEqual( - parsedRules?[0].priority, 15, "Shoule parse the rules in descending priority order" - ) - XCTAssertEqual( - parsedRules?[1].priority, 10, "Shoule parse the rules in descending priority order" - ) - XCTAssertEqual( - parsedRules?[2].priority, 7, "Shoule parse the rules in descending priority order" - ) - } - - func testParsing() { - (1 ... 100).forEach { _ in - if let data = (Fuzzer.randomize(json: self.sampleData) as? [String: Any]) { - _ = AEMConfiguration(json: data) - } - } - } - - func testIsSameBusinessID() { - let configWithBusinessID = SampleAEMConfigurations.createConfigurationWithBusinessID() - let configWithoutBusinessID = SampleAEMConfigurations.createConfigurationWithoutBusinessID() - - XCTAssertTrue( - configWithBusinessID.isSameBusinessID("test_advertiserid_123") == true, - "Should return true for the same business ID" - ) - XCTAssertFalse( - configWithBusinessID.isSameBusinessID("test_advertiserid_6666") == true, - "Should return false for the unexpected business ID" - ) - XCTAssertFalse( - configWithBusinessID.isSameBusinessID(nil) == true, - "Should return false for nil business ID if the configuration has business ID" - ) - - XCTAssertTrue( - configWithoutBusinessID.isSameBusinessID(nil) == true, - "Should return true for nil business ID if the configuration doesn't have business ID" - ) - XCTAssertFalse( - configWithoutBusinessID.isSameBusinessID("test_advertiserid_123") == true, - "Should return false for non-nil business ID if the configuration has business ID" - ) - } - - func testIsSameValidFromAndBusinessID() { - let configWithBusinessID = SampleAEMConfigurations.createConfigurationWithBusinessID() - let configWithoutBusinessID = SampleAEMConfigurations.createConfigurationWithoutBusinessID() - - XCTAssertTrue( - configWithBusinessID.isSame(validFrom: 10000, businessID: "test_advertiserid_123") == true, - "Should return true for the same validFrom and business ID" - ) - XCTAssertFalse( - configWithBusinessID.isSame(validFrom: 10000, businessID: "test_advertiserid_6666") == true, - "Should return false for the unexpected validFrom and business ID" - ) - XCTAssertFalse( - configWithBusinessID.isSame(validFrom: 10001, businessID: "test_advertiserid_123") == true, - "Should return false for the unexpected validFrom and business ID" - ) - XCTAssertFalse( - configWithBusinessID.isSame(validFrom: 10000, businessID: nil) == true, - "Should return false for the unexpected validFrom and business ID" - ) - - XCTAssertTrue( - configWithoutBusinessID.isSame(validFrom: 10000, businessID: nil) == true, - "Should return true for nil business ID if the configuration doesn't have business ID" - ) - XCTAssertFalse( - configWithoutBusinessID.isSame(validFrom: 10000, businessID: "test_advertiserid_123") == true, - "Should return false for the unexpected validFrom and business ID" - ) - XCTAssertFalse( - configWithoutBusinessID.isSame(validFrom: 10001, businessID: nil) == true, - "Should return false for the unexpected validFrom and business ID" - ) - } - - func testSecureCoding() { - XCTAssertTrue( - AEMConfiguration.supportsSecureCoding, - "AEM Configuration should support secure coding" - ) - } - - func testEncodingAndDecoding() throws { - // swiftlint:disable:next force_unwrapping - let configuration = AEMConfiguration(json: sampleData)! - let decodedObject = try CodabilityTesting.encodeAndDecode(configuration) - - // Test Object - XCTAssertNotIdentical(configuration, decodedObject) - XCTAssertNotEqual(configuration, decodedObject) // isEqual method not added yet - - // Test Properties - XCTAssertEqual(configuration.defaultCurrency, decodedObject.defaultCurrency, .isCodable) - XCTAssertEqual(configuration.cutoffTime, decodedObject.cutoffTime, .isCodable) - XCTAssertEqual(configuration.validFrom, decodedObject.validFrom, .isCodable) - XCTAssertEqual(configuration.mode, decodedObject.mode, .isCodable) - XCTAssertEqual(configuration.businessID, decodedObject.businessID, .isCodable) - XCTAssertEqual( - configuration.conversionValueRules, - decodedObject.conversionValueRules, - .isCodable - ) - } -} - -// MARK: - Assumptions - -extension String { - fileprivate static let isCodable = "AEMConfiguration should be encodable and decodable" -} diff --git a/FBAEMKit/FBAEMKitTests/AEMEventTests.swift b/FBAEMKit/FBAEMKitTests/AEMEventTests.swift deleted file mode 100644 index da78af86c5..0000000000 --- a/FBAEMKit/FBAEMKitTests/AEMEventTests.swift +++ /dev/null @@ -1,185 +0,0 @@ -/* - * 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 AEMEventTests: XCTestCase { - - enum Keys { - static let eventName = "event_name" - static let values = "values" - static let currency = "currency" - static let amount = "amount" - } - - enum Values { - static let purchase = "fb_mobile_purchase" - static let subscribe = "Subscribe" - static let usd = "usd" - static let jpy = "jpy" - static let USD = "USD" - static let JPY = "JPY" - } - - var sampleData: [String: Any] = [ - Keys.eventName: Values.purchase, - Keys.values: [ - [ - Keys.currency: Values.usd, - Keys.amount: 100, - ], - [ - Keys.currency: Values.JPY, - Keys.amount: 1000, - ], - ], - ] - var validEventWithValues: AEMEvent? = AEMEvent(dict: [ - Keys.eventName: Values.purchase, - Keys.values: [ - [ - Keys.currency: Values.usd, - Keys.amount: 100.0, - ], - [ - Keys.currency: Values.JPY, - Keys.amount: 1000.0, - ], - ], - ]) - - var validEventWithoutValues: AEMEvent? = AEMEvent(dict: [ - Keys.eventName: Values.purchase, - ]) - - func testValidCases() { - var event = validEventWithoutValues - XCTAssertEqual( - event?.eventName, - Values.purchase, - "AEM event name should match the expected event_name in the json" - ) - XCTAssertNil( - event?.values, - "AEM event should not have unexpected values" - ) - event = validEventWithValues - XCTAssertEqual( - event?.eventName, - Values.purchase, - "AEM event name should match the expected event_name in the json" - ) - let expectedValues: [String: Double] = [ - Values.USD: 100, - Values.JPY: 1000, - ] - XCTAssertEqual( - event?.values, - expectedValues, - "AEM event should have the expected values in the json" - ) - } - - func testInvalidCases() { - var invalidData: [String: Any] = [:] - XCTAssertNil(AEMEvent(dict: invalidData)) - invalidData = [ - Keys.values: [ - [ - Keys.currency: Values.usd, - Keys.amount: 100, - ], - [ - Keys.currency: Values.JPY, - Keys.amount: 1000, - ], - ], - ] - XCTAssertNil(AEMEvent(dict: invalidData)) - invalidData = [ - Keys.eventName: Values.purchase, - Keys.values: [ - [ - Keys.currency: 100, - Keys.amount: Values.usd, - ], - [ - Keys.currency: 1000, - Keys.amount: Values.jpy, - ], - ], - ] - XCTAssertNil(AEMEvent(dict: invalidData)) - invalidData = [ - Keys.eventName: [Values.purchase, Values.subscribe], - Keys.values: [ - [ - Keys.currency: 100, - Keys.amount: Values.usd, - ], - [ - Keys.currency: 1000, - Keys.amount: Values.jpy, - ], - ], - ] - XCTAssertNil(AEMEvent(dict: invalidData)) - } - - func testParsing() { - (1 ... 100).forEach { _ in - if let data = (Fuzzer.randomize(json: self.sampleData) as? [String: Any]) { - _ = AEMEvent(dict: data) - } - } - } - - func testSecureCoding() { - XCTAssertTrue( - AEMEvent.supportsSecureCoding, - "AEM Events should support secure coding" - ) - } - - func testEncodingAndDecodingWithValues() throws { - let event = validEventWithValues - // swiftlint:disable:next force_unwrapping - let decodedObject = try CodabilityTesting.encodeAndDecode(event!) - - // Test Objects - XCTAssertNotIdentical(decodedObject, event, .isCodable) - XCTAssertEqual(decodedObject, event, .isCodable) - - // Test Properties - XCTAssertEqual(event?.eventName, decodedObject.eventName) - XCTAssertEqual(event?.values, decodedObject.values) - } - - func testEncodingAndDecodingWithoutValues() throws { - let event = validEventWithoutValues - // swiftlint:disable:next force_unwrapping - let decodedObject = try CodabilityTesting.encodeAndDecode(event!) - - // Test Objects - XCTAssertNotIdentical(decodedObject, event, .isCodable) - XCTAssertEqual(decodedObject, event, .isCodable) - - // Test Properties - XCTAssertEqual(event?.eventName, decodedObject.eventName) - XCTAssertEqual(event?.values, decodedObject.values) - } -} - -// MARK: - Assumptions - -extension String { - fileprivate static let isCodable = "AEMEvents should be encodable and decodable" -} diff --git a/FBAEMKit/FBAEMKitTests/AEMInvocationTests.swift b/FBAEMKit/FBAEMKitTests/AEMInvocationTests.swift deleted file mode 100644 index 841a33bbde..0000000000 --- a/FBAEMKit/FBAEMKitTests/AEMInvocationTests.swift +++ /dev/null @@ -1,1194 +0,0 @@ -/* - * 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" -} diff --git a/FBAEMKit/FBAEMKitTests/AEMReporterTests.swift b/FBAEMKit/FBAEMKitTests/AEMReporterTests.swift deleted file mode 100644 index 265a95ac3a..0000000000 --- a/FBAEMKit/FBAEMKitTests/AEMReporterTests.swift +++ /dev/null @@ -1,1132 +0,0 @@ -/* - * 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 FBSDKCoreKit_Basics -import TestTools -import XCTest - -final class AEMReporterTests: XCTestCase { - - enum Keys { - static let defaultCurrency = "default_currency" - static let cutoffTime = "cutoff_time" - static let validFrom = "valid_from" - static let configurationMode = "config_mode" - static let conversionValueRules = "conversion_value_rules" - static let conversionValue = "conversion_value" - static let priority = "priority" - static let events = "events" - static let eventName = "event_name" - static let advertiserID = "advertiser_id" - static let businessID = "advertiser_id" - static let campaignID = "campaign_id" - static let catalogID = "catalog_id" - static let contentID = "fb_content_ids" - static let content = "fb_content" - static let token = "token" - } - - enum Values { - static let purchase = "fb_mobile_purchase" - static let donate = "Donate" - static let defaultMode = "DEFAULT" - static let brandMode = "BRAND" - static let cpasMode = "CPAS" - static let USD = "USD" - } - - let networker = TestAEMNetworker() - let reporter = TestSKAdNetworkReporter() - let userDefaultsSpy = UserDefaultsSpy() - let date = Calendar.current.date( - byAdding: .day, - value: -2, - to: Date() - )! // swiftlint:disable:this force_unwrapping - lazy var testInvocation = TestInvocation( - campaignID: name, - acsToken: name, - acsSharedSecret: nil, - acsConfigurationID: nil, - businessID: nil, - catalogID: nil, - isTestMode: false, - hasStoreKitAdNetwork: false, - isConversionFilteringEligible: true - )! // swiftlint:disable:this force_unwrapping - lazy var reportFilePath = BasicUtility.persistenceFilePath(name) - let urlWithInvocation = URL(string: "fb123://test.com?al_applink_data=%7B%22acs_token%22%3A+%22test_token_1234567%22%2C+%22campaign_ids%22%3A+%22test_campaign_1234%22%2C+%22advertiser_id%22%3A+%22test_advertiserid_12345%22%7D")! // swiftlint:disable:this force_unwrapping - let sampleCatalogOptimizationDictionary = ["data": [["content_id_belongs_to_catalog_id": true]]] - let aggregationRequestTimestampToNotDelay = Date().addingTimeInterval(-100) - let analyticsAppID = "analytics_123" - - override func setUp() { - super.setUp() - - AEMReporter.reset() - removeReportFile() - AEMReporter.configure( - networker: networker, - appID: "123", - reporter: reporter, - analyticsAppID: analyticsAppID, - store: userDefaultsSpy - ) - // Actual queue doesn't matter as long as it's not the same as the designated queue name in the class - AEMReporter.serialQueue = DispatchQueue(label: name, qos: .background) - AEMReporter.isAEMReportEnabled = true - AEMReporter.reportFile = reportFilePath - } - - func testEnable() { - AEMReporter.enable() - - XCTAssertFalse(AEMReporter.isAEMReportEnabled, "AEM Report should not be enabled") - } - - func testConversionFilteringDefaultConfigure() { - XCTAssertFalse(AEMReporter.isConversionFilteringEnabled, "AEM Conversion Filtering should be disabled by default") - } - - func testSetConversionFilteringEnabled() { - AEMReporter.isConversionFilteringEnabled = false - AEMReporter.setConversionFilteringEnabled(true) - - XCTAssertTrue(AEMReporter.isConversionFilteringEnabled, "AEM Conversion Filtering should be enabled") - } - - func testCatalogMatchingDefaultConfigure() { - XCTAssertFalse(AEMReporter.isCatalogMatchingEnabled, "AEM Catalog Matching should be disabled by default") - } - - func testSetCatalogMatchingEnabled() { - AEMReporter.isCatalogMatchingEnabled = false - AEMReporter.setCatalogMatchingEnabled(true) - - XCTAssertTrue(AEMReporter.isCatalogMatchingEnabled, "AEM Catalog Matching should be enabled") - } - - func testAdvertiserRuleMatchInServerEnabledDefaultConfigure() { - XCTAssertFalse( - AEMReporter.isAdvertiserRuleMatchInServerEnabled, - "AEM Advertiser Rule Match in server should be disabled by default" - ) - } - - func testSetAdvertiserRuleMatchInServerEnabled() { - AEMReporter.isAdvertiserRuleMatchInServerEnabled = false - AEMReporter.setAdvertiserRuleMatchInServerEnabled(true) - - XCTAssertTrue( - AEMReporter.isAdvertiserRuleMatchInServerEnabled, - "AEM Advertiser Rule Match in server should be enabled" - ) - } - - func testConfigure() { - XCTAssertEqual( - networker, - AEMReporter.networker as? TestAEMNetworker, - "Should configure with the expected AEM networker" - ) - XCTAssertEqual( - reporter, - AEMReporter.reporter as? TestSKAdNetworkReporter, - "Should configure with the expected SKAdNetwork reporter" - ) - XCTAssertEqual( - userDefaultsSpy, - AEMReporter.dataStore as? UserDefaultsSpy, - "Should configure with the expected data store" - ) - XCTAssertEqual( - AEMReporter.analyticsAppID, - analyticsAppID, - "Should configure with the expected analytics app id" - ) - } - - func testParseURL() { - var url: URL? - XCTAssertNil(AEMReporter.parseURL(url)) - - url = URL(string: "fb123://test.com") - XCTAssertNil(AEMReporter.parseURL(url)) - - url = URL(string: "fb123://test.com?al_applink_data=%7B%22acs_token%22%3A+%22test_token_1234567%22%2C+%22campaign_ids%22%3A+%22test_campaign_1234%22%7D") - var invocation = AEMReporter.parseURL(url) - XCTAssertEqual(invocation?.acsToken, "test_token_1234567") - XCTAssertEqual(invocation?.campaignID, "test_campaign_1234") - XCTAssertNil(invocation?.businessID) - - invocation = AEMReporter.parseURL(urlWithInvocation) - XCTAssertEqual(invocation?.acsToken, "test_token_1234567") - XCTAssertEqual(invocation?.campaignID, "test_campaign_1234") - XCTAssertEqual(invocation?.businessID, "test_advertiserid_12345") - } - - func testLoadReportData() { - guard let invocation = AEMReporter.parseURL(urlWithInvocation) else { - return XCTFail("Parsing Error") - } - - AEMReporter.invocations = [invocation] - AEMReporter.saveReportData() - let data = AEMReporter.loadReportData() - XCTAssertEqual(data.count, 1) - XCTAssertEqual(data[0].acsToken, "test_token_1234567") - XCTAssertEqual(data[0].campaignID, "test_campaign_1234") - XCTAssertEqual(data[0].businessID, "test_advertiserid_12345") - } - - func testClearCache() { - AEMReporter.addConfigurations([SampleAEMData.validConfigurationData1]) - AEMReporter.addConfigurations([SampleAEMData.validConfigurationData1, SampleAEMData.validConfigurationData2]) - - AEMReporter.clearCache() - var configurations = AEMReporter.configurations - var configList = configurations[Values.defaultMode] - XCTAssertEqual(configList?.count, 1, "Should have the expected number of configuration") - - guard let invocation1 = AEMInvocation( - campaignID: "test_campaign_1234", - acsToken: "test_token_1234567", - acsSharedSecret: "test_shared_secret", - acsConfigurationID: "test_config_id_123", - businessID: nil, - catalogID: nil, - isTestMode: false, - hasStoreKitAdNetwork: false, - isConversionFilteringEligible: true - ), let invocation2 = AEMInvocation( - campaignID: "test_campaign_1234", - acsToken: "test_token_1234567", - acsSharedSecret: "test_shared_secret", - acsConfigurationID: "test_config_id_123", - businessID: nil, - catalogID: nil, - isTestMode: false, - hasStoreKitAdNetwork: false, - isConversionFilteringEligible: true - ) - else { return XCTFail("Unwrapping Error") } - invocation1.configurationID = 10000 - invocation2.configurationID = 10001 - guard let date = Calendar.current.date(byAdding: .day, value: -2, to: Date()) - else { return XCTFail("Date Creation Error") } - invocation2.conversionTimestamp = date - AEMReporter.invocations = [invocation1, invocation2] - AEMReporter.addConfigurations( - [SampleAEMData.validConfigurationData1, SampleAEMData.validConfigurationData2, SampleAEMData.validConfigData3] - ) - AEMReporter.clearCache() - let invocations = AEMReporter.invocations - XCTAssertEqual(invocations.count, 1, "Should clear the expired invocation") - XCTAssertEqual(invocations[0].configurationID, 10000, "Should keep the expected invocation") - configurations = AEMReporter.configurations - configList = configurations[Values.defaultMode] - XCTAssertEqual(configList?.count, 2, "Should have the expected number of configuration") - XCTAssertEqual(configList?[0].validFrom, 10000, "Should keep the expected ") - XCTAssertEqual(configList?[1].validFrom, 20000, "Should keep the expected ") - } - - func testClearConfigurations() { - AEMReporter.configurations = [ - Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()], - Values.brandMode: [SampleAEMConfigurations.createConfigurationWithBusinessIDAndContentRule()], - Values.cpasMode: [SampleAEMConfigurations.createCpasConfiguration()], - ] - - AEMReporter.clearConfigurations() - let defaultConfigurations = AEMReporter.configurations[Values.defaultMode] - let brandConfigurations = AEMReporter.configurations[Values.brandMode] - let cpasConfigurations = AEMReporter.configurations[Values.cpasMode] - XCTAssertEqual( - defaultConfigurations?.count, - 1, - "Should have default mode " - ) - XCTAssertEqual( - brandConfigurations?.count, - 0, - "Should not have brand mode " - ) - XCTAssertEqual( - cpasConfigurations?.count, - 0, - "Should not have cpas mode " - ) - } - - func testHandleURL() throws { - let url = try XCTUnwrap( - URL(string: "fb123://test.com?al_applink_data=%7B%22acs_token%22%3A+%22test_token_1234567%22%2C+%22campaign_ids%22%3A+%22test_campaign_1234%22%7D"), - "Should be able to create URL with valid deeplink" - ) - AEMReporter.handle(url) - let invocations = AEMReporter.invocations - XCTAssertGreaterThan( - invocations.count, - 0, - "Handling a url that contains invocations should set the invocations on the reporter" - ) - } - - func testHandleDebuggingURL() { - guard let url = URL(string: "fb123://test.com?al_applink_data=%7B%22acs_token%22%3A+%22debugging_token%22%2C+%22campaign_ids%22%3A+%2210%22%2C+%22test_deeplink%22%3A+1%7D") - else { return XCTFail("Unwrapping Error") } - AEMReporter.invocations = [] - AEMReporter.handle(url) - XCTAssertEqual( - AEMReporter.invocations.count, - 0, - "Handling a debugging url should not affect production traffic" - ) - } - - func testIsConfigRefreshTimestampValid() { - AEMReporter.configRefreshTimestamp = Date() - XCTAssertTrue( - AEMReporter.isConfigRefreshTimestampValid(), - "Timestamp should be valid" - ) - - guard let date = Calendar.current.date(byAdding: .day, value: -2, to: Date()) - else { return XCTFail("Date Creation Error") } - AEMReporter.configRefreshTimestamp = date - XCTAssertFalse( - AEMReporter.isConfigRefreshTimestampValid(), - "Timestamp should not be valid" - ) - } - - func testShouldEnforceRefresh() { - AEMReporter.invocations = [SampleAEMData.invocationWithoutAdvertiserID] - AEMReporter.configRefreshTimestamp = Date() - AEMReporter.configurations = [ - Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()], - ] - - XCTAssertTrue( - AEMReporter.shouldRefresh(withIsForced: true), - "Should refresh if it's enforced" - ) - } - - func testShouldRefreshWithoutBusinessID1() { - AEMReporter.invocations = [SampleAEMData.invocationWithoutAdvertiserID] - AEMReporter.configRefreshTimestamp = Date() - AEMReporter.configurations = [ - Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()], - ] - - XCTAssertFalse( - AEMReporter.shouldRefresh(withIsForced: false), - "Should not refresh if timestamp is not expired and there is no business ID" - ) - } - - func testShouldRefreshWithoutBusinessID2() { - AEMReporter.invocations = [SampleAEMData.invocationWithoutAdvertiserID] - guard let date = Calendar.current.date(byAdding: .day, value: -2, to: Date()) - else { return XCTFail("Date Creation Error") } - AEMReporter.configRefreshTimestamp = date - AEMReporter.configurations = [ - Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()], - ] - - XCTAssertTrue( - AEMReporter.shouldRefresh(withIsForced: false), - "Should not refresh if timestamp is expired" - ) - } - - func testShouldRefreshWithoutBusinessID3() { - AEMReporter.invocations = [SampleAEMData.invocationWithoutAdvertiserID] - guard let date = Calendar.current.date(byAdding: .day, value: -2, to: Date()) - else { return XCTFail("Date Creation Error") } - AEMReporter.configRefreshTimestamp = date - AEMReporter.configurations = [:] - - XCTAssertTrue( - AEMReporter.shouldRefresh(withIsForced: false), - "Should not refresh if configuration is empty" - ) - } - - func testShouldRefreshWithBusinessID() { - AEMReporter.invocations = [ - SampleAEMData.invocationWithoutAdvertiserID, - SampleAEMData.invocationWithAdvertiserID1, - ] - AEMReporter.configRefreshTimestamp = Date() - AEMReporter.configurations = [ - Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()], - ] - - XCTAssertTrue( - AEMReporter.shouldRefresh(withIsForced: false), - "Should not refresh if there exists an invocation with business ID" - ) - } - - func testSendDebuggingRequest() { - AEMReporter.sendDebuggingRequest(SampleAEMInvocations.createDebuggingInvocation()) - - XCTAssertTrue( - networker.capturedGraphPath?.hasSuffix("aem_conversions") == true, - "GraphRequst should be created because of there is a debugging invocation" - ) - XCTAssertEqual( - networker.startCallCount, - 1, - "Should start the graph request to update the test mode" - ) - } - - func testDebuggingRequestParameters() { - XCTAssertEqual( - AEMReporter.debuggingRequestParameters(SampleAEMInvocations.createDebuggingInvocation()) as NSDictionary, - [ - "campaign_id": "debugging_campaign", - "conversion_data": 0, - "consumption_hour": 0, - "token": "debugging_token", - "delay_flow": "server", - ], - "Should have expected request parameters for debugging invocation" - ) - } - - func testRuleMatchRequestParameters() { - let businessIDs = ["123"] - let content = #"[{"id": "123", "quantity": 5}]"# - let parameters = AEMReporter.ruleMatchRequestParameters(businessIDs, content: content) - let expected = [ - "advertiser_ids": #"["123"]"#, - "fb_content_data": content, - ] - XCTAssertEqual( - parameters as? [String: String], - expected, - "Rule match request parameter is not expected" - ) - } - - func testSendAggregationRequest() { - AEMReporter.invocations = [] - AEMReporter.sendAggregationRequest() - XCTAssertNil( - networker.capturedGraphPath, - "GraphRequest should not be created because of there is no invocation" - ) - XCTAssertNil( - userDefaultsSpy.capturedSetObjectKey, - "Min aggregation request timestamp should not be updated because of there is no request" - ) - - guard let invocation = AEMReporter.parseURL(urlWithInvocation) else { return XCTFail("Parsing Error") } - invocation.isAggregated = false - AEMReporter.invocations = [invocation] - AEMReporter.sendAggregationRequest() - XCTAssertTrue( - networker.capturedGraphPath?.hasSuffix("aem_conversions") == true, - "GraphRequst should be created because of there is non-aggregated invocation" - ) - XCTAssertEqual( - userDefaultsSpy.capturedSetObjectKey, - "com.facebook.sdk:FBAEMMinAggregationRequestTimestamp", - "Min aggregation request timestamp should not be updated because of there is non-aggregated invocation" - ) - } - - func testSendAggregationRequestWithDelay() { - AEMReporter.minAggregationRequestTimestamp = Date().addingTimeInterval(100) - guard let invocation = AEMReporter.parseURL(urlWithInvocation) else { return XCTFail("Parsing Error") } - invocation.isAggregated = false - AEMReporter.invocations = [invocation] - AEMReporter.sendAggregationRequest() - XCTAssertNil( - networker.capturedGraphPath, - "GraphRequst should not be created immediately because of there is delay" - ) - XCTAssertEqual( - userDefaultsSpy.capturedSetObjectKey, - "com.facebook.sdk:FBAEMMinAggregationRequestTimestamp", - "Min aggregation request timestamp should not be updated because of there is non-aggregated invocation" - ) - } - - func testCompletingAggregationRequestWithError() { - - guard let invocation = AEMReporter.parseURL(urlWithInvocation) else { return XCTFail("Parsing Error") } - invocation.isAggregated = false - AEMReporter.invocations = [invocation] - AEMReporter.sendAggregationRequest() - - networker.capturedCompletionHandler?(nil, SampleAEMError()) - XCTAssertFalse( - invocation.isAggregated, - "Completing with an error should not mark the invocation as aggregated" - ) - XCTAssertFalse( - FileManager.default.fileExists(atPath: reportFilePath), - "Completing with an error should not write the report to the expected file path" - ) - } - - func testCompletingAggregationRequestWithoutError() { - guard let invocation = AEMReporter.parseURL(urlWithInvocation) else { return XCTFail("Parsing Error") } - invocation.isAggregated = false - AEMReporter.invocations = [invocation] - AEMReporter.sendAggregationRequest() - - networker.capturedCompletionHandler?(nil, nil) - XCTAssertTrue( - invocation.isAggregated, - "Completing with no error should mark the invocation as aggregated" - ) - XCTAssertTrue( - FileManager.default.fileExists(atPath: reportFilePath), - "Completing with no error should write the report to the expected file path" - ) - } - - func testRecordAndUpdateEvents() { - AEMReporter.configRefreshTimestamp = Date() - guard let invocation = AEMInvocation( - campaignID: "test_campaign_1234", - acsToken: "test_token_1234567", - acsSharedSecret: "test_shared_secret", - acsConfigurationID: "test_config_id_123", - businessID: nil, - catalogID: nil, - isTestMode: false, - hasStoreKitAdNetwork: false, - isConversionFilteringEligible: true - ) - else { return XCTFail("Unwrapping Error") } - guard let configuration = AEMConfiguration(json: SampleAEMData.validConfigData3) - else { return XCTFail("Unwrapping Error") } - - AEMReporter.configurations = [Values.defaultMode: [configuration]] - AEMReporter.invocations = [invocation] - AEMReporter.recordAndUpdate(event: Values.purchase, currency: Values.USD, value: 100, parameters: nil) - // Invocation should be attributed and updated while request should be sent - XCTAssertEqual( - invocation.recordedEvents, - [Values.purchase], - "Invocation's cached events should be updated" - ) - XCTAssertEqual( - invocation.recordedValues as? [String: [String: Int]], - [Values.purchase: [Values.USD: 100]], - "Invocation's cached values should be updated" - ) - XCTAssertTrue( - networker.capturedGraphPath?.hasSuffix("aem_conversions") == true, - "Should create a request to update the conversions for a valid event" - ) - XCTAssertFalse( - invocation.isAggregated, - "Should not mark the invocation as aggregated if it is recorded and sent" - ) - XCTAssertTrue( - FileManager.default.fileExists(atPath: reportFilePath), - "Should save uploaded events to disk" - ) - XCTAssertEqual( - networker.startCallCount, - 1, - "Should start the graph request to update the conversions" - ) - } - - func testRecordAndUpdateEventsWithAEMDisabled() { - AEMReporter.isAEMReportEnabled = false - AEMReporter.configRefreshTimestamp = date - - AEMReporter.recordAndUpdate(event: Values.purchase, currency: Values.USD, value: 100, parameters: nil) - XCTAssertNil( - networker.capturedGraphPath, - "Should not create a request to fetch the if AEM is disabled" - ) - } - - func testRecordAndUpdateEventsWithEmptyEvent() { - AEMReporter.configRefreshTimestamp = date - - AEMReporter.recordAndUpdate(event: "", currency: Values.USD, value: 100, parameters: nil) - - XCTAssertNil( - networker.capturedGraphPath, - "Should not create a request to fetch the if the event being recorded is empty" - ) - XCTAssertFalse( - FileManager.default.fileExists(atPath: reportFilePath), - "Should not save an empty event to disk" - ) - } - - func testRecordAndUpdateEventsWithEmptyConfigurations() throws { - AEMReporter.configRefreshTimestamp = date - AEMReporter.invocations = [testInvocation] - - AEMReporter.recordAndUpdate(event: Values.purchase, currency: Values.USD, value: 100, parameters: nil) - XCTAssertEqual( - testInvocation.attributionCallCount, - 0, - "Should not attribute events with empty configurations" - ) - XCTAssertEqual( - testInvocation.updateConversionCallCount, - 0, - "Should not update conversions with empty configurations" - ) - } - - func testLoadConfigurationWithRefreshEnforced() { - guard let configuration = AEMConfiguration(json: SampleAEMData.validConfigData3) - else { return XCTFail("Unwrapping Error") } - AEMReporter.configRefreshTimestamp = Date() - AEMReporter.configurations = [Values.defaultMode: [configuration]] - - AEMReporter.isLoadingConfiguration = false - AEMReporter.loadConfiguration(withRefreshForced: true, block: nil) - guard - let path = networker.capturedGraphPath, - path.hasSuffix("aem_conversion_configs") - else { - return XCTFail("Should load configuration when refresh is enforced") - } - } - - func testLoadConfigurationWithBlock() { - guard let configuration = AEMConfiguration(json: SampleAEMData.validConfigData3) - else { return XCTFail("Unwrapping Error") } - var blockCall = 0 - AEMReporter.configRefreshTimestamp = Date() - AEMReporter.configurations = [Values.defaultMode: [configuration]] - - AEMReporter.loadConfiguration(withRefreshForced: false) { _ in - blockCall += 1 - } - XCTAssertEqual( - blockCall, - 1, - "Should call the completion when loading the configuration" - ) - } - - func testLoadConfigurationWithoutBlock() { - AEMReporter.configRefreshTimestamp = date - - AEMReporter.isLoadingConfiguration = false - AEMReporter.loadConfiguration(withRefreshForced: false, block: nil) - guard - let path = networker.capturedGraphPath, - path.hasSuffix("aem_conversion_configs") - else { - return XCTFail("Should not require a completion block to load a configuration") - } - } - - func testGetConfigRequestParameterWithoutAdvertiserIDs() { - AEMReporter.invocations = [SampleAEMData.invocationWithoutAdvertiserID] - - XCTAssertEqual( - AEMReporter.requestParameters() as NSDictionary, - ["fields": "", "advertiser_ids": "[]"], - "Should not have unexpected advertiserIDs in configuration request params" - ) - } - - func testGetConfigRequestParameterWithAdvertiserIDs() { - AEMReporter.invocations = [SampleAEMData.invocationWithAdvertiserID1, SampleAEMData.invocationWithoutAdvertiserID] - - XCTAssertEqual( - AEMReporter.requestParameters() as NSDictionary, - ["fields": "", "advertiser_ids": #"["\#(SampleAEMData.invocationWithAdvertiserID1.businessID!)"]"#], // swiftlint:disable:this force_unwrapping - "Should have expected advertiserIDs in configuration request params" - ) - - AEMReporter.invocations = [ - SampleAEMData.invocationWithAdvertiserID1, - SampleAEMData.invocationWithAdvertiserID2, - SampleAEMData.invocationWithoutAdvertiserID, - ] - - XCTAssertEqual( - AEMReporter.requestParameters() as NSDictionary, - ["fields": "", "advertiser_ids": #"["\#(SampleAEMData.invocationWithAdvertiserID1.businessID!)","\#(SampleAEMData.invocationWithAdvertiserID2.businessID!)"]"#], // swiftlint:disable:this force_unwrapping - "Should have expected advertiserIDs in configuration request params" - ) - } - - func testGetAggregationRequestParameterWithoutAdvertiserID() { - let params: [String: Any] = - AEMReporter.aggregationRequestParameters(SampleAEMData.invocationWithoutAdvertiserID) - - XCTAssertEqual( - params[Keys.campaignID] as? String, - SampleAEMData.invocationWithoutAdvertiserID.campaignID, - "Should have expected campaign_id in aggregation request params" - ) - XCTAssertEqual( - params[Keys.token] as? String, - SampleAEMData.invocationWithoutAdvertiserID.acsToken, - "Should have expected ACS token in aggregation request params" - ) - XCTAssertNil( - params[Keys.businessID], - "Should not have unexpected advertiser_id in aggregation request params" - ) - } - - func testGetAggregationRequestParameterWithAdvertiserID() { - let params: [String: Any] = - AEMReporter.aggregationRequestParameters(SampleAEMData.invocationWithAdvertiserID1) - - XCTAssertEqual( - params[Keys.campaignID] as? String, - SampleAEMData.invocationWithAdvertiserID1.campaignID, - "Should have expected campaign_id in aggregation request params" - ) - XCTAssertEqual( - params[Keys.token] as? String, - SampleAEMData.invocationWithAdvertiserID1.acsToken, - "Should have expected ACS token in aggregation request params" - ) - XCTAssertNotNil( - params[Keys.businessID], - "Should have expected advertiser_id in aggregation request params" - ) - } - - func testAttributedInvocationWithoutParameters() { - let invocations = [ - SampleAEMData.invocationWithoutAdvertiserID, - SampleAEMData.invocationWithAdvertiserID1, - SampleAEMData.invocationWithAdvertiserID2, - ] - let configurations = [ - Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()], - Values.brandMode: [SampleAEMConfigurations.createConfigurationWithBusinessID()], - ] - - let attributedInvocation = AEMReporter.attributedInvocation( - invocations, - event: Values.purchase, - currency: nil, - value: nil, - parameters: nil, - configurations: configurations - ) - XCTAssertNotNil( - attributedInvocation, - "Should have invocation attributed" - ) - XCTAssertNil( - attributedInvocation?.businessID, - "The attributed invocation should not have advertiser ID" - ) - } - - func testAttributedInvocationWithParameters() { - let invocations = [ - SampleAEMData.invocationWithoutAdvertiserID, - SampleAEMData.invocationWithAdvertiserID1, - SampleAEMData.invocationWithAdvertiserID2, - ] - let configurations = [ - Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()], - Values.brandMode: [SampleAEMConfigurations.createConfigurationWithBusinessID()], - ] - - let attributedInvocation = AEMReporter.attributedInvocation( - invocations, - event: "test", - currency: nil, - value: nil, - parameters: ["values": "abcdefg"], - configurations: configurations - ) - XCTAssertNil( - attributedInvocation, - "Should not have invocation attributed" - ) - } - - func testAttributedInvocationWithUnmatchedParameters() { - let invocations = [ - SampleAEMData.invocationWithoutAdvertiserID, - SampleAEMData.invocationWithAdvertiserID1, - SampleAEMData.invocationWithAdvertiserID2, - ] - let configurations = [ - Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()], - Values.brandMode: [SampleAEMConfigurations.createConfigurationWithBusinessID()], - ] - - let attributedInvocation = AEMReporter.attributedInvocation( - invocations, - event: Values.purchase, - currency: nil, - value: nil, - parameters: ["value": "abcdefg"], - configurations: configurations - ) - XCTAssertNotNil( - attributedInvocation, - "Should have invocation attributed" - ) - XCTAssertEqual( - attributedInvocation?.businessID, - SampleAEMData.invocationWithAdvertiserID1.businessID, - "The attributed invocation should have advertiser ID" - ) - } - - func testAttributedInvocationWithMultipleGeneralInvocations() { - let invocation1 = SampleAEMInvocations.createGeneralInvocation1() - let invocation2 = SampleAEMInvocations.createGeneralInvocation2() - let invocations = [invocation1, invocation2] - let configurations = [ - Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()], - Values.brandMode: [SampleAEMConfigurations.createConfigurationWithBusinessID()], - ] - - let attributedInvocation = AEMReporter.attributedInvocation( - invocations, - event: Values.purchase, - currency: nil, - value: nil, - parameters: nil, - configurations: configurations - ) - XCTAssertEqual( - attributedInvocation?.campaignID, - invocation2.campaignID, - "Should attribute the event to the latest general invocation" - ) - } - - func testAttributedInvocationWithUnmatchedEvent() { - let invocation1 = SampleAEMInvocations.createGeneralInvocation1() - let invocation2 = SampleAEMInvocations.createGeneralInvocation2() - let invocations = [invocation1, invocation2] - let configurations = [ - Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()], - Values.brandMode: [SampleAEMConfigurations.createConfigurationWithBusinessID()], - ] - - let attributedInvocation = AEMReporter.attributedInvocation( - invocations, - event: "test", - currency: nil, - value: nil, - parameters: nil, - configurations: configurations - ) - XCTAssertNil( - attributedInvocation, - "Should not attribute the event with incorrect event" - ) - } - - func testAttributedInvocationWithDoubleCounting() { - reporter.cutOff = false - reporter.reportingEvents = [Values.purchase] - let invocation = SampleAEMInvocations.createSKANOverlappedInvocation() - - let configurations = [ - Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()], - ] - - let attributedInvocation = AEMReporter.attributedInvocation( - [invocation], - event: Values.purchase, - currency: Values.USD, - value: 10, - parameters: ["value": "abcdefg"], - configurations: configurations - ) - XCTAssertNil( - attributedInvocation, - "Should not have invocation attributed with double counting" - ) - XCTAssertTrue( - invocation.recordedEvents.isEmpty, - "Should not expect invocation's recorded events to be changed with double counting" - ) - XCTAssertTrue( - invocation.recordedValues.isEmpty, - "Should not expect invocation's recorded values to be changed with double counting" - ) - } - - func testAttributedInvocationWithoutDoubleCounting() { - reporter.cutOff = false - reporter.reportingEvents = [Values.purchase] - let invocation = SampleAEMInvocations.createGeneralInvocation1() - - let configurations = [ - Values.defaultMode: [SampleAEMConfigurations.createConfigurationWithoutBusinessID()], - ] - - let attributedInvocation = AEMReporter.attributedInvocation( - [invocation], - event: Values.purchase, - currency: Values.USD, - value: 10, - parameters: ["value": "abcdefg"], - configurations: configurations - ) - XCTAssertNotNil( - attributedInvocation, - "Should have invocation attributed without double counting" - ) - } - - func testIsDoubleCounting() { - reporter.cutOff = false - reporter.reportingEvents = ["fb_test"] - let invocation = SampleAEMInvocations.createSKANOverlappedInvocation() - - XCTAssertTrue( - AEMReporter.isDoubleCounting(invocation, event: "fb_test"), - "Should expect double counting" - ) - XCTAssertFalse( - AEMReporter.isDoubleCounting(invocation, event: "test"), - "Should not expect double counting" - ) - } - - func testIsDoubleCountingWithCutOff() { - reporter.cutOff = true - reporter.reportingEvents = ["fb_test"] - let invocation = SampleAEMInvocations.createSKANOverlappedInvocation() - - XCTAssertFalse( - AEMReporter.isDoubleCounting(invocation, event: "fb_test"), - "Should not expect double counting with SKAN cutoff" - ) - } - - func testIsDoubleCountingWithoutSKANClick() { - reporter.cutOff = false - reporter.reportingEvents = ["fb_test"] - let invocation = SampleAEMInvocations.createGeneralInvocation1() - - XCTAssertFalse( - AEMReporter.isDoubleCounting(invocation, event: "fb_test"), - "Should not expect double counting without SKAN click" - ) - } - - // MARK: - Catalog Reporting - - func testLoadCatalogOptimizationWithoutContentID() { - let invocation = SampleAEMInvocations.createCatalogOptimizedInvocation() - var blockCall = 0 - - AEMReporter.loadCatalogOptimization(with: invocation, contentID: nil) { - blockCall += 1 - } - XCTAssertTrue( - (networker.capturedGraphPath?.contains("aem_conversion_filter")) == true, - "Should start the catalog request" - ) - XCTAssertEqual(blockCall, 0, "Should not execute the block when contentID is nil") - } - - func testLoadCatalogOptimizationWithOptimizedContent() { - let invocation = SampleAEMInvocations.createCatalogOptimizedInvocation() - var blockCall = 0 - - AEMReporter.loadCatalogOptimization(with: invocation, contentID: "test_content_id") { - blockCall += 1 - } - XCTAssertTrue( - (networker.capturedGraphPath?.contains("aem_conversion_filter")) == true, - "Should start the catalog request" - ) - networker.capturedCompletionHandler?(nil, SampleAEMError()) - XCTAssertEqual(blockCall, 0, "Should not execute the block when there is a network error") - networker.capturedCompletionHandler?(["data": [["content_id_belongs_to_catalog_id": false]]], nil) - XCTAssertEqual(blockCall, 0, "Should not execute the block when content is not optmized") - networker.capturedCompletionHandler?(["data": [["content_id_belongs_to_catalog_id": true]]], nil) - XCTAssertEqual(blockCall, 1, "Should execute the block when content is optmized") - } - - func testLoadCatalogOptimizationWithFuzzyInput() { - let invocation = SampleAEMInvocations.createCatalogOptimizedInvocation() - - AEMReporter.loadCatalogOptimization(with: invocation, contentID: "test_content_id") {} - for _ in 0 ..< 100 { - networker.capturedCompletionHandler?( - Fuzzer.randomize(json: sampleCatalogOptimizationDictionary), - nil - ) - } - } - - func testIsContentOptimized() { - var data = [ - "data": [["content_id_belongs_to_catalog_id": true]], - ] - XCTAssertTrue(AEMReporter.isContentOptimized(data), "Should expect content is optimized") - data = ["data": [["content_id_belongs_to_catalog_id": false]]] - XCTAssertFalse(AEMReporter.isContentOptimized(data), "Should expect content is optimized") - } - - func testCatalogRequestParameters() { - let params = AEMReporter.catalogRequestParameters("test_catalog", contentID: "test_content_id") - - XCTAssertEqual( - params as NSDictionary, - [ - Keys.catalogID: "test_catalog", - Keys.contentID: "test_content_id", - ], - "Catalog request parameters are not expected" - ) - } - - func testCatalogRequestParametersWithMalformedInput() { - let malformedInput = [nil, ""] - - for catalogID in malformedInput { - for contentID in malformedInput { - _ = AEMReporter.catalogRequestParameters(catalogID, contentID: contentID) - } - } - } - - func testShouldReportConversionInCatalogLevel() { - for conversionFilteringEnabled in [true, false] { - for catalogMatchingEnabled in [true, false] { - for isOptimizedEvent in [true, false] { - for catalogID in ["test_catalog", nil] { - AEMReporter.setConversionFilteringEnabled(conversionFilteringEnabled) - AEMReporter.setCatalogMatchingEnabled(catalogMatchingEnabled) - testInvocation.isOptimizedEvent = isOptimizedEvent - testInvocation.catalogID = catalogID - if conversionFilteringEnabled, - catalogMatchingEnabled, - isOptimizedEvent, - catalogID != nil { - XCTAssertTrue( - AEMReporter.shouldReportConversion(inCatalogLevel: testInvocation, event: Values.purchase), - "Should expect to report conversion in catalog level" - ) - } else { - XCTAssertFalse( - AEMReporter.shouldReportConversion(inCatalogLevel: testInvocation, event: Values.purchase), - "Should expect not to report conversion in catalog level" - ) - } - } - } - } - } - } - - // MARK: - Rule Match in Server - - func testLoadRuleMatch() { - let content = #"[{"id": "123", "quantity": 5}]"# - AEMReporter.loadRuleMatch(["123"], event: "test", currency: nil, value: nil, parameters: [Keys.content: content]) - let expectedParameters = [ - "advertiser_ids": #"["123"]"#, - "fb_content_data": content, - ] - XCTAssertTrue( - (networker.capturedGraphPath?.contains("aem_attribution")) == true, - "Should start the rule match request" - ) - XCTAssertEqual( - networker.capturedParameters as? [String: String], - expectedParameters, - "Should have the expected parameters in the rule match request" - ) - } - - // MARK: - Aggregation Request - - func testShouldDelayAggregationRequestWithNilTimestamp() { - AEMReporter.minAggregationRequestTimestamp = nil - XCTAssertFalse( - AEMReporter.shouldDelayAggregationRequest(), - "Should not expect to delay aggregation request when timestamp is nil" - ) - } - - func testShouldDelayAggregationRequestWithExpiredTimestamp() { - AEMReporter.minAggregationRequestTimestamp = aggregationRequestTimestampToNotDelay - XCTAssertFalse( - AEMReporter.shouldDelayAggregationRequest(), - "Should not expect to delay aggregation request when timestamp is expired" - ) - } - - func testShouldDelayAggregationRequestWithValidTimestamp() { - AEMReporter.minAggregationRequestTimestamp = Date().addingTimeInterval(5) - XCTAssertTrue( - AEMReporter.shouldDelayAggregationRequest(), - "Should not expect to delay aggregation request when timestamp is within the range" - ) - } - - func testLoadMinAggregationRequestTimestamp() { - let timestamp = Date() - userDefaultsSpy.set( - timestamp, - forKey: "com.facebook.sdk:FBAEMMinAggregationRequestTimestamp" - ) - - let data = AEMReporter.loadMinAggregationRequestTimestamp() - XCTAssertEqual( - timestamp, - data, - "Should return the timestamp from the userDefaultsSpy" - ) - XCTAssertEqual( - userDefaultsSpy.capturedObjectRetrievalKey, - "com.facebook.sdk:FBAEMMinAggregationRequestTimestamp", - "Should retrieve the min aggregation request timestamp from the userDefaultsSpy" - ) - } - - func testUpdateAggregationRequestTimestamp() { - let timestamp = Date().timeIntervalSince1970 - AEMReporter.updateAggregationRequestTimestamp(timestamp) - - XCTAssertEqual( - timestamp, - AEMReporter.minAggregationRequestTimestamp?.timeIntervalSince1970, - "Should set the expected tiemstamp" - ) - XCTAssertEqual( - userDefaultsSpy.capturedSetObjectKey, - "com.facebook.sdk:FBAEMMinAggregationRequestTimestamp", - "Should persist the min aggregation request timestamp when setting a new one" - ) - } - - // MARK: - Helpers - - func removeReportFile() { - do { - try FileManager.default.removeItem(at: URL(fileURLWithPath: reportFilePath)) - } catch _ as NSError {} - } -} diff --git a/FBAEMKit/FBAEMKitTests/AEMRequestBodyTests.swift b/FBAEMKit/FBAEMKitTests/AEMRequestBodyTests.swift deleted file mode 100644 index b1b46137ac..0000000000 --- a/FBAEMKit/FBAEMKitTests/AEMRequestBodyTests.swift +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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 AEMRequestBodyTests: XCTestCase { - func testEmptyBody() throws { - let body = AEMRequestBody() - XCTAssertNil(body.compressedData()) - XCTAssertEqual(body.data.count, 0) - - let multipartData = try XCTUnwrap(body.multipartData) - XCTAssertEqual(multipartData.count, 0) - } - - func testAppendEmptyKeyWithEmptyValue() throws { - let body = AEMRequestBody() - body.append(withKey: "", formValue: "") - XCTAssertNotNil(body.compressedData()) - - let multipartString = try makeString(multipartData: body.multipartData) - let expectedMultipartString = "--\r\nContent-Disposition: form-data; name=\"\"\r\n\r\n\r\n" - XCTAssertEqual(multipartString, expectedMultipartString) - - let dictionary = try makeStringDictionary(jsonData: body.data) - XCTAssertEqual(dictionary.keys.count, 1) - XCTAssertEqual(dictionary[""], "") - } - - func testAppendEmptyKeyWithNonEmptyValue() throws { - let body = AEMRequestBody() - body.append(withKey: "", formValue: "value") - XCTAssertNotNil(body.compressedData()) - - let multipartString = try makeString(multipartData: body.multipartData) - let expectedMultipartString = "--\r\nContent-Disposition: form-data; name=\"\"\r\n\r\nvalue\r\n" - XCTAssertEqual(multipartString, expectedMultipartString) - - let dictionary = try makeStringDictionary(jsonData: body.data) - XCTAssertEqual(dictionary.keys.count, 1) - XCTAssertEqual(dictionary[""], "value") - } - - func testAppendNonEmptyKeyWithEmptyValue() throws { - let body = AEMRequestBody() - body.append(withKey: "key", formValue: "") - XCTAssertNotNil(body.compressedData()) - - let multipartString = try makeString(multipartData: body.multipartData) - let expectedMultipartString = "--\r\nContent-Disposition: form-data; name=\"key\"\r\n\r\n\r\n" - XCTAssertEqual(multipartString, expectedMultipartString) - - let dictionary = try makeStringDictionary(jsonData: body.data) - XCTAssertEqual(dictionary.keys.count, 1) - XCTAssertEqual(dictionary["key"], "") - } - - func testAppendKeysAndValuesWithEscapedCharacters() throws { - let body = AEMRequestBody() - body.append(withKey: "\u{F09F}\u{918D}", formValue: "\u{F09F}\u{918E}") - body.append(withKey: "\0", formValue: "\0") - XCTAssertNotNil(body.compressedData()) - - let multipartString = try makeString(multipartData: body.multipartData) - - // swiftlint:disable:next line_length - let expectedMultipartString = "--\r\nContent-Disposition: form-data; name=\"\u{F09F}\u{918D}\"\r\n\r\n\u{F09F}\u{918E}\r\nContent-Disposition: form-data; name=\"\0\"\r\n\r\n\0\r\n" - - XCTAssertEqual(multipartString, expectedMultipartString) - - let dictionary = try makeStringDictionary(jsonData: body.data) - XCTAssertEqual(dictionary.keys.count, 2) - XCTAssertEqual(dictionary["\u{F09F}\u{918D}"], "\u{F09F}\u{918E}") - XCTAssertEqual(dictionary["\0"], "\0") - } - - private func makeString(multipartData: Data?) throws -> String { - let multipartData = try XCTUnwrap(multipartData) - let utf8String = String(data: multipartData, encoding: .utf8) - return try XCTUnwrap(utf8String) - } - - private func makeStringDictionary(jsonData: Data) throws -> [String: String] { - let json = try JSONSerialization.jsonObject(with: jsonData, options: []) - return try XCTUnwrap(json as? [String: String]) - } -} diff --git a/FBAEMKit/FBAEMKitTests/AEMRuleTests.swift b/FBAEMKit/FBAEMKitTests/AEMRuleTests.swift deleted file mode 100644 index 0b9c99f724..0000000000 --- a/FBAEMKit/FBAEMKitTests/AEMRuleTests.swift +++ /dev/null @@ -1,358 +0,0 @@ -/* - * 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 AEMRuleTests: XCTestCase { - - enum Keys { - static let conversionValue = "conversion_value" - static let priority = "priority" - static let events = "events" - static let eventName = "event_name" - static let values = "values" - static let currency = "currency" - static let amount = "amount" - } - - enum Values { - static let purchase = "fb_mobile_purchase" - static let donate = "Donate" - static let activateApp = "fb_activate_app" - static let testEvent = "fb_test_event" - static let USD = "USD" - static let EU = "EU" - static let JPY = "JPY" - } - - var sampleData: [String: Any] = [ - Keys.conversionValue: 2, - Keys.priority: 7, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - Keys.values: [ - [ - Keys.currency: Values.USD, - Keys.amount: 100.0, - ], - ], - ], - ], - ] - - var validRule = AEMRule(json: [ - Keys.conversionValue: 10, - Keys.priority: 7, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - Keys.values: [ - [ - Keys.currency: Values.USD, - Keys.amount: 100.0, - ], - ], - ], - ], - ])! // swiftlint:disable:this force_unwrapping - - func testValidCase1() { - let validData: [String: Any] = [ - Keys.conversionValue: 2, - Keys.priority: 10, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - ], - [ - Keys.eventName: Values.donate, - ], - ], - ] - - guard let rule = AEMRule(json: validData) else { return XCTFail("Unwraping Error") } - XCTAssertEqual(2, rule.conversionValue) - XCTAssertEqual(10, rule.priority) - XCTAssertEqual(2, rule.events.count) - - let event1 = rule.events[0] - XCTAssertEqual(event1.eventName, Values.purchase) - XCTAssertNil(event1.values) - - let event2 = rule.events[1] - XCTAssertEqual(event2.eventName, Values.donate) - XCTAssertNil(event2.values) - } - - func testValidCase2() { - guard let rule = AEMRule(json: sampleData) else { return XCTFail("Unwraping Error") } - XCTAssertEqual(2, rule.conversionValue) - XCTAssertEqual(7, rule.priority) - XCTAssertEqual(1, rule.events.count) - - let event = rule.events[0] - XCTAssertEqual(event.eventName, Values.purchase) - XCTAssertEqual(event.values, [Values.USD: 100.0]) - } - - func testInvalidCases() { - var invalidData: [String: Any] = [:] - XCTAssertNil(AEMRule(json: invalidData)) - - invalidData = [Keys.conversionValue: 2] - XCTAssertNil(AEMRule(json: invalidData)) - - invalidData = [Keys.priority: 7] - XCTAssertNil(AEMRule(json: invalidData)) - - invalidData = [ - Keys.events: [ - [ - Keys.eventName: Values.purchase, - Keys.values: [ - [ - Keys.currency: Values.USD, - Keys.amount: 100, - ], - ], - ], - ], - ] - XCTAssertNil(AEMRule(json: invalidData)) - - invalidData = [ - Keys.conversionValue: 2, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - Keys.values: [ - [ - Keys.currency: 100, - Keys.amount: Values.USD, - ], - ], - ], - ], - ] - XCTAssertNil(AEMRule(json: invalidData)) - - invalidData = [ - Keys.priority: 2, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - Keys.values: [ - [ - Keys.currency: Values.USD, - Keys.amount: 100, - ], - ], - ], - ], - ] - XCTAssertNil(AEMRule(json: invalidData)) - } - - func testParsing() { - (1 ... 100).forEach { _ in - if let data = (Fuzzer.randomize(json: self.sampleData) as? [String: Any]) { - _ = AEMRule(json: data) - } - } - } - - func testContainsEvent() { - let rule = validRule - - XCTAssertTrue( - rule.containsEvent(Values.purchase), - "Should expect to return true for the event in the rule" - ) - XCTAssertFalse( - rule.containsEvent(Values.testEvent), - "Should expect to return false for the event not in the rule" - ) - } - - func testSecureCoding() { - XCTAssertTrue( - AEMRule.supportsSecureCoding, - "AEM Rule should support secure coding" - ) - } - - func testEncodingAndDecoding() throws { - let rule = validRule - let decodedObject = try CodabilityTesting.encodeAndDecode(rule) - - // Test Objects - XCTAssertNotIdentical(decodedObject, rule, .isCodable) - XCTAssertEqual(decodedObject, rule, .isCodable) - - // Test Properties - XCTAssertEqual(decodedObject.conversionValue, rule.conversionValue, .isCodable) - XCTAssertEqual(decodedObject.priority, rule.priority, .isCodable) - XCTAssertEqual(rule.events, decodedObject.events, .isCodable) - } - - func testRuleMatch() { - guard let rule = AEMRule(json: [ - Keys.conversionValue: 10, - Keys.priority: 7, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - Keys.values: [ - [ - Keys.currency: Values.USD, - Keys.amount: 100.0, - ], - [ - Keys.currency: Values.EU, - Keys.amount: 100.0, - ], - ], - ], - ], - ]) else { - return XCTFail("Fail to initalize AEM rule") - } - XCTAssertTrue( - rule.isMatched( - withRecordedEvents: [Values.activateApp, Values.purchase], - recordedValues: [Values.purchase: [Values.USD: 1000.0]] - ), - "Should match the expected events and values for the rule" - ) - XCTAssertTrue( - rule.isMatched( - withRecordedEvents: [Values.activateApp, Values.purchase], - recordedValues: [Values.purchase: [Values.EU: 1000.0]] - ), - "Should match the expected events and values for the rule" - ) - XCTAssertFalse( - rule.isMatched( - withRecordedEvents: [Values.activateApp], - recordedValues: [Values.purchase: [Values.USD: 1000.0]] - ), - "Should not match the unexpected events for the rule" - ) - XCTAssertFalse( - rule.isMatched( - withRecordedEvents: [Values.activateApp, Values.purchase], - recordedValues: [Values.purchase: [Values.JPY: 1000.0]] - ), - "Should not match the unexpected values for the rule" - ) - } - - func testRuleMatchWithEventBundle() { - guard let rule = AEMRule(json: [ - Keys.conversionValue: 10, - Keys.priority: 7, - Keys.events: [ - [ - Keys.eventName: Values.activateApp, - ], - [ - Keys.eventName: Values.purchase, - Keys.values: [ - [ - Keys.currency: Values.USD, - Keys.amount: 100.0, - ], - ], - ], - [ - Keys.eventName: Values.testEvent, - ], - ], - ]) else { - return XCTFail("Fail to initalize AEM rule") - } - XCTAssertTrue( - rule.isMatched( - withRecordedEvents: [Values.activateApp, Values.purchase, Values.testEvent], - recordedValues: [Values.purchase: [Values.USD: 1000.0]] - ), - "Should match the expected events and values for the rule" - ) - XCTAssertFalse( - rule.isMatched( - withRecordedEvents: [Values.activateApp, Values.purchase, Values.testEvent], - recordedValues: nil - ), - "Should not match the unexpected values for the rule" - ) - XCTAssertFalse( - rule.isMatched( - withRecordedEvents: [Values.activateApp, Values.purchase], - recordedValues: [Values.purchase: [Values.USD: 1000.0]] - ), - "Should not match the unexpected events for the rule" - ) - XCTAssertFalse( - rule.isMatched( - withRecordedEvents: [Values.activateApp, Values.purchase, Values.testEvent], - recordedValues: [Values.purchase: [Values.JPY: 1000]] - ), - "Should not match the unexpected values for the rule" - ) - } - - func testRuleMatchWithoutValue() { - guard let rule = AEMRule(json: [ - Keys.conversionValue: 10, - Keys.priority: 7, - Keys.events: [ - [ - Keys.eventName: Values.activateApp, - ], - [ - Keys.eventName: Values.purchase, - Keys.values: [ - [ - Keys.currency: Values.USD, - Keys.amount: 0.0, - ], - ], - ], - [ - Keys.eventName: Values.testEvent, - ], - ], - ]) else { - return XCTFail("Fail to initalize AEM rule") - } - XCTAssertTrue( - rule.isMatched( - withRecordedEvents: [Values.activateApp, Values.purchase, Values.testEvent], - recordedValues: nil - ), - "Should match the expected events and values for the rule" - ) - XCTAssertTrue( - rule.isMatched( - withRecordedEvents: [Values.activateApp, Values.purchase, Values.testEvent], - recordedValues: [Values.purchase: [Values.JPY: 1000]] - ), - "Should match the expected events and values for the rule" - ) - } -} - -// MARK: - Assumptions - -extension String { - fileprivate static let isCodable = "AEMRule should be encodable and decodable" -} diff --git a/FBAEMKit/FBAEMKitTests/AEMSettingsTests.swift b/FBAEMKit/FBAEMKitTests/AEMSettingsTests.swift deleted file mode 100644 index 1b8bd14921..0000000000 --- a/FBAEMKit/FBAEMKitTests/AEMSettingsTests.swift +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 AEMSettingsTests: XCTestCase { - var bundle: TestBundle! // swiftlint:disable:this implicitly_unwrapped_optional - - override func setUp() { - super.setUp() - bundle = TestBundle() - AEMSettings.setDependencies(.init(bundle: bundle)) - } - - override func tearDown() { - AEMSettings.resetDependencies() - bundle = nil - super.tearDown() - } - - func testDefaultDependencies() throws { - AEMSettings.resetDependencies() - let dependencies = try AEMSettings.getDependencies() - XCTAssertIdentical(dependencies.bundle, Bundle.main, .usesMainBundle) - } - - func testCustomDependencies() throws { - let dependencies = try AEMSettings.getDependencies() - XCTAssertIdentical(dependencies.bundle, bundle, .usesCustomBundle) - } - - func testIsCorrectAppId() { - bundle.stubbedInfoDictionaryObject = "com.facebook.test.appID" - let appId = AEMSettings.appID() - XCTAssertEqual(bundle.infoDictionaryKey, "FacebookAppID", .findsAppIDInInfoDictionary) - XCTAssertEqual(appId, "com.facebook.test.appID", .findsAppIDInInfoDictionary) - } -} - -// swiftformat:disable extensionaccesscontrol - -// MARK: - Assumptions - -fileprivate extension String { - static let usesMainBundle = """ - The default bundle dependency should be the Main Bundle. - """ - static let usesCustomBundle = "The bundle dependency should be configurable" - static let findsAppIDInInfoDictionary = "The app ID should exist in info dictionary" -} diff --git a/FBAEMKit/FBAEMKitTests/AEMUtilityTests.swift b/FBAEMKit/FBAEMKitTests/AEMUtilityTests.swift deleted file mode 100644 index a27c526583..0000000000 --- a/FBAEMKit/FBAEMKitTests/AEMUtilityTests.swift +++ /dev/null @@ -1,190 +0,0 @@ -/* - * 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 Foundation -import XCTest - -final class AEMUtilityTests: XCTestCase { - enum Keys { - static let content = "fb_content" - static let contentID = "fb_content_id" - static let identity = "id" - static let itemPrice = "item_price" - static let quantity = "quantity" - } - - func testGetMatchedInvocationWithoutBusinessID() { - let invocations = [SampleAEMInvocations.createGeneralInvocation1()] - XCTAssertNil( - AEMUtility.shared.getMatchedInvocation(invocations, businessID: "123"), - "Should not expect to get the matched invocation without matched business ID" - ) - } - - func testGetMatchedInvocationWithNullBusinessID() { - let invocation = SampleAEMInvocations.createGeneralInvocation1() - let invocations = [invocation, SampleAEMInvocations.createInvocationWithBusinessID()] - XCTAssertEqual( - invocation, - AEMUtility.shared.getMatchedInvocation(invocations, businessID: nil), - "Should expect to get the matched invocation without businessID" - ) - } - - func testGetMatchedInvocationWithUnmatchedBusinessID() { - let invocationWithBusinessID = SampleAEMInvocations.createInvocationWithBusinessID() - let invocations = [invocationWithBusinessID, SampleAEMInvocations.createGeneralInvocation1()] - XCTAssertNil( - AEMUtility.shared.getMatchedInvocation(invocations, businessID: "123"), - "Should not expect to get the matched invocation without matched business ID" - ) - } - - func testGetMatchedInvocationWithMatchedBusinessID() { - let invocationWithBusinessID = SampleAEMInvocations.createInvocationWithBusinessID() - let invocations = [invocationWithBusinessID, SampleAEMInvocations.createGeneralInvocation1()] - XCTAssertEqual( - invocationWithBusinessID, - AEMUtility.shared.getMatchedInvocation(invocations, businessID: invocationWithBusinessID.businessID), - "Should expect to get the matched invocation" - ) - } - - func testGetInSegmentValue() { - let parameters = [ - Keys.content: [ - [ - Keys.identity: "12345", - Keys.itemPrice: NSNumber(value: 10), - Keys.quantity: NSNumber(value: 2), - ], - [ - Keys.identity: "12345", - Keys.itemPrice: NSNumber(value: 100), - Keys.quantity: NSNumber(value: 3), - ], - [ - Keys.identity: "testing", - Keys.itemPrice: NSNumber(value: 100), - Keys.quantity: NSNumber(value: 2), - ], - ], - ] - - let value = AEMUtility.shared.getInSegmentValue(parameters, matchingRule: SampleAEMMultiEntryRules.contentRule) - XCTAssertEqual(value.intValue, 320, "Didn't get the expected in-segment value") - } - - func testGetInSegmentValueWithDefaultPrice() { - let parameters = [ - Keys.content: [ - [ - Keys.identity: "12345", - Keys.quantity: NSNumber(value: 2), - ], - ], - ] - - let value = AEMUtility.shared.getInSegmentValue(parameters, matchingRule: SampleAEMMultiEntryRules.contentRule) - XCTAssertEqual(value.intValue, 0, "Didn't get the expected in-segment value") - } - - func testGetInSegmentValueWithDefaultQuantity() { - let parameters = [ - Keys.content: [ - [ - Keys.identity: "12345", - Keys.itemPrice: NSNumber(value: 100), - ], - ], - ] - - let value = AEMUtility.shared.getInSegmentValue(parameters, matchingRule: SampleAEMMultiEntryRules.contentRule) - XCTAssertEqual(value.intValue, 100, "Didn't get the expected in-segment value") - } - - func testGetContent() { - let content = AEMUtility.shared.getContent([ - Keys.content: getJsonString(object: [ - [Keys.identity: "123"], - [Keys.identity: "456"], - ]), - ]) - XCTAssertEqual(content, #"[{"id":"123"},{"id":"456"}]"#) - } - - func testGetContentWithoutData() { - let content = AEMUtility.shared.getContent([ - Keys.contentID: getJsonString(object: [ - [Keys.identity: "123"], - [Keys.identity: "456"], - ]), - ]) - XCTAssertNil(content) - } - - func testGetContentWithIntID() { - let contentID = AEMUtility.shared.getContentID([ - Keys.content: getJsonString(object: [ - [Keys.identity: NSNumber(value: 123)], - [Keys.identity: NSNumber(value: 456)], - ]), - ]) - XCTAssertEqual(contentID, #"["123","456"]"#) - } - - func testGetContentWithStringID() { - let contentID = AEMUtility.shared.getContentID([ - Keys.content: getJsonString(object: [ - [Keys.identity: "123"], - [Keys.identity: "456"], - ]), - ]) - XCTAssertEqual(contentID, #"["123","456"]"#) - } - - func testGetContentFallback() { - let contentID = AEMUtility.shared.getContentID([ - Keys.contentID: #"["123","456"]"#, - ]) - XCTAssertEqual(contentID, #"["123","456"]"#) - } - - func testGetBusinessIDsInOrderWithoutInvocations() { - let businessIDs = AEMUtility.shared.getBusinessIDsInOrder([]) - XCTAssertEqual(businessIDs, []) - } - - func testGetBusinessIDsInOrderWithoutBusinessIDs() { - let businessIDs = AEMUtility.shared.getBusinessIDsInOrder( - [ - SampleAEMInvocations.createGeneralInvocation1(), - SampleAEMInvocations.createGeneralInvocation2(), - ] - ) - XCTAssertEqual(businessIDs, ["", ""]) - } - - func testGetBusinessIDsInOrderWithBusinessIDs() { - let invocation = SampleAEMInvocations.createInvocationWithBusinessID() - let businessIDs = AEMUtility.shared.getBusinessIDsInOrder( - [ - SampleAEMInvocations.createGeneralInvocation1(), - SampleAEMInvocations.createGeneralInvocation2(), - invocation, - ] - ) - XCTAssertEqual(businessIDs, [invocation.businessID, "", ""]) - } - - func getJsonString(object: [Any]) -> String { - let jsonData = try? JSONSerialization.data(withJSONObject: object, options: []) - return String(data: jsonData!, encoding: String.Encoding.ascii)! // swiftlint:disable:this force_unwrapping - } -} diff --git a/FBAEMKit/FBAEMKitTests/DependentAsTypeTests.swift b/FBAEMKit/FBAEMKitTests/DependentAsTypeTests.swift deleted file mode 100644 index ab89c98d95..0000000000 --- a/FBAEMKit/FBAEMKitTests/DependentAsTypeTests.swift +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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 DependentAsTypeTests: XCTestCase { - - private let defaultDependencies = TestDependencies(value: 14) - private let customDependencies = TestDependencies(value: 28) - - override func setUp() { - super.setUp() - resetDependencies() - } - - override func tearDown() { - resetDependencies() - super.tearDown() - } - - private func resetDependencies() { - DefaultImplementationDependent.configuredDependencies = nil - DefaultImplementationDependent.defaultDependencies = nil - - CustomImplementationDependent.configuredDependencies = nil - CustomImplementationDependent.wasSetDependenciesCalled = false - CustomImplementationDependent.wasResetDependenciesCalled = false - } - - func testMissingDependencies() { - XCTAssertThrowsError( - try DefaultImplementationDependent.getDependencies(), - .missingDependencies - ) { error in - XCTAssertEqual( - String(describing: error), - "The dependencies for the type 'DefaultImplementationDependent' or an instance of it have not been set", - .missingDependencies - ) - } - } - - func testDefaultDependencies() { - DefaultImplementationDependent.defaultDependencies = defaultDependencies - XCTAssertEqual( - try DefaultImplementationDependent.getDependencies(), - defaultDependencies, - .defaultDependencies - ) - } - - func testConfiguredDependencies() { - DefaultImplementationDependent.defaultDependencies = defaultDependencies - DefaultImplementationDependent.configuredDependencies = customDependencies - XCTAssertEqual( - try DefaultImplementationDependent.getDependencies(), - customDependencies, - .customDependencies - ) - } - - func testDefaultSetDependencies() { - DefaultImplementationDependent.setDependencies(customDependencies) - XCTAssertEqual( - DefaultImplementationDependent.configuredDependencies, - customDependencies, - .defaultSetDependenciesImplementation - ) - } - - func testCustomSetDependencies() { - CustomImplementationDependent.setDependencies(customDependencies) - XCTAssertTrue(CustomImplementationDependent.wasSetDependenciesCalled, .customSetDependenciesImplementation) - } - - func testDefaultResetDependencies() { - DefaultImplementationDependent.configuredDependencies = customDependencies - DefaultImplementationDependent.resetDependencies() - XCTAssertNil(DefaultImplementationDependent.configuredDependencies, .defaultResetDependenciesImplementation) - } - - func testCustomResetDependencies() { - CustomImplementationDependent.resetDependencies() - XCTAssertTrue(CustomImplementationDependent.wasResetDependenciesCalled, .customResetDependenciesImplementation) - } - - func testFailedDynamicMemberLookup() { - XCTAssertNil(DefaultImplementationDependent.value, .missingDependencyDynamicMemberLookup) - } - - func testDynamicMemberLookup() { - DefaultImplementationDependent.defaultDependencies = customDependencies - XCTAssertEqual( - DefaultImplementationDependent.value, - customDependencies.value, - .dynamicMemberLookup - ) - } -} - -// MARK: - Test Types - -private struct TestDependencies: Equatable { - let value: Int -} - -private enum DefaultImplementationDependent: DependentAsType { - static var configuredDependencies: TestDependencies? - static var defaultDependencies: TestDependencies? -} - -private enum CustomImplementationDependent: DependentAsType { - static var configuredDependencies: TestDependencies? - static var defaultDependencies: TestDependencies? - - static var wasSetDependenciesCalled = false - static var wasResetDependenciesCalled = false - - static func setDependencies(_ dependencies: TestDependencies) { - wasSetDependenciesCalled = true - } - - static func resetDependencies() { - wasResetDependenciesCalled = true - } -} - -// swiftformat:disable extensionaccesscontrol - -// MARK: - Assumptions - -fileprivate extension String { - static let missingDependencies = """ - Attempting to get the missing dependencies of a dependent throws a missing type dependencies error - """ - static let defaultDependencies = """ - When a dependent's configured dependencies are missing, its default dependencies are provided - """ - static let customDependencies = "When a dependent has configured dependencies, those dependencies are provided" - - static let defaultSetDependenciesImplementation = """ - A dependent has a default `setDependencies(_:)` implementation that sets its configured dependencies - """ - static let customSetDependenciesImplementation = """ - A dependent can override the default `setDependencies(_:)` implementation - """ - - static let defaultResetDependenciesImplementation = """ - A dependent has a default `resetDependencies()` implementation that clears its configured dependencies - """ - static let customResetDependenciesImplementation = """ - A dependent can override the default `resetDependencies()` implementation - """ - - static let missingDependencyDynamicMemberLookup = """ - When a dependent's dependencies are missing, dynamic lookup of a dependency as a property yields a nil value - """ - static let dynamicMemberLookup = "The discrete dependencies of a dependent can be accessed dynamically as properties" -} diff --git a/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMConfigurations.swift b/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMConfigurations.swift deleted file mode 100644 index 04acb917d3..0000000000 --- a/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMConfigurations.swift +++ /dev/null @@ -1,217 +0,0 @@ -/* - * 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 Foundation - -enum SampleAEMConfigurations { - - enum Keys { - static let defaultCurrency = "default_currency" - static let cutoffTime = "cutoff_time" - static let validFrom = "valid_from" - static let mode = "config_mode" - static let conversionValueRules = "conversion_value_rules" - static let conversionValue = "conversion_value" - static let priority = "priority" - static let events = "events" - static let eventName = "event_name" - static let businessID = "advertiser_id" - static let paramRule = "param_rule" - static let values = "values" - static let currency = "currency" - static let amount = "amount" - } - - enum Values { - static let purchase = "fb_mobile_purchase" - static let addToCart = "fb_mobile_add_to_cart" - static let donate = "Donate" - static let defaultMode = "DEFAULT" - static let brandMode = "BRAND" - static let cpasMode = "CPAS" - static let USD = "USD" - } - - static func createWithMultipleRules() -> AEMConfiguration { - AEMConfiguration( - json: [ - Keys.defaultCurrency: Values.USD, - Keys.cutoffTime: 1, - Keys.validFrom: 10000, - Keys.mode: Values.defaultMode, - Keys.conversionValueRules: [ - [ - Keys.conversionValue: 4, - Keys.priority: 11, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - Keys.values: [ - [ - Keys.currency: Values.USD, - Keys.amount: 100.0, - ], - ], - ], - ], - ], - [ - Keys.conversionValue: 3, - Keys.priority: 10, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - Keys.values: [ - [ - Keys.currency: Values.USD, - Keys.amount: 0.0, - ], - ], - ], - ], - ], - [ - Keys.conversionValue: 2, - Keys.priority: 9, - Keys.events: [ - [ - Keys.eventName: Values.addToCart, - ], - ], - ], - [ - Keys.conversionValue: 1, - Keys.priority: 8, - Keys.events: [ - [ - Keys.eventName: Values.donate, - ], - ], - ], - ], - ] - )! // swiftlint:disable:this force_unwrapping - } - - static func createConfigurationWithBusinessID() -> AEMConfiguration { - let advertiserRuleFactory = AEMAdvertiserRuleFactory() - - AEMConfiguration.configure(withRuleProvider: advertiserRuleFactory) - - return AEMConfiguration( - json: [ - Keys.defaultCurrency: Values.USD, - Keys.cutoffTime: 1, - Keys.validFrom: 10000, - Keys.mode: Values.defaultMode, - Keys.businessID: "test_advertiserid_123", - Keys.paramRule: #"{"and": [{"value": {"contains": "abc"}}]}"#, - Keys.conversionValueRules: [ - [ - Keys.conversionValue: 2, - Keys.priority: 10, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - ], - [ - Keys.eventName: Values.donate, - ], - ], - ], - ], - ] - )! // swiftlint:disable:this force_unwrapping - } - - static func createConfigurationWithBusinessIDAndContentRule() -> AEMConfiguration { - let advertiserRuleFactory = AEMAdvertiserRuleFactory() - - AEMConfiguration.configure(withRuleProvider: advertiserRuleFactory) - - return AEMConfiguration( - json: [ - Keys.defaultCurrency: Values.USD, - Keys.cutoffTime: 1, - Keys.validFrom: 10000, - Keys.mode: Values.brandMode, - Keys.businessID: "test_advertiserid_content_test", - Keys.paramRule: #"{"or": [{"fb_content[*].id": {"eq": "abc"}}]}"#, - Keys.conversionValueRules: [ - [ - Keys.conversionValue: 2, - Keys.priority: 10, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - ], - ], - ], - ], - ] - )! // swiftlint:disable:this force_unwrapping - } - - static func createConfigurationWithoutBusinessID() -> AEMConfiguration { - let advertiserRuleFactory = AEMAdvertiserRuleFactory() - - AEMConfiguration.configure(withRuleProvider: advertiserRuleFactory) - - return AEMConfiguration( - json: [ - Keys.defaultCurrency: Values.USD, - Keys.cutoffTime: 1, - Keys.validFrom: 10000, - Keys.mode: 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 - } - - static func createCpasConfiguration() -> AEMConfiguration { - let advertiserRuleFactory = AEMAdvertiserRuleFactory() - - AEMConfiguration.configure(withRuleProvider: advertiserRuleFactory) - - return AEMConfiguration( - json: [ - Keys.defaultCurrency: Values.USD, - Keys.cutoffTime: 1, - Keys.validFrom: 10000, - Keys.mode: Values.cpasMode, - Keys.businessID: "test_advertiserid_cpas", - Keys.paramRule: #"{"or": [{"fb_content[*].id": {"eq": "abc"}}]}"#, - Keys.conversionValueRules: [ - [ - Keys.conversionValue: 2, - Keys.priority: 10, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - ], - ], - ], - ], - ] - )! // swiftlint:disable:this force_unwrapping - } -} diff --git a/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMData.swift b/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMData.swift deleted file mode 100644 index a34b667296..0000000000 --- a/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMData.swift +++ /dev/null @@ -1,181 +0,0 @@ -/* - * 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 Foundation - -final class SampleAEMData { // swiftlint:disable:this convenience_type - - enum Keys { - static let defaultCurrency = "default_currency" - static let cutoffTime = "cutoff_time" - static let validFrom = "valid_from" - static let mode = "config_mode" - static let conversionValueRules = "conversion_value_rules" - static let conversionValue = "conversion_value" - static let priority = "priority" - static let events = "events" - static let eventName = "event_name" - static let businessID = "advertiser_id" - } - - enum Values { - static let purchase = "fb_mobile_purchase" - static let donate = "Donate" - static let defaultMode = "DEFAULT" - static let USD = "USD" - } - - static let validConfigurationData1: [String: Any] = [ - Keys.defaultCurrency: Values.USD, - Keys.cutoffTime: 1, - Keys.validFrom: 10000, - Keys.mode: Values.defaultMode, - Keys.conversionValueRules: [ - [ - Keys.conversionValue: 2, - Keys.priority: 10, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - ], - [ - Keys.eventName: Values.donate, - ], - ], - ], - ], - ] - - static let validConfigurationData2: [String: Any] = [ - Keys.defaultCurrency: Values.USD, - Keys.cutoffTime: 1, - Keys.validFrom: 10001, - Keys.mode: Values.defaultMode, - Keys.conversionValueRules: [ - [ - Keys.conversionValue: 2, - Keys.priority: 10, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - ], - [ - Keys.eventName: Values.donate, - ], - ], - ], - [ - Keys.conversionValue: 3, - Keys.priority: 11, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - ], - ], - ], - ], - ] - - static let validConfigData3: [String: Any] = [ - Keys.defaultCurrency: Values.USD, - Keys.cutoffTime: 1, - Keys.validFrom: 20000, - Keys.mode: Values.defaultMode, - Keys.conversionValueRules: [ - [ - Keys.conversionValue: 2, - Keys.priority: 10, - Keys.events: [ - [ - Keys.eventName: Values.purchase, - ], - ], - ], - ], - ] - - static let validAdvertiserSingleEntryRule = AEMAdvertiserSingleEntryRule( - with: .contains, - paramKey: "test", - linguisticCondition: "hello", - numericalCondition: 10.0, - arrayCondition: ["abv"] - ) - - static let validAdvertiserMultiEntryRule = AEMAdvertiserMultiEntryRule( - with: .and, - rules: [validAdvertiserSingleEntryRule] - ) - - static let validAdvertiserSingleEntryRuleJson1: [String: Any] = ["content": ["starts_with": "abc"]] - - static let validAdvertiserSingleEntryRuleJson2: [String: Any] = ["value": ["lt": 10]] - - static let validAdvertiserSingleEntryRuleJson3: [String: Any] = ["content": ["is_any": ["abc"]]] - - static let advertiserSingleEntryRule1 = AEMAdvertiserSingleEntryRule( - with: .startsWith, - paramKey: "content", - linguisticCondition: "abc", - numericalCondition: nil, - arrayCondition: nil - ) - - static let advertiserSingleEntryRule2 = AEMAdvertiserSingleEntryRule( - with: .lessThan, - paramKey: "value", - linguisticCondition: nil, - numericalCondition: 10, - arrayCondition: nil - ) - - static let advertiserSingleEntryRule3 = AEMAdvertiserSingleEntryRule( - with: .isAny, - paramKey: "content", - linguisticCondition: nil, - numericalCondition: nil, - arrayCondition: ["abc"] - ) - - static let invocationWithAdvertiserID1 = AEMInvocation( - campaignID: "test_campaign_1234", - acsToken: "test_token_1234567", - acsSharedSecret: "test_shared_secret", - acsConfigurationID: "test_config_id_123", - businessID: "test_advertiserid_123", - catalogID: nil, - isTestMode: false, - hasStoreKitAdNetwork: false, - isConversionFilteringEligible: true - )! // swiftlint:disable:this force_unwrapping - - static let invocationWithAdvertiserID2 = AEMInvocation( - campaignID: "test_campaign_1235", - acsToken: "test_token_2345678", - acsSharedSecret: "test_shared_secret_124", - acsConfigurationID: "test_config_id_124", - businessID: "test_advertiserid_12346", - catalogID: nil, - isTestMode: false, - hasStoreKitAdNetwork: false, - isConversionFilteringEligible: true - )! // swiftlint:disable:this force_unwrapping - - static let invocationWithoutAdvertiserID = AEMInvocation( - campaignID: "test_campaign_4321", - acsToken: "test_token_7654", - acsSharedSecret: "test_shared_secret_123", - acsConfigurationID: "test_config_id_333", - businessID: nil, - catalogID: nil, - isTestMode: false, - hasStoreKitAdNetwork: false, - isConversionFilteringEligible: true - )! // swiftlint:disable:this force_unwrapping -} diff --git a/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMError.swift b/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMError.swift deleted file mode 100644 index c81ff01fc4..0000000000 --- a/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMError.swift +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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. - */ - -struct SampleAEMError: Error {} diff --git a/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMInvocations.swift b/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMInvocations.swift deleted file mode 100644 index a1310a7bfc..0000000000 --- a/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMInvocations.swift +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 Foundation - -final class SampleAEMInvocations { // swiftlint:disable:this convenience_type - static func createGeneralInvocation1() -> AEMInvocation { - AEMInvocation( - campaignID: "test_campaign_1", - acsToken: "test_token_1234567", - acsSharedSecret: "test_shared_secret", - acsConfigurationID: "test_config_id_123", - businessID: nil, - catalogID: nil, - isTestMode: false, - hasStoreKitAdNetwork: false, - isConversionFilteringEligible: true - )! // swiftlint:disable:this force_unwrapping - } - - static func createGeneralInvocation2() -> AEMInvocation { - AEMInvocation( - campaignID: "test_campaign_2", - acsToken: "test_token_1234567", - acsSharedSecret: "test_shared_secret", - acsConfigurationID: "test_config_id_123", - businessID: nil, - catalogID: nil, - isTestMode: false, - hasStoreKitAdNetwork: false, - isConversionFilteringEligible: true - )! // swiftlint:disable:this force_unwrapping - } - - static func createDebuggingInvocation() -> AEMInvocation { - AEMInvocation( - campaignID: "debugging_campaign", - acsToken: "debugging_token", - acsSharedSecret: "debugging_shared_secret", - acsConfigurationID: "debugging_config_id_123", - businessID: nil, - catalogID: nil, - isTestMode: true, - hasStoreKitAdNetwork: false, - isConversionFilteringEligible: true - )! // swiftlint:disable:this force_unwrapping - } - - static func createSKANOverlappedInvocation() -> AEMInvocation { - AEMInvocation( - campaignID: "debugging_campaign", - acsToken: "debugging_token", - acsSharedSecret: "debugging_shared_secret", - acsConfigurationID: "debugging_config_id_123", - businessID: nil, - catalogID: nil, - isTestMode: false, - hasStoreKitAdNetwork: true, - isConversionFilteringEligible: true - )! // swiftlint:disable:this force_unwrapping - } - - static func createCatalogOptimizedInvocation() -> AEMInvocation { - AEMInvocation( - campaignID: "81", // The campaign id mod 8 (catalog optimization modulus) modulus is 1 - acsToken: "debugging_token", - acsSharedSecret: "debugging_shared_secret", - acsConfigurationID: "debugging_config_id_123", - businessID: nil, - catalogID: "test_catalog_id", - isTestMode: false, - hasStoreKitAdNetwork: true, - isConversionFilteringEligible: true - )! // swiftlint:disable:this force_unwrapping - } - - static func createInvocationWithBusinessID() -> AEMInvocation { - AEMInvocation( - campaignID: "81", // The campaign id mod 8 (catalog optimization modulus) modulus is 1 - acsToken: "debugging_token", - acsSharedSecret: "debugging_shared_secret", - acsConfigurationID: "debugging_config_id_123", - businessID: "test_business_id", - catalogID: "test_catalog_id", - isTestMode: false, - hasStoreKitAdNetwork: true, - isConversionFilteringEligible: true - )! // swiftlint:disable:this force_unwrapping - } -} diff --git a/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMMultiEntryRules.swift b/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMMultiEntryRules.swift deleted file mode 100644 index 667060f2bd..0000000000 --- a/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMMultiEntryRules.swift +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 Foundation - -enum SampleAEMMultiEntryRules { - - private static let factory = AEMAdvertiserRuleFactory() - - static let contentRule = - factory.createRule(json: #"{"or": [{"fb_content[*].id": {"eq": "12345"}}]}"#)! // swiftlint:disable:this line_length force_unwrapping -} diff --git a/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMSingleEntryRules.swift b/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMSingleEntryRules.swift deleted file mode 100644 index 10d9e03098..0000000000 --- a/FBAEMKit/FBAEMKitTests/Helpers/SampleAEMSingleEntryRules.swift +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 Foundation - -enum SampleAEMSingleEntryRules { - - static let urlRule = AEMAdvertiserSingleEntryRule( - with: .caseInsensitiveContains, - paramKey: "URL", - linguisticCondition: "thankyou.do", - numericalCondition: nil, - arrayCondition: nil - ) - - static let cardTypeRule1 = AEMAdvertiserSingleEntryRule( - with: .equal, - paramKey: "card_type", - linguisticCondition: "platium", - numericalCondition: nil, - arrayCondition: nil - ) - - static let cardTypeRule2 = AEMAdvertiserSingleEntryRule( - with: .equal, - paramKey: "card_type", - linguisticCondition: "blue_credit", - numericalCondition: nil, - arrayCondition: nil - ) - - static let cardTypeRule3 = AEMAdvertiserSingleEntryRule( - with: .equal, - paramKey: "card_type", - linguisticCondition: "gold_charge", - numericalCondition: nil, - arrayCondition: nil - ) - - static let contentCategoryRule = AEMAdvertiserSingleEntryRule( - with: .equal, - paramKey: "content_category", - linguisticCondition: "demand", - numericalCondition: nil, - arrayCondition: nil - ) - - static let contentNameRule = AEMAdvertiserSingleEntryRule( - with: .startsWith, - paramKey: "content_name", - linguisticCondition: "exit", - numericalCondition: nil, - arrayCondition: nil - ) - - static let valueRule = AEMAdvertiserSingleEntryRule( - with: .greaterThan, - paramKey: "amount", - linguisticCondition: nil, - numericalCondition: NSNumber(value: 10), - arrayCondition: nil - ) -} diff --git a/FBAEMKit/FBAEMKitTests/Helpers/TestBundle.swift b/FBAEMKit/FBAEMKitTests/Helpers/TestBundle.swift deleted file mode 100644 index edf9513333..0000000000 --- a/FBAEMKit/FBAEMKitTests/Helpers/TestBundle.swift +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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. - */ - -import Foundation - -final class TestBundle: Bundle { - var infoDictionaryKey: String? - var stubbedInfoDictionaryObject: Any? - - override func object(forInfoDictionaryKey key: String) -> Any? { - infoDictionaryKey = key - return stubbedInfoDictionaryObject - } -} diff --git a/FBAEMKit/FBAEMKitTests/Helpers/TestInvocation.swift b/FBAEMKit/FBAEMKitTests/Helpers/TestInvocation.swift deleted file mode 100644 index c19203438f..0000000000 --- a/FBAEMKit/FBAEMKitTests/Helpers/TestInvocation.swift +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 Foundation - -final class TestInvocation: AEMInvocation { - - var attributionCallCount = 0 - var updateConversionCallCount = 0 - var isOptimizedEvent = false - var shouldBoostPriority = false - - // This was copied from the superclass because the superclass version can't be called from a subclass - convenience init?( - campaignID: String, - acsToken: String, - acsSharedSecret: String?, - acsConfigurationID: String?, - businessID: String?, - catalogID: String?, - isTestMode: Bool, - hasStoreKitAdNetwork: Bool, - isConversionFilteringEligible: Bool - ) { - self.init( - campaignID: campaignID, - acsToken: acsToken, - acsSharedSecret: acsSharedSecret, - acsConfigurationID: acsConfigurationID, - businessID: businessID, - catalogID: catalogID, - timestamp: nil, - configurationMode: "DEFAULT", - configurationID: -1, - recordedEvents: nil, - recordedValues: nil, - conversionValue: -1, - priority: -1, - conversionTimestamp: nil, - isAggregated: true, - isTestMode: isTestMode, - hasStoreKitAdNetwork: hasStoreKitAdNetwork, - isConversionFilteringEligible: isConversionFilteringEligible - ) - } - - override func attributeEvent( - _ event: String, - currency: String?, - value: NSNumber?, - parameters: [String: Any]?, - configurations: [String: [AEMConfiguration]]?, - shouldUpdateCache: Bool, - isRuleMatchInServer: Bool - ) -> Bool { - attributionCallCount += 1 - return true - } - - override func updateConversionValue( - configurations: [String: [AEMConfiguration]]?, - event: String, - shouldBoostPriority: Bool - ) -> Bool { - updateConversionCallCount += 1 - self.shouldBoostPriority = shouldBoostPriority - return true - } - - override func isOptimizedEvent( - _ event: String, - configurations: [String: [AEMConfiguration]]? - ) -> Bool { - isOptimizedEvent - } -} diff --git a/FBAEMKit/FBAEMKitTests/Info.plist b/FBAEMKit/FBAEMKitTests/Info.plist deleted file mode 100644 index 60cfdddc94..0000000000 --- a/FBAEMKit/FBAEMKitTests/Info.plist +++ /dev/null @@ -1,31 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - com.facebook.sdk.FBAEMKitTests - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - FBAEMKitTests - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleURLTypes - - - CFBundleURLSchemes - - example.com - - - - CFBundleVersion - 1 - - diff --git a/FBAEMKit/FBAEMKitTests/PrivacyManifestTests.swift b/FBAEMKit/FBAEMKitTests/PrivacyManifestTests.swift deleted file mode 100644 index 717e843fc7..0000000000 --- a/FBAEMKit/FBAEMKitTests/PrivacyManifestTests.swift +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 PrivacyManifestTests: XCTestCase { - var manifestUrl: URL? - - override func setUp() { - super.setUp() - let bundle = Bundle(for: AEMConfiguration.self) - manifestUrl = bundle.url(forResource: "PrivacyInfo", withExtension: "xcprivacy") - } - - override func tearDown() { - manifestUrl = nil - super.tearDown() - } - - func testTrackingDomains() { - guard let manifestUrl else { - return XCTFail("Could not find Privacy Manifest file") - } - let manifest = NSDictionary(contentsOf: manifestUrl) - if manifest?["NSPrivacyTrackingDomains"] is NSArray { - XCTFail("Should not contain tracking domains") - } - } - - func testRequiredReasonAPIs() { - guard let manifestUrl else { - return XCTFail("Could not find Privacy Manifest file") - } - let manifest = NSDictionary(contentsOf: manifestUrl) - guard let rrAPIs = manifest?["NSPrivacyAccessedAPITypes"] as? NSArray else { - return XCTFail("Could not find Privacy Accessed API Types") - } - XCTAssertTrue(rrAPIs.count == 1, "Should only expect to have one API in the Privacy Manifest file") - guard let rrAPIDict = rrAPIs[0] as? NSDictionary else { - return XCTFail("Could not find items in Privacy Accessed API Types") - } - - XCTAssertEqual( - rrAPIDict["NSPrivacyAccessedAPIType"] as? String, - "NSPrivacyAccessedAPICategoryUserDefaults", - "Should match UserDefaults category" - ) - guard let reasons = rrAPIDict["NSPrivacyAccessedAPITypeReasons"] as? NSArray else { - return XCTFail("Could not find Privacy Accessed API Reasons") - } - XCTAssertTrue( - reasons.count == 1, - """ - Should only expect to have one reason for UserDefaults - in the Privacy Manifest file - """ - ) - XCTAssertEqual(reasons[0] as? String, "CA92.1", "Reason should match CA92.1") - } - - func testPrivacyTracking() { - guard let manifestUrl else { - return XCTFail("Could not find Privacy Manifest file") - } - let manifest = NSDictionary(contentsOf: manifestUrl) - guard let privacyTrackingFlag = manifest?["NSPrivacyTracking"] as? Bool else { - return XCTFail("Could not find NSPrivacyTracking key") - } - XCTAssertFalse(privacyTrackingFlag, "NSPrivacyTracking is expected to be true in the Privacy Manifest file") - } -} diff --git a/FBAEMKit/project.yml b/FBAEMKit/project.yml deleted file mode 100644 index 264efffe81..0000000000 --- a/FBAEMKit/project.yml +++ /dev/null @@ -1,78 +0,0 @@ -# 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. - -name: FBAEMKit - -include: - - ../xcodegen_project_common.yml - -projectReferences: - FBSDKCoreKit_Basics: - path: ../FBSDKCoreKit_Basics/FBSDKCoreKit_Basics.xcodeproj - TestTools: - path: ../TestTools/TestTools.xcodeproj - -configFiles: - Debug: Configurations/Shared/Configuration/Debug.xcconfig - Release: Configurations/Shared/Configuration/Release.xcconfig - -targets: - FBAEMKit-Static: - templates: - - SwiftlintBuildPhaseTemplate - type: framework - platform: iOS - productName: FBAEMKit - sources: - - path: FBAEMKit - dependencies: - - target: FBSDKCoreKit_Basics/FBSDKCoreKit_Basics-Static - link: false - configFiles: - Debug: Configurations/FBAEMKit-Static.xcconfig - Release: Configurations/FBAEMKit-Static.xcconfig - FBAEMKit-Dynamic: - templates: - - SwiftlintBuildPhaseTemplate - type: framework - platform: iOS - productName: FBAEMKit - sources: - - path: FBAEMKit - dependencies: - - target: FBSDKCoreKit_Basics/FBSDKCoreKit_Basics-Dynamic - - sdk: UIKit.framework - - sdk: libz.tbd - configFiles: - Debug: Configurations/FBAEMKit-Dynamic.xcconfig - Release: Configurations/FBAEMKit-Dynamic.xcconfig - FBAEMKitTests: - type: bundle.unit-test - platform: iOS - sources: - - FBAEMKitTests - configFiles: - Debug: Configurations/FBAEMKitTests.xcconfig - Release: Configurations/FBAEMKitTests.xcconfig - dependencies: - - target: FBAEMKit-Dynamic - - target: TestTools/TestTools - embed: false - -schemes: - FBAEMKit-Static: - build: - targets: - FBAEMKit-Static: all - FBAEMKit-Dynamic: - build: - targets: - FBAEMKit-Dynamic: all - test: - targets: [FBAEMKitTests] - gatherCoverageData: true - coverageTargets: - - FBAEMKit-Dynamic