From 1e06298adf46e7e5d4a7c21f9289620af9801b83 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 1 Aug 2024 16:26:48 -0500 Subject: [PATCH 01/72] updated package.swift and moved logger client to it --- Package.swift | 9 ++++++++- Sources/KlaviyoCore/LoggerClient.swift | 14 ++++++++++++++ Sources/KlaviyoSwift/KlaviyoEnvironment.swift | 1 + Sources/KlaviyoSwift/LoggerClient.swift | 14 -------------- 4 files changed, 23 insertions(+), 15 deletions(-) create mode 100644 Sources/KlaviyoCore/LoggerClient.swift delete mode 100644 Sources/KlaviyoSwift/LoggerClient.swift diff --git a/Package.swift b/Package.swift index 6b53c5d8..9a1bcbd2 100644 --- a/Package.swift +++ b/Package.swift @@ -7,6 +7,9 @@ let package = Package( name: "klaviyo-swift-sdk", platforms: [.iOS(.v13)], products: [ + .library( + name: "KlaviyoCore", + targets: ["KlaviyoCore"]), .library( name: "KlaviyoSwift", targets: ["KlaviyoSwift"]), @@ -25,8 +28,12 @@ let package = Package( ], targets: [ .target( - name: "KlaviyoSwift", + name: "KlaviyoCore", dependencies: [.product(name: "AnyCodable", package: "AnyCodable")], + path: "Sources/KlaviyoCore"), + .target( + name: "KlaviyoSwift", + dependencies: [.product(name: "AnyCodable", package: "AnyCodable"), "KlaviyoCore"], path: "Sources/KlaviyoSwift", resources: [.copy("PrivacyInfo.xcprivacy")]), .target( diff --git a/Sources/KlaviyoCore/LoggerClient.swift b/Sources/KlaviyoCore/LoggerClient.swift new file mode 100644 index 00000000..c031b1e7 --- /dev/null +++ b/Sources/KlaviyoCore/LoggerClient.swift @@ -0,0 +1,14 @@ +// +// LoggerClient.swift +// KlaviyoSwift +// +// Created by Noah Durell on 10/21/22. +// + +import Foundation +import os + +public struct LoggerClient { + public var error: (String) -> Void + public static let production = Self(error: { message in os_log("%{public}s", type: .error, message) }) +} diff --git a/Sources/KlaviyoSwift/KlaviyoEnvironment.swift b/Sources/KlaviyoSwift/KlaviyoEnvironment.swift index 85a8e85b..0d741bb3 100644 --- a/Sources/KlaviyoSwift/KlaviyoEnvironment.swift +++ b/Sources/KlaviyoSwift/KlaviyoEnvironment.swift @@ -8,6 +8,7 @@ import AnyCodable import Combine import Foundation +import KlaviyoCore import UIKit var environment = KlaviyoEnvironment.production diff --git a/Sources/KlaviyoSwift/LoggerClient.swift b/Sources/KlaviyoSwift/LoggerClient.swift deleted file mode 100644 index 8ee9f423..00000000 --- a/Sources/KlaviyoSwift/LoggerClient.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// LoggerClient.swift -// KlaviyoSwift -// -// Created by Noah Durell on 10/21/22. -// - -import Foundation -import os - -struct LoggerClient { - var error: (String) -> Void - static let production = Self(error: { message in os_log("%{public}s", type: .error, message) }) -} From 597e6290df18e0dfd9f1d7392fad6f990ad43517 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 1 Aug 2024 16:54:46 -0500 Subject: [PATCH 02/72] moved app context info to klaviyo core --- .../AppContextInfo.swift | 68 +++++++++---------- Sources/KlaviyoSwift/InternalAPIModels.swift | 1 + Sources/KlaviyoSwift/NetworkSession.swift | 1 + 3 files changed, 36 insertions(+), 34 deletions(-) rename Sources/{KlaviyoSwift => KlaviyoCore}/AppContextInfo.swift (57%) diff --git a/Sources/KlaviyoSwift/AppContextInfo.swift b/Sources/KlaviyoCore/AppContextInfo.swift similarity index 57% rename from Sources/KlaviyoSwift/AppContextInfo.swift rename to Sources/KlaviyoCore/AppContextInfo.swift index 623d7303..98e8bdde 100644 --- a/Sources/KlaviyoSwift/AppContextInfo.swift +++ b/Sources/KlaviyoCore/AppContextInfo.swift @@ -7,18 +7,18 @@ import Foundation import UIKit -struct AppContextInfo { - private static let info = Bundle.main.infoDictionary - private static let defaultExecutable: String = (info?["CFBundleExecutable"] as? String) ?? +public struct AppContextInfo { + private let info = Bundle.main.infoDictionary + public static let defaultExecutable: String = (Bundle.main.infoDictionary?["CFBundleExecutable"] as? String) ?? (ProcessInfo.processInfo.arguments.first?.split(separator: "/").last.map(String.init)) ?? "Unknown" - private static let defaultBundleId: String = info?["CFBundleIdentifier"] as? String ?? "Unknown" - private static let defaultAppVersion: String = info?["CFBundleShortVersionString"] as? String ?? "Unknown" - private static let defaultAppBuild: String = info?["CFBundleVersion"] as? String ?? "Unknown" - private static let defaultAppName: String = info?["CFBundleName"] as? String ?? "Unknown" - private static let defaultOSVersion = ProcessInfo.processInfo.operatingSystemVersion - private static let defaultManufacturer = "Apple" - private static let defaultOSName = "iOS" - private static let defaultDeviceModel: String = { + public static let defaultBundleId: String = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String ?? "Unknown" + public static let defaultAppVersion: String = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown" + public static let defaultAppBuild: String = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown" + public static let defaultAppName: String = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "Unknown" + public static let defaultOSVersion = ProcessInfo.processInfo.operatingSystemVersion + public static let defaultManufacturer = "Apple" + public static let defaultOSName = "iOS" + public static let defaultDeviceModel: String = { var size = 0 var deviceModel = "" sysctlbyname("hw.machine", nil, &size, nil, 0) @@ -32,36 +32,36 @@ struct AppContextInfo { private static let deviceIdStoreKey = "_klaviyo_device_id" - let executable: String - let bundleId: String - let appVersion: String - let appBuild: String - let appName: String - let version: OperatingSystemVersion - let osName: String - let manufacturer: String - let deviceModel: String - let deviceId: String - let environment: String + public let executable: String + public let bundleId: String + public let appVersion: String + public let appBuild: String + public let appName: String + public let version: OperatingSystemVersion + public let osName: String + public let manufacturer: String + public let deviceModel: String + public let deviceId: String + public let environment: String - var osVersion: String { + public var osVersion: String { "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)" } - var osVersionName: String { + public var osVersionName: String { "\(osName) \(osVersion)" } - init(executable: String = defaultExecutable, - bundleId: String = defaultBundleId, - appVersion: String = defaultAppVersion, - appBuild: String = defaultAppBuild, - appName: String = defaultAppName, - version: OperatingSystemVersion = defaultOSVersion, - osName: String = defaultOSName, - manufacturer: String = defaultManufacturer, - deviceModel: String = defaultDeviceModel, - deviceId: String = UIDevice.current.identifierForVendor?.uuidString ?? "") { + public init(executable: String = defaultExecutable, + bundleId: String = defaultBundleId, + appVersion: String = defaultAppVersion, + appBuild: String = defaultAppBuild, + appName: String = defaultAppName, + version: OperatingSystemVersion = defaultOSVersion, + osName: String = defaultOSName, + manufacturer: String = defaultManufacturer, + deviceModel: String = defaultDeviceModel, + deviceId: String = UIDevice.current.identifierForVendor?.uuidString ?? "") { self.executable = executable self.bundleId = bundleId self.appVersion = appVersion diff --git a/Sources/KlaviyoSwift/InternalAPIModels.swift b/Sources/KlaviyoSwift/InternalAPIModels.swift index 3b437644..330e80a4 100644 --- a/Sources/KlaviyoSwift/InternalAPIModels.swift +++ b/Sources/KlaviyoSwift/InternalAPIModels.swift @@ -8,6 +8,7 @@ import AnyCodable import Foundation +import KlaviyoCore extension KlaviyoAPI.KlaviyoRequest { private static let _appContextInfo = environment.analytics.appContextInfo() diff --git a/Sources/KlaviyoSwift/NetworkSession.swift b/Sources/KlaviyoSwift/NetworkSession.swift index d39868d4..0694c34d 100644 --- a/Sources/KlaviyoSwift/NetworkSession.swift +++ b/Sources/KlaviyoSwift/NetworkSession.swift @@ -6,6 +6,7 @@ // import Foundation +import KlaviyoCore func createEmphemeralSession(protocolClasses: [AnyClass] = URLProtocolOverrides.protocolClasses) -> URLSession { let configuration = URLSessionConfiguration.ephemeral From 05e728e2c96110e97e3428b591d44be06f372551 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Mon, 5 Aug 2024 13:59:00 -0500 Subject: [PATCH 03/72] working stuff --- Package.swift | 3 +- .../AppLifeCycleEvents.swift | 39 ++-- .../ArchivalUtils.swift | 12 +- .../FileUtils.swift | 16 +- Sources/KlaviyoCore/KlaviyoEnvironment.swift | 174 ++++++++++++++++++ .../NetworkSession.swift | 20 +- .../Vendor/ReachabilitySwift.swift | 163 ++++++++-------- .../Version.swift | 0 .../APIRequestErrorHandling.swift | 1 + .../KlaviyoSwift/AnalyticsEnvironment.swift | 53 ++++++ Sources/KlaviyoSwift/InternalAPIModels.swift | 4 +- Sources/KlaviyoSwift/Klaviyo.swift | 5 +- Sources/KlaviyoSwift/KlaviyoAPI.swift | 21 ++- Sources/KlaviyoSwift/KlaviyoEnvironment.swift | 144 --------------- Sources/KlaviyoSwift/KlaviyoModels.swift | 11 +- Sources/KlaviyoSwift/KlaviyoState.swift | 55 +----- .../KlaviyoSwift/StateChangePublisher.swift | 2 +- Sources/KlaviyoSwift/StateManagement.swift | 18 +- .../Vendor/ComposableArchitecture/Misc.swift | 2 +- .../KlaviyoSwiftTests/KlaviyoTestUtils.swift | 1 + 20 files changed, 401 insertions(+), 343 deletions(-) rename Sources/{KlaviyoSwift => KlaviyoCore}/AppLifeCycleEvents.swift (63%) rename Sources/{KlaviyoSwift => KlaviyoCore}/ArchivalUtils.swift (85%) rename Sources/{KlaviyoSwift => KlaviyoCore}/FileUtils.swift (79%) create mode 100644 Sources/KlaviyoCore/KlaviyoEnvironment.swift rename Sources/{KlaviyoSwift => KlaviyoCore}/NetworkSession.swift (73%) rename Sources/{KlaviyoSwift => KlaviyoCore}/Vendor/ReachabilitySwift.swift (61%) rename Sources/{KlaviyoSwift => KlaviyoCore}/Version.swift (100%) create mode 100644 Sources/KlaviyoSwift/AnalyticsEnvironment.swift delete mode 100644 Sources/KlaviyoSwift/KlaviyoEnvironment.swift diff --git a/Package.swift b/Package.swift index 9a1bcbd2..74ad904d 100644 --- a/Package.swift +++ b/Package.swift @@ -47,7 +47,8 @@ let package = Package( .product(name: "SnapshotTesting", package: "swift-snapshot-testing"), .product(name: "CustomDump", package: "swift-custom-dump"), .product(name: "CasePaths", package: "swift-case-paths"), - .product(name: "CombineSchedulers", package: "combine-schedulers") + .product(name: "CombineSchedulers", package: "combine-schedulers"), + "KlaviyoCore" ], exclude: [ "__Snapshots__" diff --git a/Sources/KlaviyoSwift/AppLifeCycleEvents.swift b/Sources/KlaviyoCore/AppLifeCycleEvents.swift similarity index 63% rename from Sources/KlaviyoSwift/AppLifeCycleEvents.swift rename to Sources/KlaviyoCore/AppLifeCycleEvents.swift index 97fa8b74..f40da818 100644 --- a/Sources/KlaviyoSwift/AppLifeCycleEvents.swift +++ b/Sources/KlaviyoCore/AppLifeCycleEvents.swift @@ -9,18 +9,24 @@ import Combine import Foundation import UIKit -enum LifeCycleErrors: Error { +public enum LifeCycleErrors: Error { case invalidReachaibilityStatus } -struct AppLifeCycleEvents { - var lifeCycleEvents: () -> AnyPublisher = { +public enum LifeCycleEvents { + case terminated + case forgrounded + case backgrounded +} + +public struct AppLifeCycleEvents { + public var lifeCycleEvents: () -> AnyPublisher = { let terminated = environment .notificationCenterPublisher(UIApplication.willTerminateNotification) .handleEvents(receiveOutput: { _ in environment.stopReachability() }) - .map { _ in KlaviyoAction.stop } + .map { _ in LifeCycleEvents.terminated } let foregrounded = environment .notificationCenterPublisher(UIApplication.didBecomeActiveNotification) .handleEvents(receiveOutput: { _ in @@ -30,26 +36,27 @@ struct AppLifeCycleEvents { environment.emitDeveloperWarning("failure to start reachability notifier") } }) - .map { _ in KlaviyoAction.start } + .map { _ in LifeCycleEvents.forgrounded } let backgrounded = environment .notificationCenterPublisher(UIApplication.didEnterBackgroundNotification) .handleEvents(receiveOutput: { _ in environment.stopReachability() }) - .map { _ in KlaviyoAction.stop } + .map { _ in LifeCycleEvents.backgrounded } + // TODO: fix me // The below is a bit convoluted since network status can be nil. - let reachability = environment - .notificationCenterPublisher(ReachabilityChangedNotification) - .compactMap { _ -> KlaviyoAction? in - guard let status = environment.reachabilityStatus() else { - return nil - } - return KlaviyoAction.networkConnectivityChanged(status) - } - .eraseToAnyPublisher() +// let reachability = environment +// .notificationCenterPublisher(ReachabilityChangedNotification) +// .compactMap { _ -> KlaviyoAction? in +// guard let status = environment.reachabilityStatus() else { +// return nil +// } +// return KlaviyoAction.networkConnectivityChanged(status) +// } +// .eraseToAnyPublisher() return terminated - .merge(with: reachability) +// .merge(with: reachability) // TODO: fixme .merge(with: foregrounded, backgrounded) .handleEvents(receiveSubscription: { _ in do { diff --git a/Sources/KlaviyoSwift/ArchivalUtils.swift b/Sources/KlaviyoCore/ArchivalUtils.swift similarity index 85% rename from Sources/KlaviyoSwift/ArchivalUtils.swift rename to Sources/KlaviyoCore/ArchivalUtils.swift index 4c9c1c95..c59d43d7 100644 --- a/Sources/KlaviyoSwift/ArchivalUtils.swift +++ b/Sources/KlaviyoCore/ArchivalUtils.swift @@ -7,11 +7,11 @@ import Foundation -struct ArchiverClient { - var archivedData: (Any, Bool) throws -> Data - var unarchivedMutableArray: (Data) throws -> NSMutableArray? +public struct ArchiverClient { + public var archivedData: (Any, Bool) throws -> Data + public var unarchivedMutableArray: (Data) throws -> NSMutableArray? - static let production = ArchiverClient( + public static let production = ArchiverClient( archivedData: NSKeyedArchiver.archivedData(withRootObject:requiringSecureCoding:), unarchivedMutableArray: { data in try NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, NSDictionary.self, @@ -24,7 +24,7 @@ struct ArchiverClient { }) } -func archiveQueue(queue: NSArray, to fileURL: URL) { +public func archiveQueue(queue: NSArray, to fileURL: URL) { guard let archiveData = try? environment.archiverClient.archivedData(queue, true) else { print("unable to archive the data to \(fileURL)") return @@ -37,7 +37,7 @@ func archiveQueue(queue: NSArray, to fileURL: URL) { } } -func unarchiveFromFile(fileURL: URL) -> NSMutableArray? { +public func unarchiveFromFile(fileURL: URL) -> NSMutableArray? { guard environment.fileClient.fileExists(fileURL.path) else { print("Archive file not found.") return nil diff --git a/Sources/KlaviyoSwift/FileUtils.swift b/Sources/KlaviyoCore/FileUtils.swift similarity index 79% rename from Sources/KlaviyoSwift/FileUtils.swift rename to Sources/KlaviyoCore/FileUtils.swift index ff8737e1..02350a8f 100644 --- a/Sources/KlaviyoSwift/FileUtils.swift +++ b/Sources/KlaviyoCore/FileUtils.swift @@ -11,16 +11,16 @@ func write(data: Data, url: URL) throws { try data.write(to: url, options: .atomic) } -struct FileClient { - static let production = FileClient( +public struct FileClient { + public static let production = FileClient( write: write(data:url:), fileExists: FileManager.default.fileExists(atPath:), removeItem: FileManager.default.removeItem(atPath:), libraryDirectory: { FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first! }) - var write: (Data, URL) throws -> Void - var fileExists: (String) -> Bool - var removeItem: (String) throws -> Void - var libraryDirectory: () -> URL + public var write: (Data, URL) throws -> Void + public var fileExists: (String) -> Bool + public var removeItem: (String) throws -> Void + public var libraryDirectory: () -> URL } /** @@ -30,7 +30,7 @@ struct FileClient { - Parameter data: name representing the event queue to locate (will be either people or events) - Returns: filePath string representing the file location */ -func filePathForData(apiKey: String, data: String) -> URL { +public func filePathForData(apiKey: String, data: String) -> URL { let fileName = "klaviyo-\(apiKey)-\(data).plist" let directory = environment.fileClient.libraryDirectory() let filePath = directory.appendingPathComponent(fileName, isDirectory: false) @@ -43,7 +43,7 @@ func filePathForData(apiKey: String, data: String) -> URL { - Parameter at: path of file to be removed - Returns: whether or not the file was removed */ -func removeFile(at url: URL) -> Bool { +public func removeFile(at url: URL) -> Bool { if environment.fileClient.fileExists(url.path) { do { try environment.fileClient.removeItem(url.path) diff --git a/Sources/KlaviyoCore/KlaviyoEnvironment.swift b/Sources/KlaviyoCore/KlaviyoEnvironment.swift new file mode 100644 index 00000000..45d719a7 --- /dev/null +++ b/Sources/KlaviyoCore/KlaviyoEnvironment.swift @@ -0,0 +1,174 @@ +// +// KlaviyoEnvironment.swift +// KlaviyoSwift +// +// Created by Noah Durell on 9/28/22. +// + +import AnyCodable +import Combine +import Foundation +import UIKit +#if canImport(os) +import os +#endif + +public var environment = KlaviyoEnvironment.production + +public struct KlaviyoEnvironment { + public static let productionHost = "https://a.klaviyo.com" + public static let encoder = { () -> JSONEncoder in + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + return encoder + }() + + public static let decoder = { () -> JSONDecoder in + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + return decoder + }() + + private static let reachabilityService = Reachability(hostname: URL(string: productionHost)!.host!) + + public var archiverClient: ArchiverClient + public var fileClient: FileClient + public var data: (URL) throws -> Data + public var logger: LoggerClient +// public var analytics: AnalyticsEnvironment + public var getUserDefaultString: (String) -> String? + public var appLifeCycle: AppLifeCycleEvents + public var notificationCenterPublisher: (NSNotification.Name) -> AnyPublisher + public var getNotificationSettings: (@escaping (PushEnablement) -> Void) -> Void + public var getBackgroundSetting: () -> PushBackground + public var legacyIdentifier: () -> String + public var startReachability: () throws -> Void + public var stopReachability: () -> Void + public var reachabilityStatus: () -> Reachability.NetworkStatus? + public var randomInt: () -> Int + // TODO: need to fix this +// public var stateChangePublisher: () -> AnyPublisher + public var raiseFatalError: (String) -> Void + public var emitDeveloperWarning: (String) -> Void + static var production = KlaviyoEnvironment( + archiverClient: ArchiverClient.production, + fileClient: FileClient.production, + data: { url in try Data(contentsOf: url) }, + logger: LoggerClient.production, + // TODO: fixme + // analytics: AnalyticsEnvironment.production, + getUserDefaultString: { key in UserDefaults.standard.string(forKey: key) }, + appLifeCycle: AppLifeCycleEvents.production, + notificationCenterPublisher: { name in + NotificationCenter.default.publisher(for: name) + .eraseToAnyPublisher() + }, + getNotificationSettings: { callback in + UNUserNotificationCenter.current().getNotificationSettings { settings in + callback(.create(from: settings.authorizationStatus)) + } + }, + getBackgroundSetting: { .create(from: UIApplication.shared.backgroundRefreshStatus) }, + legacyIdentifier: { "iOS:\(UIDevice.current.identifierForVendor!.uuidString)" }, + startReachability: { + try reachabilityService?.startNotifier() + }, + stopReachability: { + reachabilityService?.stopNotifier() + }, + reachabilityStatus: { + reachabilityService?.currentReachabilityStatus + }, + randomInt: { Int.random(in: 0...10) }, + // TODO: need to fix this +// stateChangePublisher: StateChangePublisher().publisher, + raiseFatalError: { msg in + #if DEBUG + fatalError(msg) + #endif + }, + emitDeveloperWarning: { runtimeWarn($0) }) +} + +public var networkSession: NetworkSession! +public func createNetworkSession() -> NetworkSession { + if networkSession == nil { + networkSession = NetworkSession.production + } + return networkSession +} + +enum KlaviyoDecodingError: Error { + case invalidType +} + +public struct DataDecoder { + public func decode(_ data: Data) throws -> T { + try jsonDecoder.decode(T.self, from: data) + } + + public var jsonDecoder: JSONDecoder + public static let production = Self(jsonDecoder: KlaviyoEnvironment.decoder) +} + +public enum PushEnablement: String, Codable { + case notDetermined = "NOT_DETERMINED" + case denied = "DENIED" + case authorized = "AUTHORIZED" + case provisional = "PROVISIONAL" + case ephemeral = "EPHEMERAL" + + static func create(from status: UNAuthorizationStatus) -> PushEnablement { + switch status { + case .denied: + return PushEnablement.denied + case .authorized: + return PushEnablement.authorized + case .provisional: + return PushEnablement.provisional + case .ephemeral: + return PushEnablement.ephemeral + default: + return PushEnablement.notDetermined + } + } +} + +public enum PushBackground: String, Codable { + case available = "AVAILABLE" + case restricted = "RESTRICTED" + case denied = "DENIED" + + static func create(from status: UIBackgroundRefreshStatus) -> PushBackground { + switch status { + case .available: + return PushBackground.available + case .restricted: + return PushBackground.restricted + case .denied: + return PushBackground.denied + @unknown default: + return PushBackground.available + } + } +} + +@usableFromInline +@inline(__always) +func runtimeWarn( + _ message: @autoclosure () -> String, + category: String? = __klaviyoSwiftName, + file: StaticString? = nil, + line: UInt? = nil) { + #if DEBUG + let message = message() + let category = category ?? "Runtime Warning" + #if canImport(os) + os_log( + .fault, + log: OSLog(subsystem: "com.apple.runtime-issues", category: category), + "%@", + message) + #endif + #endif +} diff --git a/Sources/KlaviyoSwift/NetworkSession.swift b/Sources/KlaviyoCore/NetworkSession.swift similarity index 73% rename from Sources/KlaviyoSwift/NetworkSession.swift rename to Sources/KlaviyoCore/NetworkSession.swift index 0694c34d..428b85fb 100644 --- a/Sources/KlaviyoSwift/NetworkSession.swift +++ b/Sources/KlaviyoCore/NetworkSession.swift @@ -6,9 +6,8 @@ // import Foundation -import KlaviyoCore -func createEmphemeralSession(protocolClasses: [AnyClass] = URLProtocolOverrides.protocolClasses) -> URLSession { +public func createEmphemeralSession(protocolClasses: [AnyClass] = URLProtocolOverrides.protocolClasses) -> URLSession { let configuration = URLSessionConfiguration.ephemeral configuration.httpAdditionalHeaders = [ "Accept-Encoding": NetworkSession.acceptedEncodings, @@ -22,21 +21,24 @@ func createEmphemeralSession(protocolClasses: [AnyClass] = URLProtocolOverrides. return URLSession(configuration: configuration) } -struct NetworkSession { +public struct NetworkSession { fileprivate static let currentApiRevision = "2023-07-15" fileprivate static let applicationJson = "application/json" fileprivate static let acceptedEncodings = ["br", "gzip", "deflate"] fileprivate static let mobileHeader = "1" - static let defaultUserAgent = { () -> String in - let appContext = environment.analytics.appContextInfo() - let klaivyoSDKVersion = "klaviyo-ios/\(__klaviyoSwiftVersion)" - return "\(appContext.executable)/\(appContext.appVersion) (\(appContext.bundleId); build:\(appContext.appBuild); \(appContext.osVersionName)) \(klaivyoSDKVersion)" + public static let defaultUserAgent = { () -> String in + // TODO: fixme + // let appContext = environment.analytics.appContextInfo() +// let klaivyoSDKVersion = "klaviyo-ios/\(__klaviyoSwiftVersion)" +// return "\(appContext.executable)/\(appContext.appVersion) (\(appContext.bundleId); build:\(appContext.appBuild); \(appContext.osVersionName)) \(klaivyoSDKVersion)" + + "FIXME" }() - var data: (URLRequest) async throws -> (Data, URLResponse) + public var data: (URLRequest) async throws -> (Data, URLResponse) - static let production = { () -> NetworkSession in + public static let production = { () -> NetworkSession in let session = createEmphemeralSession() return NetworkSession(data: { request async throws -> (Data, URLResponse) in diff --git a/Sources/KlaviyoSwift/Vendor/ReachabilitySwift.swift b/Sources/KlaviyoCore/Vendor/ReachabilitySwift.swift similarity index 61% rename from Sources/KlaviyoSwift/Vendor/ReachabilitySwift.swift rename to Sources/KlaviyoCore/Vendor/ReachabilitySwift.swift index d4115afd..de4ef9ca 100644 --- a/Sources/KlaviyoSwift/Vendor/ReachabilitySwift.swift +++ b/Sources/KlaviyoCore/Vendor/ReachabilitySwift.swift @@ -1,32 +1,32 @@ /* -Copyright (c) 2014, Ashley Mills -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. -*/ + Copyright (c) 2014, Ashley Mills + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ -import SystemConfiguration import Foundation +import SystemConfiguration enum ReachabilityError: Error { case FailedToCreateWithAddress(sockaddr_in) @@ -37,8 +37,7 @@ enum ReachabilityError: Error { let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification") -func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) { - +func callback(reachability: SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) { guard let info = info else { return } let reachability = Unmanaged.fromOpaque(info).takeUnretainedValue() @@ -48,16 +47,14 @@ func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFl } } -class Reachability { - - typealias NetworkReachable = (Reachability) -> () - typealias NetworkUnreachable = (Reachability) -> () - - enum NetworkStatus: CustomStringConvertible { +public class Reachability { + typealias NetworkReachable = (Reachability) -> Void + typealias NetworkUnreachable = (Reachability) -> Void + public enum NetworkStatus: CustomStringConvertible { case notReachable, reachableViaWiFi, reachableViaWWAN - var description: String { + public var description: String { switch self { case .reachableViaWWAN: return "Cellular" case .reachableViaWiFi: return "WiFi" @@ -71,10 +68,10 @@ class Reachability { var reachableOnWWAN: Bool // The notification center on which "reachability changed" events are being posted - var notificationCenter: NotificationCenter = NotificationCenter.default + var notificationCenter: NotificationCenter = .default var currentReachabilityString: String { - return "\(currentReachabilityStatus)" + "\(currentReachabilityStatus)" } var currentReachabilityStatus: NetworkStatus { @@ -90,20 +87,20 @@ class Reachability { return .notReachable } - fileprivate var previousFlags: SCNetworkReachabilityFlags? + private var previousFlags: SCNetworkReachabilityFlags? - fileprivate var isRunningOnDevice: Bool = { + private var isRunningOnDevice: Bool = { #if targetEnvironment(simulator) - return false + return false #else - return true + return true #endif }() - fileprivate var notifierRunning = false - fileprivate var reachabilityRef: SCNetworkReachability? + private var notifierRunning = false + private var reachabilityRef: SCNetworkReachability? - fileprivate let reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability") + private let reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability") required init(reachabilityRef: SCNetworkReachability) { reachableOnWWAN = true @@ -111,14 +108,12 @@ class Reachability { } convenience init?(hostname: String) { - guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { return nil } self.init(reachabilityRef: ref) } convenience init?() { - var zeroAddress = sockaddr() zeroAddress.sa_len = UInt8(MemoryLayout.size) zeroAddress.sa_family = sa_family_t(AF_INET) @@ -140,10 +135,9 @@ class Reachability { } extension Reachability { - // MARK: - *** Notifier methods *** - func startNotifier() throws { + func startNotifier() throws { guard let reachabilityRef = reachabilityRef, !notifierRunning else { return } var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) @@ -175,8 +169,8 @@ extension Reachability { } // MARK: - *** Connection test methods *** - var isReachable: Bool { + var isReachable: Bool { guard isReachableFlagSet else { return false } if isConnectionRequiredAndTransientFlagSet { @@ -195,11 +189,10 @@ extension Reachability { var isReachableViaWWAN: Bool { // Check we're not on the simulator, we're REACHABLE and check we're on WWAN - return isRunningOnDevice && isReachableFlagSet && isOnWWANFlagSet + isRunningOnDevice && isReachableFlagSet && isOnWWANFlagSet } var isReachableViaWiFi: Bool { - // Check we're reachable guard isReachableFlagSet else { return false } @@ -211,7 +204,6 @@ extension Reachability { } var description: String { - let W = isRunningOnDevice ? (isOnWWANFlagSet ? "W" : "-") : "X" let R = isReachableFlagSet ? "R" : "-" let c = isConnectionRequiredFlagSet ? "c" : "-" @@ -226,10 +218,8 @@ extension Reachability { } } -fileprivate extension Reachability { - - func reachabilityChanged() { - +extension Reachability { + fileprivate func reachabilityChanged() { let flags = reachabilityFlags guard previousFlags != flags else { return } @@ -237,51 +227,60 @@ fileprivate extension Reachability { let block = isReachable ? whenReachable : whenUnreachable block?(self) - self.notificationCenter.post(name: ReachabilityChangedNotification, object:self) + notificationCenter.post(name: ReachabilityChangedNotification, object: self) previousFlags = flags } - var isOnWWANFlagSet: Bool { + private var isOnWWANFlagSet: Bool { #if os(iOS) - return reachabilityFlags.contains(.isWWAN) + return reachabilityFlags.contains(.isWWAN) #else - return false + return false #endif } - var isReachableFlagSet: Bool { - return reachabilityFlags.contains(.reachable) - } - var isConnectionRequiredFlagSet: Bool { - return reachabilityFlags.contains(.connectionRequired) + + private var isReachableFlagSet: Bool { + reachabilityFlags.contains(.reachable) } - var isInterventionRequiredFlagSet: Bool { - return reachabilityFlags.contains(.interventionRequired) + + private var isConnectionRequiredFlagSet: Bool { + reachabilityFlags.contains(.connectionRequired) } - var isConnectionOnTrafficFlagSet: Bool { - return reachabilityFlags.contains(.connectionOnTraffic) + + private var isInterventionRequiredFlagSet: Bool { + reachabilityFlags.contains(.interventionRequired) } - var isConnectionOnDemandFlagSet: Bool { - return reachabilityFlags.contains(.connectionOnDemand) + + private var isConnectionOnTrafficFlagSet: Bool { + reachabilityFlags.contains(.connectionOnTraffic) } - var isConnectionOnTrafficOrDemandFlagSet: Bool { - return !reachabilityFlags.intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty + + private var isConnectionOnDemandFlagSet: Bool { + reachabilityFlags.contains(.connectionOnDemand) } - var isTransientConnectionFlagSet: Bool { - return reachabilityFlags.contains(.transientConnection) + + private var isConnectionOnTrafficOrDemandFlagSet: Bool { + !reachabilityFlags.intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty } - var isLocalAddressFlagSet: Bool { - return reachabilityFlags.contains(.isLocalAddress) + + private var isTransientConnectionFlagSet: Bool { + reachabilityFlags.contains(.transientConnection) } - var isDirectFlagSet: Bool { - return reachabilityFlags.contains(.isDirect) + + private var isLocalAddressFlagSet: Bool { + reachabilityFlags.contains(.isLocalAddress) } - var isConnectionRequiredAndTransientFlagSet: Bool { - return reachabilityFlags.intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection] + + private var isDirectFlagSet: Bool { + reachabilityFlags.contains(.isDirect) } - var reachabilityFlags: SCNetworkReachabilityFlags { + private var isConnectionRequiredAndTransientFlagSet: Bool { + reachabilityFlags.intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection] + } + private var reachabilityFlags: SCNetworkReachabilityFlags { guard let reachabilityRef = reachabilityRef else { return SCNetworkReachabilityFlags() } var flags = SCNetworkReachabilityFlags() diff --git a/Sources/KlaviyoSwift/Version.swift b/Sources/KlaviyoCore/Version.swift similarity index 100% rename from Sources/KlaviyoSwift/Version.swift rename to Sources/KlaviyoCore/Version.swift diff --git a/Sources/KlaviyoSwift/APIRequestErrorHandling.swift b/Sources/KlaviyoSwift/APIRequestErrorHandling.swift index 2c8a20ab..47a60d6e 100644 --- a/Sources/KlaviyoSwift/APIRequestErrorHandling.swift +++ b/Sources/KlaviyoSwift/APIRequestErrorHandling.swift @@ -6,6 +6,7 @@ // import Foundation +import KlaviyoCore struct ErrorHandlingConstants { static let maxRetries = 50 diff --git a/Sources/KlaviyoSwift/AnalyticsEnvironment.swift b/Sources/KlaviyoSwift/AnalyticsEnvironment.swift new file mode 100644 index 00000000..b8d12b1a --- /dev/null +++ b/Sources/KlaviyoSwift/AnalyticsEnvironment.swift @@ -0,0 +1,53 @@ +// +// File.swift +// +// +// Created by Ajay Subramanya on 8/2/24. +// + +import AnyCodable +import Combine +import Foundation +import KlaviyoCore +import UIKit + +var analytics = AnalyticsEnvironment.production + +struct AnalyticsEnvironment { + var networkSession: () -> NetworkSession + var apiURL: String + var encodeJSON: (AnyEncodable) throws -> Data + var decoder: DataDecoder + var uuid: () -> UUID + var date: () -> Date + var timeZone: () -> String + var appContextInfo: () -> AppContextInfo + var klaviyoAPI: KlaviyoAPI + var timer: (Double) -> AnyPublisher + var send: (KlaviyoAction) -> Task? + var state: () -> KlaviyoState + var statePublisher: () -> AnyPublisher + static let production: AnalyticsEnvironment = { + let store = Store.production + return AnalyticsEnvironment( + networkSession: createNetworkSession, + apiURL: KlaviyoEnvironment.productionHost, + encodeJSON: { encodable in try KlaviyoEnvironment.encoder.encode(encodable) }, + decoder: DataDecoder.production, + uuid: { UUID() }, + date: { Date() }, + timeZone: { TimeZone.autoupdatingCurrent.identifier }, + appContextInfo: { AppContextInfo() }, + klaviyoAPI: KlaviyoAPI(), + timer: { interval in + Timer.publish(every: interval, on: .main, in: .default) + .autoconnect() + .eraseToAnyPublisher() + }, + send: { action in + store.send(action) + }, + state: { store.state.value }, + statePublisher: { store.state.eraseToAnyPublisher() }) + }() +} diff --git a/Sources/KlaviyoSwift/InternalAPIModels.swift b/Sources/KlaviyoSwift/InternalAPIModels.swift index 330e80a4..f9e7dfb3 100644 --- a/Sources/KlaviyoSwift/InternalAPIModels.swift +++ b/Sources/KlaviyoSwift/InternalAPIModels.swift @@ -11,7 +11,7 @@ import Foundation import KlaviyoCore extension KlaviyoAPI.KlaviyoRequest { - private static let _appContextInfo = environment.analytics.appContextInfo() + private static let _appContextInfo = analytics.appContextInfo() enum KlaviyoEndpoint: Equatable, Codable { struct CreateProfilePayload: Equatable, Codable { @@ -164,7 +164,7 @@ extension KlaviyoAPI.KlaviyoRequest { "App ID": context.bundleId, "App Version": context.appVersion, "App Build": context.appBuild, - "Push Token": environment.analytics.state().pushTokenData?.pushToken as Any + "Push Token": analytics.state().pushTokenData?.pushToken as Any ] let originalProperties = data.attributes.properties.value as? [String: Any] ?? [:] data.attributes.properties = AnyCodable(originalProperties.merging(metadata) { _, new in new }) diff --git a/Sources/KlaviyoSwift/Klaviyo.swift b/Sources/KlaviyoSwift/Klaviyo.swift index dbd1e9f1..a0cc0fcf 100644 --- a/Sources/KlaviyoSwift/Klaviyo.swift +++ b/Sources/KlaviyoSwift/Klaviyo.swift @@ -7,12 +7,13 @@ import AnyCodable import Foundation +import KlaviyoCore import UIKit func dispatchOnMainThread(action: KlaviyoAction) { Task { await MainActor.run { - environment.analytics.send(action) + analytics.send(action) } } } @@ -31,7 +32,7 @@ public struct KlaviyoSDK { public init() {} private var state: KlaviyoState { - environment.analytics.state() + analytics.state() } /// Returns the email for the current user, if any. diff --git a/Sources/KlaviyoSwift/KlaviyoAPI.swift b/Sources/KlaviyoSwift/KlaviyoAPI.swift index 46fa3a3f..fadcf85a 100644 --- a/Sources/KlaviyoSwift/KlaviyoAPI.swift +++ b/Sources/KlaviyoSwift/KlaviyoAPI.swift @@ -7,17 +7,18 @@ import AnyCodable import Foundation +import KlaviyoCore @_spi(KlaviyoPrivate) public func setKlaviyoAPIURL(url: String) { - environment.analytics.apiURL = url + analytics.apiURL = url } struct KlaviyoAPI { struct KlaviyoRequest: Equatable, Codable { public let apiKey: String public let endpoint: KlaviyoEndpoint - public var uuid = environment.analytics.uuid().uuidString + public var uuid = analytics.uuid().uuidString } enum KlaviyoAPIError: Error { @@ -55,7 +56,7 @@ struct KlaviyoAPI { var response: URLResponse var data: Data do { - (data, response) = try await environment.analytics.networkSession().data(urlRequest) + (data, response) = try await analytics.networkSession().data(urlRequest) } catch { requestFailed(request, error, 0.0) return .failure(KlaviyoAPIError.networkError(error)) @@ -88,7 +89,7 @@ struct KlaviyoAPI { extension KlaviyoAPI.KlaviyoRequest { func urlRequest(_ attemptNumber: Int = StateManagementConstants.initialAttempt) throws -> URLRequest { guard let url = url else { - throw KlaviyoAPI.KlaviyoAPIError.internalError("Invalid url string. API URL: \(environment.analytics.apiURL)") + throw KlaviyoAPI.KlaviyoAPIError.internalError("Invalid url string. API URL: \(analytics.apiURL)") } var request = URLRequest(url: url) // We only support post right now @@ -105,8 +106,8 @@ extension KlaviyoAPI.KlaviyoRequest { var url: URL? { switch endpoint { case .createProfile, .createEvent, .registerPushToken, .unregisterPushToken: - if !environment.analytics.apiURL.isEmpty { - return URL(string: "\(environment.analytics.apiURL)/\(path)/?company_id=\(apiKey)") + if !analytics.apiURL.isEmpty { + return URL(string: "\(analytics.apiURL)/\(path)/?company_id=\(apiKey)") } return nil } @@ -131,17 +132,17 @@ extension KlaviyoAPI.KlaviyoRequest { func encodeBody() throws -> Data { switch endpoint { case let .createProfile(payload): - return try environment.analytics.encodeJSON(AnyEncodable(payload)) + return try analytics.encodeJSON(AnyEncodable(payload)) case var .createEvent(payload): payload.appendMetadataToProperties() - return try environment.analytics.encodeJSON(AnyEncodable(payload)) + return try analytics.encodeJSON(AnyEncodable(payload)) case let .registerPushToken(payload): - return try environment.analytics.encodeJSON(AnyEncodable(payload)) + return try analytics.encodeJSON(AnyEncodable(payload)) case let .unregisterPushToken(payload): - return try environment.analytics.encodeJSON(AnyEncodable(payload)) + return try analytics.encodeJSON(AnyEncodable(payload)) } } } diff --git a/Sources/KlaviyoSwift/KlaviyoEnvironment.swift b/Sources/KlaviyoSwift/KlaviyoEnvironment.swift deleted file mode 100644 index 0d741bb3..00000000 --- a/Sources/KlaviyoSwift/KlaviyoEnvironment.swift +++ /dev/null @@ -1,144 +0,0 @@ -// -// KlaviyoEnvironment.swift -// KlaviyoSwift -// -// Created by Noah Durell on 9/28/22. -// - -import AnyCodable -import Combine -import Foundation -import KlaviyoCore -import UIKit - -var environment = KlaviyoEnvironment.production - -struct KlaviyoEnvironment { - fileprivate static let productionHost = "https://a.klaviyo.com" - static let encoder = { () -> JSONEncoder in - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 - return encoder - }() - - static let decoder = { () -> JSONDecoder in - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - return decoder - }() - - private static let reachabilityService = Reachability(hostname: URL(string: productionHost)!.host!) - - var archiverClient: ArchiverClient - var fileClient: FileClient - var data: (URL) throws -> Data - var logger: LoggerClient - var analytics: AnalyticsEnvironment - var getUserDefaultString: (String) -> String? - var appLifeCycle: AppLifeCycleEvents - var notificationCenterPublisher: (NSNotification.Name) -> AnyPublisher - var getNotificationSettings: (@escaping (KlaviyoState.PushEnablement) -> Void) -> Void - var getBackgroundSetting: () -> KlaviyoState.PushBackground - var legacyIdentifier: () -> String - var startReachability: () throws -> Void - var stopReachability: () -> Void - var reachabilityStatus: () -> Reachability.NetworkStatus? - var randomInt: () -> Int - var stateChangePublisher: () -> AnyPublisher - var raiseFatalError: (String) -> Void - var emitDeveloperWarning: (String) -> Void - static var production = KlaviyoEnvironment( - archiverClient: ArchiverClient.production, - fileClient: FileClient.production, - data: { url in try Data(contentsOf: url) }, - logger: LoggerClient.production, - analytics: AnalyticsEnvironment.production, - getUserDefaultString: { key in UserDefaults.standard.string(forKey: key) }, - appLifeCycle: AppLifeCycleEvents.production, - notificationCenterPublisher: { name in - NotificationCenter.default.publisher(for: name) - .eraseToAnyPublisher() - }, - getNotificationSettings: { callback in - UNUserNotificationCenter.current().getNotificationSettings { settings in - callback(.create(from: settings.authorizationStatus)) - } - }, - getBackgroundSetting: { .create(from: UIApplication.shared.backgroundRefreshStatus) }, - legacyIdentifier: { "iOS:\(UIDevice.current.identifierForVendor!.uuidString)" }, - startReachability: { - try reachabilityService?.startNotifier() - }, - stopReachability: { - reachabilityService?.stopNotifier() - }, - reachabilityStatus: { - reachabilityService?.currentReachabilityStatus - }, - randomInt: { Int.random(in: 0...10) }, - stateChangePublisher: StateChangePublisher().publisher, raiseFatalError: { msg in - #if DEBUG - fatalError(msg) - #endif - }, emitDeveloperWarning: { runtimeWarn($0) }) -} - -private var networkSession: NetworkSession! -func createNetworkSession() -> NetworkSession { - if networkSession == nil { - networkSession = NetworkSession.production - } - return networkSession -} - -enum KlaviyoDecodingError: Error { - case invalidType -} - -struct DataDecoder { - func decode(_ data: Data) throws -> T { - try jsonDecoder.decode(T.self, from: data) - } - - var jsonDecoder: JSONDecoder - static let production = Self(jsonDecoder: KlaviyoEnvironment.decoder) -} - -struct AnalyticsEnvironment { - var networkSession: () -> NetworkSession - var apiURL: String - var encodeJSON: (AnyEncodable) throws -> Data - var decoder: DataDecoder - var uuid: () -> UUID - var date: () -> Date - var timeZone: () -> String - var appContextInfo: () -> AppContextInfo - var klaviyoAPI: KlaviyoAPI - var timer: (Double) -> AnyPublisher - var send: (KlaviyoAction) -> Task? - var state: () -> KlaviyoState - var statePublisher: () -> AnyPublisher - static let production: AnalyticsEnvironment = { - let store = Store.production - return AnalyticsEnvironment( - networkSession: createNetworkSession, - apiURL: KlaviyoEnvironment.productionHost, - encodeJSON: { encodable in try KlaviyoEnvironment.encoder.encode(encodable) }, - decoder: DataDecoder.production, - uuid: { UUID() }, - date: { Date() }, - timeZone: { TimeZone.autoupdatingCurrent.identifier }, - appContextInfo: { AppContextInfo() }, - klaviyoAPI: KlaviyoAPI(), - timer: { interval in - Timer.publish(every: interval, on: .main, in: .default) - .autoconnect() - .eraseToAnyPublisher() - }, - send: { action in - store.send(action) - }, - state: { store.state.value }, - statePublisher: { store.state.eraseToAnyPublisher() }) - }() -} diff --git a/Sources/KlaviyoSwift/KlaviyoModels.swift b/Sources/KlaviyoSwift/KlaviyoModels.swift index f9b25080..eee5a2fa 100644 --- a/Sources/KlaviyoSwift/KlaviyoModels.swift +++ b/Sources/KlaviyoSwift/KlaviyoModels.swift @@ -7,6 +7,7 @@ import AnyCodable import Foundation +import KlaviyoCore public struct Event: Equatable { public enum EventName: Equatable { @@ -58,9 +59,9 @@ public struct Event: Equatable { uniqueId: String? = nil) { metric = .init(name: name) _properties = AnyCodable(properties ?? [:]) - self.time = time ?? environment.analytics.date() + self.time = time ?? analytics.date() self.value = value - self.uniqueId = uniqueId ?? environment.analytics.uuid().uuidString + self.uniqueId = uniqueId ?? analytics.uuid().uuidString self.identifiers = identifiers } @@ -72,8 +73,8 @@ public struct Event: Equatable { _properties = AnyCodable(properties ?? [:]) identifiers = nil self.value = value - time = environment.analytics.date() - self.uniqueId = uniqueId ?? environment.analytics.uuid().uuidString + time = analytics.date() + self.uniqueId = uniqueId ?? analytics.uuid().uuidString } } @@ -122,7 +123,7 @@ public struct Profile: Equatable { self.longitude = longitude self.region = region self.zip = zip - self.timezone = timezone ?? environment.analytics.timeZone() + self.timezone = timezone ?? analytics.timeZone() } } diff --git a/Sources/KlaviyoSwift/KlaviyoState.swift b/Sources/KlaviyoSwift/KlaviyoState.swift index dbf6a907..07433b84 100644 --- a/Sources/KlaviyoSwift/KlaviyoState.swift +++ b/Sources/KlaviyoSwift/KlaviyoState.swift @@ -7,6 +7,7 @@ import AnyCodable import Foundation +import KlaviyoCore import UIKit typealias DeviceMetadata = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload.PushToken.Attributes.MetaData @@ -42,48 +43,6 @@ struct KlaviyoState: Equatable, Codable { } } - enum PushEnablement: String, Codable { - case notDetermined = "NOT_DETERMINED" - case denied = "DENIED" - case authorized = "AUTHORIZED" - case provisional = "PROVISIONAL" - case ephemeral = "EPHEMERAL" - - static func create(from status: UNAuthorizationStatus) -> PushEnablement { - switch status { - case .denied: - return PushEnablement.denied - case .authorized: - return PushEnablement.authorized - case .provisional: - return PushEnablement.provisional - case .ephemeral: - return PushEnablement.ephemeral - default: - return PushEnablement.notDetermined - } - } - } - - enum PushBackground: String, Codable { - case available = "AVAILABLE" - case restricted = "RESTRICTED" - case denied = "DENIED" - - static func create(from status: UIBackgroundRefreshStatus) -> PushBackground { - switch status { - case .available: - return PushBackground.available - case .restricted: - return PushBackground.restricted - case .denied: - return PushBackground.denied - @unknown default: - return PushBackground.available - } - } - } - // state related stuff var apiKey: String? var email: String? @@ -258,7 +217,7 @@ struct KlaviyoState: Equatable, Codable { mutating func reset(preserveTokenData: Bool = true) { if isIdentified { // If we are still anonymous we want to preserve our anonymous id so we can merge this profile with the new profile. - anonymousId = environment.analytics.uuid().uuidString + anonymousId = analytics.uuid().uuidString } let previousPushTokenData = pushTokenData pendingProfile = nil @@ -289,7 +248,7 @@ struct KlaviyoState: Equatable, Codable { guard let pushTokenData = pushTokenData else { return true } - let currentDeviceMetadata = DeviceMetadata(context: environment.analytics.appContextInfo()) + let currentDeviceMetadata = DeviceMetadata(context: analytics.appContextInfo()) let newPushTokenData = PushTokenData( pushToken: newToken, pushEnablement: enablement, @@ -367,7 +326,7 @@ private func klaviyoStateFile(apiKey: String) -> URL { private func storeKlaviyoState(state: KlaviyoState, file: URL) { do { - try environment.fileClient.write(environment.analytics.encodeJSON(AnyEncodable(state)), file) + try environment.fileClient.write(analytics.encodeJSON(AnyEncodable(state)), file) } catch { environment.logger.error("Unable to save klaviyo state.") } @@ -402,7 +361,7 @@ func loadKlaviyoStateFromDisk(apiKey: String) -> KlaviyoState { removeStateFile(at: fileName) return createAndStoreInitialState(with: apiKey, at: fileName) } - guard var state: KlaviyoState = try? environment.analytics.decoder.decode(stateData) else { + guard var state: KlaviyoState = try? analytics.decoder.decode(stateData) else { environment.logger.error("Unable to decode existing state file. Removing.") removeStateFile(at: fileName) return createAndStoreInitialState(with: apiKey, at: fileName) @@ -411,14 +370,14 @@ func loadKlaviyoStateFromDisk(apiKey: String) -> KlaviyoState { // Clear existing state since we are using a new api state. state = KlaviyoState( apiKey: apiKey, - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: analytics.uuid().uuidString, queue: []) } return state } private func createAndStoreInitialState(with apiKey: String, at file: URL) -> KlaviyoState { - let anonymousId = environment.analytics.uuid().uuidString + let anonymousId = analytics.uuid().uuidString let state = KlaviyoState(apiKey: apiKey, anonymousId: anonymousId, queue: [], requestsInFlight: []) storeKlaviyoState(state: state, file: file) return state diff --git a/Sources/KlaviyoSwift/StateChangePublisher.swift b/Sources/KlaviyoSwift/StateChangePublisher.swift index 03fd67dd..676c5281 100644 --- a/Sources/KlaviyoSwift/StateChangePublisher.swift +++ b/Sources/KlaviyoSwift/StateChangePublisher.swift @@ -18,7 +18,7 @@ public struct StateChangePublisher { } private static func createStatePublisher() -> AnyPublisher { - environment.analytics.statePublisher() + analytics.statePublisher() .filter { state in state.initalizationState == .initialized } .removeDuplicates() .eraseToAnyPublisher() diff --git a/Sources/KlaviyoSwift/StateManagement.swift b/Sources/KlaviyoSwift/StateManagement.swift index 9c8e8ec7..d0e0534f 100644 --- a/Sources/KlaviyoSwift/StateManagement.swift +++ b/Sources/KlaviyoSwift/StateManagement.swift @@ -13,6 +13,7 @@ import AnyCodable import Foundation +import KlaviyoCore typealias PushTokenPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload typealias UnregisterPushTokenPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.UnregisterPushTokenPayload @@ -47,7 +48,7 @@ enum KlaviyoAction: Equatable { case setExternalId(String) /// call when a new push token needs to be set. If this token is the same we don't perform a network request to register the token - case setPushToken(String, KlaviyoState.PushEnablement) + case setPushToken(String, PushEnablement) /// called when the user wants to reset the existing profile from state case resetProfile @@ -190,8 +191,9 @@ struct KlaviyoReducer: ReducerProtocol { } await send(.start) } - .merge(with: environment.appLifeCycle.lifeCycleEvents().eraseToEffect()) - .merge(with: environment.stateChangePublisher().eraseToEffect()) + // TODO: fixme +// .merge(with: environment.appLifeCycle.lifeCycleEvents().eraseToEffect()) +// .merge(with: environment.stateChangePublisher().eraseToEffect()) case let .setEmail(email): guard case .initialized = state.initalizationState else { @@ -277,7 +279,7 @@ struct KlaviyoReducer: ReducerProtocol { guard case .initialized = state.initalizationState else { return .none } - return environment.analytics.timer(state.flushInterval) + return analytics.timer(state.flushInterval) .map { _ in KlaviyoAction.flushQueue } @@ -287,8 +289,8 @@ struct KlaviyoReducer: ReducerProtocol { case let .deQueueCompletedResults(completedRequest): if case let .registerPushToken(payload) = completedRequest.endpoint { let requestData = payload.data.attributes - let enablement = KlaviyoState.PushEnablement(rawValue: requestData.enablementStatus) ?? .authorized - let backgroundStatus = KlaviyoState.PushBackground(rawValue: requestData.backgroundStatus) ?? .available + let enablement = PushEnablement(rawValue: requestData.enablementStatus) ?? .authorized + let backgroundStatus = PushBackground(rawValue: requestData.backgroundStatus) ?? .available state.pushTokenData = KlaviyoState.PushTokenData( pushToken: requestData.token, pushEnablement: enablement, @@ -324,7 +326,7 @@ struct KlaviyoReducer: ReducerProtocol { } return .run { [numAttempts] send in - let result = await environment.analytics.klaviyoAPI.send(request, numAttempts) + let result = await analytics.klaviyoAPI.send(request, numAttempts) switch result { case .success: // TODO: may want to inspect response further. @@ -360,7 +362,7 @@ struct KlaviyoReducer: ReducerProtocol { case .reachableViaWWAN: state.flushInterval = StateManagementConstants.cellularFlushInterval } - return environment.analytics.timer(state.flushInterval) + return analytics.timer(state.flushInterval) .map { _ in KlaviyoAction.flushQueue }.eraseToEffect() diff --git a/Sources/KlaviyoSwift/Vendor/ComposableArchitecture/Misc.swift b/Sources/KlaviyoSwift/Vendor/ComposableArchitecture/Misc.swift index 5a510d55..17a8d210 100644 --- a/Sources/KlaviyoSwift/Vendor/ComposableArchitecture/Misc.swift +++ b/Sources/KlaviyoSwift/Vendor/ComposableArchitecture/Misc.swift @@ -51,7 +51,7 @@ final class Box { @inline(__always) func runtimeWarn( _ message: @autoclosure () -> String, - category: String? = __klaviyoSwiftName, + category: String? = "", file: StaticString? = nil, line: UInt? = nil ) { diff --git a/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift b/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift index 251cecb4..cfb69300 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift @@ -9,6 +9,7 @@ import Combine import XCTest @_spi(KlaviyoPrivate) @testable import KlaviyoSwift import CombineSchedulers +import KlaviyoCore enum FakeFileError: Error { case fake From 32a74db0ac505c64e900cfe06f530ace15bfb7f6 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Mon, 5 Aug 2024 17:00:23 -0500 Subject: [PATCH 04/72] moved apis, internal models to core --- Sources/KlaviyoCore/InternalAPIModels.swift | 582 ++++++++++++++++++ .../KlaviyoAPI.swift | 32 +- Sources/KlaviyoCore/KlaviyoEnvironment.swift | 30 + .../KlaviyoSwift/AnalyticsEnvironment.swift | 41 +- Sources/KlaviyoSwift/InternalAPIModels.swift | 403 ------------ Sources/KlaviyoSwift/Klaviyo.swift | 6 +- Sources/KlaviyoSwift/KlaviyoState.swift | 30 +- Sources/KlaviyoSwift/SDKRequestIterator.swift | 1 + .../KlaviyoSwift/StateChangePublisher.swift | 3 +- Sources/KlaviyoSwift/StateManagement.swift | 44 +- 10 files changed, 705 insertions(+), 467 deletions(-) create mode 100644 Sources/KlaviyoCore/InternalAPIModels.swift rename Sources/{KlaviyoSwift => KlaviyoCore}/KlaviyoAPI.swift (79%) delete mode 100644 Sources/KlaviyoSwift/InternalAPIModels.swift diff --git a/Sources/KlaviyoCore/InternalAPIModels.swift b/Sources/KlaviyoCore/InternalAPIModels.swift new file mode 100644 index 00000000..a6bef67b --- /dev/null +++ b/Sources/KlaviyoCore/InternalAPIModels.swift @@ -0,0 +1,582 @@ +// +// InternalAPIModels.swift +// Internal models typically used at the networking layer. +// NOTE: Ensure that new request types are equatable and encodable. +// +// Created by Noah Durell on 11/25/22. +// + +import AnyCodable +import Foundation + +extension KlaviyoAPI.KlaviyoRequest { + private static let _appContextInfo = analytics.appContextInfo() + + public enum KlaviyoEndpoint: Equatable, Codable { + public struct CreateProfilePayload: Equatable, Codable { + public init(data: KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateProfilePayload.Profile) { + self.data = data + } + + /** + Internal structure which has details not needed by the API. + */ + public struct Profile: Equatable, Codable { + var type = "profile" + public struct Attributes: Equatable, Codable { + public let email: String? + public let phoneNumber: String? + public let externalId: String? + public let anonymousId: String + public var firstName: String? + public var lastName: String? + public var organization: String? + public var title: String? + public var image: String? + public var location: PublicProfile.Location? + public var properties: AnyCodable + enum CodingKeys: String, CodingKey { + case email + case phoneNumber = "phone_number" + case externalId = "external_id" + case anonymousId = "anonymous_id" + case firstName = "first_name" + case lastName = "last_name" + case organization + case title + case image + case location + case properties + } + + public init(attributes: PublicProfile, + anonymousId: String) { + email = attributes.email + phoneNumber = attributes.phoneNumber + externalId = attributes.externalId + firstName = attributes.firstName + lastName = attributes.lastName + organization = attributes.organization + title = attributes.title + image = attributes.image + location = attributes.location + properties = AnyCodable(attributes.properties) + self.anonymousId = anonymousId + } + } + + public var attributes: Attributes + public init(profile: PublicProfile, anonymousId: String) { + attributes = Attributes( + attributes: profile, + anonymousId: anonymousId) + } + + public init(attributes: Attributes) { + self.attributes = attributes + } + } + + public var data: Profile + } + + public struct CreateEventPayload: Equatable, Codable { + public struct Event: Equatable, Codable { + public struct Attributes: Equatable, Codable { + public struct Metric: Equatable, Codable { + public let data: MetricData + + public struct MetricData: Equatable, Codable { + var type: String = "metric" + + public let attributes: MetricAttributes + + public init(name: String) { + attributes = .init(name: name) + } + + public struct MetricAttributes: Equatable, Codable { + public let name: String + } + } + + public init(name: String) { + data = .init(name: name) + } + } + + public struct Profile: Equatable, Codable { + public let data: CreateProfilePayload.Profile + + public init(attributes: PublicProfile, + anonymousId: String) { + data = .init(profile: attributes, anonymousId: anonymousId) + } + } + + public let metric: Metric + public var properties: AnyCodable + public let profile: Profile + public let time: Date + public let value: Double? + public let uniqueId: String + public init(attributes: PublicEvent, + anonymousId: String? = nil) { + metric = Metric(name: attributes.metric.name.value) + properties = AnyCodable(attributes.properties) + value = attributes.value + time = attributes.time + uniqueId = attributes.uniqueId + + profile = .init(attributes: .init( + email: attributes.identifiers?.email, + phoneNumber: attributes.identifiers?.phoneNumber, + externalId: attributes.identifiers?.externalId), + anonymousId: anonymousId ?? "") + } + + enum CodingKeys: String, CodingKey { + case metric + case properties + case profile + case time + case value + case uniqueId = "unique_id" + } + } + + var type = "event" + public var attributes: Attributes + public init(event: PublicEvent, + anonymousId: String? = nil) { + attributes = .init(attributes: event, anonymousId: anonymousId) + } + } + + mutating func appendMetadataToProperties() { + let context = KlaviyoAPI.KlaviyoRequest._appContextInfo + // TODO: Fixme +// let metadata: [String: Any] = [ +// "Device ID": context.deviceId, +// "Device Manufacturer": context.manufacturer, +// "Device Model": context.deviceModel, +// "OS Name": context.osName, +// "OS Version": context.osVersion, +// "SDK Name": __klaviyoSwiftName, +// "SDK Version": __klaviyoSwiftVersion, +// "App Name": context.appName, +// "App ID": context.bundleId, +// "App Version": context.appVersion, +// "App Build": context.appBuild, +// "Push Token": analytics.state().pushTokenData?.pushToken as Any +// ] + + let metadata = [String: Any]() + let originalProperties = data.attributes.properties.value as? [String: Any] ?? [:] + data.attributes.properties = AnyCodable(originalProperties.merging(metadata) { _, new in new }) + } + + public var data: Event + public init(data: Event) { + self.data = data + } + } + + public struct PushTokenPayload: Equatable, Codable { + public init(data: KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload.PushToken) { + self.data = data + } + + public let data: PushToken + + public init(pushToken: String, + enablement: String, + background: String, + profile: PublicProfile, + anonymousId: String) { + data = .init( + pushToken: pushToken, + enablement: enablement, + background: background, + profile: profile, + anonymousId: anonymousId) + } + + public struct PushToken: Equatable, Codable { + var type = "push-token" + public var attributes: Attributes + + public init(pushToken: String, + enablement: String, + background: String, + profile: PublicProfile, + anonymousId: String) { + attributes = .init( + pushToken: pushToken, + enablement: enablement, + background: background, + profile: profile, + anonymousId: anonymousId) + } + + public struct Attributes: Equatable, Codable { + public let profile: Profile + public let token: String + public let enablementStatus: String + public let backgroundStatus: String + public let deviceMetadata: MetaData + public let platform: String = "ios" + public let vendor: String = "APNs" + + enum CodingKeys: String, CodingKey { + case token + case platform + case enablementStatus = "enablement_status" + case profile + case vendor + case backgroundStatus = "background" + case deviceMetadata = "device_metadata" + } + + public init(pushToken: String, + enablement: String, + background: String, + profile: PublicProfile, + anonymousId: String) { + token = pushToken + + enablementStatus = enablement + backgroundStatus = background + self.profile = .init(attributes: profile, anonymousId: anonymousId) + deviceMetadata = .init(context: KlaviyoAPI.KlaviyoRequest._appContextInfo) + } + + public struct Profile: Equatable, Codable { + public let data: CreateProfilePayload.Profile + + public init(attributes: PublicProfile, + anonymousId: String) { + data = .init(profile: attributes, anonymousId: anonymousId) + } + } + + public struct MetaData: Equatable, Codable { + public let deviceId: String + public let deviceModel: String + public let manufacturer: String + public let osName: String + public let osVersion: String + public let appId: String + public let appName: String + public let appVersion: String + public let appBuild: String + public let environment: String + public let klaviyoSdk: String + public let sdkVersion: String + + enum CodingKeys: String, CodingKey { + case deviceId = "device_id" + case klaviyoSdk = "klaviyo_sdk" + case sdkVersion = "sdk_version" + case deviceModel = "device_model" + case osName = "os_name" + case osVersion = "os_version" + case manufacturer + case appName = "app_name" + case appVersion = "app_version" + case appBuild = "app_build" + case appId = "app_id" + case environment + } + + public init(context: AppContextInfo) { + deviceId = context.deviceId + deviceModel = context.deviceModel + manufacturer = context.manufacturer + osName = context.osName + osVersion = context.osVersion + appId = context.bundleId + appName = context.appName + appVersion = context.appVersion + appBuild = context.appBuild + environment = context.environment + klaviyoSdk = __klaviyoSwiftName + sdkVersion = __klaviyoSwiftVersion + } + } + } + } + } + + public struct UnregisterPushTokenPayload: Equatable, Codable { + public let data: PushToken + + public init(pushToken: String, + profile: PublicProfile, + anonymousId: String) { + data = .init( + pushToken: pushToken, + profile: profile, + anonymousId: anonymousId) + } + + public struct PushToken: Equatable, Codable { + var type = "push-token-unregister" + public var attributes: Attributes + + public init(pushToken: String, + profile: PublicProfile, + anonymousId: String) { + attributes = .init( + pushToken: pushToken, + profile: profile, + anonymousId: anonymousId) + } + + public struct Attributes: Equatable, Codable { + public let profile: Profile + public let token: String + public let platform: String = "ios" + public let vendor: String = "APNs" + + enum CodingKeys: String, CodingKey { + case token + case platform + case profile + case vendor + } + + public init(pushToken: String, + profile: PublicProfile, + anonymousId: String) { + token = pushToken + self.profile = .init(attributes: profile, anonymousId: anonymousId) + } + + public struct Profile: Equatable, Codable { + public let data: CreateProfilePayload.Profile + + public init(attributes: PublicProfile, + anonymousId: String) { + data = .init(profile: attributes, anonymousId: anonymousId) + } + } + } + } + } + + case createProfile(CreateProfilePayload) + case createEvent(CreateEventPayload) + case registerPushToken(PushTokenPayload) + case unregisterPushToken(UnregisterPushTokenPayload) + } +} + +extension PublicProfile.Location: Codable { + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + address1 = try values.decode(String.self, forKey: .address1) + address2 = try values.decode(String.self, forKey: .address2) + city = try values.decode(String.self, forKey: .city) + latitude = try values.decode(Double.self, forKey: .latitude) + longitude = try values.decode(Double.self, forKey: .longitude) + region = try values.decode(String.self, forKey: .region) + self.zip = try values.decode(String.self, forKey: .zip) + timezone = try values.decode(String.self, forKey: .timezone) + country = try values.decode(String.self, forKey: .country) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(address1, forKey: .address1) + try container.encode(address2, forKey: .address2) + try container.encode(city, forKey: .city) + try container.encode(latitude, forKey: .latitude) + try container.encode(longitude, forKey: .longitude) + try container.encode(region, forKey: .region) + try container.encode(zip, forKey: .zip) + try container.encode(timezone, forKey: .timezone) + try container.encode(country, forKey: .country) + } + + enum CodingKeys: CodingKey { + case address1 + case address2 + case city + case country + case latitude + case longitude + case region + case zip + case timezone + } +} + +public struct PublicEvent: Equatable { + public enum EventName: Equatable { + case OpenedPush + case OpenedAppMetric + case ViewedProductMetric + case AddedToCartMetric + case StartedCheckoutMetric + case CustomEvent(String) + } + + public struct Metric: Equatable { + public let name: EventName + + public init(name: EventName) { + self.name = name + } + } + + struct Identifiers: Equatable { + public let email: String? + public let phoneNumber: String? + public let externalId: String? + public init(email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil) { + self.email = email + self.phoneNumber = phoneNumber + self.externalId = externalId + } + } + + public let metric: Metric + public var properties: [String: Any] { + _properties.value as! [String: Any] + } + + private let _properties: AnyCodable + public var time: Date + public let value: Double? + public let uniqueId: String + let identifiers: Identifiers? + + init(name: EventName, + properties: [String: Any]? = nil, + identifiers: Identifiers? = nil, + value: Double? = nil, + time: Date? = nil, + uniqueId: String? = nil) { + metric = .init(name: name) + _properties = AnyCodable(properties ?? [:]) + self.time = time ?? analytics.date() + self.value = value + self.uniqueId = uniqueId ?? analytics.uuid().uuidString + self.identifiers = identifiers + } + + public init(name: EventName, + properties: [String: Any]? = nil, + value: Double? = nil, + uniqueId: String? = nil) { + metric = .init(name: name) + _properties = AnyCodable(properties ?? [:]) + identifiers = nil + self.value = value + time = analytics.date() + self.uniqueId = uniqueId ?? analytics.uuid().uuidString + } +} + +public struct PublicProfile: Equatable { + public enum ProfileKey: Equatable, Hashable, Codable { + case firstName + case lastName + case address1 + case address2 + case title + case organization + case city + case region + case country + case zip + case image + case latitude + case longitude + case custom(customKey: String) + } + + public struct Location: Equatable { + public var address1: String? + public var address2: String? + public var city: String? + public var country: String? + public var latitude: Double? + public var longitude: Double? + public var region: String? + public var zip: String? + public var timezone: String? + public init(address1: String? = nil, + address2: String? = nil, + city: String? = nil, + country: String? = nil, + latitude: Double? = nil, + longitude: Double? = nil, + region: String? = nil, + zip: String? = nil, + timezone: String? = nil) { + self.address1 = address1 + self.address2 = address2 + self.city = city + self.country = country + self.latitude = latitude + self.longitude = longitude + self.region = region + self.zip = zip + self.timezone = timezone ?? analytics.timeZone() + } + } + + public let email: String? + public let phoneNumber: String? + public let externalId: String? + public let firstName: String? + public let lastName: String? + public let organization: String? + public let title: String? + public let image: String? + public let location: Location? + public var properties: [String: Any] { + _properties.value as! [String: Any] + } + + let _properties: AnyCodable + + public init(email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + firstName: String? = nil, + lastName: String? = nil, + organization: String? = nil, + title: String? = nil, + image: String? = nil, + location: Location? = nil, + properties: [String: Any]? = nil) { + self.email = email + self.phoneNumber = phoneNumber + self.externalId = externalId + self.firstName = firstName + self.lastName = lastName + self.organization = organization + self.title = title + self.image = image + self.location = location + _properties = AnyCodable(properties ?? [:]) + } +} + +extension PublicEvent.EventName { + public var value: String { + switch self { + case .OpenedPush: return "$opened_push" + case .OpenedAppMetric: return "Opened App" + case .ViewedProductMetric: return "Viewed Product" + case .AddedToCartMetric: return "Added to Cart" + case .StartedCheckoutMetric: return "Started Checkout" + case let .CustomEvent(value): return "\(value)" + } + } +} diff --git a/Sources/KlaviyoSwift/KlaviyoAPI.swift b/Sources/KlaviyoCore/KlaviyoAPI.swift similarity index 79% rename from Sources/KlaviyoSwift/KlaviyoAPI.swift rename to Sources/KlaviyoCore/KlaviyoAPI.swift index fadcf85a..d7e32bd8 100644 --- a/Sources/KlaviyoSwift/KlaviyoAPI.swift +++ b/Sources/KlaviyoCore/KlaviyoAPI.swift @@ -7,21 +7,31 @@ import AnyCodable import Foundation -import KlaviyoCore @_spi(KlaviyoPrivate) public func setKlaviyoAPIURL(url: String) { analytics.apiURL = url } -struct KlaviyoAPI { - struct KlaviyoRequest: Equatable, Codable { +public struct KlaviyoAPI { + public init() {} + + public struct KlaviyoRequest: Equatable, Codable { + public init( + apiKey: String, + endpoint: KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint, + uuid: String = analytics.uuid().uuidString) { + self.apiKey = apiKey + self.endpoint = endpoint + self.uuid = uuid + } + public let apiKey: String public let endpoint: KlaviyoEndpoint public var uuid = analytics.uuid().uuidString } - enum KlaviyoAPIError: Error { + public enum KlaviyoAPIError: Error { case httpError(Int, Data) case rateLimitError(Int?) case missingOrInvalidResponse(URLResponse?) @@ -34,13 +44,13 @@ struct KlaviyoAPI { } // For internal testing use only - static var requestStarted: (KlaviyoRequest) -> Void = { _ in } - static var requestCompleted: (KlaviyoRequest, Data, Double) -> Void = { _, _, _ in } - static var requestFailed: (KlaviyoRequest, Error, Double) -> Void = { _, _, _ in } - static var requestRateLimited: (KlaviyoRequest, Int?) -> Void = { _, _ in } - static var requestHttpError: (KlaviyoRequest, Int, Double) -> Void = { _, _, _ in } + public static var requestStarted: (KlaviyoRequest) -> Void = { _ in } + public static var requestCompleted: (KlaviyoRequest, Data, Double) -> Void = { _, _, _ in } + public static var requestFailed: (KlaviyoRequest, Error, Double) -> Void = { _, _, _ in } + public static var requestRateLimited: (KlaviyoRequest, Int?) -> Void = { _, _ in } + public static var requestHttpError: (KlaviyoRequest, Int, Double) -> Void = { _, _, _ in } - var send: (KlaviyoRequest, Int) async -> Result = { request, attemptNumber in + public var send: (KlaviyoRequest, Int) async -> Result = { request, attemptNumber in let start = Date() var urlRequest: URLRequest @@ -87,7 +97,7 @@ struct KlaviyoAPI { } extension KlaviyoAPI.KlaviyoRequest { - func urlRequest(_ attemptNumber: Int = StateManagementConstants.initialAttempt) throws -> URLRequest { + public func urlRequest(_ attemptNumber: Int = 1) throws -> URLRequest { guard let url = url else { throw KlaviyoAPI.KlaviyoAPIError.internalError("Invalid url string. API URL: \(analytics.apiURL)") } diff --git a/Sources/KlaviyoCore/KlaviyoEnvironment.swift b/Sources/KlaviyoCore/KlaviyoEnvironment.swift index 45d719a7..222e466e 100644 --- a/Sources/KlaviyoCore/KlaviyoEnvironment.swift +++ b/Sources/KlaviyoCore/KlaviyoEnvironment.swift @@ -172,3 +172,33 @@ func runtimeWarn( #endif #endif } + +public var analytics = AnalyticsEnvironment.production + +public struct AnalyticsEnvironment { + var networkSession: () -> NetworkSession + var apiURL: String + var encodeJSON: (AnyEncodable) throws -> Data + var decoder: DataDecoder + public var uuid: () -> UUID + var date: () -> Date + var timeZone: () -> String + var appContextInfo: () -> AppContextInfo + var klaviyoAPI: KlaviyoAPI + var timer: (Double) -> AnyPublisher + static let production: AnalyticsEnvironment = .init( + networkSession: createNetworkSession, + apiURL: KlaviyoEnvironment.productionHost, + encodeJSON: { encodable in try KlaviyoEnvironment.encoder.encode(encodable) }, + decoder: DataDecoder.production, + uuid: { UUID() }, + date: { Date() }, + timeZone: { TimeZone.autoupdatingCurrent.identifier }, + appContextInfo: { AppContextInfo() }, + klaviyoAPI: KlaviyoAPI(), + timer: { interval in + Timer.publish(every: interval, on: .main, in: .default) + .autoconnect() + .eraseToAnyPublisher() + }) +} diff --git a/Sources/KlaviyoSwift/AnalyticsEnvironment.swift b/Sources/KlaviyoSwift/AnalyticsEnvironment.swift index b8d12b1a..fd8782de 100644 --- a/Sources/KlaviyoSwift/AnalyticsEnvironment.swift +++ b/Sources/KlaviyoSwift/AnalyticsEnvironment.swift @@ -24,30 +24,19 @@ struct AnalyticsEnvironment { var appContextInfo: () -> AppContextInfo var klaviyoAPI: KlaviyoAPI var timer: (Double) -> AnyPublisher - var send: (KlaviyoAction) -> Task? - var state: () -> KlaviyoState - var statePublisher: () -> AnyPublisher - static let production: AnalyticsEnvironment = { - let store = Store.production - return AnalyticsEnvironment( - networkSession: createNetworkSession, - apiURL: KlaviyoEnvironment.productionHost, - encodeJSON: { encodable in try KlaviyoEnvironment.encoder.encode(encodable) }, - decoder: DataDecoder.production, - uuid: { UUID() }, - date: { Date() }, - timeZone: { TimeZone.autoupdatingCurrent.identifier }, - appContextInfo: { AppContextInfo() }, - klaviyoAPI: KlaviyoAPI(), - timer: { interval in - Timer.publish(every: interval, on: .main, in: .default) - .autoconnect() - .eraseToAnyPublisher() - }, - send: { action in - store.send(action) - }, - state: { store.state.value }, - statePublisher: { store.state.eraseToAnyPublisher() }) - }() + static let production: AnalyticsEnvironment = .init( + networkSession: createNetworkSession, + apiURL: KlaviyoEnvironment.productionHost, + encodeJSON: { encodable in try KlaviyoEnvironment.encoder.encode(encodable) }, + decoder: DataDecoder.production, + uuid: { UUID() }, + date: { Date() }, + timeZone: { TimeZone.autoupdatingCurrent.identifier }, + appContextInfo: { AppContextInfo() }, + klaviyoAPI: KlaviyoAPI(), + timer: { interval in + Timer.publish(every: interval, on: .main, in: .default) + .autoconnect() + .eraseToAnyPublisher() + }) } diff --git a/Sources/KlaviyoSwift/InternalAPIModels.swift b/Sources/KlaviyoSwift/InternalAPIModels.swift deleted file mode 100644 index f9e7dfb3..00000000 --- a/Sources/KlaviyoSwift/InternalAPIModels.swift +++ /dev/null @@ -1,403 +0,0 @@ -// -// InternalAPIModels.swift -// Internal models typically used at the networking layer. -// NOTE: Ensure that new request types are equatable and encodable. -// -// Created by Noah Durell on 11/25/22. -// - -import AnyCodable -import Foundation -import KlaviyoCore - -extension KlaviyoAPI.KlaviyoRequest { - private static let _appContextInfo = analytics.appContextInfo() - - enum KlaviyoEndpoint: Equatable, Codable { - struct CreateProfilePayload: Equatable, Codable { - /** - Internal structure which has details not needed by the API. - */ - struct Profile: Equatable, Codable { - var type = "profile" - struct Attributes: Equatable, Codable { - let email: String? - let phoneNumber: String? - let externalId: String? - let anonymousId: String - var firstName: String? - var lastName: String? - var organization: String? - var title: String? - var image: String? - var location: KlaviyoSwift.Profile.Location? - var properties: AnyCodable - enum CodingKeys: String, CodingKey { - case email - case phoneNumber = "phone_number" - case externalId = "external_id" - case anonymousId = "anonymous_id" - case firstName = "first_name" - case lastName = "last_name" - case organization - case title - case image - case location - case properties - } - - init(attributes: KlaviyoSwift.Profile, - anonymousId: String) { - email = attributes.email - phoneNumber = attributes.phoneNumber - externalId = attributes.externalId - firstName = attributes.firstName - lastName = attributes.lastName - organization = attributes.organization - title = attributes.title - image = attributes.image - location = attributes.location - properties = AnyCodable(attributes.properties) - self.anonymousId = anonymousId - } - } - - var attributes: Attributes - init(profile: KlaviyoSwift.Profile, anonymousId: String) { - attributes = Attributes( - attributes: profile, - anonymousId: anonymousId) - } - - init(attributes: Attributes) { - self.attributes = attributes - } - } - - var data: Profile - } - - struct CreateEventPayload: Equatable, Codable { - struct Event: Equatable, Codable { - struct Attributes: Equatable, Codable { - struct Metric: Equatable, Codable { - let data: MetricData - - struct MetricData: Equatable, Codable { - var type: String = "metric" - - let attributes: MetricAttributes - - init(name: String) { - attributes = .init(name: name) - } - - struct MetricAttributes: Equatable, Codable { - let name: String - } - } - - init(name: String) { - data = .init(name: name) - } - } - - struct Profile: Equatable, Codable { - let data: CreateProfilePayload.Profile - - init(attributes: KlaviyoSwift.Profile, - anonymousId: String) { - data = .init(profile: attributes, anonymousId: anonymousId) - } - } - - let metric: Metric - var properties: AnyCodable - let profile: Profile - let time: Date - let value: Double? - let uniqueId: String - init(attributes: KlaviyoSwift.Event, - anonymousId: String? = nil) { - metric = Metric(name: attributes.metric.name.value) - properties = AnyCodable(attributes.properties) - value = attributes.value - time = attributes.time - uniqueId = attributes.uniqueId - - profile = .init(attributes: .init( - email: attributes.identifiers?.email, - phoneNumber: attributes.identifiers?.phoneNumber, - externalId: attributes.identifiers?.externalId), - anonymousId: anonymousId ?? "") - } - - enum CodingKeys: String, CodingKey { - case metric - case properties - case profile - case time - case value - case uniqueId = "unique_id" - } - } - - var type = "event" - var attributes: Attributes - init(event: KlaviyoSwift.Event, - anonymousId: String? = nil) { - attributes = .init(attributes: event, anonymousId: anonymousId) - } - } - - mutating func appendMetadataToProperties() { - let context = KlaviyoAPI.KlaviyoRequest._appContextInfo - let metadata = [ - "Device ID": context.deviceId, - "Device Manufacturer": context.manufacturer, - "Device Model": context.deviceModel, - "OS Name": context.osName, - "OS Version": context.osVersion, - "SDK Name": __klaviyoSwiftName, - "SDK Version": __klaviyoSwiftVersion, - "App Name": context.appName, - "App ID": context.bundleId, - "App Version": context.appVersion, - "App Build": context.appBuild, - "Push Token": analytics.state().pushTokenData?.pushToken as Any - ] - let originalProperties = data.attributes.properties.value as? [String: Any] ?? [:] - data.attributes.properties = AnyCodable(originalProperties.merging(metadata) { _, new in new }) - } - - var data: Event - init(data: Event) { - self.data = data - } - } - - struct PushTokenPayload: Equatable, Codable { - let data: PushToken - - init(pushToken: String, - enablement: String, - background: String, - profile: KlaviyoSwift.Profile, - anonymousId: String) { - data = .init( - pushToken: pushToken, - enablement: enablement, - background: background, - profile: profile, - anonymousId: anonymousId) - } - - struct PushToken: Equatable, Codable { - var type = "push-token" - var attributes: Attributes - - init(pushToken: String, - enablement: String, - background: String, - profile: KlaviyoSwift.Profile, - anonymousId: String) { - attributes = .init( - pushToken: pushToken, - enablement: enablement, - background: background, - profile: profile, - anonymousId: anonymousId) - } - - struct Attributes: Equatable, Codable { - let profile: Profile - let token: String - let enablementStatus: String - let backgroundStatus: String - let deviceMetadata: MetaData - let platform: String = "ios" - let vendor: String = "APNs" - - enum CodingKeys: String, CodingKey { - case token - case platform - case enablementStatus = "enablement_status" - case profile - case vendor - case backgroundStatus = "background" - case deviceMetadata = "device_metadata" - } - - init(pushToken: String, - enablement: String, - background: String, - profile: KlaviyoSwift.Profile, - anonymousId: String) { - token = pushToken - - enablementStatus = enablement - backgroundStatus = background - self.profile = .init(attributes: profile, anonymousId: anonymousId) - deviceMetadata = .init(context: KlaviyoAPI.KlaviyoRequest._appContextInfo) - } - - struct Profile: Equatable, Codable { - let data: CreateProfilePayload.Profile - - init(attributes: KlaviyoSwift.Profile, - anonymousId: String) { - data = .init(profile: attributes, anonymousId: anonymousId) - } - } - - struct MetaData: Equatable, Codable { - let deviceId: String - let deviceModel: String - let manufacturer: String - let osName: String - let osVersion: String - let appId: String - let appName: String - let appVersion: String - let appBuild: String - let environment: String - let klaviyoSdk: String - let sdkVersion: String - - enum CodingKeys: String, CodingKey { - case deviceId = "device_id" - case klaviyoSdk = "klaviyo_sdk" - case sdkVersion = "sdk_version" - case deviceModel = "device_model" - case osName = "os_name" - case osVersion = "os_version" - case manufacturer - case appName = "app_name" - case appVersion = "app_version" - case appBuild = "app_build" - case appId = "app_id" - case environment - } - - init(context: AppContextInfo) { - deviceId = context.deviceId - deviceModel = context.deviceModel - manufacturer = context.manufacturer - osName = context.osName - osVersion = context.osVersion - appId = context.bundleId - appName = context.appName - appVersion = context.appVersion - appBuild = context.appBuild - environment = context.environment - klaviyoSdk = __klaviyoSwiftName - sdkVersion = __klaviyoSwiftVersion - } - } - } - } - } - - struct UnregisterPushTokenPayload: Equatable, Codable { - let data: PushToken - - init(pushToken: String, - profile: KlaviyoSwift.Profile, - anonymousId: String) { - data = .init( - pushToken: pushToken, - profile: profile, - anonymousId: anonymousId) - } - - struct PushToken: Equatable, Codable { - var type = "push-token-unregister" - var attributes: Attributes - - init(pushToken: String, - profile: KlaviyoSwift.Profile, - anonymousId: String) { - attributes = .init( - pushToken: pushToken, - profile: profile, - anonymousId: anonymousId) - } - - struct Attributes: Equatable, Codable { - let profile: Profile - let token: String - let platform: String = "ios" - let vendor: String = "APNs" - - enum CodingKeys: String, CodingKey { - case token - case platform - case profile - case vendor - } - - init(pushToken: String, - profile: KlaviyoSwift.Profile, - anonymousId: String) { - token = pushToken - self.profile = .init(attributes: profile, anonymousId: anonymousId) - } - - struct Profile: Equatable, Codable { - let data: CreateProfilePayload.Profile - - init(attributes: KlaviyoSwift.Profile, - anonymousId: String) { - data = .init(profile: attributes, anonymousId: anonymousId) - } - } - } - } - } - - case createProfile(CreateProfilePayload) - case createEvent(CreateEventPayload) - case registerPushToken(PushTokenPayload) - case unregisterPushToken(UnregisterPushTokenPayload) - } -} - -extension Profile.Location: Codable { - public init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - address1 = try values.decode(String.self, forKey: .address1) - address2 = try values.decode(String.self, forKey: .address2) - city = try values.decode(String.self, forKey: .city) - latitude = try values.decode(Double.self, forKey: .latitude) - longitude = try values.decode(Double.self, forKey: .longitude) - region = try values.decode(String.self, forKey: .region) - self.zip = try values.decode(String.self, forKey: .zip) - timezone = try values.decode(String.self, forKey: .timezone) - country = try values.decode(String.self, forKey: .country) - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(address1, forKey: .address1) - try container.encode(address2, forKey: .address2) - try container.encode(city, forKey: .city) - try container.encode(latitude, forKey: .latitude) - try container.encode(longitude, forKey: .longitude) - try container.encode(region, forKey: .region) - try container.encode(zip, forKey: .zip) - try container.encode(timezone, forKey: .timezone) - try container.encode(country, forKey: .country) - } - - enum CodingKeys: CodingKey { - case address1 - case address2 - case city - case country - case latitude - case longitude - case region - case zip - case timezone - } -} diff --git a/Sources/KlaviyoSwift/Klaviyo.swift b/Sources/KlaviyoSwift/Klaviyo.swift index a0cc0fcf..9f2f4c0d 100644 --- a/Sources/KlaviyoSwift/Klaviyo.swift +++ b/Sources/KlaviyoSwift/Klaviyo.swift @@ -13,7 +13,8 @@ import UIKit func dispatchOnMainThread(action: KlaviyoAction) { Task { await MainActor.run { - analytics.send(action) + // TODO: FIXME + Store.production.send(action) } } } @@ -32,7 +33,8 @@ public struct KlaviyoSDK { public init() {} private var state: KlaviyoState { - analytics.state() + // TODO: fixme + Store.production.state.value } /// Returns the email for the current user, if any. diff --git a/Sources/KlaviyoSwift/KlaviyoState.swift b/Sources/KlaviyoSwift/KlaviyoState.swift index 07433b84..553e05f7 100644 --- a/Sources/KlaviyoSwift/KlaviyoState.swift +++ b/Sources/KlaviyoSwift/KlaviyoState.swift @@ -261,7 +261,7 @@ struct KlaviyoState: Equatable, Codable { func buildProfileRequest(apiKey: String, anonymousId: String, properties: [String: Any] = [:]) -> KlaviyoAPI.KlaviyoRequest { let payload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateProfilePayload( data: .init( - profile: Profile( + profile: PublicProfile( email: email, phoneNumber: phoneNumber, externalId: externalId, @@ -284,6 +284,7 @@ struct KlaviyoState: Equatable, Codable { dict: pendingProfile) self.pendingProfile = nil } else { + // TODO: FIXME profile = Profile(email: email, phoneNumber: phoneNumber, externalId: externalId) } @@ -291,7 +292,7 @@ struct KlaviyoState: Equatable, Codable { pushToken: pushToken, enablement: enablement.rawValue, background: environment.getBackgroundSetting().rawValue, - profile: profile, + profile: PublicProfile(profile: profile), anonymousId: anonymousId) let endpoint = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.registerPushToken(payload) return KlaviyoAPI.KlaviyoRequest(apiKey: apiKey, endpoint: endpoint) @@ -473,3 +474,28 @@ extension String { return !incoming.isEmpty && incoming != state } } + +// TODO: FIXME +extension PublicProfile { + public init(profile: Profile) { + self.init( + email: profile.email, + phoneNumber: profile.phoneNumber, + externalId: profile.externalId, + firstName: profile.firstName, + lastName: profile.lastName, + organization: profile.organization, + title: profile.title, + image: profile.image, + location: profile.location.map { Location(address1: $0.address1, + address2: $0.address2, + city: $0.city, + country: $0.country, + latitude: $0.latitude, + longitude: $0.longitude, + region: $0.region, + zip: $0.zip, + timezone: $0.timezone) }, + properties: profile.properties) + } +} diff --git a/Sources/KlaviyoSwift/SDKRequestIterator.swift b/Sources/KlaviyoSwift/SDKRequestIterator.swift index 40043ad2..f2521ba1 100644 --- a/Sources/KlaviyoSwift/SDKRequestIterator.swift +++ b/Sources/KlaviyoSwift/SDKRequestIterator.swift @@ -7,6 +7,7 @@ import AnyCodable import Foundation +import KlaviyoCore @_spi(KlaviyoPrivate) public struct SDKRequest: Identifiable, Equatable { diff --git a/Sources/KlaviyoSwift/StateChangePublisher.swift b/Sources/KlaviyoSwift/StateChangePublisher.swift index 676c5281..07fc5fc9 100644 --- a/Sources/KlaviyoSwift/StateChangePublisher.swift +++ b/Sources/KlaviyoSwift/StateChangePublisher.swift @@ -18,7 +18,8 @@ public struct StateChangePublisher { } private static func createStatePublisher() -> AnyPublisher { - analytics.statePublisher() + // TODO: Fixme + Store.production.state.eraseToAnyPublisher() .filter { state in state.initalizationState == .initialized } .removeDuplicates() .eraseToAnyPublisher() diff --git a/Sources/KlaviyoSwift/StateManagement.swift b/Sources/KlaviyoSwift/StateManagement.swift index d0e0534f..18d57829 100644 --- a/Sources/KlaviyoSwift/StateManagement.swift +++ b/Sources/KlaviyoSwift/StateManagement.swift @@ -398,10 +398,10 @@ struct KlaviyoReducer: ReducerProtocol { } event = event.updateEventWithState(state: &state) - state.enqueueRequest(request: .init(apiKey: apiKey, - endpoint: .createEvent( - .init(data: .init(event: event, anonymousId: anonymousId)) - ))) +// state.enqueueRequest(request: .init(apiKey: apiKey, +// endpoint: .createEvent( +// .init(data: .init(event: event, anonymousId: anonymousId)) +// ))) /* if we receive an opened push event we want to flush the queue right away so that @@ -427,24 +427,24 @@ struct KlaviyoReducer: ReducerProtocol { } let request: KlaviyoAPI.KlaviyoRequest! - if let tokenData = pushTokenData { - request = KlaviyoAPI.KlaviyoRequest( - apiKey: apiKey, - endpoint: .registerPushToken(.init( - pushToken: tokenData.pushToken, - enablement: tokenData.pushEnablement.rawValue, - background: tokenData.pushBackground.rawValue, - profile: profile.profile(from: state), - anonymousId: anonymousId) - )) - } else { - request = KlaviyoAPI.KlaviyoRequest( - apiKey: apiKey, - endpoint: .createProfile( - .init(data: .init(profile: profile.profile(from: state), anonymousId: anonymousId)) - )) - } - state.enqueueRequest(request: request) +// if let tokenData = pushTokenData { +// request = KlaviyoAPI.KlaviyoRequest( +// apiKey: apiKey, +// endpoint: .registerPushToken(.init( +// pushToken: tokenData.pushToken, +// enablement: tokenData.pushEnablement.rawValue, +// background: tokenData.pushBackground.rawValue, +// profile: profile.profile(from: state), +// anonymousId: anonymousId) +// )) +// } else { +// request = KlaviyoAPI.KlaviyoRequest( +// apiKey: apiKey, +// endpoint: .createProfile( +// .init(data: .init(profile: profile.profile(from: state), anonymousId: anonymousId)) +// )) +// } +// state.enqueueRequest(request: request) return .none From 1530c4556f670858017820516cb1697a48c1021f Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Mon, 5 Aug 2024 21:42:39 -0500 Subject: [PATCH 05/72] unnested the internal models --- Sources/KlaviyoCore/InternalAPIModels.swift | 518 +++++++++--------- Sources/KlaviyoCore/KlaviyoAPI.swift | 32 +- .../APIRequestErrorHandling.swift | 2 +- Sources/KlaviyoSwift/KlaviyoState.swift | 34 +- Sources/KlaviyoSwift/SDKRequestIterator.swift | 4 +- Sources/KlaviyoSwift/StateManagement.swift | 50 +- 6 files changed, 320 insertions(+), 320 deletions(-) diff --git a/Sources/KlaviyoCore/InternalAPIModels.swift b/Sources/KlaviyoCore/InternalAPIModels.swift index a6bef67b..5a35f12d 100644 --- a/Sources/KlaviyoCore/InternalAPIModels.swift +++ b/Sources/KlaviyoCore/InternalAPIModels.swift @@ -9,153 +9,150 @@ import AnyCodable import Foundation -extension KlaviyoAPI.KlaviyoRequest { - private static let _appContextInfo = analytics.appContextInfo() - - public enum KlaviyoEndpoint: Equatable, Codable { - public struct CreateProfilePayload: Equatable, Codable { - public init(data: KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateProfilePayload.Profile) { - self.data = data - } - - /** - Internal structure which has details not needed by the API. - */ - public struct Profile: Equatable, Codable { - var type = "profile" - public struct Attributes: Equatable, Codable { - public let email: String? - public let phoneNumber: String? - public let externalId: String? - public let anonymousId: String - public var firstName: String? - public var lastName: String? - public var organization: String? - public var title: String? - public var image: String? - public var location: PublicProfile.Location? - public var properties: AnyCodable - enum CodingKeys: String, CodingKey { - case email - case phoneNumber = "phone_number" - case externalId = "external_id" - case anonymousId = "anonymous_id" - case firstName = "first_name" - case lastName = "last_name" - case organization - case title - case image - case location - case properties - } +public enum KlaviyoEndpoint: Equatable, Codable { + public struct CreateProfilePayload: Equatable, Codable { + public init(data: KlaviyoEndpoint.CreateProfilePayload.Profile) { + self.data = data + } - public init(attributes: PublicProfile, - anonymousId: String) { - email = attributes.email - phoneNumber = attributes.phoneNumber - externalId = attributes.externalId - firstName = attributes.firstName - lastName = attributes.lastName - organization = attributes.organization - title = attributes.title - image = attributes.image - location = attributes.location - properties = AnyCodable(attributes.properties) - self.anonymousId = anonymousId - } + /** + Internal structure which has details not needed by the API. + */ + public struct Profile: Equatable, Codable { + var type = "profile" + public struct Attributes: Equatable, Codable { + public let email: String? + public let phoneNumber: String? + public let externalId: String? + public let anonymousId: String + public var firstName: String? + public var lastName: String? + public var organization: String? + public var title: String? + public var image: String? + public var location: PublicProfile.Location? + public var properties: AnyCodable + enum CodingKeys: String, CodingKey { + case email + case phoneNumber = "phone_number" + case externalId = "external_id" + case anonymousId = "anonymous_id" + case firstName = "first_name" + case lastName = "last_name" + case organization + case title + case image + case location + case properties } - public var attributes: Attributes - public init(profile: PublicProfile, anonymousId: String) { - attributes = Attributes( - attributes: profile, - anonymousId: anonymousId) + public init(attributes: PublicProfile, + anonymousId: String) { + email = attributes.email + phoneNumber = attributes.phoneNumber + externalId = attributes.externalId + firstName = attributes.firstName + lastName = attributes.lastName + organization = attributes.organization + title = attributes.title + image = attributes.image + location = attributes.location + properties = AnyCodable(attributes.properties) + self.anonymousId = anonymousId } + } - public init(attributes: Attributes) { - self.attributes = attributes - } + public var attributes: Attributes + public init(profile: PublicProfile, anonymousId: String) { + attributes = Attributes( + attributes: profile, + anonymousId: anonymousId) } - public var data: Profile + public init(attributes: Attributes) { + self.attributes = attributes + } } - public struct CreateEventPayload: Equatable, Codable { - public struct Event: Equatable, Codable { - public struct Attributes: Equatable, Codable { - public struct Metric: Equatable, Codable { - public let data: MetricData - - public struct MetricData: Equatable, Codable { - var type: String = "metric" + public var data: Profile + } - public let attributes: MetricAttributes + public struct CreateEventPayload: Equatable, Codable { + public struct Event: Equatable, Codable { + public struct Attributes: Equatable, Codable { + public struct Metric: Equatable, Codable { + public let data: MetricData - public init(name: String) { - attributes = .init(name: name) - } + public struct MetricData: Equatable, Codable { + var type: String = "metric" - public struct MetricAttributes: Equatable, Codable { - public let name: String - } - } + public let attributes: MetricAttributes public init(name: String) { - data = .init(name: name) + attributes = .init(name: name) } - } - - public struct Profile: Equatable, Codable { - public let data: CreateProfilePayload.Profile - public init(attributes: PublicProfile, - anonymousId: String) { - data = .init(profile: attributes, anonymousId: anonymousId) + public struct MetricAttributes: Equatable, Codable { + public let name: String } } - public let metric: Metric - public var properties: AnyCodable - public let profile: Profile - public let time: Date - public let value: Double? - public let uniqueId: String - public init(attributes: PublicEvent, - anonymousId: String? = nil) { - metric = Metric(name: attributes.metric.name.value) - properties = AnyCodable(attributes.properties) - value = attributes.value - time = attributes.time - uniqueId = attributes.uniqueId - - profile = .init(attributes: .init( - email: attributes.identifiers?.email, - phoneNumber: attributes.identifiers?.phoneNumber, - externalId: attributes.identifiers?.externalId), - anonymousId: anonymousId ?? "") + public init(name: String) { + data = .init(name: name) } + } - enum CodingKeys: String, CodingKey { - case metric - case properties - case profile - case time - case value - case uniqueId = "unique_id" + public struct Profile: Equatable, Codable { + public let data: CreateProfilePayload.Profile + + public init(attributes: PublicProfile, + anonymousId: String) { + data = .init(profile: attributes, anonymousId: anonymousId) } } - var type = "event" - public var attributes: Attributes - public init(event: PublicEvent, + public let metric: Metric + public var properties: AnyCodable + public let profile: Profile + public let time: Date + public let value: Double? + public let uniqueId: String + public init(attributes: PublicEvent, anonymousId: String? = nil) { - attributes = .init(attributes: event, anonymousId: anonymousId) + metric = Metric(name: attributes.metric.name.value) + properties = AnyCodable(attributes.properties) + value = attributes.value + time = attributes.time + uniqueId = attributes.uniqueId + + profile = .init(attributes: .init( + email: attributes.identifiers?.email, + phoneNumber: attributes.identifiers?.phoneNumber, + externalId: attributes.identifiers?.externalId), + anonymousId: anonymousId ?? "") + } + + enum CodingKeys: String, CodingKey { + case metric + case properties + case profile + case time + case value + case uniqueId = "unique_id" } } - mutating func appendMetadataToProperties() { - let context = KlaviyoAPI.KlaviyoRequest._appContextInfo - // TODO: Fixme + var type = "event" + public var attributes: Attributes + public init(event: PublicEvent, + anonymousId: String? = nil) { + attributes = .init(attributes: event, anonymousId: anonymousId) + } + } + + mutating func appendMetadataToProperties() { + let context = KlaviyoAPI._appContextInfo + // TODO: Fixme // let metadata: [String: Any] = [ // "Device ID": context.deviceId, // "Device Manufacturer": context.manufacturer, @@ -171,30 +168,47 @@ extension KlaviyoAPI.KlaviyoRequest { // "Push Token": analytics.state().pushTokenData?.pushToken as Any // ] - let metadata = [String: Any]() - let originalProperties = data.attributes.properties.value as? [String: Any] ?? [:] - data.attributes.properties = AnyCodable(originalProperties.merging(metadata) { _, new in new }) - } + let metadata = [String: Any]() + let originalProperties = data.attributes.properties.value as? [String: Any] ?? [:] + data.attributes.properties = AnyCodable(originalProperties.merging(metadata) { _, new in new }) + } - public var data: Event - public init(data: Event) { - self.data = data - } + public var data: Event + public init(data: Event) { + self.data = data } + } - public struct PushTokenPayload: Equatable, Codable { - public init(data: KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload.PushToken) { - self.data = data - } + public struct PushTokenPayload: Equatable, Codable { + public init(data: KlaviyoEndpoint.PushTokenPayload.PushToken) { + self.data = data + } + + public let data: PushToken + + public init(pushToken: String, + enablement: String, + background: String, + profile: PublicProfile, + anonymousId: String) { + data = .init( + pushToken: pushToken, + enablement: enablement, + background: background, + profile: profile, + anonymousId: anonymousId) + } - public let data: PushToken + public struct PushToken: Equatable, Codable { + var type = "push-token" + public var attributes: Attributes public init(pushToken: String, enablement: String, background: String, profile: PublicProfile, anonymousId: String) { - data = .init( + attributes = .init( pushToken: pushToken, enablement: enablement, background: background, @@ -202,174 +216,160 @@ extension KlaviyoAPI.KlaviyoRequest { anonymousId: anonymousId) } - public struct PushToken: Equatable, Codable { - var type = "push-token" - public var attributes: Attributes + public struct Attributes: Equatable, Codable { + public let profile: Profile + public let token: String + public let enablementStatus: String + public let backgroundStatus: String + public let deviceMetadata: MetaData + public let platform: String = "ios" + public let vendor: String = "APNs" + + enum CodingKeys: String, CodingKey { + case token + case platform + case enablementStatus = "enablement_status" + case profile + case vendor + case backgroundStatus = "background" + case deviceMetadata = "device_metadata" + } public init(pushToken: String, enablement: String, background: String, profile: PublicProfile, anonymousId: String) { - attributes = .init( - pushToken: pushToken, - enablement: enablement, - background: background, - profile: profile, - anonymousId: anonymousId) - } + token = pushToken - public struct Attributes: Equatable, Codable { - public let profile: Profile - public let token: String - public let enablementStatus: String - public let backgroundStatus: String - public let deviceMetadata: MetaData - public let platform: String = "ios" - public let vendor: String = "APNs" + enablementStatus = enablement + backgroundStatus = background + self.profile = .init(attributes: profile, anonymousId: anonymousId) + deviceMetadata = .init(context: KlaviyoAPI._appContextInfo) + } - enum CodingKeys: String, CodingKey { - case token - case platform - case enablementStatus = "enablement_status" - case profile - case vendor - case backgroundStatus = "background" - case deviceMetadata = "device_metadata" - } + public struct Profile: Equatable, Codable { + public let data: CreateProfilePayload.Profile - public init(pushToken: String, - enablement: String, - background: String, - profile: PublicProfile, + public init(attributes: PublicProfile, anonymousId: String) { - token = pushToken - - enablementStatus = enablement - backgroundStatus = background - self.profile = .init(attributes: profile, anonymousId: anonymousId) - deviceMetadata = .init(context: KlaviyoAPI.KlaviyoRequest._appContextInfo) + data = .init(profile: attributes, anonymousId: anonymousId) } + } - public struct Profile: Equatable, Codable { - public let data: CreateProfilePayload.Profile + public struct MetaData: Equatable, Codable { + public let deviceId: String + public let deviceModel: String + public let manufacturer: String + public let osName: String + public let osVersion: String + public let appId: String + public let appName: String + public let appVersion: String + public let appBuild: String + public let environment: String + public let klaviyoSdk: String + public let sdkVersion: String - public init(attributes: PublicProfile, - anonymousId: String) { - data = .init(profile: attributes, anonymousId: anonymousId) - } + enum CodingKeys: String, CodingKey { + case deviceId = "device_id" + case klaviyoSdk = "klaviyo_sdk" + case sdkVersion = "sdk_version" + case deviceModel = "device_model" + case osName = "os_name" + case osVersion = "os_version" + case manufacturer + case appName = "app_name" + case appVersion = "app_version" + case appBuild = "app_build" + case appId = "app_id" + case environment } - public struct MetaData: Equatable, Codable { - public let deviceId: String - public let deviceModel: String - public let manufacturer: String - public let osName: String - public let osVersion: String - public let appId: String - public let appName: String - public let appVersion: String - public let appBuild: String - public let environment: String - public let klaviyoSdk: String - public let sdkVersion: String - - enum CodingKeys: String, CodingKey { - case deviceId = "device_id" - case klaviyoSdk = "klaviyo_sdk" - case sdkVersion = "sdk_version" - case deviceModel = "device_model" - case osName = "os_name" - case osVersion = "os_version" - case manufacturer - case appName = "app_name" - case appVersion = "app_version" - case appBuild = "app_build" - case appId = "app_id" - case environment - } - - public init(context: AppContextInfo) { - deviceId = context.deviceId - deviceModel = context.deviceModel - manufacturer = context.manufacturer - osName = context.osName - osVersion = context.osVersion - appId = context.bundleId - appName = context.appName - appVersion = context.appVersion - appBuild = context.appBuild - environment = context.environment - klaviyoSdk = __klaviyoSwiftName - sdkVersion = __klaviyoSwiftVersion - } + public init(context: AppContextInfo) { + deviceId = context.deviceId + deviceModel = context.deviceModel + manufacturer = context.manufacturer + osName = context.osName + osVersion = context.osVersion + appId = context.bundleId + appName = context.appName + appVersion = context.appVersion + appBuild = context.appBuild + environment = context.environment + klaviyoSdk = __klaviyoSwiftName + sdkVersion = __klaviyoSwiftVersion } } } } + } - public struct UnregisterPushTokenPayload: Equatable, Codable { - public let data: PushToken + public struct UnregisterPushTokenPayload: Equatable, Codable { + public let data: PushToken + + public init(pushToken: String, + profile: PublicProfile, + anonymousId: String) { + data = .init( + pushToken: pushToken, + profile: profile, + anonymousId: anonymousId) + } + + public struct PushToken: Equatable, Codable { + var type = "push-token-unregister" + public var attributes: Attributes public init(pushToken: String, profile: PublicProfile, anonymousId: String) { - data = .init( + attributes = .init( pushToken: pushToken, profile: profile, anonymousId: anonymousId) } - public struct PushToken: Equatable, Codable { - var type = "push-token-unregister" - public var attributes: Attributes + public struct Attributes: Equatable, Codable { + public let profile: Profile + public let token: String + public let platform: String = "ios" + public let vendor: String = "APNs" + + enum CodingKeys: String, CodingKey { + case token + case platform + case profile + case vendor + } public init(pushToken: String, profile: PublicProfile, anonymousId: String) { - attributes = .init( - pushToken: pushToken, - profile: profile, - anonymousId: anonymousId) + token = pushToken + self.profile = .init(attributes: profile, anonymousId: anonymousId) } - public struct Attributes: Equatable, Codable { - public let profile: Profile - public let token: String - public let platform: String = "ios" - public let vendor: String = "APNs" + public struct Profile: Equatable, Codable { + public let data: CreateProfilePayload.Profile - enum CodingKeys: String, CodingKey { - case token - case platform - case profile - case vendor - } - - public init(pushToken: String, - profile: PublicProfile, + public init(attributes: PublicProfile, anonymousId: String) { - token = pushToken - self.profile = .init(attributes: profile, anonymousId: anonymousId) - } - - public struct Profile: Equatable, Codable { - public let data: CreateProfilePayload.Profile - - public init(attributes: PublicProfile, - anonymousId: String) { - data = .init(profile: attributes, anonymousId: anonymousId) - } + data = .init(profile: attributes, anonymousId: anonymousId) } } } } - - case createProfile(CreateProfilePayload) - case createEvent(CreateEventPayload) - case registerPushToken(PushTokenPayload) - case unregisterPushToken(UnregisterPushTokenPayload) } + + case createProfile(CreateProfilePayload) + case createEvent(CreateEventPayload) + case registerPushToken(PushTokenPayload) + case unregisterPushToken(UnregisterPushTokenPayload) +} + +extension KlaviyoAPI { + public static let _appContextInfo = analytics.appContextInfo() } extension PublicProfile.Location: Codable { diff --git a/Sources/KlaviyoCore/KlaviyoAPI.swift b/Sources/KlaviyoCore/KlaviyoAPI.swift index d7e32bd8..dc57e94a 100644 --- a/Sources/KlaviyoCore/KlaviyoAPI.swift +++ b/Sources/KlaviyoCore/KlaviyoAPI.swift @@ -13,23 +13,23 @@ public func setKlaviyoAPIURL(url: String) { analytics.apiURL = url } -public struct KlaviyoAPI { - public init() {} +public struct KlaviyoRequest: Equatable, Codable { + public init( + apiKey: String, + endpoint: KlaviyoEndpoint, + uuid: String = analytics.uuid().uuidString) { + self.apiKey = apiKey + self.endpoint = endpoint + self.uuid = uuid + } - public struct KlaviyoRequest: Equatable, Codable { - public init( - apiKey: String, - endpoint: KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint, - uuid: String = analytics.uuid().uuidString) { - self.apiKey = apiKey - self.endpoint = endpoint - self.uuid = uuid - } + public let apiKey: String + public let endpoint: KlaviyoEndpoint + public var uuid = analytics.uuid().uuidString +} - public let apiKey: String - public let endpoint: KlaviyoEndpoint - public var uuid = analytics.uuid().uuidString - } +public struct KlaviyoAPI { + public init() {} public enum KlaviyoAPIError: Error { case httpError(Int, Data) @@ -96,7 +96,7 @@ public struct KlaviyoAPI { } } -extension KlaviyoAPI.KlaviyoRequest { +extension KlaviyoRequest { public func urlRequest(_ attemptNumber: Int = 1) throws -> URLRequest { guard let url = url else { throw KlaviyoAPI.KlaviyoAPIError.internalError("Invalid url string. API URL: \(analytics.apiURL)") diff --git a/Sources/KlaviyoSwift/APIRequestErrorHandling.swift b/Sources/KlaviyoSwift/APIRequestErrorHandling.swift index 47a60d6e..42cef843 100644 --- a/Sources/KlaviyoSwift/APIRequestErrorHandling.swift +++ b/Sources/KlaviyoSwift/APIRequestErrorHandling.swift @@ -55,7 +55,7 @@ private func parseError(_ data: Data) -> [InvalidField]? { } func handleRequestError( - request: KlaviyoAPI.KlaviyoRequest, + request: KlaviyoRequest, error: KlaviyoAPI.KlaviyoAPIError, retryInfo: RetryInfo) -> KlaviyoAction { switch error { diff --git a/Sources/KlaviyoSwift/KlaviyoState.swift b/Sources/KlaviyoSwift/KlaviyoState.swift index 553e05f7..f33d03ca 100644 --- a/Sources/KlaviyoSwift/KlaviyoState.swift +++ b/Sources/KlaviyoSwift/KlaviyoState.swift @@ -10,8 +10,8 @@ import Foundation import KlaviyoCore import UIKit -typealias DeviceMetadata = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload.PushToken.Attributes.MetaData -typealias CreateProfilePayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateProfilePayload +typealias DeviceMetadata = KlaviyoEndpoint.PushTokenPayload.PushToken.Attributes.MetaData +typealias CreateProfilePayload = KlaviyoEndpoint.CreateProfilePayload struct KlaviyoState: Equatable, Codable { enum InitializationState: Equatable, Codable { @@ -52,8 +52,8 @@ struct KlaviyoState: Equatable, Codable { var pushTokenData: PushTokenData? // queueing related stuff - var queue: [KlaviyoAPI.KlaviyoRequest] - var requestsInFlight: [KlaviyoAPI.KlaviyoRequest] = [] + var queue: [KlaviyoRequest] + var requestsInFlight: [KlaviyoRequest] = [] var initalizationState = InitializationState.uninitialized var flushing = false var flushInterval = StateManagementConstants.wifiFlushInterval @@ -71,7 +71,7 @@ struct KlaviyoState: Equatable, Codable { case pushTokenData } - mutating func enqueueRequest(request: KlaviyoAPI.KlaviyoRequest) { + mutating func enqueueRequest(request: KlaviyoRequest) { guard queue.count + 1 < StateManagementConstants.maxQueueSize else { return } @@ -127,7 +127,7 @@ struct KlaviyoState: Equatable, Codable { switch request.endpoint { case let .createProfile(payload): let updatedPayload = updateRequestAndStateWithPendingProfile(profile: payload) - let request = KlaviyoAPI.KlaviyoRequest(apiKey: apiKey, endpoint: .createProfile(updatedPayload)) + let request = KlaviyoRequest(apiKey: apiKey, endpoint: .createProfile(updatedPayload)) enqueueRequest(request: request) default: environment.raiseFatalError("Unexpected request type. \(request.endpoint)") @@ -230,7 +230,7 @@ struct KlaviyoState: Equatable, Codable { if let apiKey = apiKey, let anonymousId = anonymousId, let tokenData = previousPushTokenData { - let request = KlaviyoAPI.KlaviyoRequest( + let request = KlaviyoRequest( apiKey: apiKey, endpoint: .registerPushToken(.init( pushToken: tokenData.pushToken, @@ -258,8 +258,8 @@ struct KlaviyoState: Equatable, Codable { return pushTokenData != newPushTokenData } - func buildProfileRequest(apiKey: String, anonymousId: String, properties: [String: Any] = [:]) -> KlaviyoAPI.KlaviyoRequest { - let payload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateProfilePayload( + func buildProfileRequest(apiKey: String, anonymousId: String, properties: [String: Any] = [:]) -> KlaviyoRequest { + let payload = KlaviyoEndpoint.CreateProfilePayload( data: .init( profile: PublicProfile( email: email, @@ -268,12 +268,12 @@ struct KlaviyoState: Equatable, Codable { properties: properties), anonymousId: anonymousId) ) - let endpoint = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.createProfile(payload) + let endpoint = KlaviyoEndpoint.createProfile(payload) - return KlaviyoAPI.KlaviyoRequest(apiKey: apiKey, endpoint: endpoint) + return KlaviyoRequest(apiKey: apiKey, endpoint: endpoint) } - mutating func buildTokenRequest(apiKey: String, anonymousId: String, pushToken: String, enablement: PushEnablement) -> KlaviyoAPI.KlaviyoRequest { + mutating func buildTokenRequest(apiKey: String, anonymousId: String, pushToken: String, enablement: PushEnablement) -> KlaviyoRequest { var profile: Profile if let pendingProfile = pendingProfile { @@ -294,17 +294,17 @@ struct KlaviyoState: Equatable, Codable { background: environment.getBackgroundSetting().rawValue, profile: PublicProfile(profile: profile), anonymousId: anonymousId) - let endpoint = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.registerPushToken(payload) - return KlaviyoAPI.KlaviyoRequest(apiKey: apiKey, endpoint: endpoint) + let endpoint = KlaviyoEndpoint.registerPushToken(payload) + return KlaviyoRequest(apiKey: apiKey, endpoint: endpoint) } - func buildUnregisterRequest(apiKey: String, anonymousId: String, pushToken: String) -> KlaviyoAPI.KlaviyoRequest { + func buildUnregisterRequest(apiKey: String, anonymousId: String, pushToken: String) -> KlaviyoRequest { let payload = UnregisterPushTokenPayload( pushToken: pushToken, profile: .init(email: email, phoneNumber: phoneNumber, externalId: externalId), anonymousId: anonymousId) - let endpoint = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.unregisterPushToken(payload) - return KlaviyoAPI.KlaviyoRequest(apiKey: apiKey, endpoint: endpoint) + let endpoint = KlaviyoEndpoint.unregisterPushToken(payload) + return KlaviyoRequest(apiKey: apiKey, endpoint: endpoint) } } diff --git a/Sources/KlaviyoSwift/SDKRequestIterator.swift b/Sources/KlaviyoSwift/SDKRequestIterator.swift index f2521ba1..2381bd2f 100644 --- a/Sources/KlaviyoSwift/SDKRequestIterator.swift +++ b/Sources/KlaviyoSwift/SDKRequestIterator.swift @@ -47,7 +47,7 @@ public struct SDKRequest: Identifiable, Equatable { case saveToken(token: String, info: ProfileInfo) case unregisterToken(token: String, info: ProfileInfo) - static func fromEndpoint(request: KlaviyoAPI.KlaviyoRequest) -> RequestType { + static func fromEndpoint(request: KlaviyoRequest) -> RequestType { switch request.endpoint { case let .createProfile(payload): @@ -87,7 +87,7 @@ public struct SDKRequest: Identifiable, Equatable { case reqeustError(String, Double) } - static func fromAPIRequest(request: KlaviyoAPI.KlaviyoRequest, response: SDKRequest.Response) -> SDKRequest { + static func fromAPIRequest(request: KlaviyoRequest, response: SDKRequest.Response) -> SDKRequest { let type = RequestType.fromEndpoint(request: request) let urlRequest = try? request.urlRequest() let method = urlRequest?.httpMethod ?? "Unknown" diff --git a/Sources/KlaviyoSwift/StateManagement.swift b/Sources/KlaviyoSwift/StateManagement.swift index 18d57829..a52e7a3a 100644 --- a/Sources/KlaviyoSwift/StateManagement.swift +++ b/Sources/KlaviyoSwift/StateManagement.swift @@ -15,8 +15,8 @@ import AnyCodable import Foundation import KlaviyoCore -typealias PushTokenPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload -typealias UnregisterPushTokenPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.UnregisterPushTokenPayload +typealias PushTokenPayload = KlaviyoEndpoint.PushTokenPayload +typealias UnregisterPushTokenPayload = KlaviyoEndpoint.UnregisterPushTokenPayload enum StateManagementConstants { static let cellularFlushInterval = 30.0 @@ -54,7 +54,7 @@ enum KlaviyoAction: Equatable { case resetProfile /// dequeues requests that completed and contuinues to flush other requests if they exist. - case deQueueCompletedResults(KlaviyoAPI.KlaviyoRequest) + case deQueueCompletedResults(KlaviyoRequest) /// when the network connectivity change we want to use a different flush interval to flush out the pending requests case networkConnectivityChanged(Reachability.NetworkStatus) @@ -75,7 +75,7 @@ enum KlaviyoAction: Equatable { case cancelInFlightRequests /// called when there is a network or rate limit error - case requestFailed(KlaviyoAPI.KlaviyoRequest, RetryInfo) + case requestFailed(KlaviyoRequest, RetryInfo) /// when there is an event to be sent to klaviyo it's added to the queue case enqueueEvent(Event) @@ -89,7 +89,7 @@ enum KlaviyoAction: Equatable { /// resets the state for profile properties before dequeing the request /// this is done in the case where there is http request failure due to /// the data that was passed to the client endpoint - case resetStateAndDequeue(KlaviyoAPI.KlaviyoRequest, [InvalidField]) + case resetStateAndDequeue(KlaviyoRequest, [InvalidField]) var requiresInitialization: Bool { switch self { @@ -425,26 +425,26 @@ struct KlaviyoReducer: ReducerProtocol { else { return .none } - let request: KlaviyoAPI.KlaviyoRequest! - -// if let tokenData = pushTokenData { -// request = KlaviyoAPI.KlaviyoRequest( -// apiKey: apiKey, -// endpoint: .registerPushToken(.init( -// pushToken: tokenData.pushToken, -// enablement: tokenData.pushEnablement.rawValue, -// background: tokenData.pushBackground.rawValue, -// profile: profile.profile(from: state), -// anonymousId: anonymousId) -// )) -// } else { -// request = KlaviyoAPI.KlaviyoRequest( -// apiKey: apiKey, -// endpoint: .createProfile( -// .init(data: .init(profile: profile.profile(from: state), anonymousId: anonymousId)) -// )) -// } -// state.enqueueRequest(request: request) + let request: KlaviyoRequest! + + if let tokenData = pushTokenData { + request = KlaviyoRequest( + apiKey: apiKey, + endpoint: .registerPushToken(.init( + pushToken: tokenData.pushToken, + enablement: tokenData.pushEnablement.rawValue, + background: tokenData.pushBackground.rawValue, + profile: PublicProfile(profile: profile.profile(from: state)), + anonymousId: anonymousId) + )) + } else { + request = KlaviyoRequest( + apiKey: apiKey, + endpoint: .createProfile( + .init(data: .init(profile: PublicProfile(profile: profile.profile(from: state)), anonymousId: anonymousId)) + )) + } + state.enqueueRequest(request: request) return .none From 8f2bb08a9f1bfcb8b37dfdd2cf2d6e535c60b01c Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Mon, 5 Aug 2024 21:53:20 -0500 Subject: [PATCH 06/72] moved the network models into their own files under a models directory --- Sources/KlaviyoCore/InternalAPIModels.swift | 352 ------------------ Sources/KlaviyoCore/KlaviyoAPI.swift | 76 ---- Sources/KlaviyoCore/KlaviyoRequest.swift | 83 +++++ .../Models/CreateEventPayload.swift | 111 ++++++ .../Models/CreateProfilePayload.swift | 76 ++++ .../KlaviyoCore/Models/PushTokenPayload.swift | 134 +++++++ .../Models/UnregisterPushTokenPayload.swift | 65 ++++ Sources/KlaviyoSwift/KlaviyoState.swift | 5 +- Sources/KlaviyoSwift/StateManagement.swift | 3 - 9 files changed, 471 insertions(+), 434 deletions(-) create mode 100644 Sources/KlaviyoCore/KlaviyoRequest.swift create mode 100644 Sources/KlaviyoCore/Models/CreateEventPayload.swift create mode 100644 Sources/KlaviyoCore/Models/CreateProfilePayload.swift create mode 100644 Sources/KlaviyoCore/Models/PushTokenPayload.swift create mode 100644 Sources/KlaviyoCore/Models/UnregisterPushTokenPayload.swift diff --git a/Sources/KlaviyoCore/InternalAPIModels.swift b/Sources/KlaviyoCore/InternalAPIModels.swift index 5a35f12d..ee781cf5 100644 --- a/Sources/KlaviyoCore/InternalAPIModels.swift +++ b/Sources/KlaviyoCore/InternalAPIModels.swift @@ -10,358 +10,6 @@ import AnyCodable import Foundation public enum KlaviyoEndpoint: Equatable, Codable { - public struct CreateProfilePayload: Equatable, Codable { - public init(data: KlaviyoEndpoint.CreateProfilePayload.Profile) { - self.data = data - } - - /** - Internal structure which has details not needed by the API. - */ - public struct Profile: Equatable, Codable { - var type = "profile" - public struct Attributes: Equatable, Codable { - public let email: String? - public let phoneNumber: String? - public let externalId: String? - public let anonymousId: String - public var firstName: String? - public var lastName: String? - public var organization: String? - public var title: String? - public var image: String? - public var location: PublicProfile.Location? - public var properties: AnyCodable - enum CodingKeys: String, CodingKey { - case email - case phoneNumber = "phone_number" - case externalId = "external_id" - case anonymousId = "anonymous_id" - case firstName = "first_name" - case lastName = "last_name" - case organization - case title - case image - case location - case properties - } - - public init(attributes: PublicProfile, - anonymousId: String) { - email = attributes.email - phoneNumber = attributes.phoneNumber - externalId = attributes.externalId - firstName = attributes.firstName - lastName = attributes.lastName - organization = attributes.organization - title = attributes.title - image = attributes.image - location = attributes.location - properties = AnyCodable(attributes.properties) - self.anonymousId = anonymousId - } - } - - public var attributes: Attributes - public init(profile: PublicProfile, anonymousId: String) { - attributes = Attributes( - attributes: profile, - anonymousId: anonymousId) - } - - public init(attributes: Attributes) { - self.attributes = attributes - } - } - - public var data: Profile - } - - public struct CreateEventPayload: Equatable, Codable { - public struct Event: Equatable, Codable { - public struct Attributes: Equatable, Codable { - public struct Metric: Equatable, Codable { - public let data: MetricData - - public struct MetricData: Equatable, Codable { - var type: String = "metric" - - public let attributes: MetricAttributes - - public init(name: String) { - attributes = .init(name: name) - } - - public struct MetricAttributes: Equatable, Codable { - public let name: String - } - } - - public init(name: String) { - data = .init(name: name) - } - } - - public struct Profile: Equatable, Codable { - public let data: CreateProfilePayload.Profile - - public init(attributes: PublicProfile, - anonymousId: String) { - data = .init(profile: attributes, anonymousId: anonymousId) - } - } - - public let metric: Metric - public var properties: AnyCodable - public let profile: Profile - public let time: Date - public let value: Double? - public let uniqueId: String - public init(attributes: PublicEvent, - anonymousId: String? = nil) { - metric = Metric(name: attributes.metric.name.value) - properties = AnyCodable(attributes.properties) - value = attributes.value - time = attributes.time - uniqueId = attributes.uniqueId - - profile = .init(attributes: .init( - email: attributes.identifiers?.email, - phoneNumber: attributes.identifiers?.phoneNumber, - externalId: attributes.identifiers?.externalId), - anonymousId: anonymousId ?? "") - } - - enum CodingKeys: String, CodingKey { - case metric - case properties - case profile - case time - case value - case uniqueId = "unique_id" - } - } - - var type = "event" - public var attributes: Attributes - public init(event: PublicEvent, - anonymousId: String? = nil) { - attributes = .init(attributes: event, anonymousId: anonymousId) - } - } - - mutating func appendMetadataToProperties() { - let context = KlaviyoAPI._appContextInfo - // TODO: Fixme -// let metadata: [String: Any] = [ -// "Device ID": context.deviceId, -// "Device Manufacturer": context.manufacturer, -// "Device Model": context.deviceModel, -// "OS Name": context.osName, -// "OS Version": context.osVersion, -// "SDK Name": __klaviyoSwiftName, -// "SDK Version": __klaviyoSwiftVersion, -// "App Name": context.appName, -// "App ID": context.bundleId, -// "App Version": context.appVersion, -// "App Build": context.appBuild, -// "Push Token": analytics.state().pushTokenData?.pushToken as Any -// ] - - let metadata = [String: Any]() - let originalProperties = data.attributes.properties.value as? [String: Any] ?? [:] - data.attributes.properties = AnyCodable(originalProperties.merging(metadata) { _, new in new }) - } - - public var data: Event - public init(data: Event) { - self.data = data - } - } - - public struct PushTokenPayload: Equatable, Codable { - public init(data: KlaviyoEndpoint.PushTokenPayload.PushToken) { - self.data = data - } - - public let data: PushToken - - public init(pushToken: String, - enablement: String, - background: String, - profile: PublicProfile, - anonymousId: String) { - data = .init( - pushToken: pushToken, - enablement: enablement, - background: background, - profile: profile, - anonymousId: anonymousId) - } - - public struct PushToken: Equatable, Codable { - var type = "push-token" - public var attributes: Attributes - - public init(pushToken: String, - enablement: String, - background: String, - profile: PublicProfile, - anonymousId: String) { - attributes = .init( - pushToken: pushToken, - enablement: enablement, - background: background, - profile: profile, - anonymousId: anonymousId) - } - - public struct Attributes: Equatable, Codable { - public let profile: Profile - public let token: String - public let enablementStatus: String - public let backgroundStatus: String - public let deviceMetadata: MetaData - public let platform: String = "ios" - public let vendor: String = "APNs" - - enum CodingKeys: String, CodingKey { - case token - case platform - case enablementStatus = "enablement_status" - case profile - case vendor - case backgroundStatus = "background" - case deviceMetadata = "device_metadata" - } - - public init(pushToken: String, - enablement: String, - background: String, - profile: PublicProfile, - anonymousId: String) { - token = pushToken - - enablementStatus = enablement - backgroundStatus = background - self.profile = .init(attributes: profile, anonymousId: anonymousId) - deviceMetadata = .init(context: KlaviyoAPI._appContextInfo) - } - - public struct Profile: Equatable, Codable { - public let data: CreateProfilePayload.Profile - - public init(attributes: PublicProfile, - anonymousId: String) { - data = .init(profile: attributes, anonymousId: anonymousId) - } - } - - public struct MetaData: Equatable, Codable { - public let deviceId: String - public let deviceModel: String - public let manufacturer: String - public let osName: String - public let osVersion: String - public let appId: String - public let appName: String - public let appVersion: String - public let appBuild: String - public let environment: String - public let klaviyoSdk: String - public let sdkVersion: String - - enum CodingKeys: String, CodingKey { - case deviceId = "device_id" - case klaviyoSdk = "klaviyo_sdk" - case sdkVersion = "sdk_version" - case deviceModel = "device_model" - case osName = "os_name" - case osVersion = "os_version" - case manufacturer - case appName = "app_name" - case appVersion = "app_version" - case appBuild = "app_build" - case appId = "app_id" - case environment - } - - public init(context: AppContextInfo) { - deviceId = context.deviceId - deviceModel = context.deviceModel - manufacturer = context.manufacturer - osName = context.osName - osVersion = context.osVersion - appId = context.bundleId - appName = context.appName - appVersion = context.appVersion - appBuild = context.appBuild - environment = context.environment - klaviyoSdk = __klaviyoSwiftName - sdkVersion = __klaviyoSwiftVersion - } - } - } - } - } - - public struct UnregisterPushTokenPayload: Equatable, Codable { - public let data: PushToken - - public init(pushToken: String, - profile: PublicProfile, - anonymousId: String) { - data = .init( - pushToken: pushToken, - profile: profile, - anonymousId: anonymousId) - } - - public struct PushToken: Equatable, Codable { - var type = "push-token-unregister" - public var attributes: Attributes - - public init(pushToken: String, - profile: PublicProfile, - anonymousId: String) { - attributes = .init( - pushToken: pushToken, - profile: profile, - anonymousId: anonymousId) - } - - public struct Attributes: Equatable, Codable { - public let profile: Profile - public let token: String - public let platform: String = "ios" - public let vendor: String = "APNs" - - enum CodingKeys: String, CodingKey { - case token - case platform - case profile - case vendor - } - - public init(pushToken: String, - profile: PublicProfile, - anonymousId: String) { - token = pushToken - self.profile = .init(attributes: profile, anonymousId: anonymousId) - } - - public struct Profile: Equatable, Codable { - public let data: CreateProfilePayload.Profile - - public init(attributes: PublicProfile, - anonymousId: String) { - data = .init(profile: attributes, anonymousId: anonymousId) - } - } - } - } - } - case createProfile(CreateProfilePayload) case createEvent(CreateEventPayload) case registerPushToken(PushTokenPayload) diff --git a/Sources/KlaviyoCore/KlaviyoAPI.swift b/Sources/KlaviyoCore/KlaviyoAPI.swift index dc57e94a..69c61e86 100644 --- a/Sources/KlaviyoCore/KlaviyoAPI.swift +++ b/Sources/KlaviyoCore/KlaviyoAPI.swift @@ -13,21 +13,6 @@ public func setKlaviyoAPIURL(url: String) { analytics.apiURL = url } -public struct KlaviyoRequest: Equatable, Codable { - public init( - apiKey: String, - endpoint: KlaviyoEndpoint, - uuid: String = analytics.uuid().uuidString) { - self.apiKey = apiKey - self.endpoint = endpoint - self.uuid = uuid - } - - public let apiKey: String - public let endpoint: KlaviyoEndpoint - public var uuid = analytics.uuid().uuidString -} - public struct KlaviyoAPI { public init() {} @@ -95,64 +80,3 @@ public struct KlaviyoAPI { return .success(data) } } - -extension KlaviyoRequest { - public func urlRequest(_ attemptNumber: Int = 1) throws -> URLRequest { - guard let url = url else { - throw KlaviyoAPI.KlaviyoAPIError.internalError("Invalid url string. API URL: \(analytics.apiURL)") - } - var request = URLRequest(url: url) - // We only support post right now - guard let body = try? encodeBody() else { - throw KlaviyoAPI.KlaviyoAPIError.dataEncodingError(self) - } - request.httpBody = body - request.httpMethod = "POST" - request.setValue("\(attemptNumber)/50", forHTTPHeaderField: "X-Klaviyo-Attempt-Count") - - return request - } - - var url: URL? { - switch endpoint { - case .createProfile, .createEvent, .registerPushToken, .unregisterPushToken: - if !analytics.apiURL.isEmpty { - return URL(string: "\(analytics.apiURL)/\(path)/?company_id=\(apiKey)") - } - return nil - } - } - - var path: String { - switch endpoint { - case .createProfile: - return "client/profiles" - - case .createEvent: - return "client/events" - - case .registerPushToken: - return "client/push-tokens" - - case .unregisterPushToken: - return "client/push-token-unregister" - } - } - - func encodeBody() throws -> Data { - switch endpoint { - case let .createProfile(payload): - return try analytics.encodeJSON(AnyEncodable(payload)) - - case var .createEvent(payload): - payload.appendMetadataToProperties() - return try analytics.encodeJSON(AnyEncodable(payload)) - - case let .registerPushToken(payload): - return try analytics.encodeJSON(AnyEncodable(payload)) - - case let .unregisterPushToken(payload): - return try analytics.encodeJSON(AnyEncodable(payload)) - } - } -} diff --git a/Sources/KlaviyoCore/KlaviyoRequest.swift b/Sources/KlaviyoCore/KlaviyoRequest.swift new file mode 100644 index 00000000..3385cd53 --- /dev/null +++ b/Sources/KlaviyoCore/KlaviyoRequest.swift @@ -0,0 +1,83 @@ +// +// File.swift +// +// +// Created by Ajay Subramanya on 8/5/24. +// + +import AnyCodable +import Foundation + +public struct KlaviyoRequest: Equatable, Codable { + public init( + apiKey: String, + endpoint: KlaviyoEndpoint, + uuid: String = analytics.uuid().uuidString) { + self.apiKey = apiKey + self.endpoint = endpoint + self.uuid = uuid + } + + public let apiKey: String + public let endpoint: KlaviyoEndpoint + public var uuid = analytics.uuid().uuidString + + public func urlRequest(_ attemptNumber: Int = 1) throws -> URLRequest { + guard let url = url else { + throw KlaviyoAPI.KlaviyoAPIError.internalError("Invalid url string. API URL: \(analytics.apiURL)") + } + var request = URLRequest(url: url) + // We only support post right now + guard let body = try? encodeBody() else { + throw KlaviyoAPI.KlaviyoAPIError.dataEncodingError(self) + } + request.httpBody = body + request.httpMethod = "POST" + request.setValue("\(attemptNumber)/50", forHTTPHeaderField: "X-Klaviyo-Attempt-Count") + + return request + } + + var url: URL? { + switch endpoint { + case .createProfile, .createEvent, .registerPushToken, .unregisterPushToken: + if !analytics.apiURL.isEmpty { + return URL(string: "\(analytics.apiURL)/\(path)/?company_id=\(apiKey)") + } + return nil + } + } + + var path: String { + switch endpoint { + case .createProfile: + return "client/profiles" + + case .createEvent: + return "client/events" + + case .registerPushToken: + return "client/push-tokens" + + case .unregisterPushToken: + return "client/push-token-unregister" + } + } + + func encodeBody() throws -> Data { + switch endpoint { + case let .createProfile(payload): + return try analytics.encodeJSON(AnyEncodable(payload)) + + case var .createEvent(payload): + payload.appendMetadataToProperties() + return try analytics.encodeJSON(AnyEncodable(payload)) + + case let .registerPushToken(payload): + return try analytics.encodeJSON(AnyEncodable(payload)) + + case let .unregisterPushToken(payload): + return try analytics.encodeJSON(AnyEncodable(payload)) + } + } +} diff --git a/Sources/KlaviyoCore/Models/CreateEventPayload.swift b/Sources/KlaviyoCore/Models/CreateEventPayload.swift new file mode 100644 index 00000000..b6e43d0a --- /dev/null +++ b/Sources/KlaviyoCore/Models/CreateEventPayload.swift @@ -0,0 +1,111 @@ +// +// File.swift +// +// +// Created by Ajay Subramanya on 8/5/24. +// + +import AnyCodable +import Foundation + +public struct CreateEventPayload: Equatable, Codable { + public struct Event: Equatable, Codable { + public struct Attributes: Equatable, Codable { + public struct Metric: Equatable, Codable { + public let data: MetricData + + public struct MetricData: Equatable, Codable { + var type: String = "metric" + + public let attributes: MetricAttributes + + public init(name: String) { + attributes = .init(name: name) + } + + public struct MetricAttributes: Equatable, Codable { + public let name: String + } + } + + public init(name: String) { + data = .init(name: name) + } + } + + public struct Profile: Equatable, Codable { + public let data: CreateProfilePayload.Profile + + public init(attributes: PublicProfile, + anonymousId: String) { + data = .init(profile: attributes, anonymousId: anonymousId) + } + } + + public let metric: Metric + public var properties: AnyCodable + public let profile: Profile + public let time: Date + public let value: Double? + public let uniqueId: String + public init(attributes: PublicEvent, + anonymousId: String? = nil) { + metric = Metric(name: attributes.metric.name.value) + properties = AnyCodable(attributes.properties) + value = attributes.value + time = attributes.time + uniqueId = attributes.uniqueId + + profile = .init(attributes: .init( + email: attributes.identifiers?.email, + phoneNumber: attributes.identifiers?.phoneNumber, + externalId: attributes.identifiers?.externalId), + anonymousId: anonymousId ?? "") + } + + enum CodingKeys: String, CodingKey { + case metric + case properties + case profile + case time + case value + case uniqueId = "unique_id" + } + } + + var type = "event" + public var attributes: Attributes + public init(event: PublicEvent, + anonymousId: String? = nil) { + attributes = .init(attributes: event, anonymousId: anonymousId) + } + } + + mutating func appendMetadataToProperties() { + let context = KlaviyoAPI._appContextInfo + // TODO: Fixme +// let metadata: [String: Any] = [ +// "Device ID": context.deviceId, +// "Device Manufacturer": context.manufacturer, +// "Device Model": context.deviceModel, +// "OS Name": context.osName, +// "OS Version": context.osVersion, +// "SDK Name": __klaviyoSwiftName, +// "SDK Version": __klaviyoSwiftVersion, +// "App Name": context.appName, +// "App ID": context.bundleId, +// "App Version": context.appVersion, +// "App Build": context.appBuild, +// "Push Token": analytics.state().pushTokenData?.pushToken as Any +// ] + + let metadata = [String: Any]() + let originalProperties = data.attributes.properties.value as? [String: Any] ?? [:] + data.attributes.properties = AnyCodable(originalProperties.merging(metadata) { _, new in new }) + } + + public var data: Event + public init(data: Event) { + self.data = data + } +} diff --git a/Sources/KlaviyoCore/Models/CreateProfilePayload.swift b/Sources/KlaviyoCore/Models/CreateProfilePayload.swift new file mode 100644 index 00000000..0fdfcf44 --- /dev/null +++ b/Sources/KlaviyoCore/Models/CreateProfilePayload.swift @@ -0,0 +1,76 @@ +// +// File.swift +// +// +// Created by Ajay Subramanya on 8/5/24. +// + +import AnyCodable +import Foundation + +public struct CreateProfilePayload: Equatable, Codable { + public init(data: CreateProfilePayload.Profile) { + self.data = data + } + + /** + Internal structure which has details not needed by the API. + */ + public struct Profile: Equatable, Codable { + var type = "profile" + public struct Attributes: Equatable, Codable { + public let email: String? + public let phoneNumber: String? + public let externalId: String? + public let anonymousId: String + public var firstName: String? + public var lastName: String? + public var organization: String? + public var title: String? + public var image: String? + public var location: PublicProfile.Location? + public var properties: AnyCodable + enum CodingKeys: String, CodingKey { + case email + case phoneNumber = "phone_number" + case externalId = "external_id" + case anonymousId = "anonymous_id" + case firstName = "first_name" + case lastName = "last_name" + case organization + case title + case image + case location + case properties + } + + public init(attributes: PublicProfile, + anonymousId: String) { + email = attributes.email + phoneNumber = attributes.phoneNumber + externalId = attributes.externalId + firstName = attributes.firstName + lastName = attributes.lastName + organization = attributes.organization + title = attributes.title + image = attributes.image + location = attributes.location + properties = AnyCodable(attributes.properties) + self.anonymousId = anonymousId + } + } + + public var attributes: Attributes + public init(profile: PublicProfile, anonymousId: String) { + attributes = Attributes( + attributes: profile, + anonymousId: anonymousId) + } + + public init(attributes: Attributes) { + self.attributes = attributes + } + } + + public var data: Profile +} diff --git a/Sources/KlaviyoCore/Models/PushTokenPayload.swift b/Sources/KlaviyoCore/Models/PushTokenPayload.swift new file mode 100644 index 00000000..b55b4ac1 --- /dev/null +++ b/Sources/KlaviyoCore/Models/PushTokenPayload.swift @@ -0,0 +1,134 @@ +// +// File.swift +// +// +// Created by Ajay Subramanya on 8/5/24. +// + +import Foundation + +public struct PushTokenPayload: Equatable, Codable { + public init(data: PushTokenPayload.PushToken) { + self.data = data + } + + public let data: PushToken + + public init(pushToken: String, + enablement: String, + background: String, + profile: PublicProfile, + anonymousId: String) { + data = .init( + pushToken: pushToken, + enablement: enablement, + background: background, + profile: profile, + anonymousId: anonymousId) + } + + public struct PushToken: Equatable, Codable { + var type = "push-token" + public var attributes: Attributes + + public init(pushToken: String, + enablement: String, + background: String, + profile: PublicProfile, + anonymousId: String) { + attributes = .init( + pushToken: pushToken, + enablement: enablement, + background: background, + profile: profile, + anonymousId: anonymousId) + } + + public struct Attributes: Equatable, Codable { + public let profile: Profile + public let token: String + public let enablementStatus: String + public let backgroundStatus: String + public let deviceMetadata: MetaData + public let platform: String = "ios" + public let vendor: String = "APNs" + + enum CodingKeys: String, CodingKey { + case token + case platform + case enablementStatus = "enablement_status" + case profile + case vendor + case backgroundStatus = "background" + case deviceMetadata = "device_metadata" + } + + public init(pushToken: String, + enablement: String, + background: String, + profile: PublicProfile, + anonymousId: String) { + token = pushToken + + enablementStatus = enablement + backgroundStatus = background + self.profile = .init(attributes: profile, anonymousId: anonymousId) + deviceMetadata = .init(context: KlaviyoAPI._appContextInfo) + } + + public struct Profile: Equatable, Codable { + public let data: CreateProfilePayload.Profile + + public init(attributes: PublicProfile, + anonymousId: String) { + data = .init(profile: attributes, anonymousId: anonymousId) + } + } + + public struct MetaData: Equatable, Codable { + public let deviceId: String + public let deviceModel: String + public let manufacturer: String + public let osName: String + public let osVersion: String + public let appId: String + public let appName: String + public let appVersion: String + public let appBuild: String + public let environment: String + public let klaviyoSdk: String + public let sdkVersion: String + + enum CodingKeys: String, CodingKey { + case deviceId = "device_id" + case klaviyoSdk = "klaviyo_sdk" + case sdkVersion = "sdk_version" + case deviceModel = "device_model" + case osName = "os_name" + case osVersion = "os_version" + case manufacturer + case appName = "app_name" + case appVersion = "app_version" + case appBuild = "app_build" + case appId = "app_id" + case environment + } + + public init(context: AppContextInfo) { + deviceId = context.deviceId + deviceModel = context.deviceModel + manufacturer = context.manufacturer + osName = context.osName + osVersion = context.osVersion + appId = context.bundleId + appName = context.appName + appVersion = context.appVersion + appBuild = context.appBuild + environment = context.environment + klaviyoSdk = __klaviyoSwiftName + sdkVersion = __klaviyoSwiftVersion + } + } + } + } +} diff --git a/Sources/KlaviyoCore/Models/UnregisterPushTokenPayload.swift b/Sources/KlaviyoCore/Models/UnregisterPushTokenPayload.swift new file mode 100644 index 00000000..d14121eb --- /dev/null +++ b/Sources/KlaviyoCore/Models/UnregisterPushTokenPayload.swift @@ -0,0 +1,65 @@ +// +// File.swift +// +// +// Created by Ajay Subramanya on 8/5/24. +// + +import Foundation + +public struct UnregisterPushTokenPayload: Equatable, Codable { + public let data: PushToken + + public init(pushToken: String, + profile: PublicProfile, + anonymousId: String) { + data = .init( + pushToken: pushToken, + profile: profile, + anonymousId: anonymousId) + } + + public struct PushToken: Equatable, Codable { + var type = "push-token-unregister" + public var attributes: Attributes + + public init(pushToken: String, + profile: PublicProfile, + anonymousId: String) { + attributes = .init( + pushToken: pushToken, + profile: profile, + anonymousId: anonymousId) + } + + public struct Attributes: Equatable, Codable { + public let profile: Profile + public let token: String + public let platform: String = "ios" + public let vendor: String = "APNs" + + enum CodingKeys: String, CodingKey { + case token + case platform + case profile + case vendor + } + + public init(pushToken: String, + profile: PublicProfile, + anonymousId: String) { + token = pushToken + self.profile = .init(attributes: profile, anonymousId: anonymousId) + } + + public struct Profile: Equatable, Codable { + public let data: CreateProfilePayload.Profile + + public init(attributes: PublicProfile, + anonymousId: String) { + data = .init(profile: attributes, anonymousId: anonymousId) + } + } + } + } +} diff --git a/Sources/KlaviyoSwift/KlaviyoState.swift b/Sources/KlaviyoSwift/KlaviyoState.swift index f33d03ca..cf1e4de8 100644 --- a/Sources/KlaviyoSwift/KlaviyoState.swift +++ b/Sources/KlaviyoSwift/KlaviyoState.swift @@ -10,8 +10,7 @@ import Foundation import KlaviyoCore import UIKit -typealias DeviceMetadata = KlaviyoEndpoint.PushTokenPayload.PushToken.Attributes.MetaData -typealias CreateProfilePayload = KlaviyoEndpoint.CreateProfilePayload +typealias DeviceMetadata = PushTokenPayload.PushToken.Attributes.MetaData struct KlaviyoState: Equatable, Codable { enum InitializationState: Equatable, Codable { @@ -259,7 +258,7 @@ struct KlaviyoState: Equatable, Codable { } func buildProfileRequest(apiKey: String, anonymousId: String, properties: [String: Any] = [:]) -> KlaviyoRequest { - let payload = KlaviyoEndpoint.CreateProfilePayload( + let payload = CreateProfilePayload( data: .init( profile: PublicProfile( email: email, diff --git a/Sources/KlaviyoSwift/StateManagement.swift b/Sources/KlaviyoSwift/StateManagement.swift index a52e7a3a..d34df1ca 100644 --- a/Sources/KlaviyoSwift/StateManagement.swift +++ b/Sources/KlaviyoSwift/StateManagement.swift @@ -15,9 +15,6 @@ import AnyCodable import Foundation import KlaviyoCore -typealias PushTokenPayload = KlaviyoEndpoint.PushTokenPayload -typealias UnregisterPushTokenPayload = KlaviyoEndpoint.UnregisterPushTokenPayload - enum StateManagementConstants { static let cellularFlushInterval = 30.0 static let wifiFlushInterval = 10.0 From 9af8cdd5a809103ce15ca1251252539ca995aa91 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Tue, 6 Aug 2024 11:23:06 -0500 Subject: [PATCH 07/72] moved retry logic to core --- .../CreateEventPayload.swift | 0 .../CreateProfilePayload.swift | 0 .../PushTokenPayload.swift | 0 .../UnregisterPushTokenPayload.swift | 0 Sources/KlaviyoCore/KlaviyoAPI.swift | 18 +- .../APIRequestErrorHandling.swift | 10 +- Sources/KlaviyoSwift/KlaviyoModels.swift | 196 ------------------ Sources/KlaviyoSwift/Models/Error.swift | 25 +++ Sources/KlaviyoSwift/Models/Event.swift | 91 ++++++++ Sources/KlaviyoSwift/Models/Profile.swift | 96 +++++++++ 10 files changed, 226 insertions(+), 210 deletions(-) rename Sources/KlaviyoCore/{Models => APIModels}/CreateEventPayload.swift (100%) rename Sources/KlaviyoCore/{Models => APIModels}/CreateProfilePayload.swift (100%) rename Sources/KlaviyoCore/{Models => APIModels}/PushTokenPayload.swift (100%) rename Sources/KlaviyoCore/{Models => APIModels}/UnregisterPushTokenPayload.swift (100%) delete mode 100644 Sources/KlaviyoSwift/KlaviyoModels.swift create mode 100644 Sources/KlaviyoSwift/Models/Error.swift create mode 100644 Sources/KlaviyoSwift/Models/Event.swift create mode 100644 Sources/KlaviyoSwift/Models/Profile.swift diff --git a/Sources/KlaviyoCore/Models/CreateEventPayload.swift b/Sources/KlaviyoCore/APIModels/CreateEventPayload.swift similarity index 100% rename from Sources/KlaviyoCore/Models/CreateEventPayload.swift rename to Sources/KlaviyoCore/APIModels/CreateEventPayload.swift diff --git a/Sources/KlaviyoCore/Models/CreateProfilePayload.swift b/Sources/KlaviyoCore/APIModels/CreateProfilePayload.swift similarity index 100% rename from Sources/KlaviyoCore/Models/CreateProfilePayload.swift rename to Sources/KlaviyoCore/APIModels/CreateProfilePayload.swift diff --git a/Sources/KlaviyoCore/Models/PushTokenPayload.swift b/Sources/KlaviyoCore/APIModels/PushTokenPayload.swift similarity index 100% rename from Sources/KlaviyoCore/Models/PushTokenPayload.swift rename to Sources/KlaviyoCore/APIModels/PushTokenPayload.swift diff --git a/Sources/KlaviyoCore/Models/UnregisterPushTokenPayload.swift b/Sources/KlaviyoCore/APIModels/UnregisterPushTokenPayload.swift similarity index 100% rename from Sources/KlaviyoCore/Models/UnregisterPushTokenPayload.swift rename to Sources/KlaviyoCore/APIModels/UnregisterPushTokenPayload.swift diff --git a/Sources/KlaviyoCore/KlaviyoAPI.swift b/Sources/KlaviyoCore/KlaviyoAPI.swift index 69c61e86..0c9c8cbe 100644 --- a/Sources/KlaviyoCore/KlaviyoAPI.swift +++ b/Sources/KlaviyoCore/KlaviyoAPI.swift @@ -18,7 +18,7 @@ public struct KlaviyoAPI { public enum KlaviyoAPIError: Error { case httpError(Int, Data) - case rateLimitError(Int?) + case rateLimitError(Int) case missingOrInvalidResponse(URLResponse?) case networkError(Error) case internalError(String) @@ -64,10 +64,18 @@ public struct KlaviyoAPI { return .failure(.missingOrInvalidResponse(response)) } - if httpResponse.statusCode == 429 { - let retryAfter = Int(httpResponse.value(forHTTPHeaderField: "Retry-After") ?? "0") - requestRateLimited(request, retryAfter) - return .failure(KlaviyoAPIError.rateLimitError(retryAfter)) + if httpResponse.statusCode == 429, httpResponse.statusCode == 503 { + let exponentialBackOff = Int(pow(2.0, Double(attemptNumber))) + var nextBackoff: Int = exponentialBackOff + if let retryAfter = httpResponse.value(forHTTPHeaderField: "Retry-After") { + nextBackoff = Int(retryAfter) ?? exponentialBackOff + } + + let jitter = environment.randomInt() + let nextBackOffWithJitter = nextBackoff + jitter + + requestRateLimited(request, nextBackOffWithJitter) + return .failure(KlaviyoAPIError.rateLimitError(nextBackOffWithJitter)) } guard 200..<300 ~= httpResponse.statusCode else { diff --git a/Sources/KlaviyoSwift/APIRequestErrorHandling.swift b/Sources/KlaviyoSwift/APIRequestErrorHandling.swift index 42cef843..682254c8 100644 --- a/Sources/KlaviyoSwift/APIRequestErrorHandling.swift +++ b/Sources/KlaviyoSwift/APIRequestErrorHandling.swift @@ -34,11 +34,6 @@ enum InvalidField: Equatable { } } -private func addJitter(to value: Int) -> Int { - let jitter = environment.randomInt() - return value + jitter -} - private func parseError(_ data: Data) -> [InvalidField]? { var invalidFields: [InvalidField]? do { @@ -103,9 +98,6 @@ func handleRequestError( case let .rateLimitError(retryAfter): var requestRetryCount = 0 var totalRetryCount = 0 - let exponentialBackOff = Int(pow(2.0, Double(totalRetryCount))) - - let nextBackoff = addJitter(to: retryAfter ?? exponentialBackOff) switch retryInfo { case let .retry(count): requestRetryCount = count + 1 @@ -119,7 +111,7 @@ func handleRequestError( request, .retryWithBackoff( requestCount: requestRetryCount, totalRetryCount: totalRetryCount, - currentBackoff: nextBackoff)) + currentBackoff: retryAfter)) case .missingOrInvalidResponse: runtimeWarn("Missing or invalid response from api.") diff --git a/Sources/KlaviyoSwift/KlaviyoModels.swift b/Sources/KlaviyoSwift/KlaviyoModels.swift deleted file mode 100644 index eee5a2fa..00000000 --- a/Sources/KlaviyoSwift/KlaviyoModels.swift +++ /dev/null @@ -1,196 +0,0 @@ -// -// KlaviyoModels.swift -// -// -// Created by Noah Durell on 11/25/22. -// - -import AnyCodable -import Foundation -import KlaviyoCore - -public struct Event: Equatable { - public enum EventName: Equatable { - case OpenedPush - case OpenedAppMetric - case ViewedProductMetric - case AddedToCartMetric - case StartedCheckoutMetric - case CustomEvent(String) - } - - public struct Metric: Equatable { - public let name: EventName - - public init(name: EventName) { - self.name = name - } - } - - struct Identifiers: Equatable { - public let email: String? - public let phoneNumber: String? - public let externalId: String? - public init(email: String? = nil, - phoneNumber: String? = nil, - externalId: String? = nil) { - self.email = email - self.phoneNumber = phoneNumber - self.externalId = externalId - } - } - - public let metric: Metric - public var properties: [String: Any] { - _properties.value as! [String: Any] - } - - private let _properties: AnyCodable - public var time: Date - public let value: Double? - public let uniqueId: String - let identifiers: Identifiers? - - init(name: EventName, - properties: [String: Any]? = nil, - identifiers: Identifiers? = nil, - value: Double? = nil, - time: Date? = nil, - uniqueId: String? = nil) { - metric = .init(name: name) - _properties = AnyCodable(properties ?? [:]) - self.time = time ?? analytics.date() - self.value = value - self.uniqueId = uniqueId ?? analytics.uuid().uuidString - self.identifiers = identifiers - } - - public init(name: EventName, - properties: [String: Any]? = nil, - value: Double? = nil, - uniqueId: String? = nil) { - metric = .init(name: name) - _properties = AnyCodable(properties ?? [:]) - identifiers = nil - self.value = value - time = analytics.date() - self.uniqueId = uniqueId ?? analytics.uuid().uuidString - } -} - -public struct Profile: Equatable { - public enum ProfileKey: Equatable, Hashable, Codable { - case firstName - case lastName - case address1 - case address2 - case title - case organization - case city - case region - case country - case zip - case image - case latitude - case longitude - case custom(customKey: String) - } - - public struct Location: Equatable { - public var address1: String? - public var address2: String? - public var city: String? - public var country: String? - public var latitude: Double? - public var longitude: Double? - public var region: String? - public var zip: String? - public var timezone: String? - public init(address1: String? = nil, - address2: String? = nil, - city: String? = nil, - country: String? = nil, - latitude: Double? = nil, - longitude: Double? = nil, - region: String? = nil, - zip: String? = nil, - timezone: String? = nil) { - self.address1 = address1 - self.address2 = address2 - self.city = city - self.country = country - self.latitude = latitude - self.longitude = longitude - self.region = region - self.zip = zip - self.timezone = timezone ?? analytics.timeZone() - } - } - - public let email: String? - public let phoneNumber: String? - public let externalId: String? - public let firstName: String? - public let lastName: String? - public let organization: String? - public let title: String? - public let image: String? - public let location: Location? - public var properties: [String: Any] { - _properties.value as! [String: Any] - } - - let _properties: AnyCodable - - public init(email: String? = nil, - phoneNumber: String? = nil, - externalId: String? = nil, - firstName: String? = nil, - lastName: String? = nil, - organization: String? = nil, - title: String? = nil, - image: String? = nil, - location: Location? = nil, - properties: [String: Any]? = nil) { - self.email = email - self.phoneNumber = phoneNumber - self.externalId = externalId - self.firstName = firstName - self.lastName = lastName - self.organization = organization - self.title = title - self.image = image - self.location = location - _properties = AnyCodable(properties ?? [:]) - } -} - -extension Event.EventName { - public var value: String { - switch self { - case .OpenedPush: return "$opened_push" - case .OpenedAppMetric: return "Opened App" - case .ViewedProductMetric: return "Viewed Product" - case .AddedToCartMetric: return "Added to Cart" - case .StartedCheckoutMetric: return "Started Checkout" - case let .CustomEvent(value): return "\(value)" - } - } -} - -struct ErrorResponse: Codable { - let errors: [ErrorDetail] -} - -struct ErrorDetail: Codable { - let id: String - let status: Int - let code: String - let title: String - let detail: String - let source: ErrorSource -} - -struct ErrorSource: Codable { - let pointer: String -} diff --git a/Sources/KlaviyoSwift/Models/Error.swift b/Sources/KlaviyoSwift/Models/Error.swift new file mode 100644 index 00000000..05e1a174 --- /dev/null +++ b/Sources/KlaviyoSwift/Models/Error.swift @@ -0,0 +1,25 @@ +// +// File.swift +// +// +// Created by Ajay Subramanya on 8/6/24. +// + +import Foundation + +struct ErrorResponse: Codable { + let errors: [ErrorDetail] +} + +struct ErrorDetail: Codable { + let id: String + let status: Int + let code: String + let title: String + let detail: String + let source: ErrorSource +} + +struct ErrorSource: Codable { + let pointer: String +} diff --git a/Sources/KlaviyoSwift/Models/Event.swift b/Sources/KlaviyoSwift/Models/Event.swift new file mode 100644 index 00000000..809f8dc1 --- /dev/null +++ b/Sources/KlaviyoSwift/Models/Event.swift @@ -0,0 +1,91 @@ +// +// File.swift +// +// +// Created by Ajay Subramanya on 8/6/24. +// + +import AnyCodable +import Foundation + +public struct Event: Equatable { + public enum EventName: Equatable { + case OpenedPush + case OpenedAppMetric + case ViewedProductMetric + case AddedToCartMetric + case StartedCheckoutMetric + case CustomEvent(String) + } + + public struct Metric: Equatable { + public let name: EventName + + public init(name: EventName) { + self.name = name + } + } + + struct Identifiers: Equatable { + public let email: String? + public let phoneNumber: String? + public let externalId: String? + public init(email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil) { + self.email = email + self.phoneNumber = phoneNumber + self.externalId = externalId + } + } + + public let metric: Metric + public var properties: [String: Any] { + _properties.value as! [String: Any] + } + + private let _properties: AnyCodable + public var time: Date + public let value: Double? + public let uniqueId: String + let identifiers: Identifiers? + + init(name: EventName, + properties: [String: Any]? = nil, + identifiers: Identifiers? = nil, + value: Double? = nil, + time: Date? = nil, + uniqueId: String? = nil) { + metric = .init(name: name) + _properties = AnyCodable(properties ?? [:]) + self.time = time ?? analytics.date() + self.value = value + self.uniqueId = uniqueId ?? analytics.uuid().uuidString + self.identifiers = identifiers + } + + public init(name: EventName, + properties: [String: Any]? = nil, + value: Double? = nil, + uniqueId: String? = nil) { + metric = .init(name: name) + _properties = AnyCodable(properties ?? [:]) + identifiers = nil + self.value = value + time = analytics.date() + self.uniqueId = uniqueId ?? analytics.uuid().uuidString + } +} + +extension Event.EventName { + public var value: String { + switch self { + case .OpenedPush: return "$opened_push" + case .OpenedAppMetric: return "Opened App" + case .ViewedProductMetric: return "Viewed Product" + case .AddedToCartMetric: return "Added to Cart" + case .StartedCheckoutMetric: return "Started Checkout" + case let .CustomEvent(value): return "\(value)" + } + } +} diff --git a/Sources/KlaviyoSwift/Models/Profile.swift b/Sources/KlaviyoSwift/Models/Profile.swift new file mode 100644 index 00000000..664cb4d5 --- /dev/null +++ b/Sources/KlaviyoSwift/Models/Profile.swift @@ -0,0 +1,96 @@ +// +// File.swift +// +// +// Created by Ajay Subramanya on 8/6/24. +// + +import AnyCodable +import Foundation + +public struct Profile: Equatable { + public enum ProfileKey: Equatable, Hashable, Codable { + case firstName + case lastName + case address1 + case address2 + case title + case organization + case city + case region + case country + case zip + case image + case latitude + case longitude + case custom(customKey: String) + } + + public struct Location: Equatable { + public var address1: String? + public var address2: String? + public var city: String? + public var country: String? + public var latitude: Double? + public var longitude: Double? + public var region: String? + public var zip: String? + public var timezone: String? + public init(address1: String? = nil, + address2: String? = nil, + city: String? = nil, + country: String? = nil, + latitude: Double? = nil, + longitude: Double? = nil, + region: String? = nil, + zip: String? = nil, + timezone: String? = nil) { + self.address1 = address1 + self.address2 = address2 + self.city = city + self.country = country + self.latitude = latitude + self.longitude = longitude + self.region = region + self.zip = zip + self.timezone = timezone ?? analytics.timeZone() + } + } + + public let email: String? + public let phoneNumber: String? + public let externalId: String? + public let firstName: String? + public let lastName: String? + public let organization: String? + public let title: String? + public let image: String? + public let location: Location? + public var properties: [String: Any] { + _properties.value as! [String: Any] + } + + let _properties: AnyCodable + + public init(email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + firstName: String? = nil, + lastName: String? = nil, + organization: String? = nil, + title: String? = nil, + image: String? = nil, + location: Location? = nil, + properties: [String: Any]? = nil) { + self.email = email + self.phoneNumber = phoneNumber + self.externalId = externalId + self.firstName = firstName + self.lastName = lastName + self.organization = organization + self.title = title + self.image = image + self.location = location + _properties = AnyCodable(properties ?? [:]) + } +} From 1feb9081f9f3365d722e9912290ad2103568b840 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Tue, 6 Aug 2024 17:38:32 -0500 Subject: [PATCH 08/72] updating data models on the client side --- .../APIModels/CreateEventPayload.swift | 105 +++++---- .../APIModels/CreateProfilePayload.swift | 65 +----- .../APIModels/ProfilePayload.swift | 128 +++++++++++ .../APIModels/PushTokenPayload.swift | 100 ++++++++- .../UnregisterPushTokenPayload.swift | 74 +++++-- Sources/KlaviyoCore/InternalAPIModels.swift | 209 ------------------ Sources/KlaviyoCore/KlaviyoRequest.swift | 3 +- Sources/KlaviyoSwift/KlaviyoState.swift | 25 --- .../Models/ProfileAPIExtension.swift | 45 ++++ Sources/KlaviyoSwift/StateManagement.swift | 38 +--- 10 files changed, 405 insertions(+), 387 deletions(-) create mode 100644 Sources/KlaviyoCore/APIModels/ProfilePayload.swift create mode 100644 Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift diff --git a/Sources/KlaviyoCore/APIModels/CreateEventPayload.swift b/Sources/KlaviyoCore/APIModels/CreateEventPayload.swift index b6e43d0a..43d77929 100644 --- a/Sources/KlaviyoCore/APIModels/CreateEventPayload.swift +++ b/Sources/KlaviyoCore/APIModels/CreateEventPayload.swift @@ -1,5 +1,5 @@ // -// File.swift +// CreateEventPayload.swift // // // Created by Ajay Subramanya on 8/5/24. @@ -34,11 +34,31 @@ public struct CreateEventPayload: Equatable, Codable { } public struct Profile: Equatable, Codable { - public let data: CreateProfilePayload.Profile - - public init(attributes: PublicProfile, + public let data: ProfilePayload + + public init(email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + firstName: String? = nil, + lastName: String? = nil, + organization: String? = nil, + title: String? = nil, + image: String? = nil, + location: ProfilePayload.Attributes.Location? = nil, + properties: [String: Any]? = nil, anonymousId: String) { - data = .init(profile: attributes, anonymousId: anonymousId) + data = ProfilePayload(attributes: ProfilePayload.Attributes( + email: email, + phoneNumber: phoneNumber, + externalId: externalId, + firstName: firstName, + lastName: lastName, + organization: organization, + title: title, + image: image, + location: location, + properties: properties, + anonymousId: anonymousId)) } } @@ -48,19 +68,26 @@ public struct CreateEventPayload: Equatable, Codable { public let time: Date public let value: Double? public let uniqueId: String - public init(attributes: PublicEvent, - anonymousId: String? = nil) { - metric = Metric(name: attributes.metric.name.value) - properties = AnyCodable(attributes.properties) - value = attributes.value - time = attributes.time - uniqueId = attributes.uniqueId - - profile = .init(attributes: .init( - email: attributes.identifiers?.email, - phoneNumber: attributes.identifiers?.phoneNumber, - externalId: attributes.identifiers?.externalId), - anonymousId: anonymousId ?? "") + public init(name: String, + properties: [String: Any]? = nil, + email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + anonymousId: String? = nil, + value: Double? = nil, + time: Date? = nil, + uniqueId: String? = nil) { + metric = Metric(name: name) + self.properties = AnyCodable(properties) + self.value = value + self.time = time ?? Date() + self.uniqueId = uniqueId ?? analytics.uuid().uuidString + + profile = Profile( + email: email, + phoneNumber: phoneNumber, + externalId: externalId, + anonymousId: anonymousId ?? "") } enum CodingKeys: String, CodingKey { @@ -75,31 +102,31 @@ public struct CreateEventPayload: Equatable, Codable { var type = "event" public var attributes: Attributes - public init(event: PublicEvent, - anonymousId: String? = nil) { - attributes = .init(attributes: event, anonymousId: anonymousId) - } + // TODO: fixme +// public init(event: PublicEvent, +// anonymousId: String? = nil) { +// attributes = Attributes(attributes: event, anonymousId: anonymousId) +// } } - mutating func appendMetadataToProperties() { + mutating func appendMetadataToProperties(pushToken: String) { let context = KlaviyoAPI._appContextInfo // TODO: Fixme -// let metadata: [String: Any] = [ -// "Device ID": context.deviceId, -// "Device Manufacturer": context.manufacturer, -// "Device Model": context.deviceModel, -// "OS Name": context.osName, -// "OS Version": context.osVersion, -// "SDK Name": __klaviyoSwiftName, -// "SDK Version": __klaviyoSwiftVersion, -// "App Name": context.appName, -// "App ID": context.bundleId, -// "App Version": context.appVersion, -// "App Build": context.appBuild, -// "Push Token": analytics.state().pushTokenData?.pushToken as Any -// ] - - let metadata = [String: Any]() + let metadata: [String: Any] = [ + "Device ID": context.deviceId, + "Device Manufacturer": context.manufacturer, + "Device Model": context.deviceModel, + "OS Name": context.osName, + "OS Version": context.osVersion, + "SDK Name": __klaviyoSwiftName, + "SDK Version": __klaviyoSwiftVersion, + "App Name": context.appName, + "App ID": context.bundleId, + "App Version": context.appVersion, + "App Build": context.appBuild, + "Push Token": pushToken + ] + let originalProperties = data.attributes.properties.value as? [String: Any] ?? [:] data.attributes.properties = AnyCodable(originalProperties.merging(metadata) { _, new in new }) } diff --git a/Sources/KlaviyoCore/APIModels/CreateProfilePayload.swift b/Sources/KlaviyoCore/APIModels/CreateProfilePayload.swift index 0fdfcf44..ce198546 100644 --- a/Sources/KlaviyoCore/APIModels/CreateProfilePayload.swift +++ b/Sources/KlaviyoCore/APIModels/CreateProfilePayload.swift @@ -1,5 +1,5 @@ // -// File.swift +// CreateProfilePayload.swift // // // Created by Ajay Subramanya on 8/5/24. @@ -9,68 +9,9 @@ import AnyCodable import Foundation public struct CreateProfilePayload: Equatable, Codable { - public init(data: CreateProfilePayload.Profile) { + public init(data: ProfilePayload) { self.data = data } - /** - Internal structure which has details not needed by the API. - */ - public struct Profile: Equatable, Codable { - var type = "profile" - public struct Attributes: Equatable, Codable { - public let email: String? - public let phoneNumber: String? - public let externalId: String? - public let anonymousId: String - public var firstName: String? - public var lastName: String? - public var organization: String? - public var title: String? - public var image: String? - public var location: PublicProfile.Location? - public var properties: AnyCodable - enum CodingKeys: String, CodingKey { - case email - case phoneNumber = "phone_number" - case externalId = "external_id" - case anonymousId = "anonymous_id" - case firstName = "first_name" - case lastName = "last_name" - case organization - case title - case image - case location - case properties - } - - public init(attributes: PublicProfile, - anonymousId: String) { - email = attributes.email - phoneNumber = attributes.phoneNumber - externalId = attributes.externalId - firstName = attributes.firstName - lastName = attributes.lastName - organization = attributes.organization - title = attributes.title - image = attributes.image - location = attributes.location - properties = AnyCodable(attributes.properties) - self.anonymousId = anonymousId - } - } - - public var attributes: Attributes - public init(profile: PublicProfile, anonymousId: String) { - attributes = Attributes( - attributes: profile, - anonymousId: anonymousId) - } - - public init(attributes: Attributes) { - self.attributes = attributes - } - } - - public var data: Profile + public var data: ProfilePayload } diff --git a/Sources/KlaviyoCore/APIModels/ProfilePayload.swift b/Sources/KlaviyoCore/APIModels/ProfilePayload.swift new file mode 100644 index 00000000..3700f21e --- /dev/null +++ b/Sources/KlaviyoCore/APIModels/ProfilePayload.swift @@ -0,0 +1,128 @@ +// +// ProfilePayload.swift +// +// +// Created by Ajay Subramanya on 8/6/24. +// + +import AnyCodable +import Foundation + +/** + Internal structure which has details not needed by the API. + */ +public struct ProfilePayload: Equatable, Codable { + var type = "profile" + public struct Attributes: Equatable, Codable { + public let anonymousId: String + public let email: String? + public let phoneNumber: String? + public let externalId: String? + public var firstName: String? + public var lastName: String? + public var organization: String? + public var title: String? + public var image: String? + public var location: Location? + public var properties: AnyCodable + enum CodingKeys: String, CodingKey { + case email + case phoneNumber = "phone_number" + case externalId = "external_id" + case anonymousId = "anonymous_id" + case firstName = "first_name" + case lastName = "last_name" + case organization + case title + case image + case location + case properties + } + + public init(email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + firstName: String? = nil, + lastName: String? = nil, + organization: String? = nil, + title: String? = nil, + image: String? = nil, + location: Location? = nil, + properties: [String: Any]? = nil, + anonymousId: String) { + self.email = email + self.phoneNumber = phoneNumber + self.externalId = externalId + self.firstName = firstName + self.lastName = lastName + self.organization = organization + self.title = title + self.image = image + self.location = location + self.properties = AnyCodable(properties) + self.anonymousId = anonymousId + } + + public struct Location: Equatable, Codable { + public var address1: String? + public var address2: String? + public var city: String? + public var country: String? + public var latitude: Double? + public var longitude: Double? + public var region: String? + public var zip: String? + public var timezone: String? + public init(address1: String? = nil, + address2: String? = nil, + city: String? = nil, + country: String? = nil, + latitude: Double? = nil, + longitude: Double? = nil, + region: String? = nil, + zip: String? = nil, + timezone: String? = nil) { + self.address1 = address1 + self.address2 = address2 + self.city = city + self.country = country + self.latitude = latitude + self.longitude = longitude + self.region = region + self.zip = zip + self.timezone = timezone ?? analytics.timeZone() + } + } + } + + public var attributes: Attributes + + public init(email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + firstName: String? = nil, + lastName: String? = nil, + organization: String? = nil, + title: String? = nil, + image: String? = nil, + location: Attributes.Location? = nil, + properties: [String: Any]? = nil, + anonymousId: String) { + attributes = Attributes( + email: email, + phoneNumber: phoneNumber, + externalId: externalId, + firstName: firstName, + lastName: lastName, + organization: organization, + title: title, + image: image, + location: location, + properties: properties, + anonymousId: anonymousId) + } + + public init(attributes: Attributes) { + self.attributes = attributes + } +} diff --git a/Sources/KlaviyoCore/APIModels/PushTokenPayload.swift b/Sources/KlaviyoCore/APIModels/PushTokenPayload.swift index b55b4ac1..d02b45b7 100644 --- a/Sources/KlaviyoCore/APIModels/PushTokenPayload.swift +++ b/Sources/KlaviyoCore/APIModels/PushTokenPayload.swift @@ -1,5 +1,5 @@ // -// File.swift +// PushTokenPayload.swift // // // Created by Ajay Subramanya on 8/5/24. @@ -17,13 +17,31 @@ public struct PushTokenPayload: Equatable, Codable { public init(pushToken: String, enablement: String, background: String, - profile: PublicProfile, + email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + firstName: String? = nil, + lastName: String? = nil, + organization: String? = nil, + title: String? = nil, + image: String? = nil, + location: ProfilePayload.Attributes.Location? = nil, + properties: [String: Any]? = nil, anonymousId: String) { - data = .init( + data = PushToken( pushToken: pushToken, enablement: enablement, background: background, - profile: profile, + email: email, + phoneNumber: phoneNumber, + externalId: externalId, + firstName: firstName, + lastName: lastName, + organization: organization, + title: title, + image: image, + location: location, + properties: properties, anonymousId: anonymousId) } @@ -34,13 +52,31 @@ public struct PushTokenPayload: Equatable, Codable { public init(pushToken: String, enablement: String, background: String, - profile: PublicProfile, + email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + firstName: String? = nil, + lastName: String? = nil, + organization: String? = nil, + title: String? = nil, + image: String? = nil, + location: ProfilePayload.Attributes.Location? = nil, + properties: [String: Any]? = nil, anonymousId: String) { - attributes = .init( + attributes = Attributes( pushToken: pushToken, enablement: enablement, background: background, - profile: profile, + email: email, + phoneNumber: phoneNumber, + externalId: externalId, + firstName: firstName, + lastName: lastName, + organization: organization, + title: title, + image: image, + location: location, + properties: properties, anonymousId: anonymousId) } @@ -66,22 +102,62 @@ public struct PushTokenPayload: Equatable, Codable { public init(pushToken: String, enablement: String, background: String, - profile: PublicProfile, + email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + firstName: String? = nil, + lastName: String? = nil, + organization: String? = nil, + title: String? = nil, + image: String? = nil, + location: ProfilePayload.Attributes.Location? = nil, + properties: [String: Any]? = nil, anonymousId: String) { token = pushToken enablementStatus = enablement backgroundStatus = background - self.profile = .init(attributes: profile, anonymousId: anonymousId) + profile = Profile( + email: email, + phoneNumber: phoneNumber, + externalId: externalId, + firstName: firstName, + lastName: lastName, + organization: organization, + title: title, + image: image, + location: location, + properties: properties, + anonymousId: anonymousId) deviceMetadata = .init(context: KlaviyoAPI._appContextInfo) } public struct Profile: Equatable, Codable { - public let data: CreateProfilePayload.Profile + public let data: ProfilePayload - public init(attributes: PublicProfile, + public init(email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + firstName: String? = nil, + lastName: String? = nil, + organization: String? = nil, + title: String? = nil, + image: String? = nil, + location: ProfilePayload.Attributes.Location? = nil, + properties: [String: Any]? = nil, anonymousId: String) { - data = .init(profile: attributes, anonymousId: anonymousId) + data = ProfilePayload(attributes: ProfilePayload.Attributes( + email: email, + phoneNumber: phoneNumber, + externalId: externalId, + firstName: firstName, + lastName: lastName, + organization: organization, + title: title, + image: image, + location: location, + properties: properties, + anonymousId: anonymousId)) } } diff --git a/Sources/KlaviyoCore/APIModels/UnregisterPushTokenPayload.swift b/Sources/KlaviyoCore/APIModels/UnregisterPushTokenPayload.swift index d14121eb..cad55362 100644 --- a/Sources/KlaviyoCore/APIModels/UnregisterPushTokenPayload.swift +++ b/Sources/KlaviyoCore/APIModels/UnregisterPushTokenPayload.swift @@ -1,5 +1,5 @@ // -// File.swift +// UnregisterPushTokenPayload.swift // // // Created by Ajay Subramanya on 8/5/24. @@ -11,24 +11,32 @@ public struct UnregisterPushTokenPayload: Equatable, Codable { public let data: PushToken public init(pushToken: String, - profile: PublicProfile, + email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, anonymousId: String) { - data = .init( + data = PushToken( pushToken: pushToken, - profile: profile, + email: email, + phoneNumber: phoneNumber, + externalId: externalId, anonymousId: anonymousId) } public struct PushToken: Equatable, Codable { var type = "push-token-unregister" - public var attributes: Attributes + public let attributes: Attributes public init(pushToken: String, - profile: PublicProfile, + email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, anonymousId: String) { - attributes = .init( + attributes = Attributes( pushToken: pushToken, - profile: profile, + email: email, + phoneNumber: phoneNumber, + externalId: externalId, anonymousId: anonymousId) } @@ -46,18 +54,58 @@ public struct UnregisterPushTokenPayload: Equatable, Codable { } public init(pushToken: String, - profile: PublicProfile, + email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + firstName: String? = nil, + lastName: String? = nil, + organization: String? = nil, + title: String? = nil, + image: String? = nil, + location: ProfilePayload.Attributes.Location? = nil, + properties: [String: Any]? = nil, anonymousId: String) { token = pushToken - self.profile = .init(attributes: profile, anonymousId: anonymousId) + profile = Profile( + email: email, + phoneNumber: phoneNumber, + externalId: externalId, + firstName: firstName, + lastName: lastName, + organization: organization, + title: title, + image: image, + location: location, + properties: properties, + anonymousId: anonymousId) } public struct Profile: Equatable, Codable { - public let data: CreateProfilePayload.Profile + public let data: ProfilePayload - public init(attributes: PublicProfile, + public init(email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + firstName: String? = nil, + lastName: String? = nil, + organization: String? = nil, + title: String? = nil, + image: String? = nil, + location: ProfilePayload.Attributes.Location? = nil, + properties: [String: Any]? = nil, anonymousId: String) { - data = .init(profile: attributes, anonymousId: anonymousId) + data = ProfilePayload(attributes: ProfilePayload.Attributes( + email: email, + phoneNumber: phoneNumber, + externalId: externalId, + firstName: firstName, + lastName: lastName, + organization: organization, + title: title, + image: image, + location: location, + properties: properties, + anonymousId: anonymousId)) } } } diff --git a/Sources/KlaviyoCore/InternalAPIModels.swift b/Sources/KlaviyoCore/InternalAPIModels.swift index ee781cf5..7d5e4b44 100644 --- a/Sources/KlaviyoCore/InternalAPIModels.swift +++ b/Sources/KlaviyoCore/InternalAPIModels.swift @@ -19,212 +19,3 @@ public enum KlaviyoEndpoint: Equatable, Codable { extension KlaviyoAPI { public static let _appContextInfo = analytics.appContextInfo() } - -extension PublicProfile.Location: Codable { - public init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - address1 = try values.decode(String.self, forKey: .address1) - address2 = try values.decode(String.self, forKey: .address2) - city = try values.decode(String.self, forKey: .city) - latitude = try values.decode(Double.self, forKey: .latitude) - longitude = try values.decode(Double.self, forKey: .longitude) - region = try values.decode(String.self, forKey: .region) - self.zip = try values.decode(String.self, forKey: .zip) - timezone = try values.decode(String.self, forKey: .timezone) - country = try values.decode(String.self, forKey: .country) - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(address1, forKey: .address1) - try container.encode(address2, forKey: .address2) - try container.encode(city, forKey: .city) - try container.encode(latitude, forKey: .latitude) - try container.encode(longitude, forKey: .longitude) - try container.encode(region, forKey: .region) - try container.encode(zip, forKey: .zip) - try container.encode(timezone, forKey: .timezone) - try container.encode(country, forKey: .country) - } - - enum CodingKeys: CodingKey { - case address1 - case address2 - case city - case country - case latitude - case longitude - case region - case zip - case timezone - } -} - -public struct PublicEvent: Equatable { - public enum EventName: Equatable { - case OpenedPush - case OpenedAppMetric - case ViewedProductMetric - case AddedToCartMetric - case StartedCheckoutMetric - case CustomEvent(String) - } - - public struct Metric: Equatable { - public let name: EventName - - public init(name: EventName) { - self.name = name - } - } - - struct Identifiers: Equatable { - public let email: String? - public let phoneNumber: String? - public let externalId: String? - public init(email: String? = nil, - phoneNumber: String? = nil, - externalId: String? = nil) { - self.email = email - self.phoneNumber = phoneNumber - self.externalId = externalId - } - } - - public let metric: Metric - public var properties: [String: Any] { - _properties.value as! [String: Any] - } - - private let _properties: AnyCodable - public var time: Date - public let value: Double? - public let uniqueId: String - let identifiers: Identifiers? - - init(name: EventName, - properties: [String: Any]? = nil, - identifiers: Identifiers? = nil, - value: Double? = nil, - time: Date? = nil, - uniqueId: String? = nil) { - metric = .init(name: name) - _properties = AnyCodable(properties ?? [:]) - self.time = time ?? analytics.date() - self.value = value - self.uniqueId = uniqueId ?? analytics.uuid().uuidString - self.identifiers = identifiers - } - - public init(name: EventName, - properties: [String: Any]? = nil, - value: Double? = nil, - uniqueId: String? = nil) { - metric = .init(name: name) - _properties = AnyCodable(properties ?? [:]) - identifiers = nil - self.value = value - time = analytics.date() - self.uniqueId = uniqueId ?? analytics.uuid().uuidString - } -} - -public struct PublicProfile: Equatable { - public enum ProfileKey: Equatable, Hashable, Codable { - case firstName - case lastName - case address1 - case address2 - case title - case organization - case city - case region - case country - case zip - case image - case latitude - case longitude - case custom(customKey: String) - } - - public struct Location: Equatable { - public var address1: String? - public var address2: String? - public var city: String? - public var country: String? - public var latitude: Double? - public var longitude: Double? - public var region: String? - public var zip: String? - public var timezone: String? - public init(address1: String? = nil, - address2: String? = nil, - city: String? = nil, - country: String? = nil, - latitude: Double? = nil, - longitude: Double? = nil, - region: String? = nil, - zip: String? = nil, - timezone: String? = nil) { - self.address1 = address1 - self.address2 = address2 - self.city = city - self.country = country - self.latitude = latitude - self.longitude = longitude - self.region = region - self.zip = zip - self.timezone = timezone ?? analytics.timeZone() - } - } - - public let email: String? - public let phoneNumber: String? - public let externalId: String? - public let firstName: String? - public let lastName: String? - public let organization: String? - public let title: String? - public let image: String? - public let location: Location? - public var properties: [String: Any] { - _properties.value as! [String: Any] - } - - let _properties: AnyCodable - - public init(email: String? = nil, - phoneNumber: String? = nil, - externalId: String? = nil, - firstName: String? = nil, - lastName: String? = nil, - organization: String? = nil, - title: String? = nil, - image: String? = nil, - location: Location? = nil, - properties: [String: Any]? = nil) { - self.email = email - self.phoneNumber = phoneNumber - self.externalId = externalId - self.firstName = firstName - self.lastName = lastName - self.organization = organization - self.title = title - self.image = image - self.location = location - _properties = AnyCodable(properties ?? [:]) - } -} - -extension PublicEvent.EventName { - public var value: String { - switch self { - case .OpenedPush: return "$opened_push" - case .OpenedAppMetric: return "Opened App" - case .ViewedProductMetric: return "Viewed Product" - case .AddedToCartMetric: return "Added to Cart" - case .StartedCheckoutMetric: return "Started Checkout" - case let .CustomEvent(value): return "\(value)" - } - } -} diff --git a/Sources/KlaviyoCore/KlaviyoRequest.swift b/Sources/KlaviyoCore/KlaviyoRequest.swift index 3385cd53..9e4cae82 100644 --- a/Sources/KlaviyoCore/KlaviyoRequest.swift +++ b/Sources/KlaviyoCore/KlaviyoRequest.swift @@ -70,7 +70,8 @@ public struct KlaviyoRequest: Equatable, Codable { return try analytics.encodeJSON(AnyEncodable(payload)) case var .createEvent(payload): - payload.appendMetadataToProperties() + // TODO: fixme get push token here + payload.appendMetadataToProperties(pushToken: "") return try analytics.encodeJSON(AnyEncodable(payload)) case let .registerPushToken(payload): diff --git a/Sources/KlaviyoSwift/KlaviyoState.swift b/Sources/KlaviyoSwift/KlaviyoState.swift index cf1e4de8..0e06ca14 100644 --- a/Sources/KlaviyoSwift/KlaviyoState.swift +++ b/Sources/KlaviyoSwift/KlaviyoState.swift @@ -473,28 +473,3 @@ extension String { return !incoming.isEmpty && incoming != state } } - -// TODO: FIXME -extension PublicProfile { - public init(profile: Profile) { - self.init( - email: profile.email, - phoneNumber: profile.phoneNumber, - externalId: profile.externalId, - firstName: profile.firstName, - lastName: profile.lastName, - organization: profile.organization, - title: profile.title, - image: profile.image, - location: profile.location.map { Location(address1: $0.address1, - address2: $0.address2, - city: $0.city, - country: $0.country, - latitude: $0.latitude, - longitude: $0.longitude, - region: $0.region, - zip: $0.zip, - timezone: $0.timezone) }, - properties: profile.properties) - } -} diff --git a/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift b/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift new file mode 100644 index 00000000..34754e90 --- /dev/null +++ b/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift @@ -0,0 +1,45 @@ +// +// File.swift +// +// +// Created by Ajay Subramanya on 8/6/24. +// + +import Foundation +import KlaviyoCore + +extension Profile { + func toAPIModel( + email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + anonymousId: String) -> ProfilePayload { + ProfilePayload( + email: email ?? self.email, + phoneNumber: phoneNumber ?? self.phoneNumber, + externalId: externalId ?? self.externalId, + firstName: firstName, + lastName: lastName, + organization: organization, + title: title, + image: image, + location: location?.toAPILocation, + properties: properties, + anonymousId: anonymousId) + } +} + +extension Profile.Location { + var toAPILocation: ProfilePayload.Attributes.Location { + ProfilePayload.Attributes.Location( + address1: address1, + address2: address2, + city: city, + country: country, + latitude: latitude, + longitude: longitude, + region: region, + zip: self.zip, + timezone: timezone) + } +} diff --git a/Sources/KlaviyoSwift/StateManagement.swift b/Sources/KlaviyoSwift/StateManagement.swift index d34df1ca..fda2f26a 100644 --- a/Sources/KlaviyoSwift/StateManagement.swift +++ b/Sources/KlaviyoSwift/StateManagement.swift @@ -425,21 +425,23 @@ struct KlaviyoReducer: ReducerProtocol { let request: KlaviyoRequest! if let tokenData = pushTokenData { + let payload = PushTokenPayload( + pushToken: tokenData.pushToken, + enablement: tokenData.pushEnablement.rawValue, + background: tokenData.pushBackground.rawValue, + anonymousId: anonymousId) request = KlaviyoRequest( apiKey: apiKey, - endpoint: .registerPushToken(.init( - pushToken: tokenData.pushToken, - enablement: tokenData.pushEnablement.rawValue, - background: tokenData.pushBackground.rawValue, - profile: PublicProfile(profile: profile.profile(from: state)), - anonymousId: anonymousId) - )) + endpoint: KlaviyoEndpoint.registerPushToken(payload)) } else { + let payload = profile.toAPIModel( + email: state.email, + phoneNumber: state.phoneNumber, + externalId: state.externalId, + anonymousId: anonymousId) request = KlaviyoRequest( apiKey: apiKey, - endpoint: .createProfile( - .init(data: .init(profile: PublicProfile(profile: profile.profile(from: state)), anonymousId: anonymousId)) - )) + endpoint: KlaviyoEndpoint.createProfile(CreateProfilePayload(data: payload))) } state.enqueueRequest(request: request) @@ -502,19 +504,3 @@ extension Event { uniqueId: uniqueId) } } - -extension Profile { - fileprivate func profile(from state: KlaviyoState) -> Profile { - Profile( - email: state.email, - phoneNumber: state.phoneNumber, - externalId: state.externalId, - firstName: firstName, - lastName: lastName, - organization: organization, - title: title, - image: image, - location: location, - properties: properties) - } -} From a3c1df68736265d5cb7ec5bc05781db7b6f27b71 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 7 Aug 2024 10:27:35 -0500 Subject: [PATCH 09/72] working profile mapping --- .../APIModels/PushTokenPayload.swift | 100 ++---------------- Sources/KlaviyoSwift/KlaviyoState.swift | 38 +++---- Sources/KlaviyoSwift/StateManagement.swift | 15 +-- 3 files changed, 36 insertions(+), 117 deletions(-) diff --git a/Sources/KlaviyoCore/APIModels/PushTokenPayload.swift b/Sources/KlaviyoCore/APIModels/PushTokenPayload.swift index d02b45b7..6baacb0e 100644 --- a/Sources/KlaviyoCore/APIModels/PushTokenPayload.swift +++ b/Sources/KlaviyoCore/APIModels/PushTokenPayload.swift @@ -17,32 +17,12 @@ public struct PushTokenPayload: Equatable, Codable { public init(pushToken: String, enablement: String, background: String, - email: String? = nil, - phoneNumber: String? = nil, - externalId: String? = nil, - firstName: String? = nil, - lastName: String? = nil, - organization: String? = nil, - title: String? = nil, - image: String? = nil, - location: ProfilePayload.Attributes.Location? = nil, - properties: [String: Any]? = nil, - anonymousId: String) { + profile: ProfilePayload) { data = PushToken( pushToken: pushToken, enablement: enablement, background: background, - email: email, - phoneNumber: phoneNumber, - externalId: externalId, - firstName: firstName, - lastName: lastName, - organization: organization, - title: title, - image: image, - location: location, - properties: properties, - anonymousId: anonymousId) + profile: profile) } public struct PushToken: Equatable, Codable { @@ -52,32 +32,12 @@ public struct PushTokenPayload: Equatable, Codable { public init(pushToken: String, enablement: String, background: String, - email: String? = nil, - phoneNumber: String? = nil, - externalId: String? = nil, - firstName: String? = nil, - lastName: String? = nil, - organization: String? = nil, - title: String? = nil, - image: String? = nil, - location: ProfilePayload.Attributes.Location? = nil, - properties: [String: Any]? = nil, - anonymousId: String) { + profile: ProfilePayload) { attributes = Attributes( pushToken: pushToken, enablement: enablement, background: background, - email: email, - phoneNumber: phoneNumber, - externalId: externalId, - firstName: firstName, - lastName: lastName, - organization: organization, - title: title, - image: image, - location: location, - properties: properties, - anonymousId: anonymousId) + profile: profile) } public struct Attributes: Equatable, Codable { @@ -102,62 +62,20 @@ public struct PushTokenPayload: Equatable, Codable { public init(pushToken: String, enablement: String, background: String, - email: String? = nil, - phoneNumber: String? = nil, - externalId: String? = nil, - firstName: String? = nil, - lastName: String? = nil, - organization: String? = nil, - title: String? = nil, - image: String? = nil, - location: ProfilePayload.Attributes.Location? = nil, - properties: [String: Any]? = nil, - anonymousId: String) { + profile: ProfilePayload) { token = pushToken enablementStatus = enablement backgroundStatus = background - profile = Profile( - email: email, - phoneNumber: phoneNumber, - externalId: externalId, - firstName: firstName, - lastName: lastName, - organization: organization, - title: title, - image: image, - location: location, - properties: properties, - anonymousId: anonymousId) - deviceMetadata = .init(context: KlaviyoAPI._appContextInfo) + self.profile = Profile(data: profile) + deviceMetadata = MetaData(context: KlaviyoAPI._appContextInfo) } public struct Profile: Equatable, Codable { public let data: ProfilePayload - public init(email: String? = nil, - phoneNumber: String? = nil, - externalId: String? = nil, - firstName: String? = nil, - lastName: String? = nil, - organization: String? = nil, - title: String? = nil, - image: String? = nil, - location: ProfilePayload.Attributes.Location? = nil, - properties: [String: Any]? = nil, - anonymousId: String) { - data = ProfilePayload(attributes: ProfilePayload.Attributes( - email: email, - phoneNumber: phoneNumber, - externalId: externalId, - firstName: firstName, - lastName: lastName, - organization: organization, - title: title, - image: image, - location: location, - properties: properties, - anonymousId: anonymousId)) + public init(data: ProfilePayload) { + self.data = data } } diff --git a/Sources/KlaviyoSwift/KlaviyoState.swift b/Sources/KlaviyoSwift/KlaviyoState.swift index 0e06ca14..cfefa58c 100644 --- a/Sources/KlaviyoSwift/KlaviyoState.swift +++ b/Sources/KlaviyoSwift/KlaviyoState.swift @@ -229,14 +229,15 @@ struct KlaviyoState: Equatable, Codable { if let apiKey = apiKey, let anonymousId = anonymousId, let tokenData = previousPushTokenData { + let payload = PushTokenPayload( + pushToken: tokenData.pushToken, + enablement: tokenData.pushEnablement.rawValue, + background: tokenData.pushBackground.rawValue, + profile: Profile().toAPIModel(anonymousId: anonymousId)) + let request = KlaviyoRequest( apiKey: apiKey, - endpoint: .registerPushToken(.init( - pushToken: tokenData.pushToken, - enablement: tokenData.pushEnablement.rawValue, - background: tokenData.pushBackground.rawValue, - profile: .init(), anonymousId: anonymousId) - )) + endpoint: KlaviyoEndpoint.registerPushToken(payload)) enqueueRequest(request: request) } @@ -258,16 +259,14 @@ struct KlaviyoState: Equatable, Codable { } func buildProfileRequest(apiKey: String, anonymousId: String, properties: [String: Any] = [:]) -> KlaviyoRequest { - let payload = CreateProfilePayload( - data: .init( - profile: PublicProfile( - email: email, - phoneNumber: phoneNumber, - externalId: externalId, - properties: properties), - anonymousId: anonymousId) - ) - let endpoint = KlaviyoEndpoint.createProfile(payload) + let payload = ProfilePayload( + email: email, + phoneNumber: phoneNumber, + externalId: externalId, + properties: properties, + anonymousId: anonymousId) + + let endpoint = KlaviyoEndpoint.createProfile(CreateProfilePayload(data: payload)) return KlaviyoRequest(apiKey: apiKey, endpoint: endpoint) } @@ -291,8 +290,7 @@ struct KlaviyoState: Equatable, Codable { pushToken: pushToken, enablement: enablement.rawValue, background: environment.getBackgroundSetting().rawValue, - profile: PublicProfile(profile: profile), - anonymousId: anonymousId) + profile: profile.toAPIModel(anonymousId: anonymousId)) let endpoint = KlaviyoEndpoint.registerPushToken(payload) return KlaviyoRequest(apiKey: apiKey, endpoint: endpoint) } @@ -300,7 +298,9 @@ struct KlaviyoState: Equatable, Codable { func buildUnregisterRequest(apiKey: String, anonymousId: String, pushToken: String) -> KlaviyoRequest { let payload = UnregisterPushTokenPayload( pushToken: pushToken, - profile: .init(email: email, phoneNumber: phoneNumber, externalId: externalId), + email: email, + phoneNumber: phoneNumber, + externalId: externalId, anonymousId: anonymousId) let endpoint = KlaviyoEndpoint.unregisterPushToken(payload) return KlaviyoRequest(apiKey: apiKey, endpoint: endpoint) diff --git a/Sources/KlaviyoSwift/StateManagement.swift b/Sources/KlaviyoSwift/StateManagement.swift index fda2f26a..5a2bd18c 100644 --- a/Sources/KlaviyoSwift/StateManagement.swift +++ b/Sources/KlaviyoSwift/StateManagement.swift @@ -424,24 +424,25 @@ struct KlaviyoReducer: ReducerProtocol { } let request: KlaviyoRequest! + let profilePayload = profile.toAPIModel( + email: state.email, + phoneNumber: state.phoneNumber, + externalId: state.externalId, + anonymousId: anonymousId) + if let tokenData = pushTokenData { let payload = PushTokenPayload( pushToken: tokenData.pushToken, enablement: tokenData.pushEnablement.rawValue, background: tokenData.pushBackground.rawValue, - anonymousId: anonymousId) + profile: profilePayload) request = KlaviyoRequest( apiKey: apiKey, endpoint: KlaviyoEndpoint.registerPushToken(payload)) } else { - let payload = profile.toAPIModel( - email: state.email, - phoneNumber: state.phoneNumber, - externalId: state.externalId, - anonymousId: anonymousId) request = KlaviyoRequest( apiKey: apiKey, - endpoint: KlaviyoEndpoint.createProfile(CreateProfilePayload(data: payload))) + endpoint: KlaviyoEndpoint.createProfile(CreateProfilePayload(data: profilePayload))) } state.enqueueRequest(request: request) From c2235e6076e4d6d417d2ab99a2a55a315e6fef0b Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 7 Aug 2024 11:01:49 -0500 Subject: [PATCH 10/72] fixed event models --- .../APIModels/CreateEventPayload.swift | 61 +++++++++---------- Sources/KlaviyoSwift/StateManagement.swift | 21 +++++-- 2 files changed, 45 insertions(+), 37 deletions(-) diff --git a/Sources/KlaviyoCore/APIModels/CreateEventPayload.swift b/Sources/KlaviyoCore/APIModels/CreateEventPayload.swift index 43d77929..516c9910 100644 --- a/Sources/KlaviyoCore/APIModels/CreateEventPayload.swift +++ b/Sources/KlaviyoCore/APIModels/CreateEventPayload.swift @@ -36,29 +36,8 @@ public struct CreateEventPayload: Equatable, Codable { public struct Profile: Equatable, Codable { public let data: ProfilePayload - public init(email: String? = nil, - phoneNumber: String? = nil, - externalId: String? = nil, - firstName: String? = nil, - lastName: String? = nil, - organization: String? = nil, - title: String? = nil, - image: String? = nil, - location: ProfilePayload.Attributes.Location? = nil, - properties: [String: Any]? = nil, - anonymousId: String) { - data = ProfilePayload(attributes: ProfilePayload.Attributes( - email: email, - phoneNumber: phoneNumber, - externalId: externalId, - firstName: firstName, - lastName: lastName, - organization: organization, - title: title, - image: image, - location: location, - properties: properties, - anonymousId: anonymousId)) + public init(data: ProfilePayload) { + self.data = data } } @@ -82,12 +61,13 @@ public struct CreateEventPayload: Equatable, Codable { self.value = value self.time = time ?? Date() self.uniqueId = uniqueId ?? analytics.uuid().uuidString - profile = Profile( - email: email, - phoneNumber: phoneNumber, - externalId: externalId, - anonymousId: anonymousId ?? "") + data: ProfilePayload( + email: email, + phoneNumber: phoneNumber, + externalId: externalId, + anonymousId: anonymousId ?? "") + ) } enum CodingKeys: String, CodingKey { @@ -102,11 +82,26 @@ public struct CreateEventPayload: Equatable, Codable { var type = "event" public var attributes: Attributes - // TODO: fixme -// public init(event: PublicEvent, -// anonymousId: String? = nil) { -// attributes = Attributes(attributes: event, anonymousId: anonymousId) -// } + public init(name: String, + properties: [String: Any]? = nil, + email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + anonymousId: String? = nil, + value: Double? = nil, + time: Date? = nil, + uniqueId: String? = nil) { + attributes = Attributes( + name: name, + properties: properties, + email: email, + phoneNumber: phoneNumber, + externalId: externalId, + anonymousId: anonymousId, + value: value, + time: time, + uniqueId: uniqueId) + } } mutating func appendMetadataToProperties(pushToken: String) { diff --git a/Sources/KlaviyoSwift/StateManagement.swift b/Sources/KlaviyoSwift/StateManagement.swift index 5a2bd18c..cd35eedb 100644 --- a/Sources/KlaviyoSwift/StateManagement.swift +++ b/Sources/KlaviyoSwift/StateManagement.swift @@ -395,10 +395,23 @@ struct KlaviyoReducer: ReducerProtocol { } event = event.updateEventWithState(state: &state) -// state.enqueueRequest(request: .init(apiKey: apiKey, -// endpoint: .createEvent( -// .init(data: .init(event: event, anonymousId: anonymousId)) -// ))) + + let payload = CreateEventPayload( + data: CreateEventPayload.Event( + name: event.metric.name.value, + properties: event.properties, + email: event.identifiers?.email, + phoneNumber: event.identifiers?.phoneNumber, + externalId: event.identifiers?.externalId, + anonymousId: anonymousId, + value: event.value, + time: event.time, + uniqueId: event.uniqueId)) + + let endpoint = KlaviyoEndpoint.createEvent(payload) + let request = KlaviyoRequest(apiKey: apiKey, endpoint: endpoint) + + state.enqueueRequest(request: request) /* if we receive an opened push event we want to flush the queue right away so that From 396f65ba0e8bc55ae50e967b7b7c19494aadc959 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 7 Aug 2024 11:18:12 -0500 Subject: [PATCH 11/72] cleaned up some models --- .../APIModels/PushTokenPayload.swift | 30 +++++++++---------- .../UnregisterPushTokenPayload.swift | 26 ++++++++-------- Sources/KlaviyoSwift/Models/Event.swift | 2 +- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Sources/KlaviyoCore/APIModels/PushTokenPayload.swift b/Sources/KlaviyoCore/APIModels/PushTokenPayload.swift index 6baacb0e..fe7aca73 100644 --- a/Sources/KlaviyoCore/APIModels/PushTokenPayload.swift +++ b/Sources/KlaviyoCore/APIModels/PushTokenPayload.swift @@ -8,23 +8,8 @@ import Foundation public struct PushTokenPayload: Equatable, Codable { - public init(data: PushTokenPayload.PushToken) { - self.data = data - } - public let data: PushToken - public init(pushToken: String, - enablement: String, - background: String, - profile: ProfilePayload) { - data = PushToken( - pushToken: pushToken, - enablement: enablement, - background: background, - profile: profile) - } - public struct PushToken: Equatable, Codable { var type = "push-token" public var attributes: Attributes @@ -125,4 +110,19 @@ public struct PushTokenPayload: Equatable, Codable { } } } + + public init(data: PushTokenPayload.PushToken) { + self.data = data + } + + public init(pushToken: String, + enablement: String, + background: String, + profile: ProfilePayload) { + data = PushToken( + pushToken: pushToken, + enablement: enablement, + background: background, + profile: profile) + } } diff --git a/Sources/KlaviyoCore/APIModels/UnregisterPushTokenPayload.swift b/Sources/KlaviyoCore/APIModels/UnregisterPushTokenPayload.swift index cad55362..4e17044b 100644 --- a/Sources/KlaviyoCore/APIModels/UnregisterPushTokenPayload.swift +++ b/Sources/KlaviyoCore/APIModels/UnregisterPushTokenPayload.swift @@ -10,19 +10,6 @@ import Foundation public struct UnregisterPushTokenPayload: Equatable, Codable { public let data: PushToken - public init(pushToken: String, - email: String? = nil, - phoneNumber: String? = nil, - externalId: String? = nil, - anonymousId: String) { - data = PushToken( - pushToken: pushToken, - email: email, - phoneNumber: phoneNumber, - externalId: externalId, - anonymousId: anonymousId) - } - public struct PushToken: Equatable, Codable { var type = "push-token-unregister" public let attributes: Attributes @@ -110,4 +97,17 @@ public struct UnregisterPushTokenPayload: Equatable, Codable { } } } + + public init(pushToken: String, + email: String? = nil, + phoneNumber: String? = nil, + externalId: String? = nil, + anonymousId: String) { + data = PushToken( + pushToken: pushToken, + email: email, + phoneNumber: phoneNumber, + externalId: externalId, + anonymousId: anonymousId) + } } diff --git a/Sources/KlaviyoSwift/Models/Event.swift b/Sources/KlaviyoSwift/Models/Event.swift index 809f8dc1..78073657 100644 --- a/Sources/KlaviyoSwift/Models/Event.swift +++ b/Sources/KlaviyoSwift/Models/Event.swift @@ -78,7 +78,7 @@ public struct Event: Equatable { } extension Event.EventName { - public var value: String { + var value: String { switch self { case .OpenedPush: return "$opened_push" case .OpenedAppMetric: return "Opened App" From c26adfeec96c9b990141eb217f0151f8d0b611b4 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 7 Aug 2024 11:28:18 -0500 Subject: [PATCH 12/72] added some comments --- Sources/KlaviyoSwift/Models/Event.swift | 6 ++++++ Sources/KlaviyoSwift/Models/Profile.swift | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Sources/KlaviyoSwift/Models/Event.swift b/Sources/KlaviyoSwift/Models/Event.swift index 78073657..2349618e 100644 --- a/Sources/KlaviyoSwift/Models/Event.swift +++ b/Sources/KlaviyoSwift/Models/Event.swift @@ -64,6 +64,12 @@ public struct Event: Equatable { self.identifiers = identifiers } + /// Create a new event to track a profile's activity, the SDK will associate the event with any identified/anonymous profile in the SDK state + /// - Parameters: + /// - name: Name of the event. Must be less than 128 characters., pick from `Event.EventName` which can also contain custom events + /// - properties: Properties of this event. + /// - value: A numeric, monetary value to associate with this event. For example, the dollar amount of a purchase. + /// - uniqueId: A unique identifier for an event public init(name: EventName, properties: [String: Any]? = nil, value: Double? = nil, diff --git a/Sources/KlaviyoSwift/Models/Profile.swift b/Sources/KlaviyoSwift/Models/Profile.swift index 664cb4d5..ae223a53 100644 --- a/Sources/KlaviyoSwift/Models/Profile.swift +++ b/Sources/KlaviyoSwift/Models/Profile.swift @@ -36,6 +36,17 @@ public struct Profile: Equatable { public var region: String? public var zip: String? public var timezone: String? + + /// - Parameters: + /// - address1: First line of street address + /// - address2: Second line of street address + /// - city: city name + /// - country: country name + /// - latitude: Latitude coordinate. We recommend providing a precision of four decimal places. + /// - longitude: Longitude coordinate. We recommend providing a precision of four decimal places. + /// - region: Region within a country, such as state or province + /// - zip: Zip code + /// - timezone: Time zone name. We recommend using time zones from the IANA Time Zone Database. public init(address1: String? = nil, address2: String? = nil, city: String? = nil, @@ -72,6 +83,18 @@ public struct Profile: Equatable { let _properties: AnyCodable + /// Create or update properties about a profile without tracking an associated event. + /// - Parameters: + /// - email: Individual's email address + /// - phoneNumber: Individual's phone number in E.164 format + /// - externalId: A unique identifier used by customers to associate Klaviyo profiles with profiles in an external system, such as a point-of-sale system. Format varies based on the external system. + /// - firstName: Individual's first name + /// - lastName: Individual's last name + /// - organization: Individual's organization name + /// - title: Individual's title + /// - image: URL pointing to the location of a profile image + /// - location: Individual location + /// - properties: An object containing key/value pairs for any custom properties assigned to this profile public init(email: String? = nil, phoneNumber: String? = nil, externalId: String? = nil, From 488907aa804f776ab4c787d96f508a2adc463095 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 8 Aug 2024 10:29:57 -0500 Subject: [PATCH 13/72] consolidated environment --- .../APIModels/CreateEventPayload.swift | 2 +- .../APIModels/ProfilePayload.swift | 2 +- Sources/KlaviyoCore/InternalAPIModels.swift | 2 +- Sources/KlaviyoCore/KlaviyoAPI.swift | 4 +- Sources/KlaviyoCore/KlaviyoEnvironment.swift | 61 +++++++++---------- Sources/KlaviyoCore/KlaviyoRequest.swift | 18 +++--- .../KlaviyoSwift/AnalyticsEnvironment.swift | 42 ------------- Sources/KlaviyoSwift/KlaviyoState.swift | 12 ++-- Sources/KlaviyoSwift/Models/Event.swift | 9 +-- Sources/KlaviyoSwift/Models/Profile.swift | 3 +- Sources/KlaviyoSwift/StateManagement.swift | 6 +- 11 files changed, 59 insertions(+), 102 deletions(-) delete mode 100644 Sources/KlaviyoSwift/AnalyticsEnvironment.swift diff --git a/Sources/KlaviyoCore/APIModels/CreateEventPayload.swift b/Sources/KlaviyoCore/APIModels/CreateEventPayload.swift index 516c9910..5b049449 100644 --- a/Sources/KlaviyoCore/APIModels/CreateEventPayload.swift +++ b/Sources/KlaviyoCore/APIModels/CreateEventPayload.swift @@ -60,7 +60,7 @@ public struct CreateEventPayload: Equatable, Codable { self.properties = AnyCodable(properties) self.value = value self.time = time ?? Date() - self.uniqueId = uniqueId ?? analytics.uuid().uuidString + self.uniqueId = uniqueId ?? environment.uuid().uuidString profile = Profile( data: ProfilePayload( email: email, diff --git a/Sources/KlaviyoCore/APIModels/ProfilePayload.swift b/Sources/KlaviyoCore/APIModels/ProfilePayload.swift index 3700f21e..25a6cb85 100644 --- a/Sources/KlaviyoCore/APIModels/ProfilePayload.swift +++ b/Sources/KlaviyoCore/APIModels/ProfilePayload.swift @@ -90,7 +90,7 @@ public struct ProfilePayload: Equatable, Codable { self.longitude = longitude self.region = region self.zip = zip - self.timezone = timezone ?? analytics.timeZone() + self.timezone = timezone ?? environment.timeZone() } } } diff --git a/Sources/KlaviyoCore/InternalAPIModels.swift b/Sources/KlaviyoCore/InternalAPIModels.swift index 7d5e4b44..482c692d 100644 --- a/Sources/KlaviyoCore/InternalAPIModels.swift +++ b/Sources/KlaviyoCore/InternalAPIModels.swift @@ -17,5 +17,5 @@ public enum KlaviyoEndpoint: Equatable, Codable { } extension KlaviyoAPI { - public static let _appContextInfo = analytics.appContextInfo() + public static let _appContextInfo = environment.appContextInfo() } diff --git a/Sources/KlaviyoCore/KlaviyoAPI.swift b/Sources/KlaviyoCore/KlaviyoAPI.swift index 0c9c8cbe..adfcecf2 100644 --- a/Sources/KlaviyoCore/KlaviyoAPI.swift +++ b/Sources/KlaviyoCore/KlaviyoAPI.swift @@ -10,7 +10,7 @@ import Foundation @_spi(KlaviyoPrivate) public func setKlaviyoAPIURL(url: String) { - analytics.apiURL = url + environment.apiURL = url } public struct KlaviyoAPI { @@ -51,7 +51,7 @@ public struct KlaviyoAPI { var response: URLResponse var data: Data do { - (data, response) = try await analytics.networkSession().data(urlRequest) + (data, response) = try await environment.networkSession().data(urlRequest) } catch { requestFailed(request, error, 0.0) return .failure(KlaviyoAPIError.networkError(error)) diff --git a/Sources/KlaviyoCore/KlaviyoEnvironment.swift b/Sources/KlaviyoCore/KlaviyoEnvironment.swift index 222e466e..74af4e15 100644 --- a/Sources/KlaviyoCore/KlaviyoEnvironment.swift +++ b/Sources/KlaviyoCore/KlaviyoEnvironment.swift @@ -35,7 +35,6 @@ public struct KlaviyoEnvironment { public var fileClient: FileClient public var data: (URL) throws -> Data public var logger: LoggerClient -// public var analytics: AnalyticsEnvironment public var getUserDefaultString: (String) -> String? public var appLifeCycle: AppLifeCycleEvents public var notificationCenterPublisher: (NSNotification.Name) -> AnyPublisher @@ -50,6 +49,20 @@ public struct KlaviyoEnvironment { // public var stateChangePublisher: () -> AnyPublisher public var raiseFatalError: (String) -> Void public var emitDeveloperWarning: (String) -> Void + + // analytics environment + + public var networkSession: () -> NetworkSession + public var apiURL: String + public var encodeJSON: (AnyEncodable) throws -> Data + public var decoder: DataDecoder + public var uuid: () -> UUID + public var date: () -> Date + public var timeZone: () -> String + public var appContextInfo: () -> AppContextInfo + public var klaviyoAPI: KlaviyoAPI + public var timer: (Double) -> AnyPublisher + static var production = KlaviyoEnvironment( archiverClient: ArchiverClient.production, fileClient: FileClient.production, @@ -87,7 +100,21 @@ public struct KlaviyoEnvironment { fatalError(msg) #endif }, - emitDeveloperWarning: { runtimeWarn($0) }) + emitDeveloperWarning: { runtimeWarn($0) }, + networkSession: createNetworkSession, + apiURL: KlaviyoEnvironment.productionHost, + encodeJSON: { encodable in try KlaviyoEnvironment.encoder.encode(encodable) }, + decoder: DataDecoder.production, + uuid: { UUID() }, + date: { Date() }, + timeZone: { TimeZone.autoupdatingCurrent.identifier }, + appContextInfo: { AppContextInfo() }, + klaviyoAPI: KlaviyoAPI(), + timer: { interval in + Timer.publish(every: interval, on: .main, in: .default) + .autoconnect() + .eraseToAnyPublisher() + }) } public var networkSession: NetworkSession! @@ -172,33 +199,3 @@ func runtimeWarn( #endif #endif } - -public var analytics = AnalyticsEnvironment.production - -public struct AnalyticsEnvironment { - var networkSession: () -> NetworkSession - var apiURL: String - var encodeJSON: (AnyEncodable) throws -> Data - var decoder: DataDecoder - public var uuid: () -> UUID - var date: () -> Date - var timeZone: () -> String - var appContextInfo: () -> AppContextInfo - var klaviyoAPI: KlaviyoAPI - var timer: (Double) -> AnyPublisher - static let production: AnalyticsEnvironment = .init( - networkSession: createNetworkSession, - apiURL: KlaviyoEnvironment.productionHost, - encodeJSON: { encodable in try KlaviyoEnvironment.encoder.encode(encodable) }, - decoder: DataDecoder.production, - uuid: { UUID() }, - date: { Date() }, - timeZone: { TimeZone.autoupdatingCurrent.identifier }, - appContextInfo: { AppContextInfo() }, - klaviyoAPI: KlaviyoAPI(), - timer: { interval in - Timer.publish(every: interval, on: .main, in: .default) - .autoconnect() - .eraseToAnyPublisher() - }) -} diff --git a/Sources/KlaviyoCore/KlaviyoRequest.swift b/Sources/KlaviyoCore/KlaviyoRequest.swift index 9e4cae82..11002bde 100644 --- a/Sources/KlaviyoCore/KlaviyoRequest.swift +++ b/Sources/KlaviyoCore/KlaviyoRequest.swift @@ -12,7 +12,7 @@ public struct KlaviyoRequest: Equatable, Codable { public init( apiKey: String, endpoint: KlaviyoEndpoint, - uuid: String = analytics.uuid().uuidString) { + uuid: String = environment.uuid().uuidString) { self.apiKey = apiKey self.endpoint = endpoint self.uuid = uuid @@ -20,11 +20,11 @@ public struct KlaviyoRequest: Equatable, Codable { public let apiKey: String public let endpoint: KlaviyoEndpoint - public var uuid = analytics.uuid().uuidString + public var uuid = environment.uuid().uuidString public func urlRequest(_ attemptNumber: Int = 1) throws -> URLRequest { guard let url = url else { - throw KlaviyoAPI.KlaviyoAPIError.internalError("Invalid url string. API URL: \(analytics.apiURL)") + throw KlaviyoAPI.KlaviyoAPIError.internalError("Invalid url string. API URL: \(environment.apiURL)") } var request = URLRequest(url: url) // We only support post right now @@ -41,8 +41,8 @@ public struct KlaviyoRequest: Equatable, Codable { var url: URL? { switch endpoint { case .createProfile, .createEvent, .registerPushToken, .unregisterPushToken: - if !analytics.apiURL.isEmpty { - return URL(string: "\(analytics.apiURL)/\(path)/?company_id=\(apiKey)") + if !environment.apiURL.isEmpty { + return URL(string: "\(environment.apiURL)/\(path)/?company_id=\(apiKey)") } return nil } @@ -67,18 +67,18 @@ public struct KlaviyoRequest: Equatable, Codable { func encodeBody() throws -> Data { switch endpoint { case let .createProfile(payload): - return try analytics.encodeJSON(AnyEncodable(payload)) + return try environment.encodeJSON(AnyEncodable(payload)) case var .createEvent(payload): // TODO: fixme get push token here payload.appendMetadataToProperties(pushToken: "") - return try analytics.encodeJSON(AnyEncodable(payload)) + return try environment.encodeJSON(AnyEncodable(payload)) case let .registerPushToken(payload): - return try analytics.encodeJSON(AnyEncodable(payload)) + return try environment.encodeJSON(AnyEncodable(payload)) case let .unregisterPushToken(payload): - return try analytics.encodeJSON(AnyEncodable(payload)) + return try environment.encodeJSON(AnyEncodable(payload)) } } } diff --git a/Sources/KlaviyoSwift/AnalyticsEnvironment.swift b/Sources/KlaviyoSwift/AnalyticsEnvironment.swift deleted file mode 100644 index fd8782de..00000000 --- a/Sources/KlaviyoSwift/AnalyticsEnvironment.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// File.swift -// -// -// Created by Ajay Subramanya on 8/2/24. -// - -import AnyCodable -import Combine -import Foundation -import KlaviyoCore -import UIKit - -var analytics = AnalyticsEnvironment.production - -struct AnalyticsEnvironment { - var networkSession: () -> NetworkSession - var apiURL: String - var encodeJSON: (AnyEncodable) throws -> Data - var decoder: DataDecoder - var uuid: () -> UUID - var date: () -> Date - var timeZone: () -> String - var appContextInfo: () -> AppContextInfo - var klaviyoAPI: KlaviyoAPI - var timer: (Double) -> AnyPublisher - static let production: AnalyticsEnvironment = .init( - networkSession: createNetworkSession, - apiURL: KlaviyoEnvironment.productionHost, - encodeJSON: { encodable in try KlaviyoEnvironment.encoder.encode(encodable) }, - decoder: DataDecoder.production, - uuid: { UUID() }, - date: { Date() }, - timeZone: { TimeZone.autoupdatingCurrent.identifier }, - appContextInfo: { AppContextInfo() }, - klaviyoAPI: KlaviyoAPI(), - timer: { interval in - Timer.publish(every: interval, on: .main, in: .default) - .autoconnect() - .eraseToAnyPublisher() - }) -} diff --git a/Sources/KlaviyoSwift/KlaviyoState.swift b/Sources/KlaviyoSwift/KlaviyoState.swift index cfefa58c..17bc2e93 100644 --- a/Sources/KlaviyoSwift/KlaviyoState.swift +++ b/Sources/KlaviyoSwift/KlaviyoState.swift @@ -216,7 +216,7 @@ struct KlaviyoState: Equatable, Codable { mutating func reset(preserveTokenData: Bool = true) { if isIdentified { // If we are still anonymous we want to preserve our anonymous id so we can merge this profile with the new profile. - anonymousId = analytics.uuid().uuidString + anonymousId = environment.uuid().uuidString } let previousPushTokenData = pushTokenData pendingProfile = nil @@ -248,7 +248,7 @@ struct KlaviyoState: Equatable, Codable { guard let pushTokenData = pushTokenData else { return true } - let currentDeviceMetadata = DeviceMetadata(context: analytics.appContextInfo()) + let currentDeviceMetadata = DeviceMetadata(context: environment.appContextInfo()) let newPushTokenData = PushTokenData( pushToken: newToken, pushEnablement: enablement, @@ -326,7 +326,7 @@ private func klaviyoStateFile(apiKey: String) -> URL { private func storeKlaviyoState(state: KlaviyoState, file: URL) { do { - try environment.fileClient.write(analytics.encodeJSON(AnyEncodable(state)), file) + try environment.fileClient.write(environment.encodeJSON(AnyEncodable(state)), file) } catch { environment.logger.error("Unable to save klaviyo state.") } @@ -361,7 +361,7 @@ func loadKlaviyoStateFromDisk(apiKey: String) -> KlaviyoState { removeStateFile(at: fileName) return createAndStoreInitialState(with: apiKey, at: fileName) } - guard var state: KlaviyoState = try? analytics.decoder.decode(stateData) else { + guard var state: KlaviyoState = try? environment.decoder.decode(stateData) else { environment.logger.error("Unable to decode existing state file. Removing.") removeStateFile(at: fileName) return createAndStoreInitialState(with: apiKey, at: fileName) @@ -370,14 +370,14 @@ func loadKlaviyoStateFromDisk(apiKey: String) -> KlaviyoState { // Clear existing state since we are using a new api state. state = KlaviyoState( apiKey: apiKey, - anonymousId: analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, queue: []) } return state } private func createAndStoreInitialState(with apiKey: String, at file: URL) -> KlaviyoState { - let anonymousId = analytics.uuid().uuidString + let anonymousId = environment.uuid().uuidString let state = KlaviyoState(apiKey: apiKey, anonymousId: anonymousId, queue: [], requestsInFlight: []) storeKlaviyoState(state: state, file: file) return state diff --git a/Sources/KlaviyoSwift/Models/Event.swift b/Sources/KlaviyoSwift/Models/Event.swift index 2349618e..3bb8d282 100644 --- a/Sources/KlaviyoSwift/Models/Event.swift +++ b/Sources/KlaviyoSwift/Models/Event.swift @@ -7,6 +7,7 @@ import AnyCodable import Foundation +import KlaviyoCore public struct Event: Equatable { public enum EventName: Equatable { @@ -58,9 +59,9 @@ public struct Event: Equatable { uniqueId: String? = nil) { metric = .init(name: name) _properties = AnyCodable(properties ?? [:]) - self.time = time ?? analytics.date() + self.time = time ?? environment.date() self.value = value - self.uniqueId = uniqueId ?? analytics.uuid().uuidString + self.uniqueId = uniqueId ?? environment.uuid().uuidString self.identifiers = identifiers } @@ -78,8 +79,8 @@ public struct Event: Equatable { _properties = AnyCodable(properties ?? [:]) identifiers = nil self.value = value - time = analytics.date() - self.uniqueId = uniqueId ?? analytics.uuid().uuidString + time = environment.date() + self.uniqueId = uniqueId ?? environment.uuid().uuidString } } diff --git a/Sources/KlaviyoSwift/Models/Profile.swift b/Sources/KlaviyoSwift/Models/Profile.swift index ae223a53..fb3a9944 100644 --- a/Sources/KlaviyoSwift/Models/Profile.swift +++ b/Sources/KlaviyoSwift/Models/Profile.swift @@ -7,6 +7,7 @@ import AnyCodable import Foundation +import KlaviyoCore public struct Profile: Equatable { public enum ProfileKey: Equatable, Hashable, Codable { @@ -64,7 +65,7 @@ public struct Profile: Equatable { self.longitude = longitude self.region = region self.zip = zip - self.timezone = timezone ?? analytics.timeZone() + self.timezone = timezone ?? environment.timeZone() } } diff --git a/Sources/KlaviyoSwift/StateManagement.swift b/Sources/KlaviyoSwift/StateManagement.swift index cd35eedb..bf2a422a 100644 --- a/Sources/KlaviyoSwift/StateManagement.swift +++ b/Sources/KlaviyoSwift/StateManagement.swift @@ -276,7 +276,7 @@ struct KlaviyoReducer: ReducerProtocol { guard case .initialized = state.initalizationState else { return .none } - return analytics.timer(state.flushInterval) + return environment.timer(state.flushInterval) .map { _ in KlaviyoAction.flushQueue } @@ -323,7 +323,7 @@ struct KlaviyoReducer: ReducerProtocol { } return .run { [numAttempts] send in - let result = await analytics.klaviyoAPI.send(request, numAttempts) + let result = await environment.klaviyoAPI.send(request, numAttempts) switch result { case .success: // TODO: may want to inspect response further. @@ -359,7 +359,7 @@ struct KlaviyoReducer: ReducerProtocol { case .reachableViaWWAN: state.flushInterval = StateManagementConstants.cellularFlushInterval } - return analytics.timer(state.flushInterval) + return environment.timer(state.flushInterval) .map { _ in KlaviyoAction.flushQueue }.eraseToEffect() From 1ece589dca0893e8d260a89d512f1993e5b40890 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 8 Aug 2024 11:27:21 -0500 Subject: [PATCH 14/72] updated klaviyo environment a little --- Sources/KlaviyoCore/ArchivalUtils.swift | 2 +- Sources/KlaviyoCore/KlaviyoAPI.swift | 16 +---- Sources/KlaviyoCore/KlaviyoEnvironment.swift | 72 ++++--------------- Sources/KlaviyoCore/KlaviyoRequest.swift | 12 ++-- .../APIModels/CreateEventPayload.swift | 0 .../APIModels/CreateProfilePayload.swift | 0 .../APIModels/ProfilePayload.swift | 0 .../APIModels/PushTokenPayload.swift | 0 .../UnregisterPushTokenPayload.swift | 0 .../KlaviyoCore/Models/KlaviyoAPIError.swift | 20 ++++++ .../KlaviyoCore/Models/PushBackground.swift | 27 +++++++ .../KlaviyoCore/Models/PushEnablement.swift | 31 ++++++++ Sources/KlaviyoCore/NetworkSession.swift | 9 +-- .../APIRequestErrorHandling.swift | 2 +- Sources/KlaviyoSwift/KlaviyoState.swift | 2 +- 15 files changed, 106 insertions(+), 87 deletions(-) rename Sources/KlaviyoCore/{ => Models}/APIModels/CreateEventPayload.swift (100%) rename Sources/KlaviyoCore/{ => Models}/APIModels/CreateProfilePayload.swift (100%) rename Sources/KlaviyoCore/{ => Models}/APIModels/ProfilePayload.swift (100%) rename Sources/KlaviyoCore/{ => Models}/APIModels/PushTokenPayload.swift (100%) rename Sources/KlaviyoCore/{ => Models}/APIModels/UnregisterPushTokenPayload.swift (100%) create mode 100644 Sources/KlaviyoCore/Models/KlaviyoAPIError.swift create mode 100644 Sources/KlaviyoCore/Models/PushBackground.swift create mode 100644 Sources/KlaviyoCore/Models/PushEnablement.swift diff --git a/Sources/KlaviyoCore/ArchivalUtils.swift b/Sources/KlaviyoCore/ArchivalUtils.swift index c59d43d7..b02d8be8 100644 --- a/Sources/KlaviyoCore/ArchivalUtils.swift +++ b/Sources/KlaviyoCore/ArchivalUtils.swift @@ -42,7 +42,7 @@ public func unarchiveFromFile(fileURL: URL) -> NSMutableArray? { print("Archive file not found.") return nil } - guard let archivedData = try? environment.data(fileURL) else { + guard let archivedData = try? environment.dataFromUrl(fileURL) else { print("Unable to read archived data.") return nil } diff --git a/Sources/KlaviyoCore/KlaviyoAPI.swift b/Sources/KlaviyoCore/KlaviyoAPI.swift index adfcecf2..6135107b 100644 --- a/Sources/KlaviyoCore/KlaviyoAPI.swift +++ b/Sources/KlaviyoCore/KlaviyoAPI.swift @@ -16,18 +16,6 @@ public func setKlaviyoAPIURL(url: String) { public struct KlaviyoAPI { public init() {} - public enum KlaviyoAPIError: Error { - case httpError(Int, Data) - case rateLimitError(Int) - case missingOrInvalidResponse(URLResponse?) - case networkError(Error) - case internalError(String) - case internalRequestError(Error) - case unknownError(Error) - case dataEncodingError(KlaviyoRequest) - case invalidData - } - // For internal testing use only public static var requestStarted: (KlaviyoRequest) -> Void = { _ in } public static var requestCompleted: (KlaviyoRequest, Data, Double) -> Void = { _, _, _ in } @@ -36,7 +24,7 @@ public struct KlaviyoAPI { public static var requestHttpError: (KlaviyoRequest, Int, Double) -> Void = { _, _, _ in } public var send: (KlaviyoRequest, Int) async -> Result = { request, attemptNumber in - let start = Date() + let start = environment.date() var urlRequest: URLRequest do { @@ -57,7 +45,7 @@ public struct KlaviyoAPI { return .failure(KlaviyoAPIError.networkError(error)) } - let end = Date() + let end = environment.date() let duration = end.timeIntervalSince(start) guard let httpResponse = response as? HTTPURLResponse else { diff --git a/Sources/KlaviyoCore/KlaviyoEnvironment.swift b/Sources/KlaviyoCore/KlaviyoEnvironment.swift index 74af4e15..aa415a19 100644 --- a/Sources/KlaviyoCore/KlaviyoEnvironment.swift +++ b/Sources/KlaviyoCore/KlaviyoEnvironment.swift @@ -16,14 +16,14 @@ import os public var environment = KlaviyoEnvironment.production public struct KlaviyoEnvironment { - public static let productionHost = "https://a.klaviyo.com" - public static let encoder = { () -> JSONEncoder in + static let productionHost = "https://a.klaviyo.com" + static let encoder = { () -> JSONEncoder in let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 return encoder }() - public static let decoder = { () -> JSONDecoder in + static let decoder = { () -> JSONDecoder in let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 return decoder @@ -33,25 +33,27 @@ public struct KlaviyoEnvironment { public var archiverClient: ArchiverClient public var fileClient: FileClient - public var data: (URL) throws -> Data + public var dataFromUrl: (URL) throws -> Data + public var logger: LoggerClient - public var getUserDefaultString: (String) -> String? + public var appLifeCycle: AppLifeCycleEvents + public var notificationCenterPublisher: (NSNotification.Name) -> AnyPublisher public var getNotificationSettings: (@escaping (PushEnablement) -> Void) -> Void public var getBackgroundSetting: () -> PushBackground - public var legacyIdentifier: () -> String + public var startReachability: () throws -> Void public var stopReachability: () -> Void public var reachabilityStatus: () -> Reachability.NetworkStatus? + public var randomInt: () -> Int // TODO: need to fix this // public var stateChangePublisher: () -> AnyPublisher + public var raiseFatalError: (String) -> Void public var emitDeveloperWarning: (String) -> Void - // analytics environment - public var networkSession: () -> NetworkSession public var apiURL: String public var encodeJSON: (AnyEncodable) throws -> Data @@ -66,11 +68,8 @@ public struct KlaviyoEnvironment { static var production = KlaviyoEnvironment( archiverClient: ArchiverClient.production, fileClient: FileClient.production, - data: { url in try Data(contentsOf: url) }, + dataFromUrl: { url in try Data(contentsOf: url) }, logger: LoggerClient.production, - // TODO: fixme - // analytics: AnalyticsEnvironment.production, - getUserDefaultString: { key in UserDefaults.standard.string(forKey: key) }, appLifeCycle: AppLifeCycleEvents.production, notificationCenterPublisher: { name in NotificationCenter.default.publisher(for: name) @@ -82,7 +81,6 @@ public struct KlaviyoEnvironment { } }, getBackgroundSetting: { .create(from: UIApplication.shared.backgroundRefreshStatus) }, - legacyIdentifier: { "iOS:\(UIDevice.current.identifierForVendor!.uuidString)" }, startReachability: { try reachabilityService?.startNotifier() }, @@ -103,7 +101,7 @@ public struct KlaviyoEnvironment { emitDeveloperWarning: { runtimeWarn($0) }, networkSession: createNetworkSession, apiURL: KlaviyoEnvironment.productionHost, - encodeJSON: { encodable in try KlaviyoEnvironment.encoder.encode(encodable) }, + encodeJSON: { encodable in try encoder.encode(encodable) }, decoder: DataDecoder.production, uuid: { UUID() }, date: { Date() }, @@ -130,53 +128,11 @@ enum KlaviyoDecodingError: Error { } public struct DataDecoder { - public func decode(_ data: Data) throws -> T { - try jsonDecoder.decode(T.self, from: data) - } - public var jsonDecoder: JSONDecoder public static let production = Self(jsonDecoder: KlaviyoEnvironment.decoder) -} - -public enum PushEnablement: String, Codable { - case notDetermined = "NOT_DETERMINED" - case denied = "DENIED" - case authorized = "AUTHORIZED" - case provisional = "PROVISIONAL" - case ephemeral = "EPHEMERAL" - - static func create(from status: UNAuthorizationStatus) -> PushEnablement { - switch status { - case .denied: - return PushEnablement.denied - case .authorized: - return PushEnablement.authorized - case .provisional: - return PushEnablement.provisional - case .ephemeral: - return PushEnablement.ephemeral - default: - return PushEnablement.notDetermined - } - } -} -public enum PushBackground: String, Codable { - case available = "AVAILABLE" - case restricted = "RESTRICTED" - case denied = "DENIED" - - static func create(from status: UIBackgroundRefreshStatus) -> PushBackground { - switch status { - case .available: - return PushBackground.available - case .restricted: - return PushBackground.restricted - case .denied: - return PushBackground.denied - @unknown default: - return PushBackground.available - } + public func decode(_ data: Data) throws -> T { + try jsonDecoder.decode(T.self, from: data) } } diff --git a/Sources/KlaviyoCore/KlaviyoRequest.swift b/Sources/KlaviyoCore/KlaviyoRequest.swift index 11002bde..e58f5ec8 100644 --- a/Sources/KlaviyoCore/KlaviyoRequest.swift +++ b/Sources/KlaviyoCore/KlaviyoRequest.swift @@ -9,6 +9,10 @@ import AnyCodable import Foundation public struct KlaviyoRequest: Equatable, Codable { + public let apiKey: String + public let endpoint: KlaviyoEndpoint + public var uuid: String + public init( apiKey: String, endpoint: KlaviyoEndpoint, @@ -18,18 +22,14 @@ public struct KlaviyoRequest: Equatable, Codable { self.uuid = uuid } - public let apiKey: String - public let endpoint: KlaviyoEndpoint - public var uuid = environment.uuid().uuidString - public func urlRequest(_ attemptNumber: Int = 1) throws -> URLRequest { guard let url = url else { - throw KlaviyoAPI.KlaviyoAPIError.internalError("Invalid url string. API URL: \(environment.apiURL)") + throw KlaviyoAPIError.internalError("Invalid url string. API URL: \(environment.apiURL)") } var request = URLRequest(url: url) // We only support post right now guard let body = try? encodeBody() else { - throw KlaviyoAPI.KlaviyoAPIError.dataEncodingError(self) + throw KlaviyoAPIError.dataEncodingError(self) } request.httpBody = body request.httpMethod = "POST" diff --git a/Sources/KlaviyoCore/APIModels/CreateEventPayload.swift b/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift similarity index 100% rename from Sources/KlaviyoCore/APIModels/CreateEventPayload.swift rename to Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift diff --git a/Sources/KlaviyoCore/APIModels/CreateProfilePayload.swift b/Sources/KlaviyoCore/Models/APIModels/CreateProfilePayload.swift similarity index 100% rename from Sources/KlaviyoCore/APIModels/CreateProfilePayload.swift rename to Sources/KlaviyoCore/Models/APIModels/CreateProfilePayload.swift diff --git a/Sources/KlaviyoCore/APIModels/ProfilePayload.swift b/Sources/KlaviyoCore/Models/APIModels/ProfilePayload.swift similarity index 100% rename from Sources/KlaviyoCore/APIModels/ProfilePayload.swift rename to Sources/KlaviyoCore/Models/APIModels/ProfilePayload.swift diff --git a/Sources/KlaviyoCore/APIModels/PushTokenPayload.swift b/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift similarity index 100% rename from Sources/KlaviyoCore/APIModels/PushTokenPayload.swift rename to Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift diff --git a/Sources/KlaviyoCore/APIModels/UnregisterPushTokenPayload.swift b/Sources/KlaviyoCore/Models/APIModels/UnregisterPushTokenPayload.swift similarity index 100% rename from Sources/KlaviyoCore/APIModels/UnregisterPushTokenPayload.swift rename to Sources/KlaviyoCore/Models/APIModels/UnregisterPushTokenPayload.swift diff --git a/Sources/KlaviyoCore/Models/KlaviyoAPIError.swift b/Sources/KlaviyoCore/Models/KlaviyoAPIError.swift new file mode 100644 index 00000000..af15333c --- /dev/null +++ b/Sources/KlaviyoCore/Models/KlaviyoAPIError.swift @@ -0,0 +1,20 @@ +// +// File.swift +// +// +// Created by Ajay Subramanya on 8/8/24. +// + +import Foundation + +public enum KlaviyoAPIError: Error { + case httpError(Int, Data) + case rateLimitError(Int) + case missingOrInvalidResponse(URLResponse?) + case networkError(Error) + case internalError(String) + case internalRequestError(Error) + case unknownError(Error) + case dataEncodingError(KlaviyoRequest) + case invalidData +} diff --git a/Sources/KlaviyoCore/Models/PushBackground.swift b/Sources/KlaviyoCore/Models/PushBackground.swift new file mode 100644 index 00000000..0f3d0dda --- /dev/null +++ b/Sources/KlaviyoCore/Models/PushBackground.swift @@ -0,0 +1,27 @@ +// +// PushBackground.swift +// +// +// Created by Ajay Subramanya on 8/8/24. +// + +import UIKit + +public enum PushBackground: String, Codable { + case available = "AVAILABLE" + case restricted = "RESTRICTED" + case denied = "DENIED" + + static func create(from status: UIBackgroundRefreshStatus) -> PushBackground { + switch status { + case .available: + return PushBackground.available + case .restricted: + return PushBackground.restricted + case .denied: + return PushBackground.denied + @unknown default: + return PushBackground.available + } + } +} diff --git a/Sources/KlaviyoCore/Models/PushEnablement.swift b/Sources/KlaviyoCore/Models/PushEnablement.swift new file mode 100644 index 00000000..a49b3510 --- /dev/null +++ b/Sources/KlaviyoCore/Models/PushEnablement.swift @@ -0,0 +1,31 @@ +// +// PushEnablement.swift +// +// +// Created by Ajay Subramanya on 8/8/24. +// + +import UIKit + +public enum PushEnablement: String, Codable { + case notDetermined = "NOT_DETERMINED" + case denied = "DENIED" + case authorized = "AUTHORIZED" + case provisional = "PROVISIONAL" + case ephemeral = "EPHEMERAL" + + static func create(from status: UNAuthorizationStatus) -> PushEnablement { + switch status { + case .denied: + return PushEnablement.denied + case .authorized: + return PushEnablement.authorized + case .provisional: + return PushEnablement.provisional + case .ephemeral: + return PushEnablement.ephemeral + default: + return PushEnablement.notDetermined + } + } +} diff --git a/Sources/KlaviyoCore/NetworkSession.swift b/Sources/KlaviyoCore/NetworkSession.swift index 428b85fb..e9cca850 100644 --- a/Sources/KlaviyoCore/NetworkSession.swift +++ b/Sources/KlaviyoCore/NetworkSession.swift @@ -28,12 +28,9 @@ public struct NetworkSession { fileprivate static let mobileHeader = "1" public static let defaultUserAgent = { () -> String in - // TODO: fixme - // let appContext = environment.analytics.appContextInfo() -// let klaivyoSDKVersion = "klaviyo-ios/\(__klaviyoSwiftVersion)" -// return "\(appContext.executable)/\(appContext.appVersion) (\(appContext.bundleId); build:\(appContext.appBuild); \(appContext.osVersionName)) \(klaivyoSDKVersion)" - - "FIXME" + let appContext = environment.appContextInfo() + let klaivyoSDKVersion = "klaviyo-ios/\(__klaviyoSwiftVersion)" + return "\(appContext.executable)/\(appContext.appVersion) (\(appContext.bundleId); build:\(appContext.appBuild); \(appContext.osVersionName)) \(klaivyoSDKVersion)" }() public var data: (URLRequest) async throws -> (Data, URLResponse) diff --git a/Sources/KlaviyoSwift/APIRequestErrorHandling.swift b/Sources/KlaviyoSwift/APIRequestErrorHandling.swift index 682254c8..0cd4fef0 100644 --- a/Sources/KlaviyoSwift/APIRequestErrorHandling.swift +++ b/Sources/KlaviyoSwift/APIRequestErrorHandling.swift @@ -51,7 +51,7 @@ private func parseError(_ data: Data) -> [InvalidField]? { func handleRequestError( request: KlaviyoRequest, - error: KlaviyoAPI.KlaviyoAPIError, + error: KlaviyoAPIError, retryInfo: RetryInfo) -> KlaviyoAction { switch error { case let .httpError(statuscode, data): diff --git a/Sources/KlaviyoSwift/KlaviyoState.swift b/Sources/KlaviyoSwift/KlaviyoState.swift index 17bc2e93..34021484 100644 --- a/Sources/KlaviyoSwift/KlaviyoState.swift +++ b/Sources/KlaviyoSwift/KlaviyoState.swift @@ -356,7 +356,7 @@ func loadKlaviyoStateFromDisk(apiKey: String) -> KlaviyoState { guard environment.fileClient.fileExists(fileName.path) else { return createAndStoreInitialState(with: apiKey, at: fileName) } - guard let stateData = try? environment.data(fileName) else { + guard let stateData = try? environment.dataFromUrl(fileName) else { environment.logger.error("Klaviyo state file invalid starting from scratch.") removeStateFile(at: fileName) return createAndStoreInitialState(with: apiKey, at: fileName) From 85f9d5ed1995712383c50d51a2fb6abb09affebb Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 8 Aug 2024 15:12:32 -0500 Subject: [PATCH 15/72] fixed a todo to append metadata to events --- .../Models/APIModels/CreateEventPayload.swift | 27 ++++++++++--------- .../Models/APIModels/PushTokenPayload.swift | 2 +- .../{ => Networking}/KlaviyoAPI.swift | 0 .../KlaviyoEndpoint.swift} | 6 +---- .../{ => Networking}/KlaviyoRequest.swift | 4 +-- .../{ => Networking}/NetworkSession.swift | 0 .../{ => Utils}/ArchivalUtils.swift | 0 .../KlaviyoCore/{ => Utils}/FileUtils.swift | 0 .../{ => Utils}/LoggerClient.swift | 0 Sources/KlaviyoCore/{ => Utils}/Version.swift | 0 Sources/KlaviyoSwift/StateManagement.swift | 3 ++- 11 files changed, 19 insertions(+), 23 deletions(-) rename Sources/KlaviyoCore/{ => Networking}/KlaviyoAPI.swift (100%) rename Sources/KlaviyoCore/{InternalAPIModels.swift => Networking/KlaviyoEndpoint.swift} (78%) rename Sources/KlaviyoCore/{ => Networking}/KlaviyoRequest.swift (93%) rename Sources/KlaviyoCore/{ => Networking}/NetworkSession.swift (100%) rename Sources/KlaviyoCore/{ => Utils}/ArchivalUtils.swift (100%) rename Sources/KlaviyoCore/{ => Utils}/FileUtils.swift (100%) rename Sources/KlaviyoCore/{ => Utils}/LoggerClient.swift (100%) rename Sources/KlaviyoCore/{ => Utils}/Version.swift (100%) diff --git a/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift b/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift index 5b049449..375b0c08 100644 --- a/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift +++ b/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift @@ -90,10 +90,11 @@ public struct CreateEventPayload: Equatable, Codable { anonymousId: String? = nil, value: Double? = nil, time: Date? = nil, - uniqueId: String? = nil) { + uniqueId: String? = nil, + pushToken: String? = nil) { attributes = Attributes( name: name, - properties: properties, + properties: properties?.appendMetadataToProperties(pushToken: pushToken), email: email, phoneNumber: phoneNumber, externalId: externalId, @@ -104,9 +105,15 @@ public struct CreateEventPayload: Equatable, Codable { } } - mutating func appendMetadataToProperties(pushToken: String) { - let context = KlaviyoAPI._appContextInfo - // TODO: Fixme + public var data: Event + public init(data: Event) { + self.data = data + } +} + +extension Dictionary where Key == String, Value == Any { + fileprivate func appendMetadataToProperties(pushToken: String?) -> [String: Any]? { + let context = environment.appContextInfo() let metadata: [String: Any] = [ "Device ID": context.deviceId, "Device Manufacturer": context.manufacturer, @@ -119,15 +126,9 @@ public struct CreateEventPayload: Equatable, Codable { "App ID": context.bundleId, "App Version": context.appVersion, "App Build": context.appBuild, - "Push Token": pushToken + "Push Token": pushToken ?? "" ] - let originalProperties = data.attributes.properties.value as? [String: Any] ?? [:] - data.attributes.properties = AnyCodable(originalProperties.merging(metadata) { _, new in new }) - } - - public var data: Event - public init(data: Event) { - self.data = data + return merging(metadata) { _, new in new } } } diff --git a/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift b/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift index fe7aca73..244ef0f5 100644 --- a/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift +++ b/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift @@ -53,7 +53,7 @@ public struct PushTokenPayload: Equatable, Codable { enablementStatus = enablement backgroundStatus = background self.profile = Profile(data: profile) - deviceMetadata = MetaData(context: KlaviyoAPI._appContextInfo) + deviceMetadata = MetaData(context: environment.appContextInfo()) } public struct Profile: Equatable, Codable { diff --git a/Sources/KlaviyoCore/KlaviyoAPI.swift b/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift similarity index 100% rename from Sources/KlaviyoCore/KlaviyoAPI.swift rename to Sources/KlaviyoCore/Networking/KlaviyoAPI.swift diff --git a/Sources/KlaviyoCore/InternalAPIModels.swift b/Sources/KlaviyoCore/Networking/KlaviyoEndpoint.swift similarity index 78% rename from Sources/KlaviyoCore/InternalAPIModels.swift rename to Sources/KlaviyoCore/Networking/KlaviyoEndpoint.swift index 482c692d..1528c5e1 100644 --- a/Sources/KlaviyoCore/InternalAPIModels.swift +++ b/Sources/KlaviyoCore/Networking/KlaviyoEndpoint.swift @@ -1,5 +1,5 @@ // -// InternalAPIModels.swift +// KlaviyoEndpoint.swift // Internal models typically used at the networking layer. // NOTE: Ensure that new request types are equatable and encodable. // @@ -15,7 +15,3 @@ public enum KlaviyoEndpoint: Equatable, Codable { case registerPushToken(PushTokenPayload) case unregisterPushToken(UnregisterPushTokenPayload) } - -extension KlaviyoAPI { - public static let _appContextInfo = environment.appContextInfo() -} diff --git a/Sources/KlaviyoCore/KlaviyoRequest.swift b/Sources/KlaviyoCore/Networking/KlaviyoRequest.swift similarity index 93% rename from Sources/KlaviyoCore/KlaviyoRequest.swift rename to Sources/KlaviyoCore/Networking/KlaviyoRequest.swift index e58f5ec8..f1fada3f 100644 --- a/Sources/KlaviyoCore/KlaviyoRequest.swift +++ b/Sources/KlaviyoCore/Networking/KlaviyoRequest.swift @@ -69,9 +69,7 @@ public struct KlaviyoRequest: Equatable, Codable { case let .createProfile(payload): return try environment.encodeJSON(AnyEncodable(payload)) - case var .createEvent(payload): - // TODO: fixme get push token here - payload.appendMetadataToProperties(pushToken: "") + case let .createEvent(payload): return try environment.encodeJSON(AnyEncodable(payload)) case let .registerPushToken(payload): diff --git a/Sources/KlaviyoCore/NetworkSession.swift b/Sources/KlaviyoCore/Networking/NetworkSession.swift similarity index 100% rename from Sources/KlaviyoCore/NetworkSession.swift rename to Sources/KlaviyoCore/Networking/NetworkSession.swift diff --git a/Sources/KlaviyoCore/ArchivalUtils.swift b/Sources/KlaviyoCore/Utils/ArchivalUtils.swift similarity index 100% rename from Sources/KlaviyoCore/ArchivalUtils.swift rename to Sources/KlaviyoCore/Utils/ArchivalUtils.swift diff --git a/Sources/KlaviyoCore/FileUtils.swift b/Sources/KlaviyoCore/Utils/FileUtils.swift similarity index 100% rename from Sources/KlaviyoCore/FileUtils.swift rename to Sources/KlaviyoCore/Utils/FileUtils.swift diff --git a/Sources/KlaviyoCore/LoggerClient.swift b/Sources/KlaviyoCore/Utils/LoggerClient.swift similarity index 100% rename from Sources/KlaviyoCore/LoggerClient.swift rename to Sources/KlaviyoCore/Utils/LoggerClient.swift diff --git a/Sources/KlaviyoCore/Version.swift b/Sources/KlaviyoCore/Utils/Version.swift similarity index 100% rename from Sources/KlaviyoCore/Version.swift rename to Sources/KlaviyoCore/Utils/Version.swift diff --git a/Sources/KlaviyoSwift/StateManagement.swift b/Sources/KlaviyoSwift/StateManagement.swift index bf2a422a..93a8db0c 100644 --- a/Sources/KlaviyoSwift/StateManagement.swift +++ b/Sources/KlaviyoSwift/StateManagement.swift @@ -406,7 +406,8 @@ struct KlaviyoReducer: ReducerProtocol { anonymousId: anonymousId, value: event.value, time: event.time, - uniqueId: event.uniqueId)) + uniqueId: event.uniqueId, + pushToken: state.pushTokenData?.pushToken)) let endpoint = KlaviyoEndpoint.createEvent(payload) let request = KlaviyoRequest(apiKey: apiKey, endpoint: endpoint) From 9f218906063b9032a5e10a879c0a6008b106e852 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 8 Aug 2024 15:52:57 -0500 Subject: [PATCH 16/72] minor comment update --- Sources/KlaviyoCore/Networking/KlaviyoRequest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/KlaviyoCore/Networking/KlaviyoRequest.swift b/Sources/KlaviyoCore/Networking/KlaviyoRequest.swift index f1fada3f..cedd4070 100644 --- a/Sources/KlaviyoCore/Networking/KlaviyoRequest.swift +++ b/Sources/KlaviyoCore/Networking/KlaviyoRequest.swift @@ -1,5 +1,5 @@ // -// File.swift +// KlaviyoRequest.swift // // // Created by Ajay Subramanya on 8/5/24. From 010655e7947521fb6eee148b201285e5186893da Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 8 Aug 2024 16:44:34 -0500 Subject: [PATCH 17/72] fixed more todos --- Sources/KlaviyoSwift/Klaviyo.swift | 6 ++-- Sources/KlaviyoSwift/KlaviyoState.swift | 1 - .../KlaviyoSwiftEnvironment.swift | 28 +++++++++++++++++++ .../KlaviyoSwift/StateChangePublisher.swift | 3 +- Sources/KlaviyoSwift/StateManagement.swift | 1 - 5 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 Sources/KlaviyoSwift/KlaviyoSwiftEnvironment.swift diff --git a/Sources/KlaviyoSwift/Klaviyo.swift b/Sources/KlaviyoSwift/Klaviyo.swift index 9f2f4c0d..bffddda3 100644 --- a/Sources/KlaviyoSwift/Klaviyo.swift +++ b/Sources/KlaviyoSwift/Klaviyo.swift @@ -13,8 +13,7 @@ import UIKit func dispatchOnMainThread(action: KlaviyoAction) { Task { await MainActor.run { - // TODO: FIXME - Store.production.send(action) + klaviyoSwiftEnvironment.send(action) } } } @@ -33,8 +32,7 @@ public struct KlaviyoSDK { public init() {} private var state: KlaviyoState { - // TODO: fixme - Store.production.state.value + klaviyoSwiftEnvironment.state() } /// Returns the email for the current user, if any. diff --git a/Sources/KlaviyoSwift/KlaviyoState.swift b/Sources/KlaviyoSwift/KlaviyoState.swift index 34021484..142f323c 100644 --- a/Sources/KlaviyoSwift/KlaviyoState.swift +++ b/Sources/KlaviyoSwift/KlaviyoState.swift @@ -282,7 +282,6 @@ struct KlaviyoState: Equatable, Codable { dict: pendingProfile) self.pendingProfile = nil } else { - // TODO: FIXME profile = Profile(email: email, phoneNumber: phoneNumber, externalId: externalId) } diff --git a/Sources/KlaviyoSwift/KlaviyoSwiftEnvironment.swift b/Sources/KlaviyoSwift/KlaviyoSwiftEnvironment.swift new file mode 100644 index 00000000..d7c4edd1 --- /dev/null +++ b/Sources/KlaviyoSwift/KlaviyoSwiftEnvironment.swift @@ -0,0 +1,28 @@ +// +// KlaviyoSwiftEnvironment.swift +// +// +// Created by Ajay Subramanya on 8/8/24. +// + +import Combine +import Foundation + +var klaviyoSwiftEnvironment = KlaviyoSwiftEnvironment.production + +struct KlaviyoSwiftEnvironment { + var send: (KlaviyoAction) -> Task? + var state: () -> KlaviyoState + var statePublisher: () -> AnyPublisher + + static let production: KlaviyoSwiftEnvironment = { + let store = Store.production + + return KlaviyoSwiftEnvironment( + send: { action in + store.send(action) + }, + state: { store.state.value }, + statePublisher: { store.state.eraseToAnyPublisher() }) + }() +} diff --git a/Sources/KlaviyoSwift/StateChangePublisher.swift b/Sources/KlaviyoSwift/StateChangePublisher.swift index 07fc5fc9..5fb7e865 100644 --- a/Sources/KlaviyoSwift/StateChangePublisher.swift +++ b/Sources/KlaviyoSwift/StateChangePublisher.swift @@ -18,8 +18,7 @@ public struct StateChangePublisher { } private static func createStatePublisher() -> AnyPublisher { - // TODO: Fixme - Store.production.state.eraseToAnyPublisher() + klaviyoSwiftEnvironment.statePublisher() .filter { state in state.initalizationState == .initialized } .removeDuplicates() .eraseToAnyPublisher() diff --git a/Sources/KlaviyoSwift/StateManagement.swift b/Sources/KlaviyoSwift/StateManagement.swift index 93a8db0c..8ef946e6 100644 --- a/Sources/KlaviyoSwift/StateManagement.swift +++ b/Sources/KlaviyoSwift/StateManagement.swift @@ -326,7 +326,6 @@ struct KlaviyoReducer: ReducerProtocol { let result = await environment.klaviyoAPI.send(request, numAttempts) switch result { case .success: - // TODO: may want to inspect response further. await send(.deQueueCompletedResults(request)) case let .failure(error): await send(handleRequestError(request: request, error: error, retryInfo: retryInfo)) From 58aad7328024235266b125526876f2e91e96c6e0 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Tue, 13 Aug 2024 12:30:41 -0500 Subject: [PATCH 18/72] addressed the last todo --- Sources/KlaviyoCore/AppLifeCycleEvents.swift | 20 +++++++--------- Sources/KlaviyoCore/KlaviyoEnvironment.swift | 4 ---- .../Networking}/SDKRequestIterator.swift | 1 - .../KlaviyoSwiftEnvironment.swift | 4 +++- Sources/KlaviyoSwift/Models/File.swift | 24 +++++++++++++++++++ Sources/KlaviyoSwift/StateManagement.swift | 5 ++-- 6 files changed, 38 insertions(+), 20 deletions(-) rename Sources/{KlaviyoSwift => KlaviyoCore/Networking}/SDKRequestIterator.swift (99%) create mode 100644 Sources/KlaviyoSwift/Models/File.swift diff --git a/Sources/KlaviyoCore/AppLifeCycleEvents.swift b/Sources/KlaviyoCore/AppLifeCycleEvents.swift index f40da818..e8087286 100644 --- a/Sources/KlaviyoCore/AppLifeCycleEvents.swift +++ b/Sources/KlaviyoCore/AppLifeCycleEvents.swift @@ -17,6 +17,7 @@ public enum LifeCycleEvents { case terminated case forgrounded case backgrounded + case reachabilityChanged(status: Reachability.NetworkStatus) } public struct AppLifeCycleEvents { @@ -43,20 +44,17 @@ public struct AppLifeCycleEvents { environment.stopReachability() }) .map { _ in LifeCycleEvents.backgrounded } - // TODO: fix me // The below is a bit convoluted since network status can be nil. -// let reachability = environment -// .notificationCenterPublisher(ReachabilityChangedNotification) -// .compactMap { _ -> KlaviyoAction? in -// guard let status = environment.reachabilityStatus() else { -// return nil -// } -// return KlaviyoAction.networkConnectivityChanged(status) -// } -// .eraseToAnyPublisher() + let reachability = environment + .notificationCenterPublisher(ReachabilityChangedNotification) + .map { _ in + // TODO: compact map isn't working, need to fix + let status = environment.reachabilityStatus() ?? .reachableViaWWAN + return LifeCycleEvents.reachabilityChanged(status: status) + } return terminated -// .merge(with: reachability) // TODO: fixme + .merge(with: reachability) .merge(with: foregrounded, backgrounded) .handleEvents(receiveSubscription: { _ in do { diff --git a/Sources/KlaviyoCore/KlaviyoEnvironment.swift b/Sources/KlaviyoCore/KlaviyoEnvironment.swift index aa415a19..4044b1bc 100644 --- a/Sources/KlaviyoCore/KlaviyoEnvironment.swift +++ b/Sources/KlaviyoCore/KlaviyoEnvironment.swift @@ -48,8 +48,6 @@ public struct KlaviyoEnvironment { public var reachabilityStatus: () -> Reachability.NetworkStatus? public var randomInt: () -> Int - // TODO: need to fix this -// public var stateChangePublisher: () -> AnyPublisher public var raiseFatalError: (String) -> Void public var emitDeveloperWarning: (String) -> Void @@ -91,8 +89,6 @@ public struct KlaviyoEnvironment { reachabilityService?.currentReachabilityStatus }, randomInt: { Int.random(in: 0...10) }, - // TODO: need to fix this -// stateChangePublisher: StateChangePublisher().publisher, raiseFatalError: { msg in #if DEBUG fatalError(msg) diff --git a/Sources/KlaviyoSwift/SDKRequestIterator.swift b/Sources/KlaviyoCore/Networking/SDKRequestIterator.swift similarity index 99% rename from Sources/KlaviyoSwift/SDKRequestIterator.swift rename to Sources/KlaviyoCore/Networking/SDKRequestIterator.swift index 2381bd2f..867d5922 100644 --- a/Sources/KlaviyoSwift/SDKRequestIterator.swift +++ b/Sources/KlaviyoCore/Networking/SDKRequestIterator.swift @@ -7,7 +7,6 @@ import AnyCodable import Foundation -import KlaviyoCore @_spi(KlaviyoPrivate) public struct SDKRequest: Identifiable, Equatable { diff --git a/Sources/KlaviyoSwift/KlaviyoSwiftEnvironment.swift b/Sources/KlaviyoSwift/KlaviyoSwiftEnvironment.swift index d7c4edd1..ff8b9e0f 100644 --- a/Sources/KlaviyoSwift/KlaviyoSwiftEnvironment.swift +++ b/Sources/KlaviyoSwift/KlaviyoSwiftEnvironment.swift @@ -14,6 +14,7 @@ struct KlaviyoSwiftEnvironment { var send: (KlaviyoAction) -> Task? var state: () -> KlaviyoState var statePublisher: () -> AnyPublisher + var stateChangePublisher: () -> AnyPublisher static let production: KlaviyoSwiftEnvironment = { let store = Store.production @@ -23,6 +24,7 @@ struct KlaviyoSwiftEnvironment { store.send(action) }, state: { store.state.value }, - statePublisher: { store.state.eraseToAnyPublisher() }) + statePublisher: { store.state.eraseToAnyPublisher() }, + stateChangePublisher: StateChangePublisher().publisher) }() } diff --git a/Sources/KlaviyoSwift/Models/File.swift b/Sources/KlaviyoSwift/Models/File.swift new file mode 100644 index 00000000..bc1eda67 --- /dev/null +++ b/Sources/KlaviyoSwift/Models/File.swift @@ -0,0 +1,24 @@ +// +// LifeCycleEventsExtension.swift +// +// +// Created by Ajay Subramanya on 8/13/24. +// + +import Foundation +import KlaviyoCore + +extension LifeCycleEvents { + var transformToKlaviyoAction: KlaviyoAction { + switch self { + case .terminated: + return .stop + case .forgrounded: + return .start + case .backgrounded: + return .stop + case let .reachabilityChanged(status): + return .networkConnectivityChanged(status) + } + } +} diff --git a/Sources/KlaviyoSwift/StateManagement.swift b/Sources/KlaviyoSwift/StateManagement.swift index 8ef946e6..833407bc 100644 --- a/Sources/KlaviyoSwift/StateManagement.swift +++ b/Sources/KlaviyoSwift/StateManagement.swift @@ -188,9 +188,8 @@ struct KlaviyoReducer: ReducerProtocol { } await send(.start) } - // TODO: fixme -// .merge(with: environment.appLifeCycle.lifeCycleEvents().eraseToEffect()) -// .merge(with: environment.stateChangePublisher().eraseToEffect()) + .merge(with: environment.appLifeCycle.lifeCycleEvents().map(\.transformToKlaviyoAction).eraseToEffect()) + .merge(with: klaviyoSwiftEnvironment.stateChangePublisher().eraseToEffect()) case let .setEmail(email): guard case .initialized = state.initalizationState else { From e11afba46822f08b267572dfd0f6c98e6e3b9c49 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Tue, 13 Aug 2024 16:09:36 -0500 Subject: [PATCH 19/72] some tests building --- Sources/KlaviyoCore/AppLifeCycleEvents.swift | 6 +- Sources/KlaviyoCore/KlaviyoEnvironment.swift | 57 ++++++++++++++- .../KlaviyoCore/Networking/KlaviyoAPI.swift | 20 +++--- .../Networking/NetworkSession.swift | 8 ++- Sources/KlaviyoCore/Utils/ArchivalUtils.swift | 7 ++ Sources/KlaviyoCore/Utils/FileUtils.swift | 20 ++++-- Sources/KlaviyoCore/Utils/LoggerClient.swift | 4 ++ .../Vendor/ReachabilitySwift.swift | 2 +- .../APIRequestErrorHandling.swift | 0 .../{ => StateManagement}/KlaviyoState.swift | 0 .../StateChangePublisher.swift | 0 .../StateManagement.swift | 0 .../KlaviyoSwiftTests/KlaviyoTestUtils.swift | 72 ++++++++----------- .../StateManagementTests.swift | 3 +- 14 files changed, 136 insertions(+), 63 deletions(-) rename Sources/KlaviyoSwift/{ => StateManagement}/APIRequestErrorHandling.swift (100%) rename Sources/KlaviyoSwift/{ => StateManagement}/KlaviyoState.swift (100%) rename Sources/KlaviyoSwift/{ => StateManagement}/StateChangePublisher.swift (100%) rename Sources/KlaviyoSwift/{ => StateManagement}/StateManagement.swift (100%) diff --git a/Sources/KlaviyoCore/AppLifeCycleEvents.swift b/Sources/KlaviyoCore/AppLifeCycleEvents.swift index e8087286..36b48f45 100644 --- a/Sources/KlaviyoCore/AppLifeCycleEvents.swift +++ b/Sources/KlaviyoCore/AppLifeCycleEvents.swift @@ -21,7 +21,9 @@ public enum LifeCycleEvents { } public struct AppLifeCycleEvents { - public var lifeCycleEvents: () -> AnyPublisher = { + public var lifeCycleEvents: () -> AnyPublisher + + public init(lifeCycleEvents: @escaping () -> AnyPublisher = { let terminated = environment .notificationCenterPublisher(UIApplication.willTerminateNotification) .handleEvents(receiveOutput: { _ in @@ -65,6 +67,8 @@ public struct AppLifeCycleEvents { }) .receive(on: RunLoop.main) .eraseToAnyPublisher() + }) { + self.lifeCycleEvents = lifeCycleEvents } static let production = Self() diff --git a/Sources/KlaviyoCore/KlaviyoEnvironment.swift b/Sources/KlaviyoCore/KlaviyoEnvironment.swift index 4044b1bc..d4e1ddc6 100644 --- a/Sources/KlaviyoCore/KlaviyoEnvironment.swift +++ b/Sources/KlaviyoCore/KlaviyoEnvironment.swift @@ -16,6 +16,57 @@ import os public var environment = KlaviyoEnvironment.production public struct KlaviyoEnvironment { + public init( + archiverClient: ArchiverClient, + fileClient: FileClient, + dataFromUrl: @escaping (URL) throws -> Data, + logger: LoggerClient, + appLifeCycle: AppLifeCycleEvents, + notificationCenterPublisher: @escaping (NSNotification.Name) -> AnyPublisher, + getNotificationSettings: @escaping (@escaping (PushEnablement) -> Void) -> Void, + getBackgroundSetting: @escaping () -> PushBackground, + startReachability: @escaping () throws -> Void, + stopReachability: @escaping () -> Void, + reachabilityStatus: @escaping () -> Reachability.NetworkStatus?, + randomInt: @escaping () -> Int, + raiseFatalError: @escaping (String) -> Void, + emitDeveloperWarning: @escaping (String) -> Void, + networkSession: @escaping () -> NetworkSession, + apiURL: String, + encodeJSON: @escaping (AnyEncodable) throws -> Data, + decoder: DataDecoder, + uuid: @escaping () -> UUID, + date: @escaping () -> Date, + timeZone: @escaping () -> String, + appContextInfo: @escaping () -> AppContextInfo, + klaviyoAPI: KlaviyoAPI, + timer: @escaping (Double) -> AnyPublisher) { + self.archiverClient = archiverClient + self.fileClient = fileClient + self.dataFromUrl = dataFromUrl + self.logger = logger + self.appLifeCycle = appLifeCycle + self.notificationCenterPublisher = notificationCenterPublisher + self.getNotificationSettings = getNotificationSettings + self.getBackgroundSetting = getBackgroundSetting + self.startReachability = startReachability + self.stopReachability = stopReachability + self.reachabilityStatus = reachabilityStatus + self.randomInt = randomInt + self.raiseFatalError = raiseFatalError + self.emitDeveloperWarning = emitDeveloperWarning + self.networkSession = networkSession + self.apiURL = apiURL + self.encodeJSON = encodeJSON + self.decoder = decoder + self.uuid = uuid + self.date = date + self.timeZone = timeZone + self.appContextInfo = appContextInfo + self.klaviyoAPI = klaviyoAPI + self.timer = timer + } + static let productionHost = "https://a.klaviyo.com" static let encoder = { () -> JSONEncoder in let encoder = JSONEncoder() @@ -119,11 +170,15 @@ public func createNetworkSession() -> NetworkSession { return networkSession } -enum KlaviyoDecodingError: Error { +public enum KlaviyoDecodingError: Error { case invalidType } public struct DataDecoder { + public init(jsonDecoder: JSONDecoder) { + self.jsonDecoder = jsonDecoder + } + public var jsonDecoder: JSONDecoder public static let production = Self(jsonDecoder: KlaviyoEnvironment.decoder) diff --git a/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift b/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift index 6135107b..495eec96 100644 --- a/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift +++ b/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift @@ -14,16 +14,9 @@ public func setKlaviyoAPIURL(url: String) { } public struct KlaviyoAPI { - public init() {} + public var send: (KlaviyoRequest, Int) async -> Result - // For internal testing use only - public static var requestStarted: (KlaviyoRequest) -> Void = { _ in } - public static var requestCompleted: (KlaviyoRequest, Data, Double) -> Void = { _, _, _ in } - public static var requestFailed: (KlaviyoRequest, Error, Double) -> Void = { _, _, _ in } - public static var requestRateLimited: (KlaviyoRequest, Int?) -> Void = { _, _ in } - public static var requestHttpError: (KlaviyoRequest, Int, Double) -> Void = { _, _, _ in } - - public var send: (KlaviyoRequest, Int) async -> Result = { request, attemptNumber in + public init(send: @escaping (KlaviyoRequest, Int) async -> Result = { request, attemptNumber in let start = environment.date() var urlRequest: URLRequest @@ -74,5 +67,14 @@ public struct KlaviyoAPI { requestCompleted(request, data, duration) return .success(data) + }) { + self.send = send } + + // For internal testing use only + public static var requestStarted: (KlaviyoRequest) -> Void = { _ in } + public static var requestCompleted: (KlaviyoRequest, Data, Double) -> Void = { _, _, _ in } + public static var requestFailed: (KlaviyoRequest, Error, Double) -> Void = { _, _, _ in } + public static var requestRateLimited: (KlaviyoRequest, Int?) -> Void = { _, _ in } + public static var requestHttpError: (KlaviyoRequest, Int, Double) -> Void = { _, _, _ in } } diff --git a/Sources/KlaviyoCore/Networking/NetworkSession.swift b/Sources/KlaviyoCore/Networking/NetworkSession.swift index e9cca850..647cf55b 100644 --- a/Sources/KlaviyoCore/Networking/NetworkSession.swift +++ b/Sources/KlaviyoCore/Networking/NetworkSession.swift @@ -22,6 +22,12 @@ public func createEmphemeralSession(protocolClasses: [AnyClass] = URLProtocolOve } public struct NetworkSession { + public var data: (URLRequest) async throws -> (Data, URLResponse) + + public init(data: @escaping (URLRequest) async throws -> (Data, URLResponse)) { + self.data = data + } + fileprivate static let currentApiRevision = "2023-07-15" fileprivate static let applicationJson = "application/json" fileprivate static let acceptedEncodings = ["br", "gzip", "deflate"] @@ -33,8 +39,6 @@ public struct NetworkSession { return "\(appContext.executable)/\(appContext.appVersion) (\(appContext.bundleId); build:\(appContext.appBuild); \(appContext.osVersionName)) \(klaivyoSDKVersion)" }() - public var data: (URLRequest) async throws -> (Data, URLResponse) - public static let production = { () -> NetworkSession in let session = createEmphemeralSession() diff --git a/Sources/KlaviyoCore/Utils/ArchivalUtils.swift b/Sources/KlaviyoCore/Utils/ArchivalUtils.swift index b02d8be8..4bf44edd 100644 --- a/Sources/KlaviyoCore/Utils/ArchivalUtils.swift +++ b/Sources/KlaviyoCore/Utils/ArchivalUtils.swift @@ -8,6 +8,13 @@ import Foundation public struct ArchiverClient { + public init( + archivedData: @escaping (Any, Bool) throws -> Data, + unarchivedMutableArray: @escaping (Data) throws -> NSMutableArray?) { + self.archivedData = archivedData + self.unarchivedMutableArray = unarchivedMutableArray + } + public var archivedData: (Any, Bool) throws -> Data public var unarchivedMutableArray: (Data) throws -> NSMutableArray? diff --git a/Sources/KlaviyoCore/Utils/FileUtils.swift b/Sources/KlaviyoCore/Utils/FileUtils.swift index 02350a8f..04693de4 100644 --- a/Sources/KlaviyoCore/Utils/FileUtils.swift +++ b/Sources/KlaviyoCore/Utils/FileUtils.swift @@ -12,15 +12,27 @@ func write(data: Data, url: URL) throws { } public struct FileClient { + public init( + write: @escaping (Data, URL) throws -> Void, + fileExists: @escaping (String) -> Bool, + removeItem: @escaping (String) throws -> Void, + libraryDirectory: @escaping () -> URL) { + self.write = write + self.fileExists = fileExists + self.removeItem = removeItem + self.libraryDirectory = libraryDirectory + } + + public var write: (Data, URL) throws -> Void + public var fileExists: (String) -> Bool + public var removeItem: (String) throws -> Void + public var libraryDirectory: () -> URL + public static let production = FileClient( write: write(data:url:), fileExists: FileManager.default.fileExists(atPath:), removeItem: FileManager.default.removeItem(atPath:), libraryDirectory: { FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first! }) - public var write: (Data, URL) throws -> Void - public var fileExists: (String) -> Bool - public var removeItem: (String) throws -> Void - public var libraryDirectory: () -> URL } /** diff --git a/Sources/KlaviyoCore/Utils/LoggerClient.swift b/Sources/KlaviyoCore/Utils/LoggerClient.swift index c031b1e7..9e28667a 100644 --- a/Sources/KlaviyoCore/Utils/LoggerClient.swift +++ b/Sources/KlaviyoCore/Utils/LoggerClient.swift @@ -9,6 +9,10 @@ import Foundation import os public struct LoggerClient { + public init(error: @escaping (String) -> Void) { + self.error = error + } + public var error: (String) -> Void public static let production = Self(error: { message in os_log("%{public}s", type: .error, message) }) } diff --git a/Sources/KlaviyoCore/Vendor/ReachabilitySwift.swift b/Sources/KlaviyoCore/Vendor/ReachabilitySwift.swift index de4ef9ca..1daf2bc9 100644 --- a/Sources/KlaviyoCore/Vendor/ReachabilitySwift.swift +++ b/Sources/KlaviyoCore/Vendor/ReachabilitySwift.swift @@ -35,7 +35,7 @@ enum ReachabilityError: Error { case UnableToSetDispatchQueue } -let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification") +public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification") func callback(reachability: SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) { guard let info = info else { return } diff --git a/Sources/KlaviyoSwift/APIRequestErrorHandling.swift b/Sources/KlaviyoSwift/StateManagement/APIRequestErrorHandling.swift similarity index 100% rename from Sources/KlaviyoSwift/APIRequestErrorHandling.swift rename to Sources/KlaviyoSwift/StateManagement/APIRequestErrorHandling.swift diff --git a/Sources/KlaviyoSwift/KlaviyoState.swift b/Sources/KlaviyoSwift/StateManagement/KlaviyoState.swift similarity index 100% rename from Sources/KlaviyoSwift/KlaviyoState.swift rename to Sources/KlaviyoSwift/StateManagement/KlaviyoState.swift diff --git a/Sources/KlaviyoSwift/StateChangePublisher.swift b/Sources/KlaviyoSwift/StateManagement/StateChangePublisher.swift similarity index 100% rename from Sources/KlaviyoSwift/StateChangePublisher.swift rename to Sources/KlaviyoSwift/StateManagement/StateChangePublisher.swift diff --git a/Sources/KlaviyoSwift/StateManagement.swift b/Sources/KlaviyoSwift/StateManagement/StateManagement.swift similarity index 100% rename from Sources/KlaviyoSwift/StateManagement.swift rename to Sources/KlaviyoSwift/StateManagement/StateManagement.swift diff --git a/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift b/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift index cfb69300..59a0af4d 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift @@ -68,30 +68,39 @@ extension ArchiverClient { unarchivedMutableArray: { _ in SAMPLE_DATA }) } +// TODO: Fixme extension AppLifeCycleEvents { - static let test = Self(lifeCycleEvents: { Empty().eraseToAnyPublisher() }) + static let test = Self(lifeCycleEvents: { Empty().eraseToAnyPublisher() }) } extension KlaviyoEnvironment { static var lastLog: String? - static var test = { KlaviyoEnvironment( - archiverClient: ArchiverClient.test, - fileClient: FileClient.test, - data: { _ in TEST_RETURN_DATA }, - logger: LoggerClient.test, - analytics: AnalyticsEnvironment.test, - getUserDefaultString: { _ in "value" }, - appLifeCycle: AppLifeCycleEvents.test, - notificationCenterPublisher: { _ in Empty().eraseToAnyPublisher() }, - getNotificationSettings: { callback in callback(.authorized) }, - getBackgroundSetting: { .available }, - legacyIdentifier: { "iOS:\(UUID(uuidString: "00000000-0000-0000-0000-000000000002")!.uuidString)" }, - startReachability: {}, - stopReachability: {}, - reachabilityStatus: { nil }, - randomInt: { 0 }, - stateChangePublisher: { Empty().eraseToAnyPublisher() }, - raiseFatalError: { _ in }, emitDeveloperWarning: { _ in }) + static var test = { + KlaviyoEnvironment( + archiverClient: ArchiverClient.test, + fileClient: FileClient.test, + dataFromUrl: { _ in TEST_RETURN_DATA }, + logger: LoggerClient.test, + appLifeCycle: AppLifeCycleEvents.test, + notificationCenterPublisher: { _ in Empty().eraseToAnyPublisher() }, + getNotificationSettings: { callback in callback(.authorized) }, + getBackgroundSetting: { .available }, + startReachability: {}, + stopReachability: {}, + reachabilityStatus: { nil }, + randomInt: { 0 }, + raiseFatalError: { _ in }, + emitDeveloperWarning: { _ in }, + networkSession: { NetworkSession.test() }, + apiURL: "dead_beef", + encodeJSON: { _ in TEST_RETURN_DATA }, + decoder: DataDecoder(jsonDecoder: TestJSONDecoder()), + uuid: { UUID(uuidString: "00000000-0000-0000-0000-000000000001")! }, + date: { Date(timeIntervalSince1970: 1_234_567_890) }, + timeZone: { "EST" }, + appContextInfo: { AppContextInfo.test }, + klaviyoAPI: KlaviyoAPI.test(), + timer: { _ in Just(Date()).eraseToAnyPublisher() }) } } @@ -107,31 +116,6 @@ class InvalidJSONDecoder: JSONDecoder { } } -extension AnalyticsEnvironment { - static let testStore = Store(initialState: KlaviyoState(queue: []), reducer: KlaviyoReducer()) - - static let test = AnalyticsEnvironment( - networkSession: { NetworkSession.test() }, - apiURL: "dead_beef", - encodeJSON: { _ in TEST_RETURN_DATA }, - decoder: DataDecoder(jsonDecoder: TestJSONDecoder()), - uuid: { UUID(uuidString: "00000000-0000-0000-0000-000000000001")! }, - date: { Date(timeIntervalSince1970: 1_234_567_890) }, - timeZone: { "EST" }, - appContextInfo: { AppContextInfo.test }, - klaviyoAPI: KlaviyoAPI.test(), - timer: { _ in Just(Date()).eraseToAnyPublisher() }, - send: { action in - testStore.send(action) - }, - state: { - AnalyticsEnvironment.testStore.state.value - }, - statePublisher: { - Just(INITIALIZED_TEST_STATE()).eraseToAnyPublisher() - }) -} - struct KlaviyoTestReducer: ReducerProtocol { var reducer: (inout KlaviyoSwift.KlaviyoState, KlaviyoAction) -> EffectTask = { _, _ in .none } diff --git a/Tests/KlaviyoSwiftTests/StateManagementTests.swift b/Tests/KlaviyoSwiftTests/StateManagementTests.swift index 9fd0fb5d..f31d3c69 100644 --- a/Tests/KlaviyoSwiftTests/StateManagementTests.swift +++ b/Tests/KlaviyoSwiftTests/StateManagementTests.swift @@ -9,6 +9,7 @@ import AnyCodable import Combine import Foundation +import KlaviyoCore import XCTest class StateManagementTests: XCTestCase { @@ -26,7 +27,7 @@ class StateManagementTests: XCTestCase { let apiKey = "fake-key" // Avoids a warning in xcode despite the result being discardable. - _ = await store.send(.initialize(apiKey)) { + await store.send(.initialize(apiKey)) { $0.apiKey = apiKey $0.initalizationState = .initializing } From a285a58a4d6957cc3ff58cd4c113f58c9784cba2 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Tue, 13 Aug 2024 16:54:59 -0500 Subject: [PATCH 20/72] fixing some tests --- ...e.swift => LifecycleEventsExtension.swift} | 0 .../FileUtilsTests.swift | 0 .../NetworkSessionTests.swift | 0 .../KlaviyoSwiftTests/KlaviyoTestUtils.swift | 1 - .../StateChangePublisherTests.swift | 1 + .../StateManagementEdgeCaseTests.swift | 25 +++++++------- .../StateManagementTests.swift | 18 +++++----- Tests/KlaviyoSwiftTests/TestData.swift | 34 +++++++++++-------- 8 files changed, 42 insertions(+), 37 deletions(-) rename Sources/KlaviyoSwift/Models/{File.swift => LifecycleEventsExtension.swift} (100%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/FileUtilsTests.swift (100%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/NetworkSessionTests.swift (100%) diff --git a/Sources/KlaviyoSwift/Models/File.swift b/Sources/KlaviyoSwift/Models/LifecycleEventsExtension.swift similarity index 100% rename from Sources/KlaviyoSwift/Models/File.swift rename to Sources/KlaviyoSwift/Models/LifecycleEventsExtension.swift diff --git a/Tests/KlaviyoSwiftTests/FileUtilsTests.swift b/Tests/KlaviyoCoreTests/FileUtilsTests.swift similarity index 100% rename from Tests/KlaviyoSwiftTests/FileUtilsTests.swift rename to Tests/KlaviyoCoreTests/FileUtilsTests.swift diff --git a/Tests/KlaviyoSwiftTests/NetworkSessionTests.swift b/Tests/KlaviyoCoreTests/NetworkSessionTests.swift similarity index 100% rename from Tests/KlaviyoSwiftTests/NetworkSessionTests.swift rename to Tests/KlaviyoCoreTests/NetworkSessionTests.swift diff --git a/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift b/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift index 59a0af4d..d332b241 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift @@ -68,7 +68,6 @@ extension ArchiverClient { unarchivedMutableArray: { _ in SAMPLE_DATA }) } -// TODO: Fixme extension AppLifeCycleEvents { static let test = Self(lifeCycleEvents: { Empty().eraseToAnyPublisher() }) } diff --git a/Tests/KlaviyoSwiftTests/StateChangePublisherTests.swift b/Tests/KlaviyoSwiftTests/StateChangePublisherTests.swift index 4cfe4dec..e04123e0 100644 --- a/Tests/KlaviyoSwiftTests/StateChangePublisherTests.swift +++ b/Tests/KlaviyoSwiftTests/StateChangePublisherTests.swift @@ -10,6 +10,7 @@ import CombineSchedulers import Foundation import XCTest @_spi(KlaviyoPrivate) @testable import KlaviyoSwift +import KlaviyoCore final class StateChangePublisherTests: XCTestCase { @MainActor diff --git a/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift b/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift index 0693a6a1..eefb98eb 100644 --- a/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift +++ b/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift @@ -7,6 +7,7 @@ @testable import KlaviyoSwift import Foundation +import KlaviyoCore import XCTest class StateManagementEdgeCaseTests: XCTestCase { @@ -124,7 +125,7 @@ class StateManagementEdgeCaseTests: XCTestCase { } let apiKey = "fake-key" let initialState = KlaviyoState(apiKey: apiKey, - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [], initalizationState: .uninitialized, @@ -171,7 +172,7 @@ class StateManagementEdgeCaseTests: XCTestCase { func testSetExternalIdUninitializedDoesNotAddToPendingRequest() async throws { let apiKey = "fake-key" let initialState = KlaviyoState(apiKey: apiKey, - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [], initalizationState: .uninitialized, @@ -216,7 +217,7 @@ class StateManagementEdgeCaseTests: XCTestCase { func testSetPhoneNumberUninitializedDoesNotAddToPendingRequest() async throws { let apiKey = "fake-key" let initialState = KlaviyoState(apiKey: apiKey, - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [], initalizationState: .uninitialized, @@ -228,7 +229,7 @@ class StateManagementEdgeCaseTests: XCTestCase { @MainActor func testSetPhoneNumberMissingApiKeyStillSetsPhoneNumber() async throws { - let initialState = KlaviyoState(anonymousId: environment.analytics.uuid().uuidString, + let initialState = KlaviyoState(anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [], initalizationState: .initialized, @@ -260,7 +261,7 @@ class StateManagementEdgeCaseTests: XCTestCase { func testSetPushTokenUninitializedDoesNotAddToPendingRequest() async throws { let apiKey = "fake-key" let initialState = KlaviyoState(apiKey: apiKey, - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [], initalizationState: .uninitialized, @@ -292,7 +293,7 @@ class StateManagementEdgeCaseTests: XCTestCase { func testStopUninitialized() async { let apiKey = "fake-key" let initialState = KlaviyoState(apiKey: apiKey, - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [], initalizationState: .uninitialized, @@ -306,7 +307,7 @@ class StateManagementEdgeCaseTests: XCTestCase { func testStopInitializing() async { let apiKey = "fake-key" let initialState = KlaviyoState(apiKey: apiKey, - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [], initalizationState: .initializing, @@ -322,7 +323,7 @@ class StateManagementEdgeCaseTests: XCTestCase { func testStartUninitialized() async { let apiKey = "fake-key" let initialState = KlaviyoState(apiKey: apiKey, - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [], initalizationState: .uninitialized, @@ -338,7 +339,7 @@ class StateManagementEdgeCaseTests: XCTestCase { func testNetworkStatusChangedUninitialized() async { let apiKey = "fake-key" let initialState = KlaviyoState(apiKey: apiKey, - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [], initalizationState: .uninitialized, @@ -353,7 +354,7 @@ class StateManagementEdgeCaseTests: XCTestCase { @MainActor func testTokenRequestMissingApiKey() async { let initialState = KlaviyoState( - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [], initalizationState: .initialized, @@ -415,13 +416,13 @@ class StateManagementEdgeCaseTests: XCTestCase { let initialState = KlaviyoState( apiKey: TEST_API_KEY, email: "foo@bar.com", - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, phoneNumber: "99999999", externalId: "12345", pushTokenData: .init(pushToken: "blob_token", pushEnablement: .authorized, pushBackground: .available, - deviceData: .init(context: environment.analytics.appContextInfo())), + deviceData: .init(context: environment.appContextInfo())), queue: [], requestsInFlight: [], initalizationState: .initialized, diff --git a/Tests/KlaviyoSwiftTests/StateManagementTests.swift b/Tests/KlaviyoSwiftTests/StateManagementTests.swift index f31d3c69..e0269f4f 100644 --- a/Tests/KlaviyoSwiftTests/StateManagementTests.swift +++ b/Tests/KlaviyoSwiftTests/StateManagementTests.swift @@ -32,7 +32,7 @@ class StateManagementTests: XCTestCase { $0.initalizationState = .initializing } - let expectedState = KlaviyoState(apiKey: apiKey, anonymousId: environment.analytics.uuid().uuidString, queue: [], requestsInFlight: []) + let expectedState = KlaviyoState(apiKey: apiKey, anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: []) await store.receive(.completeInitialization(expectedState)) { $0.anonymousId = expectedState.anonymousId $0.initalizationState = .initialized @@ -47,7 +47,7 @@ class StateManagementTests: XCTestCase { func testInitializeSubscribesToAppropriatePublishers() async throws { let lifecycleExpectation = XCTestExpectation(description: "lifecycle is subscribed") let stateChangeIsSubscribed = XCTestExpectation(description: "state change is subscribed") - let lifecycleSubject = PassthroughSubject() + let lifecycleSubject = PassthroughSubject() environment.appLifeCycle.lifeCycleEvents = { lifecycleSubject.handleEvents(receiveSubscription: { _ in lifecycleExpectation.fulfill() @@ -55,7 +55,7 @@ class StateManagementTests: XCTestCase { .eraseToAnyPublisher() } let stateChangeSubject = PassthroughSubject() - environment.stateChangePublisher = { + klaviyoSwiftEnvironment.stateChangePublisher = { stateChangeSubject.handleEvents(receiveSubscription: { _ in stateChangeIsSubscribed.fulfill() }) @@ -144,7 +144,7 @@ class StateManagementTests: XCTestCase { _ = await store.receive(.deQueueCompletedResults(pushTokenRequest)) { $0.flushing = false $0.requestsInFlight = [] - $0.pushTokenData = KlaviyoState.PushTokenData(pushToken: "blobtoken", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.analytics.appContextInfo())) + $0.pushTokenData = KlaviyoState.PushTokenData(pushToken: "blobtoken", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.appContextInfo())) } } @@ -172,7 +172,7 @@ class StateManagementTests: XCTestCase { _ = await store.receive(.deQueueCompletedResults(pushTokenRequest)) { $0.flushing = false $0.requestsInFlight = [] - $0.pushTokenData = KlaviyoState.PushTokenData(pushToken: "blobtoken", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.analytics.appContextInfo())) + $0.pushTokenData = KlaviyoState.PushTokenData(pushToken: "blobtoken", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.appContextInfo())) } _ = await store.send(.setPushToken("blobtoken", .authorized)) } @@ -219,7 +219,7 @@ class StateManagementTests: XCTestCase { func testFlushQueueWithMultipleRequests() async throws { var count = 0 // request uuids need to be unique :) - environment.analytics.uuid = { + environment.uuid = { count += 1 switch count { case 1: @@ -251,7 +251,7 @@ class StateManagementTests: XCTestCase { } await store.receive(.sendRequest) await store.receive(.deQueueCompletedResults(request2)) { - $0.pushTokenData = KlaviyoState.PushTokenData(pushToken: "blob_token", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.analytics.appContextInfo())) + $0.pushTokenData = KlaviyoState.PushTokenData(pushToken: "blob_token", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.appContextInfo())) $0.flushing = false $0.requestsInFlight = [] $0.queue = [] @@ -400,7 +400,7 @@ class StateManagementTests: XCTestCase { } } - var request: KlaviyoAPI.KlaviyoRequest? + var request: KlaviyoRequest? _ = await store.send(.flushQueue) { $0.enqueueProfileOrTokenRequest() @@ -470,7 +470,7 @@ class StateManagementTests: XCTestCase { $0.externalId = Profile.test.externalId $0.pushTokenData = nil - let request = KlaviyoAPI.KlaviyoRequest( + let request = KlaviyoRequest( apiKey: initialState.apiKey!, endpoint: .registerPushToken(.init( pushToken: initialState.pushTokenData!.pushToken, diff --git a/Tests/KlaviyoSwiftTests/TestData.swift b/Tests/KlaviyoSwiftTests/TestData.swift index 0a842529..1e68203b 100644 --- a/Tests/KlaviyoSwiftTests/TestData.swift +++ b/Tests/KlaviyoSwiftTests/TestData.swift @@ -7,17 +7,18 @@ import Foundation @_spi(KlaviyoPrivate) @testable import KlaviyoSwift +import KlaviyoCore let TEST_API_KEY = "fake-key" let INITIALIZED_TEST_STATE = { KlaviyoState( apiKey: TEST_API_KEY, - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, pushTokenData: .init(pushToken: "blob_token", pushEnablement: .authorized, pushBackground: .available, - deviceData: .init(context: environment.analytics.appContextInfo())), + deviceData: .init(context: environment.appContextInfo())), queue: [], requestsInFlight: [], initalizationState: .initialized, @@ -27,7 +28,7 @@ let INITIALIZED_TEST_STATE = { let INITILIZING_TEST_STATE = { KlaviyoState( apiKey: TEST_API_KEY, - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [], initalizationState: .initializing, @@ -37,12 +38,12 @@ let INITILIZING_TEST_STATE = { let INITIALIZED_TEST_STATE_INVALID_PHONE = { KlaviyoState( apiKey: TEST_API_KEY, - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, phoneNumber: "invalid_phone_number", pushTokenData: .init(pushToken: "blob_token", pushEnablement: .authorized, pushBackground: .available, - deviceData: .init(context: environment.analytics.appContextInfo())), + deviceData: .init(context: environment.appContextInfo())), queue: [], requestsInFlight: [], initalizationState: .initialized, @@ -53,11 +54,11 @@ let INITIALIZED_TEST_STATE_INVALID_EMAIL = { KlaviyoState( apiKey: TEST_API_KEY, email: "invalid_email", - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, pushTokenData: .init(pushToken: "blob_token", pushEnablement: .authorized, pushBackground: .available, - deviceData: .init(context: environment.analytics.appContextInfo())), + deviceData: .init(context: environment.appContextInfo())), queue: [], requestsInFlight: [], initalizationState: .initialized, @@ -120,8 +121,8 @@ extension Event.Metric { static let test = Self(name: .CustomEvent("blob")) } -extension KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateEventPayload { - static let test = Self(data: .init(event: .test)) +extension CreateEventPayload { + static let test = CreateEventPayload(data: Event(name: "test")) } extension URLResponse { @@ -129,22 +130,25 @@ extension URLResponse { static let validResponse = HTTPURLResponse(url: TEST_URL, statusCode: 200, httpVersion: nil, headerFields: nil)! } -extension KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload { - static let test = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload( +extension PushTokenPayload { + static let test = PushTokenPayload( pushToken: "foo", enablement: "AUTHORIZED", background: "AVAILABLE", - profile: .init(), - anonymousId: "anon-id") + profile: ProfilePayload(anonymousId: "anon-id")) } extension KlaviyoState { static let test = KlaviyoState(apiKey: "foo", email: "test@test.com", - anonymousId: environment.analytics.uuid().uuidString, + anonymousId: environment.uuid().uuidString, phoneNumber: "phoneNumber", externalId: "externalId", - pushTokenData: .init(pushToken: "blob_token", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.analytics.appContextInfo())), + pushTokenData: PushTokenData( + pushToken: "blob_token", + pushEnablement: .authorized, + pushBackground: .available, + deviceData: DeviceMetadata(context: environment.appContextInfo())), queue: [], requestsInFlight: [], initalizationState: .initialized, From 89a4b7efa5799921b69613200b66cdd84529221d Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Tue, 13 Aug 2024 17:41:50 -0500 Subject: [PATCH 21/72] fixed some more tests --- Sources/KlaviyoCore/KlaviyoEnvironment.swift | 2 +- .../KlaviyoCore/Models/PushBackground.swift | 2 +- .../KlaviyoCore/Models/PushEnablement.swift | 2 +- .../APIRequestErrorHandlingTests.swift | 31 +++++++------- .../AppLifeCycleEventsTests.swift | 3 +- .../ArchivalUtilsTests.swift | 1 + Tests/KlaviyoSwiftTests/EncodableTests.swift | 7 ++-- .../FileUtilsTests.swift | 1 + Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift | 31 +++++++------- Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift | 3 +- .../KlaviyoSwiftTests/KlaviyoStateTests.swift | 42 +++++++++---------- .../NetworkSessionTests.swift | 3 +- .../StateChangePublisherTests.swift | 2 +- 13 files changed, 69 insertions(+), 61 deletions(-) rename Tests/{KlaviyoCoreTests => KlaviyoSwiftTests}/FileUtilsTests.swift (98%) rename Tests/{KlaviyoCoreTests => KlaviyoSwiftTests}/NetworkSessionTests.swift (89%) diff --git a/Sources/KlaviyoCore/KlaviyoEnvironment.swift b/Sources/KlaviyoCore/KlaviyoEnvironment.swift index d4e1ddc6..1478ba8a 100644 --- a/Sources/KlaviyoCore/KlaviyoEnvironment.swift +++ b/Sources/KlaviyoCore/KlaviyoEnvironment.swift @@ -68,7 +68,7 @@ public struct KlaviyoEnvironment { } static let productionHost = "https://a.klaviyo.com" - static let encoder = { () -> JSONEncoder in + public static let encoder = { () -> JSONEncoder in let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 return encoder diff --git a/Sources/KlaviyoCore/Models/PushBackground.swift b/Sources/KlaviyoCore/Models/PushBackground.swift index 0f3d0dda..ee0d58ae 100644 --- a/Sources/KlaviyoCore/Models/PushBackground.swift +++ b/Sources/KlaviyoCore/Models/PushBackground.swift @@ -12,7 +12,7 @@ public enum PushBackground: String, Codable { case restricted = "RESTRICTED" case denied = "DENIED" - static func create(from status: UIBackgroundRefreshStatus) -> PushBackground { + public static func create(from status: UIBackgroundRefreshStatus) -> PushBackground { switch status { case .available: return PushBackground.available diff --git a/Sources/KlaviyoCore/Models/PushEnablement.swift b/Sources/KlaviyoCore/Models/PushEnablement.swift index a49b3510..1a321596 100644 --- a/Sources/KlaviyoCore/Models/PushEnablement.swift +++ b/Sources/KlaviyoCore/Models/PushEnablement.swift @@ -14,7 +14,7 @@ public enum PushEnablement: String, Codable { case provisional = "PROVISIONAL" case ephemeral = "EPHEMERAL" - static func create(from status: UNAuthorizationStatus) -> PushEnablement { + public static func create(from status: UNAuthorizationStatus) -> PushEnablement { switch status { case .denied: return PushEnablement.denied diff --git a/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift b/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift index 69b6f63e..4d66bfbd 100644 --- a/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift +++ b/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift @@ -7,6 +7,7 @@ @testable import KlaviyoSwift import Foundation +import KlaviyoCore import XCTest let TIMEOUT_NANOSECONDS: UInt64 = 10_000_000_000 // 10 seconds @@ -26,7 +27,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.httpError(500, TEST_RETURN_DATA)) } + environment.klaviyoAPI.send = { _, _ in .failure(.httpError(500, TEST_RETURN_DATA)) } _ = await store.send(.sendRequest) @@ -43,7 +44,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.httpError(400, TEST_FAILURE_JSON_INVALID_PHONE_NUMBER.data(using: .utf8)!)) } + environment.klaviyoAPI.send = { _, _ in .failure(.httpError(400, TEST_FAILURE_JSON_INVALID_PHONE_NUMBER.data(using: .utf8)!)) } _ = await store.send(.sendRequest) @@ -66,7 +67,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.httpError(400, TEST_FAILURE_JSON_INVALID_EMAIL.data(using: .utf8)!)) } + environment.klaviyoAPI.send = { _, _ in .failure(.httpError(400, TEST_FAILURE_JSON_INVALID_EMAIL.data(using: .utf8)!)) } _ = await store.send(.sendRequest) @@ -92,7 +93,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request, request2] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } + environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } _ = await store.send(.sendRequest) @@ -113,7 +114,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request, request2] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } + environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } _ = await store.send(.sendRequest) @@ -136,7 +137,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request, request2] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } + environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } _ = await store.send(.sendRequest) @@ -159,7 +160,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.internalError("internal error!")) } + environment.klaviyoAPI.send = { _, _ in .failure(.internalError("internal error!")) } _ = await store.send(.sendRequest) @@ -181,7 +182,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.internalRequestError(KlaviyoAPI.KlaviyoAPIError.internalError("foo"))) } + environment.klaviyoAPI.send = { _, _ in .failure(.internalRequestError(KlaviyoAPI.KlaviyoAPIError.internalError("foo"))) } _ = await store.send(.sendRequest) @@ -203,7 +204,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.unknownError(KlaviyoAPI.KlaviyoAPIError.internalError("foo"))) } + environment.klaviyoAPI.send = { _, _ in .failure(.unknownError(KlaviyoAPI.KlaviyoAPIError.internalError("foo"))) } _ = await store.send(.sendRequest) @@ -224,7 +225,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.dataEncodingError(request)) } + environment.klaviyoAPI.send = { _, _ in .failure(.dataEncodingError(request)) } _ = await store.send(.sendRequest) @@ -245,7 +246,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.invalidData) } + environment.klaviyoAPI.send = { _, _ in .failure(.invalidData) } _ = await store.send(.sendRequest) @@ -266,7 +267,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(nil)) } + environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(nil)) } _ = await store.send(.sendRequest) @@ -286,7 +287,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(nil)) } + environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(nil)) } _ = await store.send(.sendRequest) @@ -305,7 +306,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(20)) } + environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(20)) } _ = await store.send(.sendRequest) @@ -327,7 +328,7 @@ class APIRequestErrorHandlingTests: XCTestCase { initialState.requestsInFlight = [request] let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - environment.analytics.klaviyoAPI.send = { _, _ in .failure(.missingOrInvalidResponse(nil)) } + environment.klaviyoAPI.send = { _, _ in .failure(.missingOrInvalidResponse(nil)) } _ = await store.send(.sendRequest) diff --git a/Tests/KlaviyoSwiftTests/AppLifeCycleEventsTests.swift b/Tests/KlaviyoSwiftTests/AppLifeCycleEventsTests.swift index 64e53866..64dca09d 100644 --- a/Tests/KlaviyoSwiftTests/AppLifeCycleEventsTests.swift +++ b/Tests/KlaviyoSwiftTests/AppLifeCycleEventsTests.swift @@ -8,6 +8,7 @@ @testable import KlaviyoSwift import Combine import Foundation +import KlaviyoCore import XCTest class AppLifeCycleEventsTests: XCTestCase { @@ -149,7 +150,7 @@ class AppLifeCycleEventsTests: XCTestCase { let expection = XCTestExpectation(description: "Start reachability is called.") environment.startReachability = { expection.fulfill() - throw KlaviyoAPI.KlaviyoAPIError.internalError("foo") + throw KlaviyoAPIError.internalError("foo") } let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } diff --git a/Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift b/Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift index b5a0f349..dc94f748 100644 --- a/Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift +++ b/Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift @@ -6,6 +6,7 @@ // @testable import KlaviyoSwift +import KlaviyoCore import XCTest class ArchivalUtilsTests: XCTestCase { diff --git a/Tests/KlaviyoSwiftTests/EncodableTests.swift b/Tests/KlaviyoSwiftTests/EncodableTests.swift index fb99191f..bdaadff1 100644 --- a/Tests/KlaviyoSwiftTests/EncodableTests.swift +++ b/Tests/KlaviyoSwiftTests/EncodableTests.swift @@ -6,6 +6,7 @@ // @testable import KlaviyoSwift +import KlaviyoCore import SnapshotTesting import XCTest @@ -19,7 +20,7 @@ final class EncodableTests: XCTestCase { func testProfilePayload() throws { let profile = Profile.test - let data = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateProfilePayload.Profile(profile: profile, anonymousId: "foo") + let data = CreateProfilePayload.Profile(profile: profile, anonymousId: "foo") let payload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateProfilePayload(data: data) assertSnapshot(matching: payload, as: .json(KlaviyoEnvironment.encoder)) } @@ -48,7 +49,7 @@ final class EncodableTests: XCTestCase { } func testUnregisterTokenPayload() throws { - let tokenPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.UnregisterPushTokenPayload( + let tokenPayload = UnregisterPushTokenPayload( pushToken: "foo", profile: .init(email: "foo", phoneNumber: "foo"), anonymousId: "foo") @@ -56,7 +57,7 @@ final class EncodableTests: XCTestCase { } func testKlaviyoState() throws { - let tokenPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload( + let tokenPayload = PushTokenPayload( pushToken: "foo", enablement: "AUTHORIZED", background: "AVAILABLE", diff --git a/Tests/KlaviyoCoreTests/FileUtilsTests.swift b/Tests/KlaviyoSwiftTests/FileUtilsTests.swift similarity index 98% rename from Tests/KlaviyoCoreTests/FileUtilsTests.swift rename to Tests/KlaviyoSwiftTests/FileUtilsTests.swift index b33d55aa..a3554169 100644 --- a/Tests/KlaviyoCoreTests/FileUtilsTests.swift +++ b/Tests/KlaviyoSwiftTests/FileUtilsTests.swift @@ -6,6 +6,7 @@ // @testable import KlaviyoSwift +import KlaviyoCore import XCTest class FileUtilsTests: XCTestCase { diff --git a/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift b/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift index c05a58b8..3d771c52 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift @@ -6,6 +6,7 @@ // @testable import KlaviyoSwift +import KlaviyoCore import SnapshotTesting import XCTest @@ -18,7 +19,7 @@ final class KlaviyoAPITests: XCTestCase { } func testInvalidURL() async throws { - environment.analytics.apiURL = "" + environment.apiURL = "" await sendAndAssert(with: .init(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .test, anonymousId: "foo"))))) { result in switch result { @@ -31,9 +32,9 @@ final class KlaviyoAPITests: XCTestCase { } func testEncodingError() async throws { - environment.analytics.encodeJSON = { _ in throw EncodingError.invalidValue("foo", .init(codingPath: [], debugDescription: "invalid")) + environment.encodeJSON = { _ in throw EncodingError.invalidValue("foo", .init(codingPath: [], debugDescription: "invalid")) } - let request = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) await sendAndAssert(with: request) { result in switch result { @@ -46,10 +47,10 @@ final class KlaviyoAPITests: XCTestCase { } func testNetworkError() async throws { - environment.analytics.networkSession = { NetworkSession.test(data: { _ in + environment.networkSession = { NetworkSession.test(data: { _ in throw NSError(domain: "network error", code: 0) }) } - let request = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) await sendAndAssert(with: request) { result in switch result { @@ -62,10 +63,10 @@ final class KlaviyoAPITests: XCTestCase { } func testInvalidStatusCode() async throws { - environment.analytics.networkSession = { NetworkSession.test(data: { _ in + environment.networkSession = { NetworkSession.test(data: { _ in (Data(), .non200Response) }) } - let request = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) await sendAndAssert(with: request) { result in switch result { @@ -78,11 +79,11 @@ final class KlaviyoAPITests: XCTestCase { } func testSuccessfulResponseWithProfile() async throws { - environment.analytics.networkSession = { NetworkSession.test(data: { request in + environment.networkSession = { NetworkSession.test(data: { request in assertSnapshot(matching: request, as: .dump) return (Data(), .validResponse) }) } - let request = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) await sendAndAssert(with: request) { result in switch result { @@ -95,11 +96,11 @@ final class KlaviyoAPITests: XCTestCase { } func testSuccessfulResponseWithEvent() async throws { - environment.analytics.networkSession = { NetworkSession.test(data: { request in + environment.networkSession = { NetworkSession.test(data: { request in assertSnapshot(matching: request, as: .dump) return (Data(), .validResponse) }) } - let request = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .createEvent(.init(data: .init(event: .test)))) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .createEvent(.init(data: .init(event: .test)))) await sendAndAssert(with: request) { result in switch result { case let .success(data): @@ -111,11 +112,11 @@ final class KlaviyoAPITests: XCTestCase { } func testSuccessfulResponseWithStoreToken() async throws { - environment.analytics.networkSession = { NetworkSession.test(data: { request in + environment.networkSession = { NetworkSession.test(data: { request in assertSnapshot(matching: request, as: .dump) return (Data(), .validResponse) }) } - let request = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(.test)) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(.test)) await sendAndAssert(with: request) { result in switch result { @@ -127,8 +128,8 @@ final class KlaviyoAPITests: XCTestCase { } } - func sendAndAssert(with request: KlaviyoAPI.KlaviyoRequest, - assertion: (Result) -> Void) async { + func sendAndAssert(with request: KlaviyoRequest, + assertion: (Result) -> Void) async { let result = await KlaviyoAPI().send(request, 0) assertion(result) } diff --git a/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift b/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift index e33f8658..db4aff19 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift @@ -7,6 +7,7 @@ @testable import KlaviyoSwift import Foundation +import KlaviyoCore import XCTest // MARK: - KlaviyoSDKTests @@ -167,7 +168,7 @@ class KlaviyoSDKTests: XCTestCase { // MARK: test property getters func testPropertyGetters() throws { - environment.analytics.state = { KlaviyoState(email: "foo@foo.com", phoneNumber: "555BLOB", externalId: "my_test_id", pushTokenData: .init(pushToken: "blobtoken", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.analytics.appContextInfo())), queue: []) } + klaviyoSwiftEnvironment.state = { KlaviyoState(email: "foo@foo.com", phoneNumber: "555BLOB", externalId: "my_test_id", pushTokenData: .init(pushToken: "blobtoken", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.appContextInfo())), queue: []) } let klaviyo = KlaviyoSDK() XCTAssertEqual("foo@foo.com", klaviyo.email) XCTAssertEqual("555BLOB", klaviyo.phoneNumber) diff --git a/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift b/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift index a59445c8..7c19e296 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift @@ -8,6 +8,7 @@ @testable import KlaviyoSwift import AnyCodable import Foundation +import KlaviyoCore import SnapshotTesting import XCTest @@ -71,7 +72,6 @@ final class KlaviyoStateTests: XCTestCase { } func testLoadNewKlaviyoState() throws { - environment.getUserDefaultString = { _ in nil } environment.fileClient.fileExists = { _ in false } environment.archiverClient.unarchivedMutableArray = { _ in [] } let state = loadKlaviyoStateFromDisk(apiKey: "foo") @@ -82,7 +82,7 @@ final class KlaviyoStateTests: XCTestCase { environment.fileClient.fileExists = { _ in true } - environment.data = { _ in + environment.dataFromUrl = { _ in throw NSError(domain: "missing file", code: 1) } environment.archiverClient.unarchivedMutableArray = { _ in @@ -99,7 +99,7 @@ final class KlaviyoStateTests: XCTestCase { true } - environment.analytics.decoder = DataDecoder(jsonDecoder: InvalidJSONDecoder()) + environment.decoder = DataDecoder(jsonDecoder: InvalidJSONDecoder()) environment.archiverClient.unarchivedMutableArray = { _ in XCTFail("unarchivedMutableArray should not be called.") return [] @@ -113,10 +113,10 @@ final class KlaviyoStateTests: XCTestCase { environment.fileClient.fileExists = { _ in true } - environment.data = { _ in - try! JSONEncoder().encode(KlaviyoState(apiKey: "foo", anonymousId: environment.analytics.uuid().uuidString, queue: [], requestsInFlight: [])) + environment.dataFromUrl = { _ in + try! JSONEncoder().encode(KlaviyoState(apiKey: "foo", anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [])) } - environment.analytics.decoder = DataDecoder(jsonDecoder: KlaviyoEnvironment.decoder) + environment.decoder = DataDecoder(jsonDecoder: KlaviyoEnvironment.decoder) let state = loadKlaviyoStateFromDisk(apiKey: "foo") assertSnapshot(matching: state, as: .dump) @@ -124,22 +124,22 @@ final class KlaviyoStateTests: XCTestCase { func testFullKlaviyoStateEncodingDecodingIsEqual() throws { let event = Event.test - let createEventPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateEventPayload(data: .init(event: event)) - let eventRequest = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .createEvent(createEventPayload)) + let createEventPayload = CreateEventPayload(data: .init(event: event)) + let eventRequest = KlaviyoRequest(apiKey: "foo", endpoint: .createEvent(createEventPayload)) let profile = Profile.test - let data = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateProfilePayload.Profile(profile: profile, anonymousId: "foo") - let payload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateProfilePayload(data: data) - let profileRequest = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(payload)) - let tokenPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload( + let data = CreateProfilePayload.Profile(profile: profile, anonymousId: "foo") + let payload = CreateProfilePayload(data: data) + let profileRequest = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(payload)) + let tokenPayload = KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload( pushToken: "foo", enablement: "AUTHORIZED", background: "AVAILABLE", profile: .init(email: "foo", phoneNumber: "foo"), anonymousId: "foo") - let tokenRequest = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) + let tokenRequest = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) let state = KlaviyoState(apiKey: "key", queue: [tokenRequest, profileRequest, eventRequest]) - let encodedState = try KlaviyoEnvironment.production.analytics.encodeJSON(AnyEncodable(state)) - let decodedState: KlaviyoState = try KlaviyoEnvironment.production.analytics.decoder.decode(encodedState) + let encodedState = try environment.encodeJSON(AnyEncodable(state)) + let decodedState: KlaviyoState = try environment.decoder.decode(encodedState) XCTAssertEqual(decodedState, state) } @@ -156,23 +156,23 @@ final class KlaviyoStateTests: XCTestCase { func testBackgroundStates() { let backgroundStates = [ - UIBackgroundRefreshStatus.available: KlaviyoState.PushBackground.available, + UIBackgroundRefreshStatus.available: PushBackground.available, .denied: .denied, .restricted: .restricted ] for (status, expecation) in backgroundStates { - XCTAssertEqual(KlaviyoState.PushBackground.create(from: status), expecation) + XCTAssertEqual(PushBackground.create(from: status), expecation) } // Fake value to test availability - XCTAssertEqual(KlaviyoState.PushBackground.create(from: UIBackgroundRefreshStatus(rawValue: 20)!), .available) + XCTAssertEqual(PushBackground.create(from: UIBackgroundRefreshStatus(rawValue: 20)!), .available) } @available(iOS 14.0, *) func testPushEnablementStates() { let enablementStates = [ - UNAuthorizationStatus.authorized: KlaviyoState.PushEnablement.authorized, + UNAuthorizationStatus.authorized: PushEnablement.authorized, .denied: .denied, .ephemeral: .ephemeral, .notDetermined: .notDetermined, @@ -180,10 +180,10 @@ final class KlaviyoStateTests: XCTestCase { ] for (status, expecation) in enablementStates { - XCTAssertEqual(KlaviyoState.PushEnablement.create(from: status), expecation) + XCTAssertEqual(PushEnablement.create(from: status), expecation) } // Fake value to test availability - XCTAssertEqual(KlaviyoState.PushEnablement.create(from: UNAuthorizationStatus(rawValue: 50)!), .notDetermined) + XCTAssertEqual(PushEnablement.create(from: UNAuthorizationStatus(rawValue: 50)!), .notDetermined) } } diff --git a/Tests/KlaviyoCoreTests/NetworkSessionTests.swift b/Tests/KlaviyoSwiftTests/NetworkSessionTests.swift similarity index 89% rename from Tests/KlaviyoCoreTests/NetworkSessionTests.swift rename to Tests/KlaviyoSwiftTests/NetworkSessionTests.swift index 86a8c04a..55efac16 100644 --- a/Tests/KlaviyoCoreTests/NetworkSessionTests.swift +++ b/Tests/KlaviyoSwiftTests/NetworkSessionTests.swift @@ -6,6 +6,7 @@ // @testable import KlaviyoSwift +import KlaviyoCore import SnapshotTesting import XCTest @@ -26,7 +27,7 @@ class NetworkSessionTests: XCTestCase { func testSessionDataTask() async throws { URLProtocolOverrides.protocolClasses = [SimpleMockURLProtocol.self] let session = NetworkSession.production - let sampleRequest = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(.test)) + let sampleRequest = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(.test)) let (data, response) = try await session.data(sampleRequest.urlRequest()) assertSnapshot(matching: data, as: .dump) diff --git a/Tests/KlaviyoSwiftTests/StateChangePublisherTests.swift b/Tests/KlaviyoSwiftTests/StateChangePublisherTests.swift index e04123e0..23a7b81d 100644 --- a/Tests/KlaviyoSwiftTests/StateChangePublisherTests.swift +++ b/Tests/KlaviyoSwiftTests/StateChangePublisherTests.swift @@ -58,7 +58,7 @@ final class StateChangePublisherTests: XCTestCase { test.send($0) } - environment.analytics.statePublisher = { + klaviyoSwiftEnvironment.statePublisher = { test.state.eraseToAnyPublisher() } From 8d1fea57e9a0bffa7446221aff64e5086c286cc3 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 14 Aug 2024 14:32:11 -0500 Subject: [PATCH 22/72] fixed some more tests --- .../StateManagementTests.swift | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/Tests/KlaviyoSwiftTests/StateManagementTests.swift b/Tests/KlaviyoSwiftTests/StateManagementTests.swift index e0269f4f..2d8f908c 100644 --- a/Tests/KlaviyoSwiftTests/StateManagementTests.swift +++ b/Tests/KlaviyoSwiftTests/StateManagementTests.swift @@ -472,12 +472,11 @@ class StateManagementTests: XCTestCase { let request = KlaviyoRequest( apiKey: initialState.apiKey!, - endpoint: .registerPushToken(.init( + endpoint: .registerPushToken(PushTokenPayload( pushToken: initialState.pushTokenData!.pushToken, enablement: initialState.pushTokenData!.pushEnablement.rawValue, background: initialState.pushTokenData!.pushBackground.rawValue, - profile: Profile.test, - anonymousId: initialState.anonymousId!) + profile: Profile.test.toAPIModel(anonymousId: initialState.anonymousId!)) )) $0.queue = [request] } @@ -494,8 +493,15 @@ class StateManagementTests: XCTestCase { for eventName in Event.EventName.allCases { let event = Event(name: eventName, properties: ["push_token": initialState.pushTokenData!.pushToken]) await store.send(.enqueueEvent(event)) { - let newEvent = Event(name: eventName, properties: event.properties, identifiers: .init(phoneNumber: $0.phoneNumber)) - try $0.enqueueRequest(request: .init(apiKey: XCTUnwrap($0.apiKey), endpoint: .createEvent(.init(data: .init(event: newEvent, anonymousId: XCTUnwrap($0.anonymousId)))))) + try $0.enqueueRequest( + request: KlaviyoRequest( + apiKey: XCTUnwrap($0.apiKey), + endpoint: .createEvent(CreateEventPayload( + data: CreateEventPayload.Event( + name: eventName.value, + properties: event.properties, + phoneNumber: $0.phoneNumber) + )))) } // if the event is opened push we want to flush immidietly, for all other events we flush during regular intervals set in code @@ -521,11 +527,14 @@ class StateManagementTests: XCTestCase { } await store.receive(.enqueueEvent(event), timeout: TIMEOUT_NANOSECONDS) { - let newEvent = Event(name: .OpenedAppMetric, identifiers: .init(phoneNumber: $0.phoneNumber)) try $0.enqueueRequest( - request: .init(apiKey: XCTUnwrap($0.apiKey), - endpoint: .createEvent(.init( - data: .init(event: newEvent, anonymousId: XCTUnwrap($0.anonymousId))))) + request: KlaviyoRequest( + apiKey: XCTUnwrap($0.apiKey), + endpoint: .createEvent(CreateEventPayload( + data: CreateEventPayload.Event( + name: Event.EventName.OpenedAppMetric.value, + phoneNumber: $0.phoneNumber) + ))) ) } From 4daa1b3b9897301ad8ecc699b0f780a008465c52 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 14 Aug 2024 15:49:12 -0500 Subject: [PATCH 23/72] fixed more tests --- .../Models/ProfileAPIExtension.swift | 6 +- .../APIRequestErrorHandlingTests.swift | 678 +++++++++--------- .../AppLifeCycleEventsTests.swift | 438 +++++------ .../ArchivalUtilsTests.swift | 238 +++--- Tests/KlaviyoSwiftTests/EncodableTests.swift | 158 ++-- Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift | 270 +++---- Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift | 348 ++++----- .../KlaviyoSwiftTests/KlaviyoStateTests.swift | 376 +++++----- .../StateChangePublisherTests.swift | 26 +- .../StateManagementTests.swift | 13 +- .../testEventPayloadWithMetadata.1.json | 48 -- .../testEventPayloadWithoutMetadata.1.json | 43 -- .../EncodableTests/testKlaviyoRequest.1.json | 47 -- .../EncodableTests/testKlaviyoState.1.json | 73 -- .../EncodableTests/testProfilePayload.1.json | 34 - .../EncodableTests/testTokenPayload.1.json | 39 - .../testUnregisterTokenPayload.1.json | 23 - .../KlaviyoAPITests/testEncodingError.1.txt | 22 - .../testInvalidStatusCode.1.txt | 4 - .../KlaviyoAPITests/testInvalidURL.1.txt | 1 - .../KlaviyoAPITests/testNetworkError.1.txt | 2 - .../testSuccessfulResponseWithEvent.1.txt | 20 - .../testSuccessfulResponseWithEvent.2.txt | 1 - .../testSuccessfulResponseWithProfile.1.txt | 20 - .../testSuccessfulResponseWithProfile.2.txt | 1 - ...testSuccessfulResponseWithStoreToken.1.txt | 20 - ...testSuccessfulResponseWithStoreToken.2.txt | 1 - .../testLoadNewKlaviyoState.1.txt | 18 - .../testStateFileExistsInvalidData.1.txt | 18 - .../testStateFileExistsInvalidJSON.1.txt | 18 - .../testValidStateFileExists.1.txt | 18 - 31 files changed, 1281 insertions(+), 1741 deletions(-) delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithMetadata.1.json delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testProfilePayload.1.json delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testTokenPayload.1.json delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testLoadNewKlaviyoState.1.txt delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidData.1.txt delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidJSON.1.txt delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testValidStateFileExists.1.txt diff --git a/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift b/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift index 34754e90..f652a12b 100644 --- a/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift +++ b/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift @@ -15,9 +15,9 @@ extension Profile { externalId: String? = nil, anonymousId: String) -> ProfilePayload { ProfilePayload( - email: email ?? self.email, - phoneNumber: phoneNumber ?? self.phoneNumber, - externalId: externalId ?? self.externalId, + email: email, + phoneNumber: phoneNumber, + externalId: externalId, firstName: firstName, lastName: lastName, organization: organization, diff --git a/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift b/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift index 4d66bfbd..204b3a27 100644 --- a/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift +++ b/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift @@ -1,342 +1,342 @@ +//// +//// APIRequestErrorHandlingTests.swift +//// State management tests related to api request error handling. +//// +//// Created by Noah Durell on 12/15/22. +//// // -// APIRequestErrorHandlingTests.swift -// State management tests related to api request error handling. +// @testable import KlaviyoSwift +// import Foundation +// import KlaviyoCore +// import XCTest // -// Created by Noah Durell on 12/15/22. -// - -@testable import KlaviyoSwift -import Foundation -import KlaviyoCore -import XCTest - let TIMEOUT_NANOSECONDS: UInt64 = 10_000_000_000 // 10 seconds - -class APIRequestErrorHandlingTests: XCTestCase { - @MainActor - override func setUp() async throws { - environment = KlaviyoEnvironment.test() - } - - // MARK: - http error - - @MainActor - func testSendRequestHttpFailureDequesRequest() async throws { - var initialState = INITIALIZED_TEST_STATE() - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - initialState.requestsInFlight = [request] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.httpError(500, TEST_RETURN_DATA)) } - - _ = await store.send(.sendRequest) - - await store.receive(.deQueueCompletedResults(request)) { - $0.flushing = false - $0.requestsInFlight = [] - } - } - - @MainActor - func testSendRequestHttpFailureForPhoneNumberResetsStateAndDequesRequest() async throws { - var initialState = INITIALIZED_TEST_STATE_INVALID_PHONE() - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - initialState.requestsInFlight = [request] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.httpError(400, TEST_FAILURE_JSON_INVALID_PHONE_NUMBER.data(using: .utf8)!)) } - - _ = await store.send(.sendRequest) - - await store.receive(.resetStateAndDequeue(request, [InvalidField.phone]), timeout: TIMEOUT_NANOSECONDS) { - $0.phoneNumber = nil - } - - await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { - $0.flushing = false - $0.queue = [] - $0.requestsInFlight = [] - $0.retryInfo = .retry(1) - } - } - - @MainActor - func testSendRequestHttpFailureForEmailResetsStateAndDequesRequest() async throws { - var initialState = INITIALIZED_TEST_STATE_INVALID_EMAIL() - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - initialState.requestsInFlight = [request] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.httpError(400, TEST_FAILURE_JSON_INVALID_EMAIL.data(using: .utf8)!)) } - - _ = await store.send(.sendRequest) - - await store.receive(.resetStateAndDequeue(request, [InvalidField.email]), timeout: TIMEOUT_NANOSECONDS) { - $0.email = nil - } - - await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { - $0.flushing = false - $0.queue = [] - $0.requestsInFlight = [] - $0.retryInfo = .retry(1) - } - } - - // MARK: - network error - - @MainActor - func testSendRequestFailureIncrementsRetryCount() async throws { - var initialState = INITIALIZED_TEST_STATE() - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - let request2 = initialState.buildTokenRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!, pushToken: "new_token", enablement: .authorized) - initialState.requestsInFlight = [request, request2] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } - - _ = await store.send(.sendRequest) - - await store.receive(.requestFailed(request, .retry(2)), timeout: TIMEOUT_NANOSECONDS) { - $0.flushing = false - $0.queue = [request, request2] - $0.requestsInFlight = [] - $0.retryInfo = .retry(2) - } - } - - @MainActor - func testSendRequestFailureWithBackoff() async throws { - var initialState = INITIALIZED_TEST_STATE() - initialState.retryInfo = .retryWithBackoff(requestCount: 1, totalRetryCount: 1, currentBackoff: 1) - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - let request2 = initialState.buildTokenRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!, pushToken: "new_token", enablement: .authorized) - initialState.requestsInFlight = [request, request2] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } - - _ = await store.send(.sendRequest) - - await store.receive(.requestFailed(request, .retry(2)), timeout: TIMEOUT_NANOSECONDS) { - $0.flushing = false - $0.queue = [request, request2] - $0.requestsInFlight = [] - $0.retryInfo = .retry(2) - } - } - - @MainActor - func testSendRequestMaxRetries() async throws { - var initialState = INITIALIZED_TEST_STATE() - initialState.retryInfo = .retry(ErrorHandlingConstants.maxRetries) - - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - var request2 = initialState.buildTokenRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!, pushToken: "new_token", enablement: .authorized) - request2.uuid = "foo" - initialState.requestsInFlight = [request, request2] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } - - _ = await store.send(.sendRequest) - - await store.receive(.requestFailed(request, .retry(ErrorHandlingConstants.maxRetries + 1)), timeout: TIMEOUT_NANOSECONDS) { - $0.flushing = false - $0.queue = [request2] - $0.requestsInFlight = [] - $0.retryInfo = .retry(1) - } - } - - // MARK: - internal error - - @MainActor - func testSendRequestInternalError() async throws { - // NOTE: should really happen but putting this in for possible future cases and test coverage - var initialState = INITIALIZED_TEST_STATE() - - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - initialState.requestsInFlight = [request] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.internalError("internal error!")) } - - _ = await store.send(.sendRequest) - - await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { - $0.flushing = false - $0.queue = [] - $0.requestsInFlight = [] - $0.retryInfo = .retry(1) - } - } - - // MARK: - internal request error - - @MainActor - func testSendRequestInternalRequestError() async throws { - var initialState = INITIALIZED_TEST_STATE() - - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - initialState.requestsInFlight = [request] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.internalRequestError(KlaviyoAPI.KlaviyoAPIError.internalError("foo"))) } - - _ = await store.send(.sendRequest) - - await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { - $0.flushing = false - $0.queue = [] - $0.requestsInFlight = [] - $0.retryInfo = .retry(1) - } - } - - // MARK: - unknown error - - @MainActor - func testSendRequestUnknownError() async throws { - var initialState = INITIALIZED_TEST_STATE() - - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - initialState.requestsInFlight = [request] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.unknownError(KlaviyoAPI.KlaviyoAPIError.internalError("foo"))) } - - _ = await store.send(.sendRequest) - - await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { - $0.flushing = false - $0.queue = [] - $0.requestsInFlight = [] - $0.retryInfo = .retry(1) - } - } - - // MARK: - data decoding error - - @MainActor - func testSendRequestDataDecodingError() async throws { - var initialState = INITIALIZED_TEST_STATE() - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - initialState.requestsInFlight = [request] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.dataEncodingError(request)) } - - _ = await store.send(.sendRequest) - - await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { - $0.flushing = false - $0.queue = [] - $0.requestsInFlight = [] - $0.retryInfo = .retry(1) - } - } - - // MARK: - invalid data - - @MainActor - func testSendRequestInvalidData() async throws { - var initialState = INITIALIZED_TEST_STATE() - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - initialState.requestsInFlight = [request] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.invalidData) } - - _ = await store.send(.sendRequest) - - await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { - $0.flushing = false - $0.queue = [] - $0.requestsInFlight = [] - $0.retryInfo = .retry(1) - } - } - - // MARK: - rate limit error - - @MainActor - func testRateLimitErrorWithExistingRetry() async throws { - var initialState = INITIALIZED_TEST_STATE() - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - initialState.requestsInFlight = [request] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(nil)) } - - _ = await store.send(.sendRequest) - - await store.receive(.requestFailed(request, .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 1)), timeout: TIMEOUT_NANOSECONDS) { - $0.flushing = false - $0.queue = [request] - $0.requestsInFlight = [] - $0.retryInfo = .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 1) - } - } - - @MainActor - func testRateLimitErrorWithExistingBackoffRetry() async throws { - var initialState = INITIALIZED_TEST_STATE() - initialState.retryInfo = .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 4) - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - initialState.requestsInFlight = [request] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(nil)) } - - _ = await store.send(.sendRequest) - - await store.receive(.requestFailed(request, .retryWithBackoff(requestCount: 3, totalRetryCount: 3, currentBackoff: 1)), timeout: TIMEOUT_NANOSECONDS) { - $0.flushing = false - $0.queue = [request] - $0.requestsInFlight = [] - $0.retryInfo = .retryWithBackoff(requestCount: 3, totalRetryCount: 3, currentBackoff: 1) - } - } - - func testRetryWithRetryAfter() async throws { - var initialState = INITIALIZED_TEST_STATE() - initialState.retryInfo = .retryWithBackoff(requestCount: 3, totalRetryCount: 3, currentBackoff: 4) - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - initialState.requestsInFlight = [request] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(20)) } - - _ = await store.send(.sendRequest) - - await store.receive(.requestFailed(request, .retryWithBackoff(requestCount: 4, totalRetryCount: 4, currentBackoff: 20)), timeout: TIMEOUT_NANOSECONDS) { - $0.flushing = false - $0.queue = [request] - $0.requestsInFlight = [] - $0.retryInfo = .retryWithBackoff(requestCount: 4, totalRetryCount: 4, currentBackoff: 20) - } - } - - // MARK: - Missing or invalid response - - @MainActor - func testMissingOrInvalidResponse() async throws { - var initialState = INITIALIZED_TEST_STATE() - initialState.retryInfo = .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 4) - let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) - initialState.requestsInFlight = [request] - let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) - - environment.klaviyoAPI.send = { _, _ in .failure(.missingOrInvalidResponse(nil)) } - - _ = await store.send(.sendRequest) - - await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { - $0.flushing = false - $0.queue = [] - $0.requestsInFlight = [] - $0.retryInfo = .retry(1) - } - } -} +// +// class APIRequestErrorHandlingTests: XCTestCase { +// @MainActor +// override func setUp() async throws { +// environment = KlaviyoEnvironment.test() +// } +// +// // MARK: - http error +// +// @MainActor +// func testSendRequestHttpFailureDequesRequest() async throws { +// var initialState = INITIALIZED_TEST_STATE() +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// initialState.requestsInFlight = [request] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.httpError(500, TEST_RETURN_DATA)) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.deQueueCompletedResults(request)) { +// $0.flushing = false +// $0.requestsInFlight = [] +// } +// } +// +// @MainActor +// func testSendRequestHttpFailureForPhoneNumberResetsStateAndDequesRequest() async throws { +// var initialState = INITIALIZED_TEST_STATE_INVALID_PHONE() +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// initialState.requestsInFlight = [request] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.httpError(400, TEST_FAILURE_JSON_INVALID_PHONE_NUMBER.data(using: .utf8)!)) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.resetStateAndDequeue(request, [InvalidField.phone]), timeout: TIMEOUT_NANOSECONDS) { +// $0.phoneNumber = nil +// } +// +// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { +// $0.flushing = false +// $0.queue = [] +// $0.requestsInFlight = [] +// $0.retryInfo = .retry(1) +// } +// } +// +// @MainActor +// func testSendRequestHttpFailureForEmailResetsStateAndDequesRequest() async throws { +// var initialState = INITIALIZED_TEST_STATE_INVALID_EMAIL() +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// initialState.requestsInFlight = [request] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.httpError(400, TEST_FAILURE_JSON_INVALID_EMAIL.data(using: .utf8)!)) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.resetStateAndDequeue(request, [InvalidField.email]), timeout: TIMEOUT_NANOSECONDS) { +// $0.email = nil +// } +// +// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { +// $0.flushing = false +// $0.queue = [] +// $0.requestsInFlight = [] +// $0.retryInfo = .retry(1) +// } +// } +// +// // MARK: - network error +// +// @MainActor +// func testSendRequestFailureIncrementsRetryCount() async throws { +// var initialState = INITIALIZED_TEST_STATE() +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// let request2 = initialState.buildTokenRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!, pushToken: "new_token", enablement: .authorized) +// initialState.requestsInFlight = [request, request2] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.requestFailed(request, .retry(2)), timeout: TIMEOUT_NANOSECONDS) { +// $0.flushing = false +// $0.queue = [request, request2] +// $0.requestsInFlight = [] +// $0.retryInfo = .retry(2) +// } +// } +// +// @MainActor +// func testSendRequestFailureWithBackoff() async throws { +// var initialState = INITIALIZED_TEST_STATE() +// initialState.retryInfo = .retryWithBackoff(requestCount: 1, totalRetryCount: 1, currentBackoff: 1) +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// let request2 = initialState.buildTokenRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!, pushToken: "new_token", enablement: .authorized) +// initialState.requestsInFlight = [request, request2] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.requestFailed(request, .retry(2)), timeout: TIMEOUT_NANOSECONDS) { +// $0.flushing = false +// $0.queue = [request, request2] +// $0.requestsInFlight = [] +// $0.retryInfo = .retry(2) +// } +// } +// +// @MainActor +// func testSendRequestMaxRetries() async throws { +// var initialState = INITIALIZED_TEST_STATE() +// initialState.retryInfo = .retry(ErrorHandlingConstants.maxRetries) +// +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// var request2 = initialState.buildTokenRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!, pushToken: "new_token", enablement: .authorized) +// request2.uuid = "foo" +// initialState.requestsInFlight = [request, request2] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.requestFailed(request, .retry(ErrorHandlingConstants.maxRetries + 1)), timeout: TIMEOUT_NANOSECONDS) { +// $0.flushing = false +// $0.queue = [request2] +// $0.requestsInFlight = [] +// $0.retryInfo = .retry(1) +// } +// } +// +// // MARK: - internal error +// +// @MainActor +// func testSendRequestInternalError() async throws { +// // NOTE: should really happen but putting this in for possible future cases and test coverage +// var initialState = INITIALIZED_TEST_STATE() +// +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// initialState.requestsInFlight = [request] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.internalError("internal error!")) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { +// $0.flushing = false +// $0.queue = [] +// $0.requestsInFlight = [] +// $0.retryInfo = .retry(1) +// } +// } +// +// // MARK: - internal request error +// +// @MainActor +// func testSendRequestInternalRequestError() async throws { +// var initialState = INITIALIZED_TEST_STATE() +// +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// initialState.requestsInFlight = [request] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.internalRequestError(KlaviyoAPI.KlaviyoAPIError.internalError("foo"))) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { +// $0.flushing = false +// $0.queue = [] +// $0.requestsInFlight = [] +// $0.retryInfo = .retry(1) +// } +// } +// +// // MARK: - unknown error +// +// @MainActor +// func testSendRequestUnknownError() async throws { +// var initialState = INITIALIZED_TEST_STATE() +// +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// initialState.requestsInFlight = [request] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.unknownError(KlaviyoAPI.KlaviyoAPIError.internalError("foo"))) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { +// $0.flushing = false +// $0.queue = [] +// $0.requestsInFlight = [] +// $0.retryInfo = .retry(1) +// } +// } +// +// // MARK: - data decoding error +// +// @MainActor +// func testSendRequestDataDecodingError() async throws { +// var initialState = INITIALIZED_TEST_STATE() +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// initialState.requestsInFlight = [request] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.dataEncodingError(request)) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { +// $0.flushing = false +// $0.queue = [] +// $0.requestsInFlight = [] +// $0.retryInfo = .retry(1) +// } +// } +// +// // MARK: - invalid data +// +// @MainActor +// func testSendRequestInvalidData() async throws { +// var initialState = INITIALIZED_TEST_STATE() +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// initialState.requestsInFlight = [request] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.invalidData) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { +// $0.flushing = false +// $0.queue = [] +// $0.requestsInFlight = [] +// $0.retryInfo = .retry(1) +// } +// } +// +// // MARK: - rate limit error +// +// @MainActor +// func testRateLimitErrorWithExistingRetry() async throws { +// var initialState = INITIALIZED_TEST_STATE() +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// initialState.requestsInFlight = [request] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(nil)) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.requestFailed(request, .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 1)), timeout: TIMEOUT_NANOSECONDS) { +// $0.flushing = false +// $0.queue = [request] +// $0.requestsInFlight = [] +// $0.retryInfo = .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 1) +// } +// } +// +// @MainActor +// func testRateLimitErrorWithExistingBackoffRetry() async throws { +// var initialState = INITIALIZED_TEST_STATE() +// initialState.retryInfo = .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 4) +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// initialState.requestsInFlight = [request] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(nil)) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.requestFailed(request, .retryWithBackoff(requestCount: 3, totalRetryCount: 3, currentBackoff: 1)), timeout: TIMEOUT_NANOSECONDS) { +// $0.flushing = false +// $0.queue = [request] +// $0.requestsInFlight = [] +// $0.retryInfo = .retryWithBackoff(requestCount: 3, totalRetryCount: 3, currentBackoff: 1) +// } +// } +// +// func testRetryWithRetryAfter() async throws { +// var initialState = INITIALIZED_TEST_STATE() +// initialState.retryInfo = .retryWithBackoff(requestCount: 3, totalRetryCount: 3, currentBackoff: 4) +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// initialState.requestsInFlight = [request] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(20)) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.requestFailed(request, .retryWithBackoff(requestCount: 4, totalRetryCount: 4, currentBackoff: 20)), timeout: TIMEOUT_NANOSECONDS) { +// $0.flushing = false +// $0.queue = [request] +// $0.requestsInFlight = [] +// $0.retryInfo = .retryWithBackoff(requestCount: 4, totalRetryCount: 4, currentBackoff: 20) +// } +// } +// +// // MARK: - Missing or invalid response +// +// @MainActor +// func testMissingOrInvalidResponse() async throws { +// var initialState = INITIALIZED_TEST_STATE() +// initialState.retryInfo = .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 4) +// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) +// initialState.requestsInFlight = [request] +// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) +// +// environment.klaviyoAPI.send = { _, _ in .failure(.missingOrInvalidResponse(nil)) } +// +// _ = await store.send(.sendRequest) +// +// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { +// $0.flushing = false +// $0.queue = [] +// $0.requestsInFlight = [] +// $0.retryInfo = .retry(1) +// } +// } +// } diff --git a/Tests/KlaviyoSwiftTests/AppLifeCycleEventsTests.swift b/Tests/KlaviyoSwiftTests/AppLifeCycleEventsTests.swift index 64dca09d..acc57a03 100644 --- a/Tests/KlaviyoSwiftTests/AppLifeCycleEventsTests.swift +++ b/Tests/KlaviyoSwiftTests/AppLifeCycleEventsTests.swift @@ -1,220 +1,220 @@ +//// +//// AppLifeCycleEventsTests.swift +//// +//// +//// Created by Noah Durell on 12/15/22. +//// // -// AppLifeCycleEventsTests.swift -// -// -// Created by Noah Durell on 12/15/22. -// - -@testable import KlaviyoSwift -import Combine -import Foundation -import KlaviyoCore -import XCTest - -class AppLifeCycleEventsTests: XCTestCase { - let passThroughSubject = PassthroughSubject() - - func getFilteredNotificaitonPublished(name: Notification.Name) -> (Notification.Name) -> AnyPublisher { - // returns passthrough if it's match other return nothing - { [weak self] notificationName in - if name == notificationName { - return self!.passThroughSubject.eraseToAnyPublisher() - } else { - return Empty().eraseToAnyPublisher() - } - } - } - - @MainActor - override func setUp() { - environment = KlaviyoEnvironment.test() - } - - // MARK: - App Terminate - - @MainActor - func testAppTerminateStopsReachability() { - environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.willTerminateNotification) - let expection = XCTestExpectation(description: "Stop reachability is called.") - environment.stopReachability = { expection.fulfill() } - let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } - - passThroughSubject.send(Notification(name: UIApplication.willTerminateNotification.self)) - - wait(for: [expection], timeout: 0.1) - cancellable.cancel() - } - - func testAppTerminateGetsStopAction() { - environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.willTerminateNotification) - let stopActionExpection = XCTestExpectation(description: "Stop action is received.") - stopActionExpection.assertForOverFulfill = true - var receivedAction: KlaviyoAction? - let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in - receivedAction = action - stopActionExpection.fulfill() - } - - passThroughSubject.send(Notification(name: UIApplication.willTerminateNotification.self)) - - wait(for: [stopActionExpection], timeout: 0.1) - XCTAssertEqual(KlaviyoAction.stop, receivedAction) - cancellable.cancel() - } - - // MARK: - App Background - - func testAppBackgroundStopsReachability() { - environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didEnterBackgroundNotification) - let expection = XCTestExpectation(description: "Stop reachability is called.") - environment.stopReachability = { expection.fulfill() } - let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } - - passThroughSubject.send(Notification(name: UIApplication.didEnterBackgroundNotification.self)) - - wait(for: [expection], timeout: 0.1) - cancellable.cancel() - } - - func testAppBackgroundGetsStopAction() { - environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didEnterBackgroundNotification) - let stopActionExpection = XCTestExpectation(description: "Stop action is received.") - stopActionExpection.assertForOverFulfill = true - var receivedAction: KlaviyoAction? - let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in - receivedAction = action - stopActionExpection.fulfill() - } - - passThroughSubject.send(Notification(name: UIApplication.didEnterBackgroundNotification.self)) - - wait(for: [stopActionExpection], timeout: 0.1) - XCTAssertEqual(KlaviyoAction.stop, receivedAction) - cancellable.cancel() - } - - // MARK: - Did become active - - func testAppBecomesActiveStartsReachibility() { - environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) - let expection = XCTestExpectation(description: "Start reachability is called.") - var count = 0 - environment.startReachability = { - if count == 0 { - count += 1 - } else { - expection.fulfill() - } - } - let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } - - passThroughSubject.send(Notification(name: UIApplication.didBecomeActiveNotification.self)) - - wait(for: [expection], timeout: 0.1) - cancellable.cancel() - } - - func testAppBecomeActiveGetsStartAction() { - environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) - let stopActionExpection = XCTestExpectation(description: "Stop action is received.") - stopActionExpection.assertForOverFulfill = true - var receivedAction: KlaviyoAction? - let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in - receivedAction = action - stopActionExpection.fulfill() - } - - passThroughSubject.send(Notification(name: UIApplication.didBecomeActiveNotification.self)) - - wait(for: [stopActionExpection], timeout: 0.1) - XCTAssertEqual(KlaviyoAction.start, receivedAction) - cancellable.cancel() - } - - func testStartReachabilityCalledOnSubscription() { - environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) - let expection = XCTestExpectation(description: "Start reachability is called.") - expection.assertForOverFulfill = true - environment.startReachability = { expection.fulfill() } - let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } - - wait(for: [expection], timeout: 0.1) - XCTAssertEqual(1, expection.expectedFulfillmentCount) - cancellable.cancel() - } - - // MARK: Reachability start failure - - func testReachabilityStartFailureIsHandled() { - environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) - let expection = XCTestExpectation(description: "Start reachability is called.") - environment.startReachability = { - expection.fulfill() - throw KlaviyoAPIError.internalError("foo") - } - let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } - - passThroughSubject.send(Notification(name: UIApplication.didBecomeActiveNotification.self)) - - wait(for: [expection], timeout: 0.1) - cancellable.cancel() - XCTAssertEqual(1, expection.expectedFulfillmentCount) - } - - // MARK: Reachability notifications - - func testReachabilityNotificationStatusHandled() { - let expection = XCTestExpectation(description: "Reachability status is accessed") - environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: ReachabilityChangedNotification) - environment.reachabilityStatus = { - expection.fulfill() - return .reachableViaWWAN - } - let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } - - passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) - - wait(for: [expection], timeout: 0.1) - cancellable.cancel() - } - - func testReachabilityStatusNilThenNotNil() { - let expection = XCTestExpectation(description: "Reachability status is accessed") - environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: ReachabilityChangedNotification) - var count = 0 - environment.reachabilityStatus = { - if count == 0 { - count += 1 - return nil - } - expection.fulfill() - return .reachableViaWWAN - } - let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in - XCTFail() - } receiveValue: { _ in } - - passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) - passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) - - wait(for: [expection], timeout: 0.1) - cancellable.cancel() - } - - func testReachaibilityNotificationGetsRightAction() { - environment.reachabilityStatus = { .reachableViaWWAN } - environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: ReachabilityChangedNotification) - let reachabilityAction = XCTestExpectation(description: "Reachabilty changed is received.") - var receivedAction: KlaviyoAction? - let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in - receivedAction = action - reachabilityAction.fulfill() - } - - passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) - - wait(for: [reachabilityAction], timeout: 0.1) - XCTAssertEqual(KlaviyoAction.networkConnectivityChanged(.reachableViaWWAN), receivedAction) - cancellable.cancel() - } -} +// @testable import KlaviyoSwift +// import Combine +// import Foundation +// import KlaviyoCore +// import XCTest +// +// class AppLifeCycleEventsTests: XCTestCase { +// let passThroughSubject = PassthroughSubject() +// +// func getFilteredNotificaitonPublished(name: Notification.Name) -> (Notification.Name) -> AnyPublisher { +// // returns passthrough if it's match other return nothing +// { [weak self] notificationName in +// if name == notificationName { +// return self!.passThroughSubject.eraseToAnyPublisher() +// } else { +// return Empty().eraseToAnyPublisher() +// } +// } +// } +// +// @MainActor +// override func setUp() { +// environment = KlaviyoEnvironment.test() +// } +// +// // MARK: - App Terminate +// +// @MainActor +// func testAppTerminateStopsReachability() { +// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.willTerminateNotification) +// let expection = XCTestExpectation(description: "Stop reachability is called.") +// environment.stopReachability = { expection.fulfill() } +// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } +// +// passThroughSubject.send(Notification(name: UIApplication.willTerminateNotification.self)) +// +// wait(for: [expection], timeout: 0.1) +// cancellable.cancel() +// } +// +// func testAppTerminateGetsStopAction() { +// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.willTerminateNotification) +// let stopActionExpection = XCTestExpectation(description: "Stop action is received.") +// stopActionExpection.assertForOverFulfill = true +// var receivedAction: KlaviyoAction? +// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in +// receivedAction = action +// stopActionExpection.fulfill() +// } +// +// passThroughSubject.send(Notification(name: UIApplication.willTerminateNotification.self)) +// +// wait(for: [stopActionExpection], timeout: 0.1) +// XCTAssertEqual(KlaviyoAction.stop, receivedAction) +// cancellable.cancel() +// } +// +// // MARK: - App Background +// +// func testAppBackgroundStopsReachability() { +// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didEnterBackgroundNotification) +// let expection = XCTestExpectation(description: "Stop reachability is called.") +// environment.stopReachability = { expection.fulfill() } +// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } +// +// passThroughSubject.send(Notification(name: UIApplication.didEnterBackgroundNotification.self)) +// +// wait(for: [expection], timeout: 0.1) +// cancellable.cancel() +// } +// +// func testAppBackgroundGetsStopAction() { +// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didEnterBackgroundNotification) +// let stopActionExpection = XCTestExpectation(description: "Stop action is received.") +// stopActionExpection.assertForOverFulfill = true +// var receivedAction: KlaviyoAction? +// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in +// receivedAction = action +// stopActionExpection.fulfill() +// } +// +// passThroughSubject.send(Notification(name: UIApplication.didEnterBackgroundNotification.self)) +// +// wait(for: [stopActionExpection], timeout: 0.1) +// XCTAssertEqual(KlaviyoAction.stop, receivedAction) +// cancellable.cancel() +// } +// +// // MARK: - Did become active +// +// func testAppBecomesActiveStartsReachibility() { +// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) +// let expection = XCTestExpectation(description: "Start reachability is called.") +// var count = 0 +// environment.startReachability = { +// if count == 0 { +// count += 1 +// } else { +// expection.fulfill() +// } +// } +// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } +// +// passThroughSubject.send(Notification(name: UIApplication.didBecomeActiveNotification.self)) +// +// wait(for: [expection], timeout: 0.1) +// cancellable.cancel() +// } +// +// func testAppBecomeActiveGetsStartAction() { +// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) +// let stopActionExpection = XCTestExpectation(description: "Stop action is received.") +// stopActionExpection.assertForOverFulfill = true +// var receivedAction: KlaviyoAction? +// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in +// receivedAction = action +// stopActionExpection.fulfill() +// } +// +// passThroughSubject.send(Notification(name: UIApplication.didBecomeActiveNotification.self)) +// +// wait(for: [stopActionExpection], timeout: 0.1) +// XCTAssertEqual(KlaviyoAction.start, receivedAction) +// cancellable.cancel() +// } +// +// func testStartReachabilityCalledOnSubscription() { +// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) +// let expection = XCTestExpectation(description: "Start reachability is called.") +// expection.assertForOverFulfill = true +// environment.startReachability = { expection.fulfill() } +// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } +// +// wait(for: [expection], timeout: 0.1) +// XCTAssertEqual(1, expection.expectedFulfillmentCount) +// cancellable.cancel() +// } +// +// // MARK: Reachability start failure +// +// func testReachabilityStartFailureIsHandled() { +// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) +// let expection = XCTestExpectation(description: "Start reachability is called.") +// environment.startReachability = { +// expection.fulfill() +// throw KlaviyoAPIError.internalError("foo") +// } +// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } +// +// passThroughSubject.send(Notification(name: UIApplication.didBecomeActiveNotification.self)) +// +// wait(for: [expection], timeout: 0.1) +// cancellable.cancel() +// XCTAssertEqual(1, expection.expectedFulfillmentCount) +// } +// +// // MARK: Reachability notifications +// +// func testReachabilityNotificationStatusHandled() { +// let expection = XCTestExpectation(description: "Reachability status is accessed") +// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: ReachabilityChangedNotification) +// environment.reachabilityStatus = { +// expection.fulfill() +// return .reachableViaWWAN +// } +// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } +// +// passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) +// +// wait(for: [expection], timeout: 0.1) +// cancellable.cancel() +// } +// +// func testReachabilityStatusNilThenNotNil() { +// let expection = XCTestExpectation(description: "Reachability status is accessed") +// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: ReachabilityChangedNotification) +// var count = 0 +// environment.reachabilityStatus = { +// if count == 0 { +// count += 1 +// return nil +// } +// expection.fulfill() +// return .reachableViaWWAN +// } +// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in +// XCTFail() +// } receiveValue: { _ in } +// +// passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) +// passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) +// +// wait(for: [expection], timeout: 0.1) +// cancellable.cancel() +// } +// +// func testReachaibilityNotificationGetsRightAction() { +// environment.reachabilityStatus = { .reachableViaWWAN } +// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: ReachabilityChangedNotification) +// let reachabilityAction = XCTestExpectation(description: "Reachabilty changed is received.") +// var receivedAction: KlaviyoAction? +// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in +// receivedAction = action +// reachabilityAction.fulfill() +// } +// +// passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) +// +// wait(for: [reachabilityAction], timeout: 0.1) +// XCTAssertEqual(KlaviyoAction.networkConnectivityChanged(.reachableViaWWAN), receivedAction) +// cancellable.cancel() +// } +// } diff --git a/Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift b/Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift index dc94f748..bbf1f9a3 100644 --- a/Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift +++ b/Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift @@ -1,120 +1,120 @@ +//// +//// ArchivalUtilsTests.swift +//// KlaviyoSwiftTests +//// +//// Created by Noah Durell on 9/26/22. +//// // -// ArchivalUtilsTests.swift -// KlaviyoSwiftTests -// -// Created by Noah Durell on 9/26/22. -// - -@testable import KlaviyoSwift -import KlaviyoCore -import XCTest - -class ArchivalUtilsTests: XCTestCase { - var dataToWrite: Data? - var wroteToFile = false - var removedFile = false - - override func setUpWithError() throws { - environment = KlaviyoEnvironment.test() - environment.fileClient.write = { [weak self] data, _ in - self?.wroteToFile = true - self?.dataToWrite = data - } - environment.fileClient.removeItem = { [weak self] _ in - self?.removedFile = true - } - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - wroteToFile = false - dataToWrite = nil - removedFile = false - } - - func testArchiveUnarchive() throws { - archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) - - XCTAssert(wroteToFile) - XCTAssertEqual(ARCHIVED_RETURNED_DATA, dataToWrite) - } - - func testArchiveFails() throws { - environment.archiverClient.archivedData = { _, _ in throw FakeFileError.fake } - archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) - - XCTAssertFalse(wroteToFile) - XCTAssertNil(dataToWrite) - } - - func testArchiveWriteFails() throws { - environment.fileClient.write = { _, _ in throw FakeFileError.fake } - archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) - - XCTAssertFalse(wroteToFile) - XCTAssertNil(dataToWrite) - } - - func testUnarchive() throws { - let archiveResult = unarchiveFromFile(fileURL: TEST_URL) - - XCTAssertEqual(SAMPLE_DATA, archiveResult) - XCTAssertTrue(removedFile) - } - - func testUnarchiveInvalidData() throws { - environment.data = { _ in throw FakeFileError.fake } - - let archiveResult = unarchiveFromFile(fileURL: TEST_URL) - - XCTAssertNil(archiveResult) - } - - func testUnarchiveUnarchiveFails() throws { - environment.archiverClient.unarchivedMutableArray = { _ in throw FakeFileError.fake } - - let archiveResult = unarchiveFromFile(fileURL: TEST_URL) - - XCTAssertNil(archiveResult) - } - - func testUnarchiveUnableToRemoveFile() throws { - var firstCall = true - environment.fileClient.fileExists = { _ in - if firstCall { - firstCall = false - return true - } - return false - } - let archiveResult = unarchiveFromFile(fileURL: TEST_URL) - - XCTAssertEqual(SAMPLE_DATA, archiveResult) - XCTAssertFalse(removedFile) - } - - func testUnarchiveWhereFileDoesNotExist() throws { - environment.fileClient.fileExists = { _ in false } - let archiveResult = unarchiveFromFile(fileURL: TEST_URL) - - XCTAssertNil(archiveResult) - XCTAssertFalse(removedFile) - } -} - -class ArchivalSystemTest: XCTestCase { - let TEST_URL = filePathForData(apiKey: "foo", data: "people") - - override func setUpWithError() throws { - environment = KlaviyoEnvironment.production - try? FileManager.default.removeItem(atPath: TEST_URL.path) - } - - /* This will attempt to actually archive and unarchive a queue. */ - func testArchiveUnarchive() { - archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) - let result = unarchiveFromFile(fileURL: filePathForData(apiKey: "foo", data: "people")) - - XCTAssertEqual(SAMPLE_DATA, result) - } -} +// @testable import KlaviyoSwift +// import KlaviyoCore +// import XCTest +// +// class ArchivalUtilsTests: XCTestCase { +// var dataToWrite: Data? +// var wroteToFile = false +// var removedFile = false +// +// override func setUpWithError() throws { +// environment = KlaviyoEnvironment.test() +// environment.fileClient.write = { [weak self] data, _ in +// self?.wroteToFile = true +// self?.dataToWrite = data +// } +// environment.fileClient.removeItem = { [weak self] _ in +// self?.removedFile = true +// } +// } +// +// override func tearDownWithError() throws { +// // Put teardown code here. This method is called after the invocation of each test method in the class. +// wroteToFile = false +// dataToWrite = nil +// removedFile = false +// } +// +// func testArchiveUnarchive() throws { +// archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) +// +// XCTAssert(wroteToFile) +// XCTAssertEqual(ARCHIVED_RETURNED_DATA, dataToWrite) +// } +// +// func testArchiveFails() throws { +// environment.archiverClient.archivedData = { _, _ in throw FakeFileError.fake } +// archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) +// +// XCTAssertFalse(wroteToFile) +// XCTAssertNil(dataToWrite) +// } +// +// func testArchiveWriteFails() throws { +// environment.fileClient.write = { _, _ in throw FakeFileError.fake } +// archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) +// +// XCTAssertFalse(wroteToFile) +// XCTAssertNil(dataToWrite) +// } +// +// func testUnarchive() throws { +// let archiveResult = unarchiveFromFile(fileURL: TEST_URL) +// +// XCTAssertEqual(SAMPLE_DATA, archiveResult) +// XCTAssertTrue(removedFile) +// } +// +// func testUnarchiveInvalidData() throws { +// environment.data = { _ in throw FakeFileError.fake } +// +// let archiveResult = unarchiveFromFile(fileURL: TEST_URL) +// +// XCTAssertNil(archiveResult) +// } +// +// func testUnarchiveUnarchiveFails() throws { +// environment.archiverClient.unarchivedMutableArray = { _ in throw FakeFileError.fake } +// +// let archiveResult = unarchiveFromFile(fileURL: TEST_URL) +// +// XCTAssertNil(archiveResult) +// } +// +// func testUnarchiveUnableToRemoveFile() throws { +// var firstCall = true +// environment.fileClient.fileExists = { _ in +// if firstCall { +// firstCall = false +// return true +// } +// return false +// } +// let archiveResult = unarchiveFromFile(fileURL: TEST_URL) +// +// XCTAssertEqual(SAMPLE_DATA, archiveResult) +// XCTAssertFalse(removedFile) +// } +// +// func testUnarchiveWhereFileDoesNotExist() throws { +// environment.fileClient.fileExists = { _ in false } +// let archiveResult = unarchiveFromFile(fileURL: TEST_URL) +// +// XCTAssertNil(archiveResult) +// XCTAssertFalse(removedFile) +// } +// } +// +// class ArchivalSystemTest: XCTestCase { +// let TEST_URL = filePathForData(apiKey: "foo", data: "people") +// +// override func setUpWithError() throws { +// environment = KlaviyoEnvironment.production +// try? FileManager.default.removeItem(atPath: TEST_URL.path) +// } +// +// /* This will attempt to actually archive and unarchive a queue. */ +// func testArchiveUnarchive() { +// archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) +// let result = unarchiveFromFile(fileURL: filePathForData(apiKey: "foo", data: "people")) +// +// XCTAssertEqual(SAMPLE_DATA, result) +// } +// } diff --git a/Tests/KlaviyoSwiftTests/EncodableTests.swift b/Tests/KlaviyoSwiftTests/EncodableTests.swift index bdaadff1..816cbd83 100644 --- a/Tests/KlaviyoSwiftTests/EncodableTests.swift +++ b/Tests/KlaviyoSwiftTests/EncodableTests.swift @@ -1,83 +1,83 @@ +//// +//// EncodableTests.swift +//// +//// +//// Created by Noah Durell on 11/14/22. +//// // -// EncodableTests.swift +// @testable import KlaviyoSwift +// import KlaviyoCore +// import SnapshotTesting +// import XCTest // +// final class EncodableTests: XCTestCase { +// let testEncoder = KlaviyoEnvironment.encoder // -// Created by Noah Durell on 11/14/22. +// override func setUpWithError() throws { +// environment = KlaviyoEnvironment.test() +// testEncoder.outputFormatting = .prettyPrinted.union(.sortedKeys) +// } // - -@testable import KlaviyoSwift -import KlaviyoCore -import SnapshotTesting -import XCTest - -final class EncodableTests: XCTestCase { - let testEncoder = KlaviyoEnvironment.encoder - - override func setUpWithError() throws { - environment = KlaviyoEnvironment.test() - testEncoder.outputFormatting = .prettyPrinted.union(.sortedKeys) - } - - func testProfilePayload() throws { - let profile = Profile.test - let data = CreateProfilePayload.Profile(profile: profile, anonymousId: "foo") - let payload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateProfilePayload(data: data) - assertSnapshot(matching: payload, as: .json(KlaviyoEnvironment.encoder)) - } - - func testEventPayloadWithoutMetadata() throws { - let event = Event.test - let createEventPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateEventPayload(data: .init(event: event, anonymousId: "anon-id")) - assertSnapshot(matching: createEventPayload, as: .json(KlaviyoEnvironment.encoder)) - } - - func testEventPayloadWithMetadata() throws { - let event = Event.test - var createEventPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateEventPayload(data: .init(event: event, anonymousId: "anon-id")) - createEventPayload.appendMetadataToProperties() - assertSnapshot(matching: createEventPayload, as: .json(KlaviyoEnvironment.encoder)) - } - - func testTokenPayload() throws { - let tokenPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload( - pushToken: "foo", - enablement: "AUTHORIZED", - background: "AVAILABLE", - profile: .init(email: "foo", phoneNumber: "foo"), - anonymousId: "foo") - assertSnapshot(matching: tokenPayload, as: .json(KlaviyoEnvironment.encoder)) - } - - func testUnregisterTokenPayload() throws { - let tokenPayload = UnregisterPushTokenPayload( - pushToken: "foo", - profile: .init(email: "foo", phoneNumber: "foo"), - anonymousId: "foo") - assertSnapshot(matching: tokenPayload, as: .json) - } - - func testKlaviyoState() throws { - let tokenPayload = PushTokenPayload( - pushToken: "foo", - enablement: "AUTHORIZED", - background: "AVAILABLE", - profile: .init(email: "foo", phoneNumber: "foo"), - anonymousId: "foo") - let request = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) - let klaviyoState = KlaviyoState(email: "foo", anonymousId: "foo", - phoneNumber: "foo", pushTokenData: .init(pushToken: "foo", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.analytics.appContextInfo())), - queue: [request], requestsInFlight: [request]) - assertSnapshot(matching: klaviyoState, as: .json) - } - - func testKlaviyoRequest() throws { - let tokenPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload( - pushToken: "foo", - enablement: "AUTHORIZED", - background: "AVAILABLE", - profile: .init(email: "foo", phoneNumber: "foo"), - anonymousId: "foo") - let request = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) - assertSnapshot(matching: request, as: .json) - } -} +// func testProfilePayload() throws { +// let profile = Profile.test +// let data = CreateProfilePayload.Profile(profile: profile, anonymousId: "foo") +// let payload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateProfilePayload(data: data) +// assertSnapshot(matching: payload, as: .json(KlaviyoEnvironment.encoder)) +// } +// +// func testEventPayloadWithoutMetadata() throws { +// let event = Event.test +// let createEventPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateEventPayload(data: .init(event: event, anonymousId: "anon-id")) +// assertSnapshot(matching: createEventPayload, as: .json(KlaviyoEnvironment.encoder)) +// } +// +// func testEventPayloadWithMetadata() throws { +// let event = Event.test +// var createEventPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateEventPayload(data: .init(event: event, anonymousId: "anon-id")) +// createEventPayload.appendMetadataToProperties() +// assertSnapshot(matching: createEventPayload, as: .json(KlaviyoEnvironment.encoder)) +// } +// +// func testTokenPayload() throws { +// let tokenPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload( +// pushToken: "foo", +// enablement: "AUTHORIZED", +// background: "AVAILABLE", +// profile: .init(email: "foo", phoneNumber: "foo"), +// anonymousId: "foo") +// assertSnapshot(matching: tokenPayload, as: .json(KlaviyoEnvironment.encoder)) +// } +// +// func testUnregisterTokenPayload() throws { +// let tokenPayload = UnregisterPushTokenPayload( +// pushToken: "foo", +// profile: .init(email: "foo", phoneNumber: "foo"), +// anonymousId: "foo") +// assertSnapshot(matching: tokenPayload, as: .json) +// } +// +// func testKlaviyoState() throws { +// let tokenPayload = PushTokenPayload( +// pushToken: "foo", +// enablement: "AUTHORIZED", +// background: "AVAILABLE", +// profile: .init(email: "foo", phoneNumber: "foo"), +// anonymousId: "foo") +// let request = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) +// let klaviyoState = KlaviyoState(email: "foo", anonymousId: "foo", +// phoneNumber: "foo", pushTokenData: .init(pushToken: "foo", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.analytics.appContextInfo())), +// queue: [request], requestsInFlight: [request]) +// assertSnapshot(matching: klaviyoState, as: .json) +// } +// +// func testKlaviyoRequest() throws { +// let tokenPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload( +// pushToken: "foo", +// enablement: "AUTHORIZED", +// background: "AVAILABLE", +// profile: .init(email: "foo", phoneNumber: "foo"), +// anonymousId: "foo") +// let request = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) +// assertSnapshot(matching: request, as: .json) +// } +// } diff --git a/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift b/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift index 3d771c52..06b6ff37 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift @@ -1,136 +1,136 @@ +//// +//// KlaviyoAPITests.swift +//// +//// +//// Created by Noah Durell on 11/16/22. +//// // -// KlaviyoAPITests.swift -// -// -// Created by Noah Durell on 11/16/22. -// - -@testable import KlaviyoSwift -import KlaviyoCore -import SnapshotTesting -import XCTest - -@MainActor -final class KlaviyoAPITests: XCTestCase { - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - - environment = KlaviyoEnvironment.test() - } - - func testInvalidURL() async throws { - environment.apiURL = "" - - await sendAndAssert(with: .init(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .test, anonymousId: "foo"))))) { result in - switch result { - case let .failure(error): - assertSnapshot(matching: error, as: .description) - default: - XCTFail("Expected url failure") - } - } - } - - func testEncodingError() async throws { - environment.encodeJSON = { _ in throw EncodingError.invalidValue("foo", .init(codingPath: [], debugDescription: "invalid")) - } - let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) - await sendAndAssert(with: request) { result in - - switch result { - case let .failure(error): - assertSnapshot(matching: error, as: .dump) - default: - XCTFail("Expected encoding error.") - } - } - } - - func testNetworkError() async throws { - environment.networkSession = { NetworkSession.test(data: { _ in - throw NSError(domain: "network error", code: 0) - }) } - let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) - await sendAndAssert(with: request) { result in - - switch result { - case let .failure(error): - assertSnapshot(matching: error, as: .dump) - default: - XCTFail("Expected failure here.") - } - } - } - - func testInvalidStatusCode() async throws { - environment.networkSession = { NetworkSession.test(data: { _ in - (Data(), .non200Response) - }) } - let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) - await sendAndAssert(with: request) { result in - - switch result { - case let .failure(error): - assertSnapshot(matching: error, as: .dump) - default: - XCTFail("Expected failure here.") - } - } - } - - func testSuccessfulResponseWithProfile() async throws { - environment.networkSession = { NetworkSession.test(data: { request in - assertSnapshot(matching: request, as: .dump) - return (Data(), .validResponse) - }) } - let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) - await sendAndAssert(with: request) { result in - - switch result { - case let .success(data): - assertSnapshot(matching: data, as: .dump) - default: - XCTFail("Expected failure here.") - } - } - } - - func testSuccessfulResponseWithEvent() async throws { - environment.networkSession = { NetworkSession.test(data: { request in - assertSnapshot(matching: request, as: .dump) - return (Data(), .validResponse) - }) } - let request = KlaviyoRequest(apiKey: "foo", endpoint: .createEvent(.init(data: .init(event: .test)))) - await sendAndAssert(with: request) { result in - switch result { - case let .success(data): - assertSnapshot(matching: data, as: .dump) - default: - XCTFail("Expected failure here.") - } - } - } - - func testSuccessfulResponseWithStoreToken() async throws { - environment.networkSession = { NetworkSession.test(data: { request in - assertSnapshot(matching: request, as: .dump) - return (Data(), .validResponse) - }) } - let request = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(.test)) - await sendAndAssert(with: request) { result in - - switch result { - case let .success(data): - assertSnapshot(matching: data, as: .dump) - default: - XCTFail("Expected failure here.") - } - } - } - - func sendAndAssert(with request: KlaviyoRequest, - assertion: (Result) -> Void) async { - let result = await KlaviyoAPI().send(request, 0) - assertion(result) - } -} +// @testable import KlaviyoSwift +// import KlaviyoCore +// import SnapshotTesting +// import XCTest +// +// @MainActor +// final class KlaviyoAPITests: XCTestCase { +// override func setUpWithError() throws { +// // Put setup code here. This method is called before the invocation of each test method in the class. +// +// environment = KlaviyoEnvironment.test() +// } +// +// func testInvalidURL() async throws { +// environment.apiURL = "" +// +// await sendAndAssert(with: .init(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .test, anonymousId: "foo"))))) { result in +// switch result { +// case let .failure(error): +// assertSnapshot(matching: error, as: .description) +// default: +// XCTFail("Expected url failure") +// } +// } +// } +// +// func testEncodingError() async throws { +// environment.encodeJSON = { _ in throw EncodingError.invalidValue("foo", .init(codingPath: [], debugDescription: "invalid")) +// } +// let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) +// await sendAndAssert(with: request) { result in +// +// switch result { +// case let .failure(error): +// assertSnapshot(matching: error, as: .dump) +// default: +// XCTFail("Expected encoding error.") +// } +// } +// } +// +// func testNetworkError() async throws { +// environment.networkSession = { NetworkSession.test(data: { _ in +// throw NSError(domain: "network error", code: 0) +// }) } +// let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) +// await sendAndAssert(with: request) { result in +// +// switch result { +// case let .failure(error): +// assertSnapshot(matching: error, as: .dump) +// default: +// XCTFail("Expected failure here.") +// } +// } +// } +// +// func testInvalidStatusCode() async throws { +// environment.networkSession = { NetworkSession.test(data: { _ in +// (Data(), .non200Response) +// }) } +// let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) +// await sendAndAssert(with: request) { result in +// +// switch result { +// case let .failure(error): +// assertSnapshot(matching: error, as: .dump) +// default: +// XCTFail("Expected failure here.") +// } +// } +// } +// +// func testSuccessfulResponseWithProfile() async throws { +// environment.networkSession = { NetworkSession.test(data: { request in +// assertSnapshot(matching: request, as: .dump) +// return (Data(), .validResponse) +// }) } +// let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) +// await sendAndAssert(with: request) { result in +// +// switch result { +// case let .success(data): +// assertSnapshot(matching: data, as: .dump) +// default: +// XCTFail("Expected failure here.") +// } +// } +// } +// +// func testSuccessfulResponseWithEvent() async throws { +// environment.networkSession = { NetworkSession.test(data: { request in +// assertSnapshot(matching: request, as: .dump) +// return (Data(), .validResponse) +// }) } +// let request = KlaviyoRequest(apiKey: "foo", endpoint: .createEvent(.init(data: .init(event: .test)))) +// await sendAndAssert(with: request) { result in +// switch result { +// case let .success(data): +// assertSnapshot(matching: data, as: .dump) +// default: +// XCTFail("Expected failure here.") +// } +// } +// } +// +// func testSuccessfulResponseWithStoreToken() async throws { +// environment.networkSession = { NetworkSession.test(data: { request in +// assertSnapshot(matching: request, as: .dump) +// return (Data(), .validResponse) +// }) } +// let request = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(.test)) +// await sendAndAssert(with: request) { result in +// +// switch result { +// case let .success(data): +// assertSnapshot(matching: data, as: .dump) +// default: +// XCTFail("Expected failure here.") +// } +// } +// } +// +// func sendAndAssert(with request: KlaviyoRequest, +// assertion: (Result) -> Void) async { +// let result = await KlaviyoAPI().send(request, 0) +// assertion(result) +// } +// } diff --git a/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift b/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift index db4aff19..5da2dff6 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift @@ -1,178 +1,180 @@ +//// +//// File.swift +//// +//// +//// Created by Noah Durell on 2/21/23. +//// // -// File.swift +// @testable import KlaviyoSwift +// import Foundation +// import KlaviyoCore +// import XCTest // -// -// Created by Noah Durell on 2/21/23. -// - -@testable import KlaviyoSwift -import Foundation -import KlaviyoCore -import XCTest // MARK: - KlaviyoSDKTests -class KlaviyoSDKTests: XCTestCase { - // MARK: Properties - - var klaviyo = KlaviyoSDK() - - // MARK: Setup - - override func setUpWithError() throws { - klaviyo = KlaviyoSDK() - environment = KlaviyoEnvironment.test() - } - - override func tearDown() async throws { - environment = KlaviyoEnvironment.test() - } - - func setupActionAssertion(expectedAction: KlaviyoAction, file: StaticString = #filePath, line: UInt = #line) -> XCTestExpectation { - let expectation = XCTestExpectation(description: "wait for action \(expectedAction)") - environment.analytics.send = { action in - XCTAssertEqual(action, expectedAction, file: file, line: line) - expectation.fulfill() - return nil - } - return expectation - } - - // MARK: Tests - - func testKlaviyoSDKInit() { - XCTAssertNotNil(klaviyo) - } - - // MARK: test initialize - - func testInitializeSDk() throws { - let expectation = setupActionAssertion(expectedAction: .initialize(TEST_API_KEY)) - - klaviyo.initialize(with: TEST_API_KEY) - - wait(for: [expectation], timeout: 1.0) - } - - // MARK: test set proprety - - func testSetFirstName() throws { - let expectation = setupActionAssertion(expectedAction: .setProfileProperty(.firstName, "test")) - - klaviyo.set(profileAttribute: .firstName, value: "test") - - wait(for: [expectation], timeout: 1.0) - } - - // MARK: test set profile - - func testSetProfile() throws { - let profile = Profile( - email: "john.smith@example.com", - phoneNumber: "+15555551212", - firstName: "John", - lastName: "Smith") - let expectation = setupActionAssertion(expectedAction: .enqueueProfile(profile)) - - klaviyo.set(profile: profile) - - wait(for: [expectation], timeout: 1.0) - } - - // MARK: test create event - - func testCreateEvent() throws { - let event = Event(name: .OpenedAppMetric) - let expectation = setupActionAssertion(expectedAction: .enqueueEvent(event)) - - klaviyo.create(event: event) - - wait(for: [expectation], timeout: 1.0) - } - - func testCreateEventFromDocumentation() throws { - let event = Event(name: .AddedToCartMetric, properties: [ - "Total Price": 10.99, - "Items Purchased": ["Hot Dog", "Fries", "Shake"] - ], value: 10.99) - let expectation = setupActionAssertion(expectedAction: .enqueueEvent(event)) - - klaviyo.create(event: event) - - wait(for: [expectation], timeout: 1.0) - } - - // MARK: test set push token - - func testSetPushToken() throws { - let tokenData = "mytoken".data(using: .utf8)! - let strToken = tokenData.reduce("") { $0 + String(format: "%02.2hhx", $1) } - let expectation = setupActionAssertion(expectedAction: .setPushToken(strToken, .authorized)) - - klaviyo.set(pushToken: tokenData) - - wait(for: [expectation], timeout: 1.0) - } - - // MARK: test set external id - - func testSetExternalId() throws { - let expectation = setupActionAssertion(expectedAction: .setExternalId("foo")) - - _ = klaviyo.set(externalId: "foo") - - wait(for: [expectation], timeout: 1.0) - } - - // MARK: test handle push notification - - func testHandlePushNotification() throws { - let callback = XCTestExpectation(description: "callback is made") - let push_body = ["body": [ - "_k": [ - "foo": "bar" - ] - ]] - let expectation = setupActionAssertion(expectedAction: .enqueueEvent(.init(name: .OpenedPush, properties: push_body))) - let response = try UNNotificationResponse.with(userInfo: push_body) - let handled = klaviyo.handle(notificationResponse: response) { - callback.fulfill() - } - - wait(for: [expectation, callback], timeout: 1.0) - XCTAssertTrue(handled) - } - - // MARK: test unhandle push notification - - func testUnhandlePushNotification() throws { - let callback = XCTestExpectation(description: "callback is not made") - callback.isInverted = true - let data: [AnyHashable: Any] = [ - "data": [ - "type": "OPEN_ARTICLE", - "articleId": "1", - "articleType": "Fiction", - "articleTag": "1" - ] - ] - let response = try UNNotificationResponse.with(userInfo: data) - let handled = klaviyo.handle(notificationResponse: response) { - callback.fulfill() - } - - wait(for: [callback], timeout: 1.0) - XCTAssertFalse(handled) - } - - // MARK: test property getters - - func testPropertyGetters() throws { - klaviyoSwiftEnvironment.state = { KlaviyoState(email: "foo@foo.com", phoneNumber: "555BLOB", externalId: "my_test_id", pushTokenData: .init(pushToken: "blobtoken", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.appContextInfo())), queue: []) } - let klaviyo = KlaviyoSDK() - XCTAssertEqual("foo@foo.com", klaviyo.email) - XCTAssertEqual("555BLOB", klaviyo.phoneNumber) - XCTAssertEqual("blobtoken", klaviyo.pushToken) - XCTAssertEqual("my_test_id", klaviyo.externalId) - } -} +// +// class KlaviyoSDKTests: XCTestCase { +// // MARK: Properties +// +// var klaviyo = KlaviyoSDK() +// +// // MARK: Setup +// +// override func setUpWithError() throws { +// klaviyo = KlaviyoSDK() +// environment = KlaviyoEnvironment.test() +// } +// +// override func tearDown() async throws { +// environment = KlaviyoEnvironment.test() +// } +// +// func setupActionAssertion(expectedAction: KlaviyoAction, file: StaticString = #filePath, line: UInt = #line) -> XCTestExpectation { +// let expectation = XCTestExpectation(description: "wait for action \(expectedAction)") +// environment.analytics.send = { action in +// XCTAssertEqual(action, expectedAction, file: file, line: line) +// expectation.fulfill() +// return nil +// } +// return expectation +// } +// +// // MARK: Tests +// +// func testKlaviyoSDKInit() { +// XCTAssertNotNil(klaviyo) +// } +// +// // MARK: test initialize +// +// func testInitializeSDk() throws { +// let expectation = setupActionAssertion(expectedAction: .initialize(TEST_API_KEY)) +// +// klaviyo.initialize(with: TEST_API_KEY) +// +// wait(for: [expectation], timeout: 1.0) +// } +// +// // MARK: test set proprety +// +// func testSetFirstName() throws { +// let expectation = setupActionAssertion(expectedAction: .setProfileProperty(.firstName, "test")) +// +// klaviyo.set(profileAttribute: .firstName, value: "test") +// +// wait(for: [expectation], timeout: 1.0) +// } +// +// // MARK: test set profile +// +// func testSetProfile() throws { +// let profile = Profile( +// email: "john.smith@example.com", +// phoneNumber: "+15555551212", +// firstName: "John", +// lastName: "Smith") +// let expectation = setupActionAssertion(expectedAction: .enqueueProfile(profile)) +// +// klaviyo.set(profile: profile) +// +// wait(for: [expectation], timeout: 1.0) +// } +// +// // MARK: test create event +// +// func testCreateEvent() throws { +// let event = Event(name: .OpenedAppMetric) +// let expectation = setupActionAssertion(expectedAction: .enqueueEvent(event)) +// +// klaviyo.create(event: event) +// +// wait(for: [expectation], timeout: 1.0) +// } +// +// func testCreateEventFromDocumentation() throws { +// let event = Event(name: .AddedToCartMetric, properties: [ +// "Total Price": 10.99, +// "Items Purchased": ["Hot Dog", "Fries", "Shake"] +// ], value: 10.99) +// let expectation = setupActionAssertion(expectedAction: .enqueueEvent(event)) +// +// klaviyo.create(event: event) +// +// wait(for: [expectation], timeout: 1.0) +// } +// +// // MARK: test set push token +// +// func testSetPushToken() throws { +// let tokenData = "mytoken".data(using: .utf8)! +// let strToken = tokenData.reduce("") { $0 + String(format: "%02.2hhx", $1) } +// let expectation = setupActionAssertion(expectedAction: .setPushToken(strToken, .authorized)) +// +// klaviyo.set(pushToken: tokenData) +// +// wait(for: [expectation], timeout: 1.0) +// } +// +// // MARK: test set external id +// +// func testSetExternalId() throws { +// let expectation = setupActionAssertion(expectedAction: .setExternalId("foo")) +// +// _ = klaviyo.set(externalId: "foo") +// +// wait(for: [expectation], timeout: 1.0) +// } +// +// // MARK: test handle push notification +// +// func testHandlePushNotification() throws { +// let callback = XCTestExpectation(description: "callback is made") +// let push_body = ["body": [ +// "_k": [ +// "foo": "bar" +// ] +// ]] +// let expectation = setupActionAssertion(expectedAction: .enqueueEvent(.init(name: .OpenedPush, properties: push_body))) +// let response = try UNNotificationResponse.with(userInfo: push_body) +// let handled = klaviyo.handle(notificationResponse: response) { +// callback.fulfill() +// } +// +// wait(for: [expectation, callback], timeout: 1.0) +// XCTAssertTrue(handled) +// } +// +// // MARK: test unhandle push notification +// +// func testUnhandlePushNotification() throws { +// let callback = XCTestExpectation(description: "callback is not made") +// callback.isInverted = true +// let data: [AnyHashable: Any] = [ +// "data": [ +// "type": "OPEN_ARTICLE", +// "articleId": "1", +// "articleType": "Fiction", +// "articleTag": "1" +// ] +// ] +// let response = try UNNotificationResponse.with(userInfo: data) +// let handled = klaviyo.handle(notificationResponse: response) { +// callback.fulfill() +// } +// +// wait(for: [callback], timeout: 1.0) +// XCTAssertFalse(handled) +// } +// +// // MARK: test property getters +// +// func testPropertyGetters() throws { +// klaviyoSwiftEnvironment.state = { KlaviyoState(email: "foo@foo.com", phoneNumber: "555BLOB", externalId: "my_test_id", pushTokenData: .init(pushToken: "blobtoken", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.appContextInfo())), queue: []) } +// let klaviyo = KlaviyoSDK() +// XCTAssertEqual("foo@foo.com", klaviyo.email) +// XCTAssertEqual("555BLOB", klaviyo.phoneNumber) +// XCTAssertEqual("blobtoken", klaviyo.pushToken) +// XCTAssertEqual("my_test_id", klaviyo.externalId) +// } +// } diff --git a/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift b/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift index 7c19e296..613414e8 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift @@ -1,189 +1,189 @@ +//// +//// KlaviyoStateTests.swift +//// +//// +//// Created by Noah Durell on 12/1/22. +//// // -// KlaviyoStateTests.swift -// -// -// Created by Noah Durell on 12/1/22. -// - -@testable import KlaviyoSwift -import AnyCodable -import Foundation -import KlaviyoCore -import SnapshotTesting -import XCTest - -final class KlaviyoStateTests: XCTestCase { - let TEST_EVENT = [ - "event": "$opened_push", - "properties": [ - "prop1": "propValue" - ], - "customer_properties": [ - "foo": "bar" - ] - ] as [String: Any] - - let TEST_PROFILE = [ - "properties": [ - "foo2": "bar2" - ] - ] - - let TEST_INVALID_EVENT = [ - "properties": [ - "prop1": "propValue" - ], - "customer_properties": [ - "foo": "bar" - ] - ] - let TEST_INVALID_PROFILE = [ - "garbage_key": [ - "foo": "bar" - ] - ] - let TEST_INVALID_PROPERTIES_EVENT = [ - "properties": [ - 1: "propValue" - ] as [AnyHashable: String], - "customer_properties": [ - "foo": "bar" - ] - ] - - let TEST_INVALID_CUSTOMER_PROPERTIES_EVENT = [ - "event": "$opened_push", - "properties": [ - "fo": "propValue" - ], - "customer_properties": [ - 1: "bar" - ] - ] as [String: Any] - let TEST_INVALID_PROPERTIES_PROFILE = [ - "event": "$opened_push", - "properties": [ - 1: "propValue" - ] - ] as [String: Any] - - override func setUp() async throws { - environment = KlaviyoEnvironment.test() - } - - func testLoadNewKlaviyoState() throws { - environment.fileClient.fileExists = { _ in false } - environment.archiverClient.unarchivedMutableArray = { _ in [] } - let state = loadKlaviyoStateFromDisk(apiKey: "foo") - assertSnapshot(matching: state, as: .dump) - } - - func testStateFileExistsInvalidData() throws { - environment.fileClient.fileExists = { _ in - true - } - environment.dataFromUrl = { _ in - throw NSError(domain: "missing file", code: 1) - } - environment.archiverClient.unarchivedMutableArray = { _ in - XCTFail("unarchivedMutableArray should not be called.") - return [] - } - - let state = loadKlaviyoStateFromDisk(apiKey: "foo") - assertSnapshot(matching: state, as: .dump) - } - - func testStateFileExistsInvalidJSON() throws { - environment.fileClient.fileExists = { _ in - true - } - - environment.decoder = DataDecoder(jsonDecoder: InvalidJSONDecoder()) - environment.archiverClient.unarchivedMutableArray = { _ in - XCTFail("unarchivedMutableArray should not be called.") - return [] - } - - let state = loadKlaviyoStateFromDisk(apiKey: "foo") - assertSnapshot(matching: state, as: .dump) - } - - func testValidStateFileExists() throws { - environment.fileClient.fileExists = { _ in - true - } - environment.dataFromUrl = { _ in - try! JSONEncoder().encode(KlaviyoState(apiKey: "foo", anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [])) - } - environment.decoder = DataDecoder(jsonDecoder: KlaviyoEnvironment.decoder) - - let state = loadKlaviyoStateFromDisk(apiKey: "foo") - assertSnapshot(matching: state, as: .dump) - } - - func testFullKlaviyoStateEncodingDecodingIsEqual() throws { - let event = Event.test - let createEventPayload = CreateEventPayload(data: .init(event: event)) - let eventRequest = KlaviyoRequest(apiKey: "foo", endpoint: .createEvent(createEventPayload)) - let profile = Profile.test - let data = CreateProfilePayload.Profile(profile: profile, anonymousId: "foo") - let payload = CreateProfilePayload(data: data) - let profileRequest = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(payload)) - let tokenPayload = KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload( - pushToken: "foo", - enablement: "AUTHORIZED", - background: "AVAILABLE", - profile: .init(email: "foo", phoneNumber: "foo"), - anonymousId: "foo") - let tokenRequest = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) - let state = KlaviyoState(apiKey: "key", queue: [tokenRequest, profileRequest, eventRequest]) - let encodedState = try environment.encodeJSON(AnyEncodable(state)) - let decodedState: KlaviyoState = try environment.decoder.decode(encodedState) - XCTAssertEqual(decodedState, state) - } - - func testSaveKlaviyoStateWithMissingApiKeyLogsError() { - var savedMsg: String? - environment.logger.error = { msg in savedMsg = msg } - let state = KlaviyoState(queue: []) - saveKlaviyoState(state: state) - - XCTAssertEqual(savedMsg, "Attempt to save state without an api key.") - } - - // MARK: test background and authorization states - - func testBackgroundStates() { - let backgroundStates = [ - UIBackgroundRefreshStatus.available: PushBackground.available, - .denied: .denied, - .restricted: .restricted - ] - - for (status, expecation) in backgroundStates { - XCTAssertEqual(PushBackground.create(from: status), expecation) - } - - // Fake value to test availability - XCTAssertEqual(PushBackground.create(from: UIBackgroundRefreshStatus(rawValue: 20)!), .available) - } - - @available(iOS 14.0, *) - func testPushEnablementStates() { - let enablementStates = [ - UNAuthorizationStatus.authorized: PushEnablement.authorized, - .denied: .denied, - .ephemeral: .ephemeral, - .notDetermined: .notDetermined, - .provisional: .provisional - ] - - for (status, expecation) in enablementStates { - XCTAssertEqual(PushEnablement.create(from: status), expecation) - } - - // Fake value to test availability - XCTAssertEqual(PushEnablement.create(from: UNAuthorizationStatus(rawValue: 50)!), .notDetermined) - } -} +// @testable import KlaviyoSwift +// import AnyCodable +// import Foundation +// import KlaviyoCore +// import SnapshotTesting +// import XCTest +// +// final class KlaviyoStateTests: XCTestCase { +// let TEST_EVENT = [ +// "event": "$opened_push", +// "properties": [ +// "prop1": "propValue" +// ], +// "customer_properties": [ +// "foo": "bar" +// ] +// ] as [String: Any] +// +// let TEST_PROFILE = [ +// "properties": [ +// "foo2": "bar2" +// ] +// ] +// +// let TEST_INVALID_EVENT = [ +// "properties": [ +// "prop1": "propValue" +// ], +// "customer_properties": [ +// "foo": "bar" +// ] +// ] +// let TEST_INVALID_PROFILE = [ +// "garbage_key": [ +// "foo": "bar" +// ] +// ] +// let TEST_INVALID_PROPERTIES_EVENT = [ +// "properties": [ +// 1: "propValue" +// ] as [AnyHashable: String], +// "customer_properties": [ +// "foo": "bar" +// ] +// ] +// +// let TEST_INVALID_CUSTOMER_PROPERTIES_EVENT = [ +// "event": "$opened_push", +// "properties": [ +// "fo": "propValue" +// ], +// "customer_properties": [ +// 1: "bar" +// ] +// ] as [String: Any] +// let TEST_INVALID_PROPERTIES_PROFILE = [ +// "event": "$opened_push", +// "properties": [ +// 1: "propValue" +// ] +// ] as [String: Any] +// +// override func setUp() async throws { +// environment = KlaviyoEnvironment.test() +// } +// +// func testLoadNewKlaviyoState() throws { +// environment.fileClient.fileExists = { _ in false } +// environment.archiverClient.unarchivedMutableArray = { _ in [] } +// let state = loadKlaviyoStateFromDisk(apiKey: "foo") +// assertSnapshot(matching: state, as: .dump) +// } +// +// func testStateFileExistsInvalidData() throws { +// environment.fileClient.fileExists = { _ in +// true +// } +// environment.dataFromUrl = { _ in +// throw NSError(domain: "missing file", code: 1) +// } +// environment.archiverClient.unarchivedMutableArray = { _ in +// XCTFail("unarchivedMutableArray should not be called.") +// return [] +// } +// +// let state = loadKlaviyoStateFromDisk(apiKey: "foo") +// assertSnapshot(matching: state, as: .dump) +// } +// +// func testStateFileExistsInvalidJSON() throws { +// environment.fileClient.fileExists = { _ in +// true +// } +// +// environment.decoder = DataDecoder(jsonDecoder: InvalidJSONDecoder()) +// environment.archiverClient.unarchivedMutableArray = { _ in +// XCTFail("unarchivedMutableArray should not be called.") +// return [] +// } +// +// let state = loadKlaviyoStateFromDisk(apiKey: "foo") +// assertSnapshot(matching: state, as: .dump) +// } +// +// func testValidStateFileExists() throws { +// environment.fileClient.fileExists = { _ in +// true +// } +// environment.dataFromUrl = { _ in +// try! JSONEncoder().encode(KlaviyoState(apiKey: "foo", anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [])) +// } +//// environment.decoder = DataDecoder(jsonDecoder: KlaviyoEnvironment.decoder) +// +// let state = loadKlaviyoStateFromDisk(apiKey: "foo") +// assertSnapshot(matching: state, as: .dump) +// } +// +// func testFullKlaviyoStateEncodingDecodingIsEqual() throws { +// let event = Event.test +// let createEventPayload = CreateEventPayload(data: .init(event: event)) +// let eventRequest = KlaviyoRequest(apiKey: "foo", endpoint: .createEvent(createEventPayload)) +// let profile = Profile.test +// let data = CreateProfilePayload.Profile(profile: profile, anonymousId: "foo") +// let payload = CreateProfilePayload(data: data) +// let profileRequest = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(payload)) +// let tokenPayload = KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload( +// pushToken: "foo", +// enablement: "AUTHORIZED", +// background: "AVAILABLE", +// profile: .init(email: "foo", phoneNumber: "foo"), +// anonymousId: "foo") +// let tokenRequest = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) +// let state = KlaviyoState(apiKey: "key", queue: [tokenRequest, profileRequest, eventRequest]) +// let encodedState = try environment.encodeJSON(AnyEncodable(state)) +// let decodedState: KlaviyoState = try environment.decoder.decode(encodedState) +// XCTAssertEqual(decodedState, state) +// } +// +// func testSaveKlaviyoStateWithMissingApiKeyLogsError() { +// var savedMsg: String? +// environment.logger.error = { msg in savedMsg = msg } +// let state = KlaviyoState(queue: []) +// saveKlaviyoState(state: state) +// +// XCTAssertEqual(savedMsg, "Attempt to save state without an api key.") +// } +// +// // MARK: test background and authorization states +// +// func testBackgroundStates() { +// let backgroundStates = [ +// UIBackgroundRefreshStatus.available: PushBackground.available, +// .denied: .denied, +// .restricted: .restricted +// ] +// +// for (status, expecation) in backgroundStates { +// XCTAssertEqual(PushBackground.create(from: status), expecation) +// } +// +// // Fake value to test availability +// XCTAssertEqual(PushBackground.create(from: UIBackgroundRefreshStatus(rawValue: 20)!), .available) +// } +// +// @available(iOS 14.0, *) +// func testPushEnablementStates() { +// let enablementStates = [ +// UNAuthorizationStatus.authorized: PushEnablement.authorized, +// .denied: .denied, +// .ephemeral: .ephemeral, +// .notDetermined: .notDetermined, +// .provisional: .provisional +// ] +// +// for (status, expecation) in enablementStates { +// XCTAssertEqual(PushEnablement.create(from: status), expecation) +// } +// +// // Fake value to test availability +// XCTAssertEqual(PushEnablement.create(from: UNAuthorizationStatus(rawValue: 50)!), .notDetermined) +// } +// } diff --git a/Tests/KlaviyoSwiftTests/StateChangePublisherTests.swift b/Tests/KlaviyoSwiftTests/StateChangePublisherTests.swift index 23a7b81d..7ac9e32c 100644 --- a/Tests/KlaviyoSwiftTests/StateChangePublisherTests.swift +++ b/Tests/KlaviyoSwiftTests/StateChangePublisherTests.swift @@ -54,7 +54,7 @@ final class StateChangePublisherTests: XCTestCase { let reducer = KlaviyoTestReducer(reducer: initializationReducer) let test = Store(initialState: .test, reducer: reducer) - environment.analytics.send = { + klaviyoSwiftEnvironment.send = { test.send($0) } @@ -64,11 +64,11 @@ final class StateChangePublisherTests: XCTestCase { testScheduler.run() @MainActor func runDebouncedEffect() { - _ = environment.analytics.send(.initialize("foo")) + _ = klaviyoSwiftEnvironment.send(.initialize("foo")) testScheduler.run() // This should not trigger a save since in our reducer it does not change the state. - _ = environment.analytics.send(.setPushToken("foo", .authorized)) - _ = environment.analytics.send(.setEmail("foo")) + _ = klaviyoSwiftEnvironment.send(.setPushToken("foo", .authorized)) + _ = klaviyoSwiftEnvironment.send(.setEmail("foo")) } runDebouncedEffect() testScheduler.advance(by: .seconds(2.0)) @@ -100,18 +100,18 @@ final class StateChangePublisherTests: XCTestCase { let reducer = KlaviyoTestReducer(reducer: initializationReducer) let test = Store(initialState: .test, reducer: reducer) - environment.analytics.send = { + klaviyoSwiftEnvironment.send = { test.send($0) } - environment.analytics.statePublisher = { + klaviyoSwiftEnvironment.statePublisher = { test.state.eraseToAnyPublisher() } @MainActor func runDebouncedEffect() { - _ = environment.analytics.send(.initialize("foo")) - _ = environment.analytics.send(.flushQueue) - _ = environment.analytics.send(.flushQueue) + _ = klaviyoSwiftEnvironment.send(.initialize("foo")) + _ = klaviyoSwiftEnvironment.send(.flushQueue) + _ = klaviyoSwiftEnvironment.send(.flushQueue) } runDebouncedEffect() @@ -150,17 +150,17 @@ final class StateChangePublisherTests: XCTestCase { let reducer = KlaviyoTestReducer(reducer: initializationReducer) let test = Store(initialState: .test, reducer: reducer) - environment.analytics.send = { + klaviyoSwiftEnvironment.send = { test.send($0) } - environment.analytics.statePublisher = { + klaviyoSwiftEnvironment.statePublisher = { test.state.eraseToAnyPublisher() } - _ = environment.analytics.send(.initialize("foo")) + _ = klaviyoSwiftEnvironment.send(.initialize("foo")) testScheduler.run() for i in 0...10 { - _ = environment.analytics.send(.setEmail("foo\(i)")) + _ = klaviyoSwiftEnvironment.send(.setEmail("foo\(i)")) } testScheduler.advance(by: 1.0) wait(for: [savedCalledExpectation], timeout: 1.0) diff --git a/Tests/KlaviyoSwiftTests/StateManagementTests.swift b/Tests/KlaviyoSwiftTests/StateManagementTests.swift index 2d8f908c..cb7c161a 100644 --- a/Tests/KlaviyoSwiftTests/StateManagementTests.swift +++ b/Tests/KlaviyoSwiftTests/StateManagementTests.swift @@ -500,7 +500,10 @@ class StateManagementTests: XCTestCase { data: CreateEventPayload.Event( name: eventName.value, properties: event.properties, - phoneNumber: $0.phoneNumber) + phoneNumber: $0.phoneNumber, + anonymousId: initialState.anonymousId!, + time: event.time, + pushToken: initialState.pushTokenData!.pushToken) )))) } @@ -521,6 +524,9 @@ class StateManagementTests: XCTestCase { $0.pendingRequests = [KlaviyoState.PendingRequest.event(event)] } + // TODO: this passes if I comment the below line in state management + // `.merge(with: klaviyoSwiftEnvironment.stateChangePublisher().eraseToEffect())` + // fails with - An effect returned for this action is still running. It must complete before the end of the test. … await store.send(.completeInitialization(initialState)) { $0.pendingRequests = [] $0.initalizationState = .initialized @@ -533,7 +539,10 @@ class StateManagementTests: XCTestCase { endpoint: .createEvent(CreateEventPayload( data: CreateEventPayload.Event( name: Event.EventName.OpenedAppMetric.value, - phoneNumber: $0.phoneNumber) + properties: event.properties, + phoneNumber: $0.phoneNumber, + anonymousId: initialState.anonymousId!, + time: event.time) ))) ) } diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithMetadata.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithMetadata.1.json deleted file mode 100644 index 2d758300..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithMetadata.1.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "data" : { - "attributes" : { - "metric" : { - "data" : { - "attributes" : { - "name" : "blob" - }, - "type" : "metric" - } - }, - "profile" : { - "data" : { - "attributes" : { - "anonymous_id" : "anon-id", - "properties" : { - - } - }, - "type" : "profile" - } - }, - "properties" : { - "App Build" : "1", - "App ID" : "com.klaviyo.fooapp", - "App Name" : "FooApp", - "App Version" : "1.2.3", - "Application ID" : "com.klaviyo.fooapp", - "blob" : "blob", - "Device ID" : "fe-fi-fo-fum", - "Device Manufacturer" : "Orange", - "Device Model" : "jPhone 1,1", - "hello" : { - "sub" : "dict" - }, - "OS Name" : "iOS", - "OS Version" : "1.1.1", - "Push Token" : null, - "SDK Name" : "swift", - "SDK Version" : "3.2.0", - "stuff" : 2 - }, - "time" : "2009-02-13T23:31:30Z", - "unique_id" : "00000000-0000-0000-0000-000000000001" - }, - "type" : "event" - } -} \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json deleted file mode 100644 index 2ad70580..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "data" : { - "attributes" : { - "metric" : { - "data" : { - "attributes" : { - "name" : "blob" - }, - "type" : "metric" - } - }, - "profile" : { - "data" : { - "attributes" : { - "anonymous_id" : "anon-id", - "properties" : { - - } - }, - "type" : "profile" - } - }, - "properties" : { - "App Build" : "1", - "App Name" : "FooApp", - "App Version" : "1.2.3", - "Application ID" : "com.klaviyo.fooapp", - "blob" : "blob", - "Device Manufacturer" : "Orange", - "Device Model" : "jPhone 1,1", - "hello" : { - "sub" : "dict" - }, - "OS Name" : "iOS", - "OS Version" : "1.1.1", - "stuff" : 2 - }, - "time" : "2009-02-13T23:31:30Z", - "unique_id" : "00000000-0000-0000-0000-000000000001" - }, - "type" : "event" - } -} \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json deleted file mode 100644 index 4e9b45c7..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "apiKey" : "foo", - "endpoint" : { - "registerPushToken" : { - "_0" : { - "data" : { - "attributes" : { - "background" : "AVAILABLE", - "device_metadata" : { - "app_build" : "1", - "app_id" : "com.klaviyo.fooapp", - "app_name" : "FooApp", - "app_version" : "1.2.3", - "device_id" : "fe-fi-fo-fum", - "device_model" : "jPhone 1,1", - "environment" : "debug", - "klaviyo_sdk" : "swift", - "manufacturer" : "Orange", - "os_name" : "iOS", - "os_version" : "1.1.1", - "sdk_version" : "3.2.0" - }, - "enablement_status" : "AUTHORIZED", - "platform" : "ios", - "profile" : { - "data" : { - "attributes" : { - "anonymous_id" : "foo", - "email" : "foo", - "phone_number" : "foo", - "properties" : { - - } - }, - "type" : "profile" - } - }, - "token" : "foo", - "vendor" : "APNs" - }, - "type" : "push-token" - } - } - } - }, - "uuid" : "00000000-0000-0000-0000-000000000001" -} \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json deleted file mode 100644 index edb7e4c7..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "anonymousId" : "foo", - "email" : "foo", - "phoneNumber" : "foo", - "pushTokenData" : { - "deviceData" : { - "app_build" : "1", - "app_id" : "com.klaviyo.fooapp", - "app_name" : "FooApp", - "app_version" : "1.2.3", - "device_id" : "fe-fi-fo-fum", - "device_model" : "jPhone 1,1", - "environment" : "debug", - "klaviyo_sdk" : "swift", - "manufacturer" : "Orange", - "os_name" : "iOS", - "os_version" : "1.1.1", - "sdk_version" : "3.2.0" - }, - "pushBackground" : "AVAILABLE", - "pushEnablement" : "AUTHORIZED", - "pushToken" : "foo" - }, - "queue" : [ - { - "apiKey" : "foo", - "endpoint" : { - "registerPushToken" : { - "_0" : { - "data" : { - "attributes" : { - "background" : "AVAILABLE", - "device_metadata" : { - "app_build" : "1", - "app_id" : "com.klaviyo.fooapp", - "app_name" : "FooApp", - "app_version" : "1.2.3", - "device_id" : "fe-fi-fo-fum", - "device_model" : "jPhone 1,1", - "environment" : "debug", - "klaviyo_sdk" : "swift", - "manufacturer" : "Orange", - "os_name" : "iOS", - "os_version" : "1.1.1", - "sdk_version" : "3.2.0" - }, - "enablement_status" : "AUTHORIZED", - "platform" : "ios", - "profile" : { - "data" : { - "attributes" : { - "anonymous_id" : "foo", - "email" : "foo", - "phone_number" : "foo", - "properties" : { - - } - }, - "type" : "profile" - } - }, - "token" : "foo", - "vendor" : "APNs" - }, - "type" : "push-token" - } - } - } - }, - "uuid" : "00000000-0000-0000-0000-000000000001" - } - ] -} \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testProfilePayload.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testProfilePayload.1.json deleted file mode 100644 index cc974d65..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testProfilePayload.1.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "data" : { - "attributes" : { - "anonymous_id" : "foo", - "email" : "blobemail", - "external_id" : "blobid", - "first_name" : "Blob", - "image" : "foo", - "last_name" : "Junior", - "location" : { - "address1" : "blob", - "address2" : "blob", - "city" : "blob city", - "country" : "Blobland", - "latitude" : 1, - "longitude" : 1, - "region" : "BL", - "timezone" : "EST", - "zip" : "0BLOB" - }, - "organization" : "Blobco", - "phone_number" : "+15555555555", - "properties" : { - "blob" : "blob", - "hello" : { - "sub" : "dict" - }, - "stuff" : 2 - }, - "title" : "Jelly" - }, - "type" : "profile" - } -} \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testTokenPayload.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testTokenPayload.1.json deleted file mode 100644 index 8935058a..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testTokenPayload.1.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "data" : { - "attributes" : { - "background" : "AVAILABLE", - "device_metadata" : { - "app_build" : "1", - "app_id" : "com.klaviyo.fooapp", - "app_name" : "FooApp", - "app_version" : "1.2.3", - "device_id" : "fe-fi-fo-fum", - "device_model" : "jPhone 1,1", - "environment" : "debug", - "klaviyo_sdk" : "swift", - "manufacturer" : "Orange", - "os_name" : "iOS", - "os_version" : "1.1.1", - "sdk_version" : "3.2.0" - }, - "enablement_status" : "AUTHORIZED", - "platform" : "ios", - "profile" : { - "data" : { - "attributes" : { - "anonymous_id" : "foo", - "email" : "foo", - "phone_number" : "foo", - "properties" : { - - } - }, - "type" : "profile" - } - }, - "token" : "foo", - "vendor" : "APNs" - }, - "type" : "push-token" - } -} \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json deleted file mode 100644 index 74ed5677..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "data" : { - "attributes" : { - "platform" : "ios", - "profile" : { - "data" : { - "attributes" : { - "anonymous_id" : "foo", - "email" : "foo", - "phone_number" : "foo", - "properties" : { - - } - }, - "type" : "profile" - } - }, - "token" : "foo", - "vendor" : "APNs" - }, - "type" : "push-token-unregister" - } -} \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt deleted file mode 100644 index 41e20556..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt +++ /dev/null @@ -1,22 +0,0 @@ -▿ KlaviyoAPIError - ▿ internalRequestError: KlaviyoAPIError - ▿ dataEncodingError: KlaviyoRequest - - apiKey: "foo" - ▿ endpoint: KlaviyoEndpoint - ▿ createProfile: CreateProfilePayload - ▿ data: Profile - ▿ attributes: Attributes - - anonymousId: "foo" - - email: Optional.none - - externalId: Optional.none - - firstName: Optional.none - - image: Optional.none - - lastName: Optional.none - - location: Optional.none - - organization: Optional.none - - phoneNumber: Optional.none - ▿ properties: [:] - - value: 0 key/value pairs - - title: Optional.none - - type: "profile" - - uuid: "00000000-0000-0000-0000-000000000001" diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt deleted file mode 100644 index ccb902f1..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt +++ /dev/null @@ -1,4 +0,0 @@ -▿ KlaviyoAPIError - ▿ httpError: (2 elements) - - .0: 500 - - .1: 0 bytes diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt deleted file mode 100644 index edd8b8ef..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt +++ /dev/null @@ -1 +0,0 @@ -internalRequestError(KlaviyoSwift.KlaviyoAPI.KlaviyoAPIError.internalError("Invalid url string. API URL: ")) \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt deleted file mode 100644 index 8c73f9ae..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt +++ /dev/null @@ -1,2 +0,0 @@ -▿ KlaviyoAPIError - - networkError: Error Domain=network error Code=0 "(null)" diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt deleted file mode 100644 index fafe69a5..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt +++ /dev/null @@ -1,20 +0,0 @@ -▿ dead_beef/client/events/?company_id=foo - ▿ url: Optional - - some: dead_beef/client/events/?company_id=foo - - cachePolicy: 0 - - timeoutInterval: 60.0 - - mainDocumentURL: Optional.none - - networkServiceType: NSURLRequestNetworkServiceType.NSURLRequestNetworkServiceType - - allowsCellularAccess: true - ▿ httpMethod: Optional - - some: "POST" - ▿ allHTTPHeaderFields: Optional> - ▿ some: 1 key/value pair - ▿ (2 elements) - - key: "X-Klaviyo-Attempt-Count" - - value: "0/50" - ▿ httpBody: Optional - - some: 0 bytes - - httpBodyStream: Optional.none - - httpShouldHandleCookies: true - - httpShouldUsePipelining: false diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt deleted file mode 100644 index eff1843b..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt +++ /dev/null @@ -1 +0,0 @@ -- 0 bytes diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt deleted file mode 100644 index 9563cd57..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt +++ /dev/null @@ -1,20 +0,0 @@ -▿ dead_beef/client/profiles/?company_id=foo - ▿ url: Optional - - some: dead_beef/client/profiles/?company_id=foo - - cachePolicy: 0 - - timeoutInterval: 60.0 - - mainDocumentURL: Optional.none - - networkServiceType: NSURLRequestNetworkServiceType.NSURLRequestNetworkServiceType - - allowsCellularAccess: true - ▿ httpMethod: Optional - - some: "POST" - ▿ allHTTPHeaderFields: Optional> - ▿ some: 1 key/value pair - ▿ (2 elements) - - key: "X-Klaviyo-Attempt-Count" - - value: "0/50" - ▿ httpBody: Optional - - some: 0 bytes - - httpBodyStream: Optional.none - - httpShouldHandleCookies: true - - httpShouldUsePipelining: false diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt deleted file mode 100644 index eff1843b..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt +++ /dev/null @@ -1 +0,0 @@ -- 0 bytes diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt deleted file mode 100644 index b80d173f..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt +++ /dev/null @@ -1,20 +0,0 @@ -▿ dead_beef/client/push-tokens/?company_id=foo - ▿ url: Optional - - some: dead_beef/client/push-tokens/?company_id=foo - - cachePolicy: 0 - - timeoutInterval: 60.0 - - mainDocumentURL: Optional.none - - networkServiceType: NSURLRequestNetworkServiceType.NSURLRequestNetworkServiceType - - allowsCellularAccess: true - ▿ httpMethod: Optional - - some: "POST" - ▿ allHTTPHeaderFields: Optional> - ▿ some: 1 key/value pair - ▿ (2 elements) - - key: "X-Klaviyo-Attempt-Count" - - value: "0/50" - ▿ httpBody: Optional - - some: 0 bytes - - httpBodyStream: Optional.none - - httpShouldHandleCookies: true - - httpShouldUsePipelining: false diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt deleted file mode 100644 index eff1843b..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt +++ /dev/null @@ -1 +0,0 @@ -- 0 bytes diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testLoadNewKlaviyoState.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testLoadNewKlaviyoState.1.txt deleted file mode 100644 index 7be1f88b..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testLoadNewKlaviyoState.1.txt +++ /dev/null @@ -1,18 +0,0 @@ -▿ KlaviyoState - ▿ anonymousId: Optional - - some: "00000000-0000-0000-0000-000000000001" - ▿ apiKey: Optional - - some: "foo" - - email: Optional.none - - externalId: Optional.none - - flushInterval: 10.0 - - flushing: false - - initalizationState: InitializationState.uninitialized - - pendingProfile: Optional>.none - - pendingRequests: 0 elements - - phoneNumber: Optional.none - - pushTokenData: Optional.none - - queue: 0 elements - - requestsInFlight: 0 elements - ▿ retryInfo: RetryInfo - - retry: 1 diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidData.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidData.1.txt deleted file mode 100644 index 7be1f88b..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidData.1.txt +++ /dev/null @@ -1,18 +0,0 @@ -▿ KlaviyoState - ▿ anonymousId: Optional - - some: "00000000-0000-0000-0000-000000000001" - ▿ apiKey: Optional - - some: "foo" - - email: Optional.none - - externalId: Optional.none - - flushInterval: 10.0 - - flushing: false - - initalizationState: InitializationState.uninitialized - - pendingProfile: Optional>.none - - pendingRequests: 0 elements - - phoneNumber: Optional.none - - pushTokenData: Optional.none - - queue: 0 elements - - requestsInFlight: 0 elements - ▿ retryInfo: RetryInfo - - retry: 1 diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidJSON.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidJSON.1.txt deleted file mode 100644 index 7be1f88b..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidJSON.1.txt +++ /dev/null @@ -1,18 +0,0 @@ -▿ KlaviyoState - ▿ anonymousId: Optional - - some: "00000000-0000-0000-0000-000000000001" - ▿ apiKey: Optional - - some: "foo" - - email: Optional.none - - externalId: Optional.none - - flushInterval: 10.0 - - flushing: false - - initalizationState: InitializationState.uninitialized - - pendingProfile: Optional>.none - - pendingRequests: 0 elements - - phoneNumber: Optional.none - - pushTokenData: Optional.none - - queue: 0 elements - - requestsInFlight: 0 elements - ▿ retryInfo: RetryInfo - - retry: 1 diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testValidStateFileExists.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testValidStateFileExists.1.txt deleted file mode 100644 index 7be1f88b..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testValidStateFileExists.1.txt +++ /dev/null @@ -1,18 +0,0 @@ -▿ KlaviyoState - ▿ anonymousId: Optional - - some: "00000000-0000-0000-0000-000000000001" - ▿ apiKey: Optional - - some: "foo" - - email: Optional.none - - externalId: Optional.none - - flushInterval: 10.0 - - flushing: false - - initalizationState: InitializationState.uninitialized - - pendingProfile: Optional>.none - - pendingRequests: 0 elements - - phoneNumber: Optional.none - - pushTokenData: Optional.none - - queue: 0 elements - - requestsInFlight: 0 elements - ▿ retryInfo: RetryInfo - - retry: 1 From 8b27d559aaa1a7bf207f6565fa2fd8bb6c6e2439 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 14 Aug 2024 16:02:17 -0500 Subject: [PATCH 24/72] fixed more tests --- .../Vendor/ReachabilitySwift.swift | 29 +- .../AppLifeCycleEventsTests.swift | 438 +++++++++--------- 2 files changed, 246 insertions(+), 221 deletions(-) diff --git a/Sources/KlaviyoCore/Vendor/ReachabilitySwift.swift b/Sources/KlaviyoCore/Vendor/ReachabilitySwift.swift index 1daf2bc9..3f383bb9 100644 --- a/Sources/KlaviyoCore/Vendor/ReachabilitySwift.swift +++ b/Sources/KlaviyoCore/Vendor/ReachabilitySwift.swift @@ -48,8 +48,33 @@ func callback(reachability: SCNetworkReachability, flags: SCNetworkReachabilityF } public class Reachability { - typealias NetworkReachable = (Reachability) -> Void - typealias NetworkUnreachable = (Reachability) -> Void + public init( + whenReachable: Reachability.NetworkReachable? = nil, + whenUnreachable: Reachability.NetworkUnreachable? = nil, + reachableOnWWAN: Bool = false, + notificationCenter: NotificationCenter = .default, + previousFlags: SCNetworkReachabilityFlags? = nil, + isRunningOnDevice: Bool = { + #if targetEnvironment(simulator) + return false + #else + return true + #endif + }(), + notifierRunning: Bool = false, + reachabilityRef: SCNetworkReachability? = nil) { + self.whenReachable = whenReachable + self.whenUnreachable = whenUnreachable + self.reachableOnWWAN = reachableOnWWAN + self.notificationCenter = notificationCenter + self.previousFlags = previousFlags + self.isRunningOnDevice = isRunningOnDevice + self.notifierRunning = notifierRunning + self.reachabilityRef = reachabilityRef + } + + public typealias NetworkReachable = (Reachability) -> Void + public typealias NetworkUnreachable = (Reachability) -> Void public enum NetworkStatus: CustomStringConvertible { case notReachable, reachableViaWiFi, reachableViaWWAN diff --git a/Tests/KlaviyoSwiftTests/AppLifeCycleEventsTests.swift b/Tests/KlaviyoSwiftTests/AppLifeCycleEventsTests.swift index acc57a03..782ee5df 100644 --- a/Tests/KlaviyoSwiftTests/AppLifeCycleEventsTests.swift +++ b/Tests/KlaviyoSwiftTests/AppLifeCycleEventsTests.swift @@ -1,220 +1,220 @@ -//// -//// AppLifeCycleEventsTests.swift -//// -//// -//// Created by Noah Durell on 12/15/22. -//// // -// @testable import KlaviyoSwift -// import Combine -// import Foundation -// import KlaviyoCore -// import XCTest -// -// class AppLifeCycleEventsTests: XCTestCase { -// let passThroughSubject = PassthroughSubject() -// -// func getFilteredNotificaitonPublished(name: Notification.Name) -> (Notification.Name) -> AnyPublisher { -// // returns passthrough if it's match other return nothing -// { [weak self] notificationName in -// if name == notificationName { -// return self!.passThroughSubject.eraseToAnyPublisher() -// } else { -// return Empty().eraseToAnyPublisher() -// } -// } -// } -// -// @MainActor -// override func setUp() { -// environment = KlaviyoEnvironment.test() -// } -// -// // MARK: - App Terminate -// -// @MainActor -// func testAppTerminateStopsReachability() { -// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.willTerminateNotification) -// let expection = XCTestExpectation(description: "Stop reachability is called.") -// environment.stopReachability = { expection.fulfill() } -// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } -// -// passThroughSubject.send(Notification(name: UIApplication.willTerminateNotification.self)) -// -// wait(for: [expection], timeout: 0.1) -// cancellable.cancel() -// } -// -// func testAppTerminateGetsStopAction() { -// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.willTerminateNotification) -// let stopActionExpection = XCTestExpectation(description: "Stop action is received.") -// stopActionExpection.assertForOverFulfill = true -// var receivedAction: KlaviyoAction? -// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in -// receivedAction = action -// stopActionExpection.fulfill() -// } -// -// passThroughSubject.send(Notification(name: UIApplication.willTerminateNotification.self)) -// -// wait(for: [stopActionExpection], timeout: 0.1) -// XCTAssertEqual(KlaviyoAction.stop, receivedAction) -// cancellable.cancel() -// } -// -// // MARK: - App Background -// -// func testAppBackgroundStopsReachability() { -// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didEnterBackgroundNotification) -// let expection = XCTestExpectation(description: "Stop reachability is called.") -// environment.stopReachability = { expection.fulfill() } -// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } -// -// passThroughSubject.send(Notification(name: UIApplication.didEnterBackgroundNotification.self)) -// -// wait(for: [expection], timeout: 0.1) -// cancellable.cancel() -// } -// -// func testAppBackgroundGetsStopAction() { -// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didEnterBackgroundNotification) -// let stopActionExpection = XCTestExpectation(description: "Stop action is received.") -// stopActionExpection.assertForOverFulfill = true -// var receivedAction: KlaviyoAction? -// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in -// receivedAction = action -// stopActionExpection.fulfill() -// } -// -// passThroughSubject.send(Notification(name: UIApplication.didEnterBackgroundNotification.self)) -// -// wait(for: [stopActionExpection], timeout: 0.1) -// XCTAssertEqual(KlaviyoAction.stop, receivedAction) -// cancellable.cancel() -// } -// -// // MARK: - Did become active -// -// func testAppBecomesActiveStartsReachibility() { -// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) -// let expection = XCTestExpectation(description: "Start reachability is called.") -// var count = 0 -// environment.startReachability = { -// if count == 0 { -// count += 1 -// } else { -// expection.fulfill() -// } -// } -// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } -// -// passThroughSubject.send(Notification(name: UIApplication.didBecomeActiveNotification.self)) -// -// wait(for: [expection], timeout: 0.1) -// cancellable.cancel() -// } -// -// func testAppBecomeActiveGetsStartAction() { -// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) -// let stopActionExpection = XCTestExpectation(description: "Stop action is received.") -// stopActionExpection.assertForOverFulfill = true -// var receivedAction: KlaviyoAction? -// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in -// receivedAction = action -// stopActionExpection.fulfill() -// } -// -// passThroughSubject.send(Notification(name: UIApplication.didBecomeActiveNotification.self)) -// -// wait(for: [stopActionExpection], timeout: 0.1) -// XCTAssertEqual(KlaviyoAction.start, receivedAction) -// cancellable.cancel() -// } -// -// func testStartReachabilityCalledOnSubscription() { -// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) -// let expection = XCTestExpectation(description: "Start reachability is called.") -// expection.assertForOverFulfill = true -// environment.startReachability = { expection.fulfill() } -// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } -// -// wait(for: [expection], timeout: 0.1) -// XCTAssertEqual(1, expection.expectedFulfillmentCount) -// cancellable.cancel() -// } -// -// // MARK: Reachability start failure -// -// func testReachabilityStartFailureIsHandled() { -// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) -// let expection = XCTestExpectation(description: "Start reachability is called.") -// environment.startReachability = { -// expection.fulfill() -// throw KlaviyoAPIError.internalError("foo") -// } -// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } -// -// passThroughSubject.send(Notification(name: UIApplication.didBecomeActiveNotification.self)) -// -// wait(for: [expection], timeout: 0.1) -// cancellable.cancel() -// XCTAssertEqual(1, expection.expectedFulfillmentCount) -// } -// -// // MARK: Reachability notifications -// -// func testReachabilityNotificationStatusHandled() { -// let expection = XCTestExpectation(description: "Reachability status is accessed") -// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: ReachabilityChangedNotification) -// environment.reachabilityStatus = { -// expection.fulfill() -// return .reachableViaWWAN -// } -// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } -// -// passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) -// -// wait(for: [expection], timeout: 0.1) -// cancellable.cancel() -// } -// -// func testReachabilityStatusNilThenNotNil() { -// let expection = XCTestExpectation(description: "Reachability status is accessed") -// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: ReachabilityChangedNotification) -// var count = 0 -// environment.reachabilityStatus = { -// if count == 0 { -// count += 1 -// return nil -// } -// expection.fulfill() -// return .reachableViaWWAN -// } -// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in -// XCTFail() -// } receiveValue: { _ in } -// -// passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) -// passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) -// -// wait(for: [expection], timeout: 0.1) -// cancellable.cancel() -// } -// -// func testReachaibilityNotificationGetsRightAction() { -// environment.reachabilityStatus = { .reachableViaWWAN } -// environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: ReachabilityChangedNotification) -// let reachabilityAction = XCTestExpectation(description: "Reachabilty changed is received.") -// var receivedAction: KlaviyoAction? -// let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in -// receivedAction = action -// reachabilityAction.fulfill() -// } -// -// passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) -// -// wait(for: [reachabilityAction], timeout: 0.1) -// XCTAssertEqual(KlaviyoAction.networkConnectivityChanged(.reachableViaWWAN), receivedAction) -// cancellable.cancel() -// } -// } +// AppLifeCycleEventsTests.swift +// +// +// Created by Noah Durell on 12/15/22. +// + +@testable import KlaviyoSwift +import Combine +import Foundation +import KlaviyoCore +import XCTest + +class AppLifeCycleEventsTests: XCTestCase { + let passThroughSubject = PassthroughSubject() + + func getFilteredNotificaitonPublished(name: Notification.Name) -> (Notification.Name) -> AnyPublisher { + // returns passthrough if it's match other return nothing + { [weak self] notificationName in + if name == notificationName { + return self!.passThroughSubject.eraseToAnyPublisher() + } else { + return Empty().eraseToAnyPublisher() + } + } + } + + @MainActor + override func setUp() { + environment = KlaviyoEnvironment.test() + } + + // MARK: - App Terminate + + @MainActor + func testAppTerminateStopsReachability() { + environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.willTerminateNotification) + let expection = XCTestExpectation(description: "Stop reachability is called.") + environment.stopReachability = { expection.fulfill() } + let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } + + passThroughSubject.send(Notification(name: UIApplication.willTerminateNotification.self)) + + wait(for: [expection], timeout: 0.1) + cancellable.cancel() + } + + func testAppTerminateGetsStopAction() { + environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.willTerminateNotification) + let stopActionExpection = XCTestExpectation(description: "Stop action is received.") + stopActionExpection.assertForOverFulfill = true + var receivedAction: KlaviyoAction? + let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in + receivedAction = action.transformToKlaviyoAction + stopActionExpection.fulfill() + } + + passThroughSubject.send(Notification(name: UIApplication.willTerminateNotification.self)) + + wait(for: [stopActionExpection], timeout: 0.1) + XCTAssertEqual(KlaviyoAction.stop, receivedAction) + cancellable.cancel() + } + + // MARK: - App Background + + func testAppBackgroundStopsReachability() { + environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didEnterBackgroundNotification) + let expection = XCTestExpectation(description: "Stop reachability is called.") + environment.stopReachability = { expection.fulfill() } + let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } + + passThroughSubject.send(Notification(name: UIApplication.didEnterBackgroundNotification.self)) + + wait(for: [expection], timeout: 0.1) + cancellable.cancel() + } + + func testAppBackgroundGetsStopAction() { + environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didEnterBackgroundNotification) + let stopActionExpection = XCTestExpectation(description: "Stop action is received.") + stopActionExpection.assertForOverFulfill = true + var receivedAction: KlaviyoAction? + let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in + receivedAction = action.transformToKlaviyoAction + stopActionExpection.fulfill() + } + + passThroughSubject.send(Notification(name: UIApplication.didEnterBackgroundNotification.self)) + + wait(for: [stopActionExpection], timeout: 0.1) + XCTAssertEqual(KlaviyoAction.stop, receivedAction) + cancellable.cancel() + } + + // MARK: - Did become active + + func testAppBecomesActiveStartsReachibility() { + environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) + let expection = XCTestExpectation(description: "Start reachability is called.") + var count = 0 + environment.startReachability = { + if count == 0 { + count += 1 + } else { + expection.fulfill() + } + } + let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } + + passThroughSubject.send(Notification(name: UIApplication.didBecomeActiveNotification.self)) + + wait(for: [expection], timeout: 0.1) + cancellable.cancel() + } + + func testAppBecomeActiveGetsStartAction() { + environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) + let stopActionExpection = XCTestExpectation(description: "Stop action is received.") + stopActionExpection.assertForOverFulfill = true + var receivedAction: KlaviyoAction? + let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in + receivedAction = action.transformToKlaviyoAction + stopActionExpection.fulfill() + } + + passThroughSubject.send(Notification(name: UIApplication.didBecomeActiveNotification.self)) + + wait(for: [stopActionExpection], timeout: 0.1) + XCTAssertEqual(KlaviyoAction.start, receivedAction) + cancellable.cancel() + } + + func testStartReachabilityCalledOnSubscription() { + environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) + let expection = XCTestExpectation(description: "Start reachability is called.") + expection.assertForOverFulfill = true + environment.startReachability = { expection.fulfill() } + let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } + + wait(for: [expection], timeout: 0.1) + XCTAssertEqual(1, expection.expectedFulfillmentCount) + cancellable.cancel() + } + + // MARK: Reachability start failure + + func testReachabilityStartFailureIsHandled() { + environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: UIApplication.didBecomeActiveNotification) + let expection = XCTestExpectation(description: "Start reachability is called.") + environment.startReachability = { + expection.fulfill() + throw KlaviyoAPIError.internalError("foo") + } + let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } + + passThroughSubject.send(Notification(name: UIApplication.didBecomeActiveNotification.self)) + + wait(for: [expection], timeout: 0.1) + cancellable.cancel() + XCTAssertEqual(1, expection.expectedFulfillmentCount) + } + + // MARK: Reachability notifications + + func testReachabilityNotificationStatusHandled() { + let expection = XCTestExpectation(description: "Reachability status is accessed") + environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: ReachabilityChangedNotification) + environment.reachabilityStatus = { + expection.fulfill() + return .reachableViaWWAN + } + let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in } + + passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) + + wait(for: [expection], timeout: 0.1) + cancellable.cancel() + } + + func testReachabilityStatusNilThenNotNil() { + let expection = XCTestExpectation(description: "Reachability status is accessed") + environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: ReachabilityChangedNotification) + var count = 0 + environment.reachabilityStatus = { + if count == 0 { + count += 1 + return nil + } + expection.fulfill() + return .reachableViaWWAN + } + let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { _ in + XCTFail() + } receiveValue: { _ in } + + passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) + passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) + + wait(for: [expection], timeout: 0.1) + cancellable.cancel() + } + + func testReachaibilityNotificationGetsRightAction() { + environment.reachabilityStatus = { .reachableViaWWAN } + environment.notificationCenterPublisher = getFilteredNotificaitonPublished(name: ReachabilityChangedNotification) + let reachabilityAction = XCTestExpectation(description: "Reachabilty changed is received.") + var receivedAction: KlaviyoAction? + let cancellable = AppLifeCycleEvents().lifeCycleEvents().sink { action in + receivedAction = action.transformToKlaviyoAction + reachabilityAction.fulfill() + } + + passThroughSubject.send(Notification(name: ReachabilityChangedNotification, object: Reachability())) + + wait(for: [reachabilityAction], timeout: 0.1) + XCTAssertEqual(KlaviyoAction.networkConnectivityChanged(.reachableViaWWAN), receivedAction) + cancellable.cancel() + } +} From 76f0b4384401ba4a8aa98c0a870558821dd21d2b Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 14 Aug 2024 16:04:04 -0500 Subject: [PATCH 25/72] fixed archival tests --- .../ArchivalUtilsTests.swift | 237 +++++++++--------- 1 file changed, 118 insertions(+), 119 deletions(-) diff --git a/Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift b/Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift index bbf1f9a3..3a933a29 100644 --- a/Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift +++ b/Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift @@ -1,120 +1,119 @@ -//// -//// ArchivalUtilsTests.swift -//// KlaviyoSwiftTests -//// -//// Created by Noah Durell on 9/26/22. -//// // -// @testable import KlaviyoSwift -// import KlaviyoCore -// import XCTest -// -// class ArchivalUtilsTests: XCTestCase { -// var dataToWrite: Data? -// var wroteToFile = false -// var removedFile = false -// -// override func setUpWithError() throws { -// environment = KlaviyoEnvironment.test() -// environment.fileClient.write = { [weak self] data, _ in -// self?.wroteToFile = true -// self?.dataToWrite = data -// } -// environment.fileClient.removeItem = { [weak self] _ in -// self?.removedFile = true -// } -// } -// -// override func tearDownWithError() throws { -// // Put teardown code here. This method is called after the invocation of each test method in the class. -// wroteToFile = false -// dataToWrite = nil -// removedFile = false -// } -// -// func testArchiveUnarchive() throws { -// archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) -// -// XCTAssert(wroteToFile) -// XCTAssertEqual(ARCHIVED_RETURNED_DATA, dataToWrite) -// } -// -// func testArchiveFails() throws { -// environment.archiverClient.archivedData = { _, _ in throw FakeFileError.fake } -// archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) -// -// XCTAssertFalse(wroteToFile) -// XCTAssertNil(dataToWrite) -// } -// -// func testArchiveWriteFails() throws { -// environment.fileClient.write = { _, _ in throw FakeFileError.fake } -// archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) -// -// XCTAssertFalse(wroteToFile) -// XCTAssertNil(dataToWrite) -// } -// -// func testUnarchive() throws { -// let archiveResult = unarchiveFromFile(fileURL: TEST_URL) -// -// XCTAssertEqual(SAMPLE_DATA, archiveResult) -// XCTAssertTrue(removedFile) -// } -// -// func testUnarchiveInvalidData() throws { -// environment.data = { _ in throw FakeFileError.fake } -// -// let archiveResult = unarchiveFromFile(fileURL: TEST_URL) -// -// XCTAssertNil(archiveResult) -// } -// -// func testUnarchiveUnarchiveFails() throws { -// environment.archiverClient.unarchivedMutableArray = { _ in throw FakeFileError.fake } -// -// let archiveResult = unarchiveFromFile(fileURL: TEST_URL) -// -// XCTAssertNil(archiveResult) -// } -// -// func testUnarchiveUnableToRemoveFile() throws { -// var firstCall = true -// environment.fileClient.fileExists = { _ in -// if firstCall { -// firstCall = false -// return true -// } -// return false -// } -// let archiveResult = unarchiveFromFile(fileURL: TEST_URL) -// -// XCTAssertEqual(SAMPLE_DATA, archiveResult) -// XCTAssertFalse(removedFile) -// } -// -// func testUnarchiveWhereFileDoesNotExist() throws { -// environment.fileClient.fileExists = { _ in false } -// let archiveResult = unarchiveFromFile(fileURL: TEST_URL) -// -// XCTAssertNil(archiveResult) -// XCTAssertFalse(removedFile) -// } -// } -// -// class ArchivalSystemTest: XCTestCase { -// let TEST_URL = filePathForData(apiKey: "foo", data: "people") -// -// override func setUpWithError() throws { -// environment = KlaviyoEnvironment.production -// try? FileManager.default.removeItem(atPath: TEST_URL.path) -// } -// -// /* This will attempt to actually archive and unarchive a queue. */ -// func testArchiveUnarchive() { -// archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) -// let result = unarchiveFromFile(fileURL: filePathForData(apiKey: "foo", data: "people")) -// -// XCTAssertEqual(SAMPLE_DATA, result) -// } -// } +// ArchivalUtilsTests.swift +// KlaviyoSwiftTests +// +// Created by Noah Durell on 9/26/22. +// + +@testable import KlaviyoSwift +import KlaviyoCore +import XCTest + +class ArchivalUtilsTests: XCTestCase { + var dataToWrite: Data? + var wroteToFile = false + var removedFile = false + + override func setUpWithError() throws { + environment = KlaviyoEnvironment.test() + environment.fileClient.write = { [weak self] data, _ in + self?.wroteToFile = true + self?.dataToWrite = data + } + environment.fileClient.removeItem = { [weak self] _ in + self?.removedFile = true + } + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + wroteToFile = false + dataToWrite = nil + removedFile = false + } + + func testArchiveUnarchive() throws { + archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) + + XCTAssert(wroteToFile) + XCTAssertEqual(ARCHIVED_RETURNED_DATA, dataToWrite) + } + + func testArchiveFails() throws { + environment.archiverClient.archivedData = { _, _ in throw FakeFileError.fake } + archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) + + XCTAssertFalse(wroteToFile) + XCTAssertNil(dataToWrite) + } + + func testArchiveWriteFails() throws { + environment.fileClient.write = { _, _ in throw FakeFileError.fake } + archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) + + XCTAssertFalse(wroteToFile) + XCTAssertNil(dataToWrite) + } + + func testUnarchive() throws { + let archiveResult = unarchiveFromFile(fileURL: TEST_URL) + + XCTAssertEqual(SAMPLE_DATA, archiveResult) + XCTAssertTrue(removedFile) + } + + func testUnarchiveInvalidData() throws { + environment.dataFromUrl = { _ in throw FakeFileError.fake } + + let archiveResult = unarchiveFromFile(fileURL: TEST_URL) + + XCTAssertNil(archiveResult) + } + + func testUnarchiveUnarchiveFails() throws { + environment.archiverClient.unarchivedMutableArray = { _ in throw FakeFileError.fake } + + let archiveResult = unarchiveFromFile(fileURL: TEST_URL) + + XCTAssertNil(archiveResult) + } + + func testUnarchiveUnableToRemoveFile() throws { + var firstCall = true + environment.fileClient.fileExists = { _ in + if firstCall { + firstCall = false + return true + } + return false + } + let archiveResult = unarchiveFromFile(fileURL: TEST_URL) + + XCTAssertEqual(SAMPLE_DATA, archiveResult) + XCTAssertFalse(removedFile) + } + + func testUnarchiveWhereFileDoesNotExist() throws { + environment.fileClient.fileExists = { _ in false } + let archiveResult = unarchiveFromFile(fileURL: TEST_URL) + + XCTAssertNil(archiveResult) + XCTAssertFalse(removedFile) + } +} + +class ArchivalSystemTest: XCTestCase { + let TEST_URL = filePathForData(apiKey: "foo", data: "people") + + override func setUpWithError() throws { + try? FileManager.default.removeItem(atPath: TEST_URL.path) + } + + /* This will attempt to actually archive and unarchive a queue. */ + func testArchiveUnarchive() { + archiveQueue(queue: SAMPLE_DATA, to: TEST_URL) + let result = unarchiveFromFile(fileURL: filePathForData(apiKey: "foo", data: "people")) + + XCTAssertEqual(SAMPLE_DATA, result) + } +} From f64fbf511583ece7ed7b95060a9e7c96a15e5d9d Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 14 Aug 2024 17:11:47 -0500 Subject: [PATCH 26/72] fixed encodable tests --- .../Models/APIModels/CreateEventPayload.swift | 2 +- Tests/KlaviyoSwiftTests/EncodableTests.swift | 156 +++++++++--------- Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift | 7 +- Tests/KlaviyoSwiftTests/TestData.swift | 2 +- .../testEventPayloadWithoutMetadata.1.json | 27 +++ .../EncodableTests/testKlaviyoRequest.1.json | 45 +++++ .../EncodableTests/testKlaviyoState.1.json | 71 ++++++++ .../EncodableTests/testProfilePayload.1.json | 31 ++++ .../EncodableTests/testTokenPayload.1.json | 37 +++++ .../testUnregisterTokenPayload.1.json | 21 +++ 10 files changed, 316 insertions(+), 83 deletions(-) create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testProfilePayload.1.json create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testTokenPayload.1.json create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json diff --git a/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift b/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift index 375b0c08..dd3c7fe3 100644 --- a/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift +++ b/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift @@ -59,7 +59,7 @@ public struct CreateEventPayload: Equatable, Codable { metric = Metric(name: name) self.properties = AnyCodable(properties) self.value = value - self.time = time ?? Date() + self.time = time ?? environment.date() self.uniqueId = uniqueId ?? environment.uuid().uuidString profile = Profile( data: ProfilePayload( diff --git a/Tests/KlaviyoSwiftTests/EncodableTests.swift b/Tests/KlaviyoSwiftTests/EncodableTests.swift index 816cbd83..fe30d828 100644 --- a/Tests/KlaviyoSwiftTests/EncodableTests.swift +++ b/Tests/KlaviyoSwiftTests/EncodableTests.swift @@ -1,83 +1,81 @@ -//// -//// EncodableTests.swift -//// -//// -//// Created by Noah Durell on 11/14/22. -//// // -// @testable import KlaviyoSwift -// import KlaviyoCore -// import SnapshotTesting -// import XCTest +// EncodableTests.swift // -// final class EncodableTests: XCTestCase { -// let testEncoder = KlaviyoEnvironment.encoder // -// override func setUpWithError() throws { -// environment = KlaviyoEnvironment.test() -// testEncoder.outputFormatting = .prettyPrinted.union(.sortedKeys) -// } +// Created by Noah Durell on 11/14/22. // -// func testProfilePayload() throws { -// let profile = Profile.test -// let data = CreateProfilePayload.Profile(profile: profile, anonymousId: "foo") -// let payload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateProfilePayload(data: data) -// assertSnapshot(matching: payload, as: .json(KlaviyoEnvironment.encoder)) -// } -// -// func testEventPayloadWithoutMetadata() throws { -// let event = Event.test -// let createEventPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateEventPayload(data: .init(event: event, anonymousId: "anon-id")) -// assertSnapshot(matching: createEventPayload, as: .json(KlaviyoEnvironment.encoder)) -// } -// -// func testEventPayloadWithMetadata() throws { -// let event = Event.test -// var createEventPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.CreateEventPayload(data: .init(event: event, anonymousId: "anon-id")) -// createEventPayload.appendMetadataToProperties() -// assertSnapshot(matching: createEventPayload, as: .json(KlaviyoEnvironment.encoder)) -// } -// -// func testTokenPayload() throws { -// let tokenPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload( -// pushToken: "foo", -// enablement: "AUTHORIZED", -// background: "AVAILABLE", -// profile: .init(email: "foo", phoneNumber: "foo"), -// anonymousId: "foo") -// assertSnapshot(matching: tokenPayload, as: .json(KlaviyoEnvironment.encoder)) -// } -// -// func testUnregisterTokenPayload() throws { -// let tokenPayload = UnregisterPushTokenPayload( -// pushToken: "foo", -// profile: .init(email: "foo", phoneNumber: "foo"), -// anonymousId: "foo") -// assertSnapshot(matching: tokenPayload, as: .json) -// } -// -// func testKlaviyoState() throws { -// let tokenPayload = PushTokenPayload( -// pushToken: "foo", -// enablement: "AUTHORIZED", -// background: "AVAILABLE", -// profile: .init(email: "foo", phoneNumber: "foo"), -// anonymousId: "foo") -// let request = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) -// let klaviyoState = KlaviyoState(email: "foo", anonymousId: "foo", -// phoneNumber: "foo", pushTokenData: .init(pushToken: "foo", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.analytics.appContextInfo())), -// queue: [request], requestsInFlight: [request]) -// assertSnapshot(matching: klaviyoState, as: .json) -// } -// -// func testKlaviyoRequest() throws { -// let tokenPayload = KlaviyoAPI.KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload( -// pushToken: "foo", -// enablement: "AUTHORIZED", -// background: "AVAILABLE", -// profile: .init(email: "foo", phoneNumber: "foo"), -// anonymousId: "foo") -// let request = KlaviyoAPI.KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) -// assertSnapshot(matching: request, as: .json) -// } -// } + +@testable import KlaviyoSwift +import KlaviyoCore +import SnapshotTesting +import XCTest + +final class EncodableTests: XCTestCase { + let testEncoder = KlaviyoEnvironment.encoder + + override func setUpWithError() throws { + environment = KlaviyoEnvironment.test() + testEncoder.outputFormatting = .prettyPrinted.union(.sortedKeys) + } + + func testProfilePayload() throws { + let profile = Profile.test + let payload = CreateProfilePayload(data: profile.toAPIModel(anonymousId: "foo")) + assertSnapshot(matching: payload, as: .json(KlaviyoEnvironment.encoder)) + } + + func testEventPayloadWithoutMetadata() throws { + let event = Event.test + let createEventPayload = CreateEventPayload(data: CreateEventPayload.Event(name: event.metric.name.value, anonymousId: "anon-id")) + assertSnapshot(matching: createEventPayload, as: .json(KlaviyoEnvironment.encoder)) + } + + func testTokenPayload() throws { + let tokenPayload = PushTokenPayload( + pushToken: "foo", + enablement: "AUTHORIZED", + background: "AVAILABLE", + profile: ProfilePayload(email: "foo", phoneNumber: "foo", anonymousId: "foo")) + assertSnapshot(matching: tokenPayload, as: .json(KlaviyoEnvironment.encoder)) + } + + func testUnregisterTokenPayload() throws { + let tokenPayload = UnregisterPushTokenPayload( + pushToken: "foo", + email: "foo", + phoneNumber: "foo", + anonymousId: "foo") + assertSnapshot(matching: tokenPayload, as: .json) + } + + func testKlaviyoState() throws { + let tokenPayload = PushTokenPayload( + pushToken: "foo", + enablement: "AUTHORIZED", + background: "AVAILABLE", + profile: ProfilePayload(email: "foo", phoneNumber: "foo", anonymousId: "foo")) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) + let klaviyoState = KlaviyoState( + email: "foo", + anonymousId: "foo", + phoneNumber: "foo", + pushTokenData: .init( + pushToken: "foo", + pushEnablement: .authorized, + pushBackground: .available, + deviceData: .init(context: environment.appContextInfo())), + queue: [request], + requestsInFlight: [request]) + assertSnapshot(matching: klaviyoState, as: .json) + } + + func testKlaviyoRequest() throws { + let tokenPayload = PushTokenPayload( + pushToken: "foo", + enablement: "AUTHORIZED", + background: "AVAILABLE", + profile: ProfilePayload(email: "foo", phoneNumber: "foo", anonymousId: "foo")) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) + assertSnapshot(matching: request, as: .json) + } +} diff --git a/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift b/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift index 06b6ff37..1e08eb75 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift @@ -21,7 +21,10 @@ // func testInvalidURL() async throws { // environment.apiURL = "" // -// await sendAndAssert(with: .init(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .test, anonymousId: "foo"))))) { result in +// await sendAndAssert(with: KlaviyoRequest( +// apiKey: "foo", +// endpoint: .createProfile(.init(data: .init(profile: .test, anonymousId: "foo")))) +// ) { result in // switch result { // case let .failure(error): // assertSnapshot(matching: error, as: .description) @@ -66,7 +69,7 @@ // environment.networkSession = { NetworkSession.test(data: { _ in // (Data(), .non200Response) // }) } -// let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) +//// let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) // await sendAndAssert(with: request) { result in // // switch result { diff --git a/Tests/KlaviyoSwiftTests/TestData.swift b/Tests/KlaviyoSwiftTests/TestData.swift index 1e68203b..90e54853 100644 --- a/Tests/KlaviyoSwiftTests/TestData.swift +++ b/Tests/KlaviyoSwiftTests/TestData.swift @@ -114,7 +114,7 @@ extension Event { "Device Manufacturer": "Orange", "Device Model": "jPhone 1,1" ] as [String: Any] - static let test = Self(name: .CustomEvent("blob"), properties: SAMPLE_PROPERTIES) + static let test = Self(name: .CustomEvent("blob"), properties: SAMPLE_PROPERTIES, time: KlaviyoEnvironment.test().date()) } extension Event.Metric { diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json new file mode 100644 index 00000000..1de491fa --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json @@ -0,0 +1,27 @@ +{ + "data" : { + "attributes" : { + "metric" : { + "data" : { + "attributes" : { + "name" : "blob" + }, + "type" : "metric" + } + }, + "profile" : { + "data" : { + "attributes" : { + "anonymous_id" : "anon-id", + "properties" : null + }, + "type" : "profile" + } + }, + "properties" : null, + "time" : "2009-02-13T23:31:30Z", + "unique_id" : "00000000-0000-0000-0000-000000000001" + }, + "type" : "event" + } +} \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json new file mode 100644 index 00000000..dfac3ffa --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json @@ -0,0 +1,45 @@ +{ + "apiKey" : "foo", + "endpoint" : { + "registerPushToken" : { + "_0" : { + "data" : { + "attributes" : { + "background" : "AVAILABLE", + "device_metadata" : { + "app_build" : "1", + "app_id" : "com.klaviyo.fooapp", + "app_name" : "FooApp", + "app_version" : "1.2.3", + "device_id" : "fe-fi-fo-fum", + "device_model" : "jPhone 1,1", + "environment" : "debug", + "klaviyo_sdk" : "swift", + "manufacturer" : "Orange", + "os_name" : "iOS", + "os_version" : "1.1.1", + "sdk_version" : "3.2.0" + }, + "enablement_status" : "AUTHORIZED", + "platform" : "ios", + "profile" : { + "data" : { + "attributes" : { + "anonymous_id" : "foo", + "email" : "foo", + "phone_number" : "foo", + "properties" : null + }, + "type" : "profile" + } + }, + "token" : "foo", + "vendor" : "APNs" + }, + "type" : "push-token" + } + } + } + }, + "uuid" : "00000000-0000-0000-0000-000000000001" +} \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json new file mode 100644 index 00000000..ef713322 --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json @@ -0,0 +1,71 @@ +{ + "anonymousId" : "foo", + "email" : "foo", + "phoneNumber" : "foo", + "pushTokenData" : { + "deviceData" : { + "app_build" : "1", + "app_id" : "com.klaviyo.fooapp", + "app_name" : "FooApp", + "app_version" : "1.2.3", + "device_id" : "fe-fi-fo-fum", + "device_model" : "jPhone 1,1", + "environment" : "debug", + "klaviyo_sdk" : "swift", + "manufacturer" : "Orange", + "os_name" : "iOS", + "os_version" : "1.1.1", + "sdk_version" : "3.2.0" + }, + "pushBackground" : "AVAILABLE", + "pushEnablement" : "AUTHORIZED", + "pushToken" : "foo" + }, + "queue" : [ + { + "apiKey" : "foo", + "endpoint" : { + "registerPushToken" : { + "_0" : { + "data" : { + "attributes" : { + "background" : "AVAILABLE", + "device_metadata" : { + "app_build" : "1", + "app_id" : "com.klaviyo.fooapp", + "app_name" : "FooApp", + "app_version" : "1.2.3", + "device_id" : "fe-fi-fo-fum", + "device_model" : "jPhone 1,1", + "environment" : "debug", + "klaviyo_sdk" : "swift", + "manufacturer" : "Orange", + "os_name" : "iOS", + "os_version" : "1.1.1", + "sdk_version" : "3.2.0" + }, + "enablement_status" : "AUTHORIZED", + "platform" : "ios", + "profile" : { + "data" : { + "attributes" : { + "anonymous_id" : "foo", + "email" : "foo", + "phone_number" : "foo", + "properties" : null + }, + "type" : "profile" + } + }, + "token" : "foo", + "vendor" : "APNs" + }, + "type" : "push-token" + } + } + } + }, + "uuid" : "00000000-0000-0000-0000-000000000001" + } + ] +} \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testProfilePayload.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testProfilePayload.1.json new file mode 100644 index 00000000..8cf9d3c9 --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testProfilePayload.1.json @@ -0,0 +1,31 @@ +{ + "data" : { + "attributes" : { + "anonymous_id" : "foo", + "first_name" : "Blob", + "image" : "foo", + "last_name" : "Junior", + "location" : { + "address1" : "blob", + "address2" : "blob", + "city" : "blob city", + "country" : "Blobland", + "latitude" : 1, + "longitude" : 1, + "region" : "BL", + "timezone" : "EST", + "zip" : "0BLOB" + }, + "organization" : "Blobco", + "properties" : { + "blob" : "blob", + "hello" : { + "sub" : "dict" + }, + "stuff" : 2 + }, + "title" : "Jelly" + }, + "type" : "profile" + } +} \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testTokenPayload.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testTokenPayload.1.json new file mode 100644 index 00000000..729d22ad --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testTokenPayload.1.json @@ -0,0 +1,37 @@ +{ + "data" : { + "attributes" : { + "background" : "AVAILABLE", + "device_metadata" : { + "app_build" : "1", + "app_id" : "com.klaviyo.fooapp", + "app_name" : "FooApp", + "app_version" : "1.2.3", + "device_id" : "fe-fi-fo-fum", + "device_model" : "jPhone 1,1", + "environment" : "debug", + "klaviyo_sdk" : "swift", + "manufacturer" : "Orange", + "os_name" : "iOS", + "os_version" : "1.1.1", + "sdk_version" : "3.2.0" + }, + "enablement_status" : "AUTHORIZED", + "platform" : "ios", + "profile" : { + "data" : { + "attributes" : { + "anonymous_id" : "foo", + "email" : "foo", + "phone_number" : "foo", + "properties" : null + }, + "type" : "profile" + } + }, + "token" : "foo", + "vendor" : "APNs" + }, + "type" : "push-token" + } +} \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json new file mode 100644 index 00000000..63111f09 --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json @@ -0,0 +1,21 @@ +{ + "data" : { + "attributes" : { + "platform" : "ios", + "profile" : { + "data" : { + "attributes" : { + "anonymous_id" : "foo", + "email" : "foo", + "phone_number" : "foo", + "properties" : null + }, + "type" : "profile" + } + }, + "token" : "foo", + "vendor" : "APNs" + }, + "type" : "push-token-unregister" + } +} \ No newline at end of file From e3afa15ca90736048d13be9447e10b98e66b2d63 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 14 Aug 2024 17:20:50 -0500 Subject: [PATCH 27/72] fixed klaviyo api tests --- Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift | 276 +++++++++--------- .../KlaviyoAPITests/testEncodingError.1.txt | 22 ++ .../testInvalidStatusCode.1.txt | 4 + .../KlaviyoAPITests/testInvalidURL.1.txt | 1 + .../KlaviyoAPITests/testNetworkError.1.txt | 2 + .../testSuccessfulResponseWithEvent.1.txt | 20 ++ .../testSuccessfulResponseWithEvent.2.txt | 1 + .../testSuccessfulResponseWithProfile.1.txt | 20 ++ .../testSuccessfulResponseWithProfile.2.txt | 1 + ...testSuccessfulResponseWithStoreToken.1.txt | 20 ++ ...testSuccessfulResponseWithStoreToken.2.txt | 1 + 11 files changed, 230 insertions(+), 138 deletions(-) create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt diff --git a/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift b/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift index 1e08eb75..dcf4cfc1 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift @@ -1,139 +1,139 @@ -//// -//// KlaviyoAPITests.swift -//// -//// -//// Created by Noah Durell on 11/16/22. -//// // -// @testable import KlaviyoSwift -// import KlaviyoCore -// import SnapshotTesting -// import XCTest -// -// @MainActor -// final class KlaviyoAPITests: XCTestCase { -// override func setUpWithError() throws { -// // Put setup code here. This method is called before the invocation of each test method in the class. -// -// environment = KlaviyoEnvironment.test() -// } -// -// func testInvalidURL() async throws { -// environment.apiURL = "" -// -// await sendAndAssert(with: KlaviyoRequest( -// apiKey: "foo", -// endpoint: .createProfile(.init(data: .init(profile: .test, anonymousId: "foo")))) -// ) { result in -// switch result { -// case let .failure(error): -// assertSnapshot(matching: error, as: .description) -// default: -// XCTFail("Expected url failure") -// } -// } -// } -// -// func testEncodingError() async throws { -// environment.encodeJSON = { _ in throw EncodingError.invalidValue("foo", .init(codingPath: [], debugDescription: "invalid")) -// } -// let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) -// await sendAndAssert(with: request) { result in -// -// switch result { -// case let .failure(error): -// assertSnapshot(matching: error, as: .dump) -// default: -// XCTFail("Expected encoding error.") -// } -// } -// } -// -// func testNetworkError() async throws { -// environment.networkSession = { NetworkSession.test(data: { _ in -// throw NSError(domain: "network error", code: 0) -// }) } -// let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) -// await sendAndAssert(with: request) { result in -// -// switch result { -// case let .failure(error): -// assertSnapshot(matching: error, as: .dump) -// default: -// XCTFail("Expected failure here.") -// } -// } -// } -// -// func testInvalidStatusCode() async throws { -// environment.networkSession = { NetworkSession.test(data: { _ in -// (Data(), .non200Response) -// }) } -//// let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) -// await sendAndAssert(with: request) { result in -// -// switch result { -// case let .failure(error): -// assertSnapshot(matching: error, as: .dump) -// default: -// XCTFail("Expected failure here.") -// } -// } -// } -// -// func testSuccessfulResponseWithProfile() async throws { -// environment.networkSession = { NetworkSession.test(data: { request in -// assertSnapshot(matching: request, as: .dump) -// return (Data(), .validResponse) -// }) } -// let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(.init(data: .init(profile: .init(), anonymousId: "foo")))) -// await sendAndAssert(with: request) { result in -// -// switch result { -// case let .success(data): -// assertSnapshot(matching: data, as: .dump) -// default: -// XCTFail("Expected failure here.") -// } -// } -// } -// -// func testSuccessfulResponseWithEvent() async throws { -// environment.networkSession = { NetworkSession.test(data: { request in -// assertSnapshot(matching: request, as: .dump) -// return (Data(), .validResponse) -// }) } -// let request = KlaviyoRequest(apiKey: "foo", endpoint: .createEvent(.init(data: .init(event: .test)))) -// await sendAndAssert(with: request) { result in -// switch result { -// case let .success(data): -// assertSnapshot(matching: data, as: .dump) -// default: -// XCTFail("Expected failure here.") -// } -// } -// } -// -// func testSuccessfulResponseWithStoreToken() async throws { -// environment.networkSession = { NetworkSession.test(data: { request in -// assertSnapshot(matching: request, as: .dump) -// return (Data(), .validResponse) -// }) } -// let request = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(.test)) -// await sendAndAssert(with: request) { result in -// -// switch result { -// case let .success(data): -// assertSnapshot(matching: data, as: .dump) -// default: -// XCTFail("Expected failure here.") -// } -// } -// } -// -// func sendAndAssert(with request: KlaviyoRequest, -// assertion: (Result) -> Void) async { -// let result = await KlaviyoAPI().send(request, 0) -// assertion(result) -// } -// } +// KlaviyoAPITests.swift +// +// +// Created by Noah Durell on 11/16/22. +// + +@testable import KlaviyoSwift +import KlaviyoCore +import SnapshotTesting +import XCTest + +@MainActor +final class KlaviyoAPITests: XCTestCase { + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + environment = KlaviyoEnvironment.test() + } + + func testInvalidURL() async throws { + environment.apiURL = "" + + await sendAndAssert(with: KlaviyoRequest( + apiKey: "foo", + endpoint: .createProfile(CreateProfilePayload(data: Profile.test.toAPIModel(anonymousId: "foo")))) + ) { result in + switch result { + case let .failure(error): + assertSnapshot(matching: error, as: .description) + default: + XCTFail("Expected url failure") + } + } + } + + func testEncodingError() async throws { + environment.encodeJSON = { _ in throw EncodingError.invalidValue("foo", .init(codingPath: [], debugDescription: "invalid")) + } + let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(CreateProfilePayload(data: Profile().toAPIModel(anonymousId: "foo")))) + await sendAndAssert(with: request) { result in + + switch result { + case let .failure(error): + assertSnapshot(matching: error, as: .dump) + default: + XCTFail("Expected encoding error.") + } + } + } + + func testNetworkError() async throws { + environment.networkSession = { NetworkSession.test(data: { _ in + throw NSError(domain: "network error", code: 0) + }) } + let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(CreateProfilePayload(data: Profile().toAPIModel(anonymousId: "foo")))) + await sendAndAssert(with: request) { result in + + switch result { + case let .failure(error): + assertSnapshot(matching: error, as: .dump) + default: + XCTFail("Expected failure here.") + } + } + } + + func testInvalidStatusCode() async throws { + environment.networkSession = { NetworkSession.test(data: { _ in + (Data(), .non200Response) + }) } + let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(CreateProfilePayload(data: Profile().toAPIModel(anonymousId: "foo")))) + await sendAndAssert(with: request) { result in + + switch result { + case let .failure(error): + assertSnapshot(matching: error, as: .dump) + default: + XCTFail("Expected failure here.") + } + } + } + + func testSuccessfulResponseWithProfile() async throws { + environment.networkSession = { NetworkSession.test(data: { request in + assertSnapshot(matching: request, as: .dump) + return (Data(), .validResponse) + }) } + let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(CreateProfilePayload(data: Profile().toAPIModel(anonymousId: "foo")))) + await sendAndAssert(with: request) { result in + + switch result { + case let .success(data): + assertSnapshot(matching: data, as: .dump) + default: + XCTFail("Expected failure here.") + } + } + } + + func testSuccessfulResponseWithEvent() async throws { + environment.networkSession = { NetworkSession.test(data: { request in + assertSnapshot(matching: request, as: .dump) + return (Data(), .validResponse) + }) } + let request = KlaviyoRequest(apiKey: "foo", endpoint: .createEvent(CreateEventPayload(data: CreateEventPayload.Event(name: "test")))) + await sendAndAssert(with: request) { result in + switch result { + case let .success(data): + assertSnapshot(matching: data, as: .dump) + default: + XCTFail("Expected failure here.") + } + } + } + + func testSuccessfulResponseWithStoreToken() async throws { + environment.networkSession = { NetworkSession.test(data: { request in + assertSnapshot(matching: request, as: .dump) + return (Data(), .validResponse) + }) } + let request = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(.test)) + await sendAndAssert(with: request) { result in + + switch result { + case let .success(data): + assertSnapshot(matching: data, as: .dump) + default: + XCTFail("Expected failure here.") + } + } + } + + func sendAndAssert(with request: KlaviyoRequest, + assertion: (Result) -> Void) async { + let result = await KlaviyoAPI().send(request, 0) + assertion(result) + } +} diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt new file mode 100644 index 00000000..d415f44e --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt @@ -0,0 +1,22 @@ +▿ KlaviyoAPIError + ▿ internalRequestError: KlaviyoAPIError + ▿ dataEncodingError: KlaviyoRequest + - apiKey: "foo" + ▿ endpoint: KlaviyoEndpoint + ▿ createProfile: CreateProfilePayload + ▿ data: ProfilePayload + ▿ attributes: Attributes + - anonymousId: "foo" + - email: Optional.none + - externalId: Optional.none + - firstName: Optional.none + - image: Optional.none + - lastName: Optional.none + - location: Optional.none + - organization: Optional.none + - phoneNumber: Optional.none + ▿ properties: [:] + - value: 0 key/value pairs + - title: Optional.none + - type: "profile" + - uuid: "00000000-0000-0000-0000-000000000001" diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt new file mode 100644 index 00000000..ccb902f1 --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt @@ -0,0 +1,4 @@ +▿ KlaviyoAPIError + ▿ httpError: (2 elements) + - .0: 500 + - .1: 0 bytes diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt new file mode 100644 index 00000000..675c1934 --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt @@ -0,0 +1 @@ +internalRequestError(KlaviyoCore.KlaviyoAPIError.internalError("Invalid url string. API URL: ")) \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt new file mode 100644 index 00000000..8c73f9ae --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt @@ -0,0 +1,2 @@ +▿ KlaviyoAPIError + - networkError: Error Domain=network error Code=0 "(null)" diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt new file mode 100644 index 00000000..fafe69a5 --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt @@ -0,0 +1,20 @@ +▿ dead_beef/client/events/?company_id=foo + ▿ url: Optional + - some: dead_beef/client/events/?company_id=foo + - cachePolicy: 0 + - timeoutInterval: 60.0 + - mainDocumentURL: Optional.none + - networkServiceType: NSURLRequestNetworkServiceType.NSURLRequestNetworkServiceType + - allowsCellularAccess: true + ▿ httpMethod: Optional + - some: "POST" + ▿ allHTTPHeaderFields: Optional> + ▿ some: 1 key/value pair + ▿ (2 elements) + - key: "X-Klaviyo-Attempt-Count" + - value: "0/50" + ▿ httpBody: Optional + - some: 0 bytes + - httpBodyStream: Optional.none + - httpShouldHandleCookies: true + - httpShouldUsePipelining: false diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt new file mode 100644 index 00000000..eff1843b --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt @@ -0,0 +1 @@ +- 0 bytes diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt new file mode 100644 index 00000000..9563cd57 --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt @@ -0,0 +1,20 @@ +▿ dead_beef/client/profiles/?company_id=foo + ▿ url: Optional + - some: dead_beef/client/profiles/?company_id=foo + - cachePolicy: 0 + - timeoutInterval: 60.0 + - mainDocumentURL: Optional.none + - networkServiceType: NSURLRequestNetworkServiceType.NSURLRequestNetworkServiceType + - allowsCellularAccess: true + ▿ httpMethod: Optional + - some: "POST" + ▿ allHTTPHeaderFields: Optional> + ▿ some: 1 key/value pair + ▿ (2 elements) + - key: "X-Klaviyo-Attempt-Count" + - value: "0/50" + ▿ httpBody: Optional + - some: 0 bytes + - httpBodyStream: Optional.none + - httpShouldHandleCookies: true + - httpShouldUsePipelining: false diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt new file mode 100644 index 00000000..eff1843b --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt @@ -0,0 +1 @@ +- 0 bytes diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt new file mode 100644 index 00000000..b80d173f --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt @@ -0,0 +1,20 @@ +▿ dead_beef/client/push-tokens/?company_id=foo + ▿ url: Optional + - some: dead_beef/client/push-tokens/?company_id=foo + - cachePolicy: 0 + - timeoutInterval: 60.0 + - mainDocumentURL: Optional.none + - networkServiceType: NSURLRequestNetworkServiceType.NSURLRequestNetworkServiceType + - allowsCellularAccess: true + ▿ httpMethod: Optional + - some: "POST" + ▿ allHTTPHeaderFields: Optional> + ▿ some: 1 key/value pair + ▿ (2 elements) + - key: "X-Klaviyo-Attempt-Count" + - value: "0/50" + ▿ httpBody: Optional + - some: 0 bytes + - httpBodyStream: Optional.none + - httpShouldHandleCookies: true + - httpShouldUsePipelining: false diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt new file mode 100644 index 00000000..eff1843b --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt @@ -0,0 +1 @@ +- 0 bytes From 8585058578b7ca60e6305005a1da62e043a11039 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 14 Aug 2024 17:22:26 -0500 Subject: [PATCH 28/72] fixed klaviyo sdk tests --- Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift | 348 +++++++++--------- 1 file changed, 173 insertions(+), 175 deletions(-) diff --git a/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift b/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift index 5da2dff6..0cb5553f 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift @@ -1,180 +1,178 @@ -//// -//// File.swift -//// -//// -//// Created by Noah Durell on 2/21/23. -//// // -// @testable import KlaviyoSwift -// import Foundation -// import KlaviyoCore -// import XCTest +// File.swift // +// +// Created by Noah Durell on 2/21/23. +// + +@testable import KlaviyoSwift +import Foundation +import KlaviyoCore +import XCTest // MARK: - KlaviyoSDKTests -// -// class KlaviyoSDKTests: XCTestCase { -// // MARK: Properties -// -// var klaviyo = KlaviyoSDK() -// -// // MARK: Setup -// -// override func setUpWithError() throws { -// klaviyo = KlaviyoSDK() -// environment = KlaviyoEnvironment.test() -// } -// -// override func tearDown() async throws { -// environment = KlaviyoEnvironment.test() -// } -// -// func setupActionAssertion(expectedAction: KlaviyoAction, file: StaticString = #filePath, line: UInt = #line) -> XCTestExpectation { -// let expectation = XCTestExpectation(description: "wait for action \(expectedAction)") -// environment.analytics.send = { action in -// XCTAssertEqual(action, expectedAction, file: file, line: line) -// expectation.fulfill() -// return nil -// } -// return expectation -// } -// -// // MARK: Tests -// -// func testKlaviyoSDKInit() { -// XCTAssertNotNil(klaviyo) -// } -// -// // MARK: test initialize -// -// func testInitializeSDk() throws { -// let expectation = setupActionAssertion(expectedAction: .initialize(TEST_API_KEY)) -// -// klaviyo.initialize(with: TEST_API_KEY) -// -// wait(for: [expectation], timeout: 1.0) -// } -// -// // MARK: test set proprety -// -// func testSetFirstName() throws { -// let expectation = setupActionAssertion(expectedAction: .setProfileProperty(.firstName, "test")) -// -// klaviyo.set(profileAttribute: .firstName, value: "test") -// -// wait(for: [expectation], timeout: 1.0) -// } -// -// // MARK: test set profile -// -// func testSetProfile() throws { -// let profile = Profile( -// email: "john.smith@example.com", -// phoneNumber: "+15555551212", -// firstName: "John", -// lastName: "Smith") -// let expectation = setupActionAssertion(expectedAction: .enqueueProfile(profile)) -// -// klaviyo.set(profile: profile) -// -// wait(for: [expectation], timeout: 1.0) -// } -// -// // MARK: test create event -// -// func testCreateEvent() throws { -// let event = Event(name: .OpenedAppMetric) -// let expectation = setupActionAssertion(expectedAction: .enqueueEvent(event)) -// -// klaviyo.create(event: event) -// -// wait(for: [expectation], timeout: 1.0) -// } -// -// func testCreateEventFromDocumentation() throws { -// let event = Event(name: .AddedToCartMetric, properties: [ -// "Total Price": 10.99, -// "Items Purchased": ["Hot Dog", "Fries", "Shake"] -// ], value: 10.99) -// let expectation = setupActionAssertion(expectedAction: .enqueueEvent(event)) -// -// klaviyo.create(event: event) -// -// wait(for: [expectation], timeout: 1.0) -// } -// -// // MARK: test set push token -// -// func testSetPushToken() throws { -// let tokenData = "mytoken".data(using: .utf8)! -// let strToken = tokenData.reduce("") { $0 + String(format: "%02.2hhx", $1) } -// let expectation = setupActionAssertion(expectedAction: .setPushToken(strToken, .authorized)) -// -// klaviyo.set(pushToken: tokenData) -// -// wait(for: [expectation], timeout: 1.0) -// } -// -// // MARK: test set external id -// -// func testSetExternalId() throws { -// let expectation = setupActionAssertion(expectedAction: .setExternalId("foo")) -// -// _ = klaviyo.set(externalId: "foo") -// -// wait(for: [expectation], timeout: 1.0) -// } -// -// // MARK: test handle push notification -// -// func testHandlePushNotification() throws { -// let callback = XCTestExpectation(description: "callback is made") -// let push_body = ["body": [ -// "_k": [ -// "foo": "bar" -// ] -// ]] -// let expectation = setupActionAssertion(expectedAction: .enqueueEvent(.init(name: .OpenedPush, properties: push_body))) -// let response = try UNNotificationResponse.with(userInfo: push_body) -// let handled = klaviyo.handle(notificationResponse: response) { -// callback.fulfill() -// } -// -// wait(for: [expectation, callback], timeout: 1.0) -// XCTAssertTrue(handled) -// } -// -// // MARK: test unhandle push notification -// -// func testUnhandlePushNotification() throws { -// let callback = XCTestExpectation(description: "callback is not made") -// callback.isInverted = true -// let data: [AnyHashable: Any] = [ -// "data": [ -// "type": "OPEN_ARTICLE", -// "articleId": "1", -// "articleType": "Fiction", -// "articleTag": "1" -// ] -// ] -// let response = try UNNotificationResponse.with(userInfo: data) -// let handled = klaviyo.handle(notificationResponse: response) { -// callback.fulfill() -// } -// -// wait(for: [callback], timeout: 1.0) -// XCTAssertFalse(handled) -// } -// -// // MARK: test property getters -// -// func testPropertyGetters() throws { -// klaviyoSwiftEnvironment.state = { KlaviyoState(email: "foo@foo.com", phoneNumber: "555BLOB", externalId: "my_test_id", pushTokenData: .init(pushToken: "blobtoken", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.appContextInfo())), queue: []) } -// let klaviyo = KlaviyoSDK() -// XCTAssertEqual("foo@foo.com", klaviyo.email) -// XCTAssertEqual("555BLOB", klaviyo.phoneNumber) -// XCTAssertEqual("blobtoken", klaviyo.pushToken) -// XCTAssertEqual("my_test_id", klaviyo.externalId) -// } -// } +class KlaviyoSDKTests: XCTestCase { + // MARK: Properties + + var klaviyo = KlaviyoSDK() + + // MARK: Setup + + override func setUpWithError() throws { + klaviyo = KlaviyoSDK() + environment = KlaviyoEnvironment.test() + } + + override func tearDown() async throws { + environment = KlaviyoEnvironment.test() + } + + func setupActionAssertion(expectedAction: KlaviyoAction, file: StaticString = #filePath, line: UInt = #line) -> XCTestExpectation { + let expectation = XCTestExpectation(description: "wait for action \(expectedAction)") + klaviyoSwiftEnvironment.send = { action in + XCTAssertEqual(action, expectedAction, file: file, line: line) + expectation.fulfill() + return nil + } + return expectation + } + + // MARK: Tests + + func testKlaviyoSDKInit() { + XCTAssertNotNil(klaviyo) + } + + // MARK: test initialize + + func testInitializeSDk() throws { + let expectation = setupActionAssertion(expectedAction: .initialize(TEST_API_KEY)) + + klaviyo.initialize(with: TEST_API_KEY) + + wait(for: [expectation], timeout: 1.0) + } + + // MARK: test set proprety + + func testSetFirstName() throws { + let expectation = setupActionAssertion(expectedAction: .setProfileProperty(.firstName, "test")) + + klaviyo.set(profileAttribute: .firstName, value: "test") + + wait(for: [expectation], timeout: 1.0) + } + + // MARK: test set profile + + func testSetProfile() throws { + let profile = Profile( + email: "john.smith@example.com", + phoneNumber: "+15555551212", + firstName: "John", + lastName: "Smith") + let expectation = setupActionAssertion(expectedAction: .enqueueProfile(profile)) + + klaviyo.set(profile: profile) + + wait(for: [expectation], timeout: 1.0) + } + + // MARK: test create event + + func testCreateEvent() throws { + let event = Event(name: .OpenedAppMetric) + let expectation = setupActionAssertion(expectedAction: .enqueueEvent(event)) + + klaviyo.create(event: event) + + wait(for: [expectation], timeout: 1.0) + } + + func testCreateEventFromDocumentation() throws { + let event = Event(name: .AddedToCartMetric, properties: [ + "Total Price": 10.99, + "Items Purchased": ["Hot Dog", "Fries", "Shake"] + ], value: 10.99) + let expectation = setupActionAssertion(expectedAction: .enqueueEvent(event)) + + klaviyo.create(event: event) + + wait(for: [expectation], timeout: 1.0) + } + + // MARK: test set push token + + func testSetPushToken() throws { + let tokenData = "mytoken".data(using: .utf8)! + let strToken = tokenData.reduce("") { $0 + String(format: "%02.2hhx", $1) } + let expectation = setupActionAssertion(expectedAction: .setPushToken(strToken, .authorized)) + + klaviyo.set(pushToken: tokenData) + + wait(for: [expectation], timeout: 1.0) + } + + // MARK: test set external id + + func testSetExternalId() throws { + let expectation = setupActionAssertion(expectedAction: .setExternalId("foo")) + + _ = klaviyo.set(externalId: "foo") + + wait(for: [expectation], timeout: 1.0) + } + + // MARK: test handle push notification + + func testHandlePushNotification() throws { + let callback = XCTestExpectation(description: "callback is made") + let push_body = ["body": [ + "_k": [ + "foo": "bar" + ] + ]] + let expectation = setupActionAssertion(expectedAction: .enqueueEvent(.init(name: .OpenedPush, properties: push_body))) + let response = try UNNotificationResponse.with(userInfo: push_body) + let handled = klaviyo.handle(notificationResponse: response) { + callback.fulfill() + } + + wait(for: [expectation, callback], timeout: 1.0) + XCTAssertTrue(handled) + } + + // MARK: test unhandle push notification + + func testUnhandlePushNotification() throws { + let callback = XCTestExpectation(description: "callback is not made") + callback.isInverted = true + let data: [AnyHashable: Any] = [ + "data": [ + "type": "OPEN_ARTICLE", + "articleId": "1", + "articleType": "Fiction", + "articleTag": "1" + ] + ] + let response = try UNNotificationResponse.with(userInfo: data) + let handled = klaviyo.handle(notificationResponse: response) { + callback.fulfill() + } + + wait(for: [callback], timeout: 1.0) + XCTAssertFalse(handled) + } + + // MARK: test property getters + + func testPropertyGetters() throws { + klaviyoSwiftEnvironment.state = { KlaviyoState(email: "foo@foo.com", phoneNumber: "555BLOB", externalId: "my_test_id", pushTokenData: .init(pushToken: "blobtoken", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.appContextInfo())), queue: []) } + let klaviyo = KlaviyoSDK() + XCTAssertEqual("foo@foo.com", klaviyo.email) + XCTAssertEqual("555BLOB", klaviyo.phoneNumber) + XCTAssertEqual("blobtoken", klaviyo.pushToken) + XCTAssertEqual("my_test_id", klaviyo.externalId) + } +} From 546a0a1c72186dbb6c8c69902371182f6d96c41e Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 14 Aug 2024 17:39:59 -0500 Subject: [PATCH 29/72] fixed api request error handling tests --- .../KlaviyoCore/Models/KlaviyoAPIError.swift | 2 +- .../KlaviyoCore/Networking/KlaviyoAPI.swift | 2 +- .../APIRequestErrorHandlingTests.swift | 670 +++++++++--------- .../KlaviyoSwiftTests/KlaviyoStateTests.swift | 378 +++++----- .../testLoadNewKlaviyoState.1.txt | 18 + .../testStateFileExistsInvalidData.1.txt | 18 + .../testStateFileExistsInvalidJSON.1.txt | 18 + .../testValidStateFileExists.1.txt | 38 + 8 files changed, 619 insertions(+), 525 deletions(-) create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testLoadNewKlaviyoState.1.txt create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidData.1.txt create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidJSON.1.txt create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testValidStateFileExists.1.txt diff --git a/Sources/KlaviyoCore/Models/KlaviyoAPIError.swift b/Sources/KlaviyoCore/Models/KlaviyoAPIError.swift index af15333c..88135176 100644 --- a/Sources/KlaviyoCore/Models/KlaviyoAPIError.swift +++ b/Sources/KlaviyoCore/Models/KlaviyoAPIError.swift @@ -9,7 +9,7 @@ import Foundation public enum KlaviyoAPIError: Error { case httpError(Int, Data) - case rateLimitError(Int) + case rateLimitError(backOff: Int) case missingOrInvalidResponse(URLResponse?) case networkError(Error) case internalError(String) diff --git a/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift b/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift index 495eec96..7e6518eb 100644 --- a/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift +++ b/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift @@ -56,7 +56,7 @@ public struct KlaviyoAPI { let nextBackOffWithJitter = nextBackoff + jitter requestRateLimited(request, nextBackOffWithJitter) - return .failure(KlaviyoAPIError.rateLimitError(nextBackOffWithJitter)) + return .failure(KlaviyoAPIError.rateLimitError(backOff: nextBackOffWithJitter)) } guard 200..<300 ~= httpResponse.statusCode else { diff --git a/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift b/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift index 204b3a27..22b2cdc5 100644 --- a/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift +++ b/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift @@ -4,339 +4,339 @@ //// //// Created by Noah Durell on 12/15/22. //// -// -// @testable import KlaviyoSwift -// import Foundation -// import KlaviyoCore -// import XCTest -// + +@testable import KlaviyoSwift +import Foundation +import KlaviyoCore +import XCTest + let TIMEOUT_NANOSECONDS: UInt64 = 10_000_000_000 // 10 seconds -// -// class APIRequestErrorHandlingTests: XCTestCase { -// @MainActor -// override func setUp() async throws { -// environment = KlaviyoEnvironment.test() -// } -// -// // MARK: - http error -// -// @MainActor -// func testSendRequestHttpFailureDequesRequest() async throws { -// var initialState = INITIALIZED_TEST_STATE() -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// initialState.requestsInFlight = [request] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.httpError(500, TEST_RETURN_DATA)) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.deQueueCompletedResults(request)) { -// $0.flushing = false -// $0.requestsInFlight = [] -// } -// } -// -// @MainActor -// func testSendRequestHttpFailureForPhoneNumberResetsStateAndDequesRequest() async throws { -// var initialState = INITIALIZED_TEST_STATE_INVALID_PHONE() -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// initialState.requestsInFlight = [request] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.httpError(400, TEST_FAILURE_JSON_INVALID_PHONE_NUMBER.data(using: .utf8)!)) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.resetStateAndDequeue(request, [InvalidField.phone]), timeout: TIMEOUT_NANOSECONDS) { -// $0.phoneNumber = nil -// } -// -// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { -// $0.flushing = false -// $0.queue = [] -// $0.requestsInFlight = [] -// $0.retryInfo = .retry(1) -// } -// } -// -// @MainActor -// func testSendRequestHttpFailureForEmailResetsStateAndDequesRequest() async throws { -// var initialState = INITIALIZED_TEST_STATE_INVALID_EMAIL() -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// initialState.requestsInFlight = [request] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.httpError(400, TEST_FAILURE_JSON_INVALID_EMAIL.data(using: .utf8)!)) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.resetStateAndDequeue(request, [InvalidField.email]), timeout: TIMEOUT_NANOSECONDS) { -// $0.email = nil -// } -// -// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { -// $0.flushing = false -// $0.queue = [] -// $0.requestsInFlight = [] -// $0.retryInfo = .retry(1) -// } -// } -// -// // MARK: - network error -// -// @MainActor -// func testSendRequestFailureIncrementsRetryCount() async throws { -// var initialState = INITIALIZED_TEST_STATE() -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// let request2 = initialState.buildTokenRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!, pushToken: "new_token", enablement: .authorized) -// initialState.requestsInFlight = [request, request2] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.requestFailed(request, .retry(2)), timeout: TIMEOUT_NANOSECONDS) { -// $0.flushing = false -// $0.queue = [request, request2] -// $0.requestsInFlight = [] -// $0.retryInfo = .retry(2) -// } -// } -// -// @MainActor -// func testSendRequestFailureWithBackoff() async throws { -// var initialState = INITIALIZED_TEST_STATE() -// initialState.retryInfo = .retryWithBackoff(requestCount: 1, totalRetryCount: 1, currentBackoff: 1) -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// let request2 = initialState.buildTokenRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!, pushToken: "new_token", enablement: .authorized) -// initialState.requestsInFlight = [request, request2] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.requestFailed(request, .retry(2)), timeout: TIMEOUT_NANOSECONDS) { -// $0.flushing = false -// $0.queue = [request, request2] -// $0.requestsInFlight = [] -// $0.retryInfo = .retry(2) -// } -// } -// -// @MainActor -// func testSendRequestMaxRetries() async throws { -// var initialState = INITIALIZED_TEST_STATE() -// initialState.retryInfo = .retry(ErrorHandlingConstants.maxRetries) -// -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// var request2 = initialState.buildTokenRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!, pushToken: "new_token", enablement: .authorized) -// request2.uuid = "foo" -// initialState.requestsInFlight = [request, request2] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.requestFailed(request, .retry(ErrorHandlingConstants.maxRetries + 1)), timeout: TIMEOUT_NANOSECONDS) { -// $0.flushing = false -// $0.queue = [request2] -// $0.requestsInFlight = [] -// $0.retryInfo = .retry(1) -// } -// } -// -// // MARK: - internal error -// -// @MainActor -// func testSendRequestInternalError() async throws { -// // NOTE: should really happen but putting this in for possible future cases and test coverage -// var initialState = INITIALIZED_TEST_STATE() -// -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// initialState.requestsInFlight = [request] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.internalError("internal error!")) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { -// $0.flushing = false -// $0.queue = [] -// $0.requestsInFlight = [] -// $0.retryInfo = .retry(1) -// } -// } -// -// // MARK: - internal request error -// -// @MainActor -// func testSendRequestInternalRequestError() async throws { -// var initialState = INITIALIZED_TEST_STATE() -// -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// initialState.requestsInFlight = [request] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.internalRequestError(KlaviyoAPI.KlaviyoAPIError.internalError("foo"))) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { -// $0.flushing = false -// $0.queue = [] -// $0.requestsInFlight = [] -// $0.retryInfo = .retry(1) -// } -// } -// -// // MARK: - unknown error -// -// @MainActor -// func testSendRequestUnknownError() async throws { -// var initialState = INITIALIZED_TEST_STATE() -// -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// initialState.requestsInFlight = [request] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.unknownError(KlaviyoAPI.KlaviyoAPIError.internalError("foo"))) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { -// $0.flushing = false -// $0.queue = [] -// $0.requestsInFlight = [] -// $0.retryInfo = .retry(1) -// } -// } -// -// // MARK: - data decoding error -// -// @MainActor -// func testSendRequestDataDecodingError() async throws { -// var initialState = INITIALIZED_TEST_STATE() -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// initialState.requestsInFlight = [request] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.dataEncodingError(request)) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { -// $0.flushing = false -// $0.queue = [] -// $0.requestsInFlight = [] -// $0.retryInfo = .retry(1) -// } -// } -// -// // MARK: - invalid data -// -// @MainActor -// func testSendRequestInvalidData() async throws { -// var initialState = INITIALIZED_TEST_STATE() -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// initialState.requestsInFlight = [request] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.invalidData) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { -// $0.flushing = false -// $0.queue = [] -// $0.requestsInFlight = [] -// $0.retryInfo = .retry(1) -// } -// } -// -// // MARK: - rate limit error -// -// @MainActor -// func testRateLimitErrorWithExistingRetry() async throws { -// var initialState = INITIALIZED_TEST_STATE() -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// initialState.requestsInFlight = [request] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(nil)) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.requestFailed(request, .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 1)), timeout: TIMEOUT_NANOSECONDS) { -// $0.flushing = false -// $0.queue = [request] -// $0.requestsInFlight = [] -// $0.retryInfo = .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 1) -// } -// } -// -// @MainActor -// func testRateLimitErrorWithExistingBackoffRetry() async throws { -// var initialState = INITIALIZED_TEST_STATE() -// initialState.retryInfo = .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 4) -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// initialState.requestsInFlight = [request] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(nil)) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.requestFailed(request, .retryWithBackoff(requestCount: 3, totalRetryCount: 3, currentBackoff: 1)), timeout: TIMEOUT_NANOSECONDS) { -// $0.flushing = false -// $0.queue = [request] -// $0.requestsInFlight = [] -// $0.retryInfo = .retryWithBackoff(requestCount: 3, totalRetryCount: 3, currentBackoff: 1) -// } -// } -// -// func testRetryWithRetryAfter() async throws { -// var initialState = INITIALIZED_TEST_STATE() -// initialState.retryInfo = .retryWithBackoff(requestCount: 3, totalRetryCount: 3, currentBackoff: 4) -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// initialState.requestsInFlight = [request] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(20)) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.requestFailed(request, .retryWithBackoff(requestCount: 4, totalRetryCount: 4, currentBackoff: 20)), timeout: TIMEOUT_NANOSECONDS) { -// $0.flushing = false -// $0.queue = [request] -// $0.requestsInFlight = [] -// $0.retryInfo = .retryWithBackoff(requestCount: 4, totalRetryCount: 4, currentBackoff: 20) -// } -// } -// -// // MARK: - Missing or invalid response -// -// @MainActor -// func testMissingOrInvalidResponse() async throws { -// var initialState = INITIALIZED_TEST_STATE() -// initialState.retryInfo = .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 4) -// let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) -// initialState.requestsInFlight = [request] -// let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) -// -// environment.klaviyoAPI.send = { _, _ in .failure(.missingOrInvalidResponse(nil)) } -// -// _ = await store.send(.sendRequest) -// -// await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { -// $0.flushing = false -// $0.queue = [] -// $0.requestsInFlight = [] -// $0.retryInfo = .retry(1) -// } -// } -// } + +class APIRequestErrorHandlingTests: XCTestCase { + @MainActor + override func setUp() async throws { + environment = KlaviyoEnvironment.test() + } + + // MARK: - http error + + @MainActor + func testSendRequestHttpFailureDequesRequest() async throws { + var initialState = INITIALIZED_TEST_STATE() + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + initialState.requestsInFlight = [request] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.httpError(500, TEST_RETURN_DATA)) } + + _ = await store.send(.sendRequest) + + await store.receive(.deQueueCompletedResults(request)) { + $0.flushing = false + $0.requestsInFlight = [] + } + } + + @MainActor + func testSendRequestHttpFailureForPhoneNumberResetsStateAndDequesRequest() async throws { + var initialState = INITIALIZED_TEST_STATE_INVALID_PHONE() + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + initialState.requestsInFlight = [request] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.httpError(400, TEST_FAILURE_JSON_INVALID_PHONE_NUMBER.data(using: .utf8)!)) } + + _ = await store.send(.sendRequest) + + await store.receive(.resetStateAndDequeue(request, [InvalidField.phone]), timeout: TIMEOUT_NANOSECONDS) { + $0.phoneNumber = nil + } + + await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { + $0.flushing = false + $0.queue = [] + $0.requestsInFlight = [] + $0.retryInfo = .retry(1) + } + } + + @MainActor + func testSendRequestHttpFailureForEmailResetsStateAndDequesRequest() async throws { + var initialState = INITIALIZED_TEST_STATE_INVALID_EMAIL() + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + initialState.requestsInFlight = [request] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.httpError(400, TEST_FAILURE_JSON_INVALID_EMAIL.data(using: .utf8)!)) } + + _ = await store.send(.sendRequest) + + await store.receive(.resetStateAndDequeue(request, [InvalidField.email]), timeout: TIMEOUT_NANOSECONDS) { + $0.email = nil + } + + await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { + $0.flushing = false + $0.queue = [] + $0.requestsInFlight = [] + $0.retryInfo = .retry(1) + } + } + + // MARK: - network error + + @MainActor + func testSendRequestFailureIncrementsRetryCount() async throws { + var initialState = INITIALIZED_TEST_STATE() + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + let request2 = initialState.buildTokenRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!, pushToken: "new_token", enablement: .authorized) + initialState.requestsInFlight = [request, request2] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } + + _ = await store.send(.sendRequest) + + await store.receive(.requestFailed(request, .retry(2)), timeout: TIMEOUT_NANOSECONDS) { + $0.flushing = false + $0.queue = [request, request2] + $0.requestsInFlight = [] + $0.retryInfo = .retry(2) + } + } + + @MainActor + func testSendRequestFailureWithBackoff() async throws { + var initialState = INITIALIZED_TEST_STATE() + initialState.retryInfo = .retryWithBackoff(requestCount: 1, totalRetryCount: 1, currentBackoff: 1) + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + let request2 = initialState.buildTokenRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!, pushToken: "new_token", enablement: .authorized) + initialState.requestsInFlight = [request, request2] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } + + _ = await store.send(.sendRequest) + + await store.receive(.requestFailed(request, .retry(2)), timeout: TIMEOUT_NANOSECONDS) { + $0.flushing = false + $0.queue = [request, request2] + $0.requestsInFlight = [] + $0.retryInfo = .retry(2) + } + } + + @MainActor + func testSendRequestMaxRetries() async throws { + var initialState = INITIALIZED_TEST_STATE() + initialState.retryInfo = .retry(ErrorHandlingConstants.maxRetries) + + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + var request2 = initialState.buildTokenRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!, pushToken: "new_token", enablement: .authorized) + request2.uuid = "foo" + initialState.requestsInFlight = [request, request2] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.networkError(NSError(domain: "foo", code: NSURLErrorCancelled))) } + + _ = await store.send(.sendRequest) + + await store.receive(.requestFailed(request, .retry(ErrorHandlingConstants.maxRetries + 1)), timeout: TIMEOUT_NANOSECONDS) { + $0.flushing = false + $0.queue = [request2] + $0.requestsInFlight = [] + $0.retryInfo = .retry(1) + } + } + + // MARK: - internal error + + @MainActor + func testSendRequestInternalError() async throws { + // NOTE: should really happen but putting this in for possible future cases and test coverage + var initialState = INITIALIZED_TEST_STATE() + + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + initialState.requestsInFlight = [request] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.internalError("internal error!")) } + + _ = await store.send(.sendRequest) + + await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { + $0.flushing = false + $0.queue = [] + $0.requestsInFlight = [] + $0.retryInfo = .retry(1) + } + } + + // MARK: - internal request error + + @MainActor + func testSendRequestInternalRequestError() async throws { + var initialState = INITIALIZED_TEST_STATE() + + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + initialState.requestsInFlight = [request] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.internalRequestError(KlaviyoAPIError.internalError("foo"))) } + + _ = await store.send(.sendRequest) + + await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { + $0.flushing = false + $0.queue = [] + $0.requestsInFlight = [] + $0.retryInfo = .retry(1) + } + } + + // MARK: - unknown error + + @MainActor + func testSendRequestUnknownError() async throws { + var initialState = INITIALIZED_TEST_STATE() + + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + initialState.requestsInFlight = [request] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.unknownError(KlaviyoAPIError.internalError("foo"))) } + + _ = await store.send(.sendRequest) + + await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { + $0.flushing = false + $0.queue = [] + $0.requestsInFlight = [] + $0.retryInfo = .retry(1) + } + } + + // MARK: - data decoding error + + @MainActor + func testSendRequestDataDecodingError() async throws { + var initialState = INITIALIZED_TEST_STATE() + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + initialState.requestsInFlight = [request] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.dataEncodingError(request)) } + + _ = await store.send(.sendRequest) + + await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { + $0.flushing = false + $0.queue = [] + $0.requestsInFlight = [] + $0.retryInfo = .retry(1) + } + } + + // MARK: - invalid data + + @MainActor + func testSendRequestInvalidData() async throws { + var initialState = INITIALIZED_TEST_STATE() + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + initialState.requestsInFlight = [request] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.invalidData) } + + _ = await store.send(.sendRequest) + + await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { + $0.flushing = false + $0.queue = [] + $0.requestsInFlight = [] + $0.retryInfo = .retry(1) + } + } + + // MARK: - rate limit error + + @MainActor + func testRateLimitErrorWithExistingRetry() async throws { + var initialState = INITIALIZED_TEST_STATE() + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + initialState.requestsInFlight = [request] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(backOff: 30)) } + + _ = await store.send(.sendRequest) + + await store.receive(.requestFailed(request, .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 30)), timeout: TIMEOUT_NANOSECONDS) { + $0.flushing = false + $0.queue = [request] + $0.requestsInFlight = [] + $0.retryInfo = .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 30) + } + } + + @MainActor + func testRateLimitErrorWithExistingBackoffRetry() async throws { + var initialState = INITIALIZED_TEST_STATE() + initialState.retryInfo = .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 4) + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + initialState.requestsInFlight = [request] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(backOff: 30)) } + + _ = await store.send(.sendRequest) + + await store.receive(.requestFailed(request, .retryWithBackoff(requestCount: 3, totalRetryCount: 3, currentBackoff: 30)), timeout: TIMEOUT_NANOSECONDS) { + $0.flushing = false + $0.queue = [request] + $0.requestsInFlight = [] + $0.retryInfo = .retryWithBackoff(requestCount: 3, totalRetryCount: 3, currentBackoff: 30) + } + } + + func testRetryWithRetryAfter() async throws { + var initialState = INITIALIZED_TEST_STATE() + initialState.retryInfo = .retryWithBackoff(requestCount: 3, totalRetryCount: 3, currentBackoff: 4) + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + initialState.requestsInFlight = [request] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.rateLimitError(backOff: 20)) } + + _ = await store.send(.sendRequest) + + await store.receive(.requestFailed(request, .retryWithBackoff(requestCount: 4, totalRetryCount: 4, currentBackoff: 20)), timeout: TIMEOUT_NANOSECONDS) { + $0.flushing = false + $0.queue = [request] + $0.requestsInFlight = [] + $0.retryInfo = .retryWithBackoff(requestCount: 4, totalRetryCount: 4, currentBackoff: 20) + } + } + + // MARK: - Missing or invalid response + + @MainActor + func testMissingOrInvalidResponse() async throws { + var initialState = INITIALIZED_TEST_STATE() + initialState.retryInfo = .retryWithBackoff(requestCount: 2, totalRetryCount: 2, currentBackoff: 4) + let request = initialState.buildProfileRequest(apiKey: initialState.apiKey!, anonymousId: initialState.anonymousId!) + initialState.requestsInFlight = [request] + let store = TestStore(initialState: initialState, reducer: KlaviyoReducer()) + + environment.klaviyoAPI.send = { _, _ in .failure(.missingOrInvalidResponse(nil)) } + + _ = await store.send(.sendRequest) + + await store.receive(.deQueueCompletedResults(request), timeout: TIMEOUT_NANOSECONDS) { + $0.flushing = false + $0.queue = [] + $0.requestsInFlight = [] + $0.retryInfo = .retry(1) + } + } +} diff --git a/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift b/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift index 613414e8..b964d496 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift @@ -1,189 +1,191 @@ -//// -//// KlaviyoStateTests.swift -//// -//// -//// Created by Noah Durell on 12/1/22. -//// // -// @testable import KlaviyoSwift -// import AnyCodable -// import Foundation -// import KlaviyoCore -// import SnapshotTesting -// import XCTest -// -// final class KlaviyoStateTests: XCTestCase { -// let TEST_EVENT = [ -// "event": "$opened_push", -// "properties": [ -// "prop1": "propValue" -// ], -// "customer_properties": [ -// "foo": "bar" -// ] -// ] as [String: Any] -// -// let TEST_PROFILE = [ -// "properties": [ -// "foo2": "bar2" -// ] -// ] -// -// let TEST_INVALID_EVENT = [ -// "properties": [ -// "prop1": "propValue" -// ], -// "customer_properties": [ -// "foo": "bar" -// ] -// ] -// let TEST_INVALID_PROFILE = [ -// "garbage_key": [ -// "foo": "bar" -// ] -// ] -// let TEST_INVALID_PROPERTIES_EVENT = [ -// "properties": [ -// 1: "propValue" -// ] as [AnyHashable: String], -// "customer_properties": [ -// "foo": "bar" -// ] -// ] -// -// let TEST_INVALID_CUSTOMER_PROPERTIES_EVENT = [ -// "event": "$opened_push", -// "properties": [ -// "fo": "propValue" -// ], -// "customer_properties": [ -// 1: "bar" -// ] -// ] as [String: Any] -// let TEST_INVALID_PROPERTIES_PROFILE = [ -// "event": "$opened_push", -// "properties": [ -// 1: "propValue" -// ] -// ] as [String: Any] -// -// override func setUp() async throws { -// environment = KlaviyoEnvironment.test() -// } -// -// func testLoadNewKlaviyoState() throws { -// environment.fileClient.fileExists = { _ in false } -// environment.archiverClient.unarchivedMutableArray = { _ in [] } -// let state = loadKlaviyoStateFromDisk(apiKey: "foo") -// assertSnapshot(matching: state, as: .dump) -// } -// -// func testStateFileExistsInvalidData() throws { -// environment.fileClient.fileExists = { _ in -// true -// } -// environment.dataFromUrl = { _ in -// throw NSError(domain: "missing file", code: 1) -// } -// environment.archiverClient.unarchivedMutableArray = { _ in -// XCTFail("unarchivedMutableArray should not be called.") -// return [] -// } -// -// let state = loadKlaviyoStateFromDisk(apiKey: "foo") -// assertSnapshot(matching: state, as: .dump) -// } -// -// func testStateFileExistsInvalidJSON() throws { -// environment.fileClient.fileExists = { _ in -// true -// } -// -// environment.decoder = DataDecoder(jsonDecoder: InvalidJSONDecoder()) -// environment.archiverClient.unarchivedMutableArray = { _ in -// XCTFail("unarchivedMutableArray should not be called.") -// return [] -// } -// -// let state = loadKlaviyoStateFromDisk(apiKey: "foo") -// assertSnapshot(matching: state, as: .dump) -// } -// -// func testValidStateFileExists() throws { -// environment.fileClient.fileExists = { _ in -// true -// } -// environment.dataFromUrl = { _ in -// try! JSONEncoder().encode(KlaviyoState(apiKey: "foo", anonymousId: environment.uuid().uuidString, queue: [], requestsInFlight: [])) -// } -//// environment.decoder = DataDecoder(jsonDecoder: KlaviyoEnvironment.decoder) -// -// let state = loadKlaviyoStateFromDisk(apiKey: "foo") -// assertSnapshot(matching: state, as: .dump) -// } -// -// func testFullKlaviyoStateEncodingDecodingIsEqual() throws { -// let event = Event.test -// let createEventPayload = CreateEventPayload(data: .init(event: event)) -// let eventRequest = KlaviyoRequest(apiKey: "foo", endpoint: .createEvent(createEventPayload)) -// let profile = Profile.test -// let data = CreateProfilePayload.Profile(profile: profile, anonymousId: "foo") -// let payload = CreateProfilePayload(data: data) -// let profileRequest = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(payload)) -// let tokenPayload = KlaviyoRequest.KlaviyoEndpoint.PushTokenPayload( -// pushToken: "foo", -// enablement: "AUTHORIZED", -// background: "AVAILABLE", -// profile: .init(email: "foo", phoneNumber: "foo"), -// anonymousId: "foo") -// let tokenRequest = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) -// let state = KlaviyoState(apiKey: "key", queue: [tokenRequest, profileRequest, eventRequest]) -// let encodedState = try environment.encodeJSON(AnyEncodable(state)) -// let decodedState: KlaviyoState = try environment.decoder.decode(encodedState) -// XCTAssertEqual(decodedState, state) -// } -// -// func testSaveKlaviyoStateWithMissingApiKeyLogsError() { -// var savedMsg: String? -// environment.logger.error = { msg in savedMsg = msg } -// let state = KlaviyoState(queue: []) -// saveKlaviyoState(state: state) -// -// XCTAssertEqual(savedMsg, "Attempt to save state without an api key.") -// } -// -// // MARK: test background and authorization states -// -// func testBackgroundStates() { -// let backgroundStates = [ -// UIBackgroundRefreshStatus.available: PushBackground.available, -// .denied: .denied, -// .restricted: .restricted -// ] -// -// for (status, expecation) in backgroundStates { -// XCTAssertEqual(PushBackground.create(from: status), expecation) -// } -// -// // Fake value to test availability -// XCTAssertEqual(PushBackground.create(from: UIBackgroundRefreshStatus(rawValue: 20)!), .available) -// } -// -// @available(iOS 14.0, *) -// func testPushEnablementStates() { -// let enablementStates = [ -// UNAuthorizationStatus.authorized: PushEnablement.authorized, -// .denied: .denied, -// .ephemeral: .ephemeral, -// .notDetermined: .notDetermined, -// .provisional: .provisional -// ] -// -// for (status, expecation) in enablementStates { -// XCTAssertEqual(PushEnablement.create(from: status), expecation) -// } -// -// // Fake value to test availability -// XCTAssertEqual(PushEnablement.create(from: UNAuthorizationStatus(rawValue: 50)!), .notDetermined) -// } -// } +// KlaviyoStateTests.swift +// +// +// Created by Noah Durell on 12/1/22. +// + +@testable import KlaviyoSwift +import AnyCodable +import Foundation +import KlaviyoCore +import SnapshotTesting +import XCTest + +final class KlaviyoStateTests: XCTestCase { + let TEST_EVENT = [ + "event": "$opened_push", + "properties": [ + "prop1": "propValue" + ], + "customer_properties": [ + "foo": "bar" + ] + ] as [String: Any] + + let TEST_PROFILE = [ + "properties": [ + "foo2": "bar2" + ] + ] + + let TEST_INVALID_EVENT = [ + "properties": [ + "prop1": "propValue" + ], + "customer_properties": [ + "foo": "bar" + ] + ] + let TEST_INVALID_PROFILE = [ + "garbage_key": [ + "foo": "bar" + ] + ] + let TEST_INVALID_PROPERTIES_EVENT = [ + "properties": [ + 1: "propValue" + ] as [AnyHashable: String], + "customer_properties": [ + "foo": "bar" + ] + ] + + let TEST_INVALID_CUSTOMER_PROPERTIES_EVENT = [ + "event": "$opened_push", + "properties": [ + "fo": "propValue" + ], + "customer_properties": [ + 1: "bar" + ] + ] as [String: Any] + let TEST_INVALID_PROPERTIES_PROFILE = [ + "event": "$opened_push", + "properties": [ + 1: "propValue" + ] + ] as [String: Any] + + override func setUp() async throws { + environment = KlaviyoEnvironment.test() + } + + func testLoadNewKlaviyoState() throws { + environment.fileClient.fileExists = { _ in false } + environment.archiverClient.unarchivedMutableArray = { _ in [] } + let state = loadKlaviyoStateFromDisk(apiKey: "foo") + assertSnapshot(matching: state, as: .dump) + } + + func testStateFileExistsInvalidData() throws { + environment.fileClient.fileExists = { _ in + true + } + environment.dataFromUrl = { _ in + throw NSError(domain: "missing file", code: 1) + } + environment.archiverClient.unarchivedMutableArray = { _ in + XCTFail("unarchivedMutableArray should not be called.") + return [] + } + + let state = loadKlaviyoStateFromDisk(apiKey: "foo") + assertSnapshot(matching: state, as: .dump) + } + + func testStateFileExistsInvalidJSON() throws { + environment.fileClient.fileExists = { _ in + true + } + + environment.decoder = DataDecoder(jsonDecoder: InvalidJSONDecoder()) + environment.archiverClient.unarchivedMutableArray = { _ in + XCTFail("unarchivedMutableArray should not be called.") + return [] + } + + let state = loadKlaviyoStateFromDisk(apiKey: "foo") + assertSnapshot(matching: state, as: .dump) + } + + func testValidStateFileExists() throws { + environment.fileClient.fileExists = { _ in + true + } + environment.dataFromUrl = { _ in + try! JSONEncoder().encode(KlaviyoState( + apiKey: "foo", + anonymousId: environment.uuid().uuidString, + queue: [], + requestsInFlight: [])) + } +// environment.decoder = DataDecoder(jsonDecoder: KlaviyoEnvironment.decoder) + + let state = loadKlaviyoStateFromDisk(apiKey: "foo") + assertSnapshot(matching: state, as: .dump) + } + + func testFullKlaviyoStateEncodingDecodingIsEqual() throws { + let event = Event.test + let createEventPayload = CreateEventPayload(data: CreateEventPayload.Event(name: event.metric.name.value)) + let eventRequest = KlaviyoRequest(apiKey: "foo", endpoint: .createEvent(createEventPayload)) + let profile = Profile.test + let payload = CreateProfilePayload(data: profile.toAPIModel(anonymousId: "foo")) + let profileRequest = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(payload)) + let tokenPayload = PushTokenPayload( + pushToken: "foo", + enablement: "AUTHORIZED", + background: "AVAILABLE", + profile: ProfilePayload(email: "foo", phoneNumber: "foo", anonymousId: "foo")) + let tokenRequest = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) + let state = KlaviyoState(apiKey: "key", queue: [tokenRequest, profileRequest, eventRequest]) + let encodedState = try environment.encodeJSON(AnyEncodable(state)) + let decodedState: KlaviyoState = try environment.decoder.decode(encodedState) + XCTAssertEqual(decodedState, state) + } + + func testSaveKlaviyoStateWithMissingApiKeyLogsError() { + var savedMsg: String? + environment.logger.error = { msg in savedMsg = msg } + let state = KlaviyoState(queue: []) + saveKlaviyoState(state: state) + + XCTAssertEqual(savedMsg, "Attempt to save state without an api key.") + } + + // MARK: test background and authorization states + + func testBackgroundStates() { + let backgroundStates = [ + UIBackgroundRefreshStatus.available: PushBackground.available, + .denied: .denied, + .restricted: .restricted + ] + + for (status, expecation) in backgroundStates { + XCTAssertEqual(PushBackground.create(from: status), expecation) + } + + // Fake value to test availability + XCTAssertEqual(PushBackground.create(from: UIBackgroundRefreshStatus(rawValue: 20)!), .available) + } + + @available(iOS 14.0, *) + func testPushEnablementStates() { + let enablementStates = [ + UNAuthorizationStatus.authorized: PushEnablement.authorized, + .denied: .denied, + .ephemeral: .ephemeral, + .notDetermined: .notDetermined, + .provisional: .provisional + ] + + for (status, expecation) in enablementStates { + XCTAssertEqual(PushEnablement.create(from: status), expecation) + } + + // Fake value to test availability + XCTAssertEqual(PushEnablement.create(from: UNAuthorizationStatus(rawValue: 50)!), .notDetermined) + } +} diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testLoadNewKlaviyoState.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testLoadNewKlaviyoState.1.txt new file mode 100644 index 00000000..7be1f88b --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testLoadNewKlaviyoState.1.txt @@ -0,0 +1,18 @@ +▿ KlaviyoState + ▿ anonymousId: Optional + - some: "00000000-0000-0000-0000-000000000001" + ▿ apiKey: Optional + - some: "foo" + - email: Optional.none + - externalId: Optional.none + - flushInterval: 10.0 + - flushing: false + - initalizationState: InitializationState.uninitialized + - pendingProfile: Optional>.none + - pendingRequests: 0 elements + - phoneNumber: Optional.none + - pushTokenData: Optional.none + - queue: 0 elements + - requestsInFlight: 0 elements + ▿ retryInfo: RetryInfo + - retry: 1 diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidData.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidData.1.txt new file mode 100644 index 00000000..7be1f88b --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidData.1.txt @@ -0,0 +1,18 @@ +▿ KlaviyoState + ▿ anonymousId: Optional + - some: "00000000-0000-0000-0000-000000000001" + ▿ apiKey: Optional + - some: "foo" + - email: Optional.none + - externalId: Optional.none + - flushInterval: 10.0 + - flushing: false + - initalizationState: InitializationState.uninitialized + - pendingProfile: Optional>.none + - pendingRequests: 0 elements + - phoneNumber: Optional.none + - pushTokenData: Optional.none + - queue: 0 elements + - requestsInFlight: 0 elements + ▿ retryInfo: RetryInfo + - retry: 1 diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidJSON.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidJSON.1.txt new file mode 100644 index 00000000..7be1f88b --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testStateFileExistsInvalidJSON.1.txt @@ -0,0 +1,18 @@ +▿ KlaviyoState + ▿ anonymousId: Optional + - some: "00000000-0000-0000-0000-000000000001" + ▿ apiKey: Optional + - some: "foo" + - email: Optional.none + - externalId: Optional.none + - flushInterval: 10.0 + - flushing: false + - initalizationState: InitializationState.uninitialized + - pendingProfile: Optional>.none + - pendingRequests: 0 elements + - phoneNumber: Optional.none + - pushTokenData: Optional.none + - queue: 0 elements + - requestsInFlight: 0 elements + ▿ retryInfo: RetryInfo + - retry: 1 diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testValidStateFileExists.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testValidStateFileExists.1.txt new file mode 100644 index 00000000..751eaaf8 --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoStateTests/testValidStateFileExists.1.txt @@ -0,0 +1,38 @@ +▿ KlaviyoState + ▿ anonymousId: Optional + - some: "00000000-0000-0000-0000-000000000001" + ▿ apiKey: Optional + - some: "foo" + ▿ email: Optional + - some: "test@test.com" + ▿ externalId: Optional + - some: "externalId" + - flushInterval: 10.0 + - flushing: true + - initalizationState: InitializationState.initialized + - pendingProfile: Optional>.none + - pendingRequests: 0 elements + ▿ phoneNumber: Optional + - some: "phoneNumber" + ▿ pushTokenData: Optional + ▿ some: PushTokenData + ▿ deviceData: MetaData + - appBuild: "1" + - appId: "com.klaviyo.fooapp" + - appName: "FooApp" + - appVersion: "1.2.3" + - deviceId: "fe-fi-fo-fum" + - deviceModel: "jPhone 1,1" + - environment: "debug" + - klaviyoSdk: "swift" + - manufacturer: "Orange" + - osName: "iOS" + - osVersion: "1.1.1" + - sdkVersion: "3.2.0" + - pushBackground: PushBackground.available + - pushEnablement: PushEnablement.authorized + - pushToken: "blob_token" + - queue: 0 elements + - requestsInFlight: 0 elements + ▿ retryInfo: RetryInfo + - retry: 1 From f17548d9db8a32afcd2f297fa1ff1b7e8632eaf9 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 15 Aug 2024 15:24:39 -0500 Subject: [PATCH 30/72] moved archival utils --- Package.swift | 13 +- .../ArchivalUtilsTests.swift | 2 +- Tests/KlaviyoCoreTests/TestUtils.swift | 151 ++++++++++++++++++ .../KlaviyoSwiftTests/KlaviyoTestUtils.swift | 45 ------ Tests/KlaviyoSwiftTests/TestData.swift | 46 ++++++ 5 files changed, 206 insertions(+), 51 deletions(-) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/ArchivalUtilsTests.swift (99%) create mode 100644 Tests/KlaviyoCoreTests/TestUtils.swift diff --git a/Package.swift b/Package.swift index 74ad904d..edcad818 100644 --- a/Package.swift +++ b/Package.swift @@ -31,15 +31,14 @@ let package = Package( name: "KlaviyoCore", dependencies: [.product(name: "AnyCodable", package: "AnyCodable")], path: "Sources/KlaviyoCore"), + .testTarget( + name: "KlaviyoCoreTests", + dependencies: ["KlaviyoCore"]), .target( name: "KlaviyoSwift", dependencies: [.product(name: "AnyCodable", package: "AnyCodable"), "KlaviyoCore"], path: "Sources/KlaviyoSwift", resources: [.copy("PrivacyInfo.xcprivacy")]), - .target( - name: "KlaviyoSwiftExtension", - dependencies: [], - path: "Sources/KlaviyoSwiftExtension"), .testTarget( name: "KlaviyoSwiftTests", dependencies: [ @@ -52,5 +51,9 @@ let package = Package( ], exclude: [ "__Snapshots__" - ]) + ]), + .target( + name: "KlaviyoSwiftExtension", + dependencies: [], + path: "Sources/KlaviyoSwiftExtension") ]) diff --git a/Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift b/Tests/KlaviyoCoreTests/ArchivalUtilsTests.swift similarity index 99% rename from Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift rename to Tests/KlaviyoCoreTests/ArchivalUtilsTests.swift index 3a933a29..228073f4 100644 --- a/Tests/KlaviyoSwiftTests/ArchivalUtilsTests.swift +++ b/Tests/KlaviyoCoreTests/ArchivalUtilsTests.swift @@ -5,7 +5,7 @@ // Created by Noah Durell on 9/26/22. // -@testable import KlaviyoSwift +import Combine import KlaviyoCore import XCTest diff --git a/Tests/KlaviyoCoreTests/TestUtils.swift b/Tests/KlaviyoCoreTests/TestUtils.swift new file mode 100644 index 00000000..4e208224 --- /dev/null +++ b/Tests/KlaviyoCoreTests/TestUtils.swift @@ -0,0 +1,151 @@ +// +// TestUtils.swift +// +// +// Created by Ajay Subramanya on 8/15/24. +// + +import Combine +import Foundation +import KlaviyoCore + +enum FakeFileError: Error { + case fake +} + +let ARCHIVED_RETURNED_DATA = Data() +let SAMPLE_DATA: NSMutableArray = [ + [ + "properties": [ + "foo": "bar" + ] + ] +] +let TEST_URL = URL(string: "fake_url")! +let TEST_RETURN_DATA = Data() + +let TEST_FAILURE_JSON_INVALID_PHONE_NUMBER = """ +{ + "errors": [ + { + "id": "9997bd4f-7d5f-4f01-bbd1-df0065ef4faa", + "status": 400, + "code": "invalid", + "title": "Invalid input.", + "detail": "Invalid phone number format (Example of a valid format: +12345678901)", + "source": { + "pointer": "/data/attributes/phone_number" + }, + "meta": {} + } + ] +} +""" + +let TEST_FAILURE_JSON_INVALID_EMAIL = """ +{ + "errors": [ + { + "id": "dce2d180-0f36-4312-aa6d-92d025c17147", + "status": 400, + "code": "invalid", + "title": "Invalid input.", + "detail": "Invalid email address", + "source": { + "pointer": "/data/attributes/email" + }, + "meta": {} + } + ] +} +""" + +extension ArchiverClient { + static let test = ArchiverClient( + archivedData: { _, _ in ARCHIVED_RETURNED_DATA }, + unarchivedMutableArray: { _ in SAMPLE_DATA }) +} + +extension KlaviyoEnvironment { + static var lastLog: String? + static var test = { + KlaviyoEnvironment( + archiverClient: ArchiverClient.test, + fileClient: FileClient.test, + dataFromUrl: { _ in TEST_RETURN_DATA }, + logger: LoggerClient.test, + appLifeCycle: AppLifeCycleEvents.test, + notificationCenterPublisher: { _ in Empty().eraseToAnyPublisher() }, + getNotificationSettings: { callback in callback(.authorized) }, + getBackgroundSetting: { .available }, + startReachability: {}, + stopReachability: {}, + reachabilityStatus: { nil }, + randomInt: { 0 }, + raiseFatalError: { _ in }, + emitDeveloperWarning: { _ in }, + networkSession: { NetworkSession.test() }, + apiURL: "dead_beef", + encodeJSON: { _ in TEST_RETURN_DATA }, + decoder: DataDecoder(jsonDecoder: TestJSONDecoder()), + uuid: { UUID(uuidString: "00000000-0000-0000-0000-000000000001")! }, + date: { Date(timeIntervalSince1970: 1_234_567_890) }, + timeZone: { "EST" }, + appContextInfo: { AppContextInfo.test }, + klaviyoAPI: KlaviyoAPI.test(), + timer: { _ in Just(Date()).eraseToAnyPublisher() }) + } +} + +extension FileClient { + static let test = FileClient( + write: { _, _ in }, + fileExists: { _ in true }, + removeItem: { _ in }, + libraryDirectory: { TEST_URL }) +} + +extension KlaviyoAPI { + static let test = { KlaviyoAPI(send: { _, _ in .success(TEST_RETURN_DATA) }) } +} + +extension LoggerClient { + static var lastLoggedMessage: String? + static let test = LoggerClient { message in + lastLoggedMessage = message + } +} + +extension AppLifeCycleEvents { + static let test = Self(lifeCycleEvents: { Empty().eraseToAnyPublisher() }) +} + +extension NetworkSession { + static let successfulRepsonse = HTTPURLResponse(url: TEST_URL, statusCode: 200, httpVersion: nil, headerFields: nil)! + static let DEFAULT_CALLBACK: (URLRequest) async throws -> (Data, URLResponse) = { _ in + (Data(), successfulRepsonse) + } + + static func test(data: @escaping (URLRequest) async throws -> (Data, URLResponse) = DEFAULT_CALLBACK) -> NetworkSession { + NetworkSession(data: data) + } +} + +class TestJSONDecoder: JSONDecoder { + override func decode(_: T.Type, from _: Data) throws -> T where T: Decodable { + AppLifeCycleEvents.test as! T + } +} + +extension AppContextInfo { + static let test = Self(executable: "FooApp", + bundleId: "com.klaviyo.fooapp", + appVersion: "1.2.3", + appBuild: "1", + appName: "FooApp", + version: OperatingSystemVersion(majorVersion: 1, minorVersion: 1, patchVersion: 1), + osName: "iOS", + manufacturer: "Orange", + deviceModel: "jPhone 1,1", + deviceId: "fe-fi-fo-fum") +} diff --git a/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift b/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift index d332b241..1bfee09e 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift @@ -16,51 +16,6 @@ enum FakeFileError: Error { } let ARCHIVED_RETURNED_DATA = Data() -let SAMPLE_DATA: NSMutableArray = [ - [ - "properties": [ - "foo": "bar" - ] - ] -] -let TEST_URL = URL(string: "fake_url")! -let TEST_RETURN_DATA = Data() - -let TEST_FAILURE_JSON_INVALID_PHONE_NUMBER = """ -{ - "errors": [ - { - "id": "9997bd4f-7d5f-4f01-bbd1-df0065ef4faa", - "status": 400, - "code": "invalid", - "title": "Invalid input.", - "detail": "Invalid phone number format (Example of a valid format: +12345678901)", - "source": { - "pointer": "/data/attributes/phone_number" - }, - "meta": {} - } - ] -} -""" - -let TEST_FAILURE_JSON_INVALID_EMAIL = """ -{ - "errors": [ - { - "id": "dce2d180-0f36-4312-aa6d-92d025c17147", - "status": 400, - "code": "invalid", - "title": "Invalid input.", - "detail": "Invalid email address", - "source": { - "pointer": "/data/attributes/email" - }, - "meta": {} - } - ] -} -""" extension ArchiverClient { static let test = ArchiverClient( diff --git a/Tests/KlaviyoSwiftTests/TestData.swift b/Tests/KlaviyoSwiftTests/TestData.swift index 90e54853..b086fd53 100644 --- a/Tests/KlaviyoSwiftTests/TestData.swift +++ b/Tests/KlaviyoSwiftTests/TestData.swift @@ -154,3 +154,49 @@ extension KlaviyoState { initalizationState: .initialized, flushing: true) } + +let SAMPLE_DATA: NSMutableArray = [ + [ + "properties": [ + "foo": "bar" + ] + ] +] +let TEST_URL = URL(string: "fake_url")! +let TEST_RETURN_DATA = Data() + +let TEST_FAILURE_JSON_INVALID_PHONE_NUMBER = """ +{ + "errors": [ + { + "id": "9997bd4f-7d5f-4f01-bbd1-df0065ef4faa", + "status": 400, + "code": "invalid", + "title": "Invalid input.", + "detail": "Invalid phone number format (Example of a valid format: +12345678901)", + "source": { + "pointer": "/data/attributes/phone_number" + }, + "meta": {} + } + ] +} +""" + +let TEST_FAILURE_JSON_INVALID_EMAIL = """ +{ + "errors": [ + { + "id": "dce2d180-0f36-4312-aa6d-92d025c17147", + "status": 400, + "code": "invalid", + "title": "Invalid input.", + "detail": "Invalid email address", + "source": { + "pointer": "/data/attributes/email" + }, + "meta": {} + } + ] +} +""" From bf397781aef4d9d49574ce9bdcfef4e6757cbb19 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 15 Aug 2024 15:27:03 -0500 Subject: [PATCH 31/72] moved file utils --- .../{KlaviyoSwiftTests => KlaviyoCoreTests}/FileUtilsTests.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/FileUtilsTests.swift (100%) diff --git a/Tests/KlaviyoSwiftTests/FileUtilsTests.swift b/Tests/KlaviyoCoreTests/FileUtilsTests.swift similarity index 100% rename from Tests/KlaviyoSwiftTests/FileUtilsTests.swift rename to Tests/KlaviyoCoreTests/FileUtilsTests.swift From 041319b519334d9845540abda0091197eb4b8a78 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 15 Aug 2024 15:48:12 -0500 Subject: [PATCH 32/72] moved network session tests --- Package.swift | 7 +++++- .../NetworkSessionTests.swift | 1 - .../SimpleMockURLProtocol.swift | 0 Tests/KlaviyoCoreTests/TestUtils.swift | 13 +++++++++++ .../testCreateEmphemeralSesionHeaders.1.txt | 23 +++++++++++++++++++ .../testDefaultUserAgent.1.txt | 1 + .../testSessionDataTask.1.txt | 1 + .../testSessionDataTask.2.txt | 2 ++ 8 files changed, 46 insertions(+), 2 deletions(-) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/NetworkSessionTests.swift (97%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/SimpleMockURLProtocol.swift (100%) create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.2.txt diff --git a/Package.swift b/Package.swift index edcad818..e27d82d7 100644 --- a/Package.swift +++ b/Package.swift @@ -33,7 +33,12 @@ let package = Package( path: "Sources/KlaviyoCore"), .testTarget( name: "KlaviyoCoreTests", - dependencies: ["KlaviyoCore"]), + dependencies: [ + "KlaviyoCore", + .product(name: "SnapshotTesting", package: "swift-snapshot-testing"), + .product(name: "CustomDump", package: "swift-custom-dump"), + .product(name: "CasePaths", package: "swift-case-paths") + ]), .target( name: "KlaviyoSwift", dependencies: [.product(name: "AnyCodable", package: "AnyCodable"), "KlaviyoCore"], diff --git a/Tests/KlaviyoSwiftTests/NetworkSessionTests.swift b/Tests/KlaviyoCoreTests/NetworkSessionTests.swift similarity index 97% rename from Tests/KlaviyoSwiftTests/NetworkSessionTests.swift rename to Tests/KlaviyoCoreTests/NetworkSessionTests.swift index 55efac16..0423d516 100644 --- a/Tests/KlaviyoSwiftTests/NetworkSessionTests.swift +++ b/Tests/KlaviyoCoreTests/NetworkSessionTests.swift @@ -5,7 +5,6 @@ // Created by Noah Durell on 11/18/22. // -@testable import KlaviyoSwift import KlaviyoCore import SnapshotTesting import XCTest diff --git a/Tests/KlaviyoSwiftTests/SimpleMockURLProtocol.swift b/Tests/KlaviyoCoreTests/SimpleMockURLProtocol.swift similarity index 100% rename from Tests/KlaviyoSwiftTests/SimpleMockURLProtocol.swift rename to Tests/KlaviyoCoreTests/SimpleMockURLProtocol.swift diff --git a/Tests/KlaviyoCoreTests/TestUtils.swift b/Tests/KlaviyoCoreTests/TestUtils.swift index 4e208224..e8189a84 100644 --- a/Tests/KlaviyoCoreTests/TestUtils.swift +++ b/Tests/KlaviyoCoreTests/TestUtils.swift @@ -149,3 +149,16 @@ extension AppContextInfo { deviceModel: "jPhone 1,1", deviceId: "fe-fi-fo-fum") } + +extension URLResponse { + static let non200Response = HTTPURLResponse(url: TEST_URL, statusCode: 500, httpVersion: nil, headerFields: nil)! + static let validResponse = HTTPURLResponse(url: TEST_URL, statusCode: 200, httpVersion: nil, headerFields: nil)! +} + +extension PushTokenPayload { + static let test = PushTokenPayload( + pushToken: "foo", + enablement: "AUTHORIZED", + background: "AVAILABLE", + profile: ProfilePayload(anonymousId: "anon-id")) +} diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt new file mode 100644 index 00000000..19cd06cb --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt @@ -0,0 +1,23 @@ +▿ Optional> + ▿ some: 6 key/value pairs + ▿ (2 elements) + - key: "Accept-Encoding" + ▿ value: 3 elements + - "br" + - "gzip" + - "deflate" + ▿ (2 elements) + - key: "User-Agent" + - value: "FooApp/1.2.3 (com.klaviyo.fooapp; build:1; iOS 1.1.1) klaviyo-ios/3.2.0" + ▿ (2 elements) + - key: "X-Klaviyo-Mobile" + - value: "1" + ▿ (2 elements) + - key: "accept" + - value: "application/json" + ▿ (2 elements) + - key: "content-type" + - value: "application/json" + ▿ (2 elements) + - key: "revision" + - value: "2023-07-15" diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt new file mode 100644 index 00000000..40374900 --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt @@ -0,0 +1 @@ +- "FooApp/1.2.3 (com.klaviyo.fooapp; build:1; iOS 1.1.1) klaviyo-ios/3.2.0" diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt new file mode 100644 index 00000000..eff1843b --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt @@ -0,0 +1 @@ +- 0 bytes diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.2.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.2.txt new file mode 100644 index 00000000..2d85f9c7 --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.2.txt @@ -0,0 +1,2 @@ +- { URL: fake_url } { Status Code: 200, Headers { +} } From 5a889ec41c04352f8dac9e48807a1c6541252856 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 15 Aug 2024 16:37:32 -0500 Subject: [PATCH 33/72] moved klaviyo api tests --- Tests/KlaviyoCoreTests/EncodableTests.swift | 57 +++++++++++++++++++ .../KlaviyoAPITests.swift | 11 ++-- Tests/KlaviyoCoreTests/TestUtils.swift | 24 ++++++++ .../testEventPayloadWithoutMetadata.1.json | 4 +- .../EncodableTests/testKlaviyoRequest.1.json | 2 +- .../EncodableTests/testProfilePayload.1.json | 13 ++--- .../EncodableTests/testTokenPayload.1.json | 2 +- .../testUnregisterTokenPayload.1.json | 2 +- .../KlaviyoAPITests/testEncodingError.1.txt | 49 ++++++++++++++++ .../testInvalidStatusCode.1.txt | 0 .../KlaviyoAPITests/testInvalidURL.1.txt | 2 +- .../KlaviyoAPITests/testNetworkError.1.txt | 0 .../testSuccessfulResponseWithEvent.1.txt | 0 .../testSuccessfulResponseWithEvent.2.txt | 0 .../testSuccessfulResponseWithProfile.1.txt | 0 .../testSuccessfulResponseWithProfile.2.txt | 0 ...testSuccessfulResponseWithStoreToken.1.txt | 0 ...testSuccessfulResponseWithStoreToken.2.txt | 0 Tests/KlaviyoSwiftTests/EncodableTests.swift | 53 ++--------------- .../EncodableTests/testKlaviyoState.1.json | 34 +++++------ .../KlaviyoAPITests/testEncodingError.1.txt | 22 ------- 21 files changed, 168 insertions(+), 107 deletions(-) create mode 100644 Tests/KlaviyoCoreTests/EncodableTests.swift rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/KlaviyoAPITests.swift (91%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json (94%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json (99%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/EncodableTests/testProfilePayload.1.json (79%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/EncodableTests/testTokenPayload.1.json (99%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json (99%) create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt (100%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt (65%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt (100%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt (100%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt (100%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt (100%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt (100%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt (100%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt (100%) delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt diff --git a/Tests/KlaviyoCoreTests/EncodableTests.swift b/Tests/KlaviyoCoreTests/EncodableTests.swift new file mode 100644 index 00000000..5814a3a1 --- /dev/null +++ b/Tests/KlaviyoCoreTests/EncodableTests.swift @@ -0,0 +1,57 @@ +// +// EncodableTests.swift +// +// +// Created by Noah Durell on 11/14/22. +// + +import KlaviyoCore +import SnapshotTesting +import XCTest + +final class EncodableTests: XCTestCase { + let testEncoder = KlaviyoEnvironment.encoder + + override func setUpWithError() throws { + environment = KlaviyoEnvironment.test() + testEncoder.outputFormatting = .prettyPrinted.union(.sortedKeys) + } + + func testProfilePayload() throws { + let payload = CreateProfilePayload(data: .test) + assertSnapshot(matching: payload, as: .json(KlaviyoEnvironment.encoder)) + } + + func testEventPayloadWithoutMetadata() throws { + let createEventPayload = CreateEventPayload(data: CreateEventPayload.Event(name: "test", anonymousId: "anon-id")) + assertSnapshot(matching: createEventPayload, as: .json(KlaviyoEnvironment.encoder)) + } + + func testTokenPayload() throws { + let tokenPayload = PushTokenPayload( + pushToken: "foo", + enablement: "AUTHORIZED", + background: "AVAILABLE", + profile: ProfilePayload(email: "foo", phoneNumber: "foo", anonymousId: "foo")) + assertSnapshot(matching: tokenPayload, as: .json(KlaviyoEnvironment.encoder)) + } + + func testUnregisterTokenPayload() throws { + let tokenPayload = UnregisterPushTokenPayload( + pushToken: "foo", + email: "foo", + phoneNumber: "foo", + anonymousId: "foo") + assertSnapshot(matching: tokenPayload, as: .json) + } + + func testKlaviyoRequest() throws { + let tokenPayload = PushTokenPayload( + pushToken: "foo", + enablement: "AUTHORIZED", + background: "AVAILABLE", + profile: ProfilePayload(email: "foo", phoneNumber: "foo", anonymousId: "foo")) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) + assertSnapshot(matching: request, as: .json) + } +} diff --git a/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift b/Tests/KlaviyoCoreTests/KlaviyoAPITests.swift similarity index 91% rename from Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift rename to Tests/KlaviyoCoreTests/KlaviyoAPITests.swift index dcf4cfc1..3857408f 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoAPITests.swift +++ b/Tests/KlaviyoCoreTests/KlaviyoAPITests.swift @@ -5,7 +5,6 @@ // Created by Noah Durell on 11/16/22. // -@testable import KlaviyoSwift import KlaviyoCore import SnapshotTesting import XCTest @@ -23,7 +22,7 @@ final class KlaviyoAPITests: XCTestCase { await sendAndAssert(with: KlaviyoRequest( apiKey: "foo", - endpoint: .createProfile(CreateProfilePayload(data: Profile.test.toAPIModel(anonymousId: "foo")))) + endpoint: .createProfile(CreateProfilePayload(data: .test))) ) { result in switch result { case let .failure(error): @@ -37,7 +36,7 @@ final class KlaviyoAPITests: XCTestCase { func testEncodingError() async throws { environment.encodeJSON = { _ in throw EncodingError.invalidValue("foo", .init(codingPath: [], debugDescription: "invalid")) } - let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(CreateProfilePayload(data: Profile().toAPIModel(anonymousId: "foo")))) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(CreateProfilePayload(data: .test))) await sendAndAssert(with: request) { result in switch result { @@ -53,7 +52,7 @@ final class KlaviyoAPITests: XCTestCase { environment.networkSession = { NetworkSession.test(data: { _ in throw NSError(domain: "network error", code: 0) }) } - let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(CreateProfilePayload(data: Profile().toAPIModel(anonymousId: "foo")))) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(CreateProfilePayload(data: .test))) await sendAndAssert(with: request) { result in switch result { @@ -69,7 +68,7 @@ final class KlaviyoAPITests: XCTestCase { environment.networkSession = { NetworkSession.test(data: { _ in (Data(), .non200Response) }) } - let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(CreateProfilePayload(data: Profile().toAPIModel(anonymousId: "foo")))) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(CreateProfilePayload(data: .test))) await sendAndAssert(with: request) { result in switch result { @@ -86,7 +85,7 @@ final class KlaviyoAPITests: XCTestCase { assertSnapshot(matching: request, as: .dump) return (Data(), .validResponse) }) } - let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(CreateProfilePayload(data: Profile().toAPIModel(anonymousId: "foo")))) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(CreateProfilePayload(data: .test))) await sendAndAssert(with: request) { result in switch result { diff --git a/Tests/KlaviyoCoreTests/TestUtils.swift b/Tests/KlaviyoCoreTests/TestUtils.swift index e8189a84..a212c6e0 100644 --- a/Tests/KlaviyoCoreTests/TestUtils.swift +++ b/Tests/KlaviyoCoreTests/TestUtils.swift @@ -162,3 +162,27 @@ extension PushTokenPayload { background: "AVAILABLE", profile: ProfilePayload(anonymousId: "anon-id")) } + +extension ProfilePayload { + static let location = ProfilePayload.Attributes.Location( + address1: "blob", + address2: "blob", + city: "blob city", + country: "Blobland", + latitude: 1, + longitude: 1, + region: "BL", + zip: "0BLOB") + + static let test = ProfilePayload( + email: "blobemail", + phoneNumber: "+15555555555", + externalId: "blobid", + firstName: "Blob", + lastName: "Junior", + organization: "Blobco", + title: "Jelly", + image: "foo", + location: location, + anonymousId: "foo") +} diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json similarity index 94% rename from Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json rename to Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json index 1de491fa..436e0823 100644 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json @@ -4,7 +4,7 @@ "metric" : { "data" : { "attributes" : { - "name" : "blob" + "name" : "test" }, "type" : "metric" } @@ -24,4 +24,4 @@ }, "type" : "event" } -} \ No newline at end of file +} diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json similarity index 99% rename from Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json rename to Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json index dfac3ffa..0902a3b9 100644 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json @@ -42,4 +42,4 @@ } }, "uuid" : "00000000-0000-0000-0000-000000000001" -} \ No newline at end of file +} diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testProfilePayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json similarity index 79% rename from Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testProfilePayload.1.json rename to Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json index 8cf9d3c9..31137db8 100644 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testProfilePayload.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json @@ -2,6 +2,8 @@ "data" : { "attributes" : { "anonymous_id" : "foo", + "email" : "blobemail", + "external_id" : "blobid", "first_name" : "Blob", "image" : "foo", "last_name" : "Junior", @@ -17,15 +19,10 @@ "zip" : "0BLOB" }, "organization" : "Blobco", - "properties" : { - "blob" : "blob", - "hello" : { - "sub" : "dict" - }, - "stuff" : 2 - }, + "phone_number" : "+15555555555", + "properties" : null, "title" : "Jelly" }, "type" : "profile" } -} \ No newline at end of file +} diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testTokenPayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json similarity index 99% rename from Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testTokenPayload.1.json rename to Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json index 729d22ad..0e64c3ab 100644 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testTokenPayload.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json @@ -34,4 +34,4 @@ }, "type" : "push-token" } -} \ No newline at end of file +} diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json similarity index 99% rename from Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json rename to Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json index 63111f09..f65ceca1 100644 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json @@ -18,4 +18,4 @@ }, "type" : "push-token-unregister" } -} \ No newline at end of file +} diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt new file mode 100644 index 00000000..0c9910e5 --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt @@ -0,0 +1,49 @@ +▿ KlaviyoAPIError + ▿ internalRequestError: KlaviyoAPIError + ▿ dataEncodingError: KlaviyoRequest + - apiKey: "foo" + ▿ endpoint: KlaviyoEndpoint + ▿ createProfile: CreateProfilePayload + ▿ data: ProfilePayload + ▿ attributes: Attributes + - anonymousId: "foo" + ▿ email: Optional + - some: "blobemail" + ▿ externalId: Optional + - some: "blobid" + ▿ firstName: Optional + - some: "Blob" + ▿ image: Optional + - some: "foo" + ▿ lastName: Optional + - some: "Junior" + ▿ location: Optional + ▿ some: Location + ▿ address1: Optional + - some: "blob" + ▿ address2: Optional + - some: "blob" + ▿ city: Optional + - some: "blob city" + ▿ country: Optional + - some: "Blobland" + ▿ latitude: Optional + - some: 1.0 + ▿ longitude: Optional + - some: 1.0 + ▿ region: Optional + - some: "BL" + ▿ timezone: Optional + - some: "EST" + ▿ zip: Optional + - some: "0BLOB" + ▿ organization: Optional + - some: "Blobco" + ▿ phoneNumber: Optional + - some: "+15555555555" + ▿ properties: nil + - value: (0 elements) + ▿ title: Optional + - some: "Jelly" + - type: "profile" + - uuid: "00000000-0000-0000-0000-000000000001" diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt similarity index 100% rename from Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt rename to Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt similarity index 65% rename from Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt rename to Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt index 675c1934..da5e392f 100644 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt +++ b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt @@ -1 +1 @@ -internalRequestError(KlaviyoCore.KlaviyoAPIError.internalError("Invalid url string. API URL: ")) \ No newline at end of file +internalRequestError(KlaviyoCore.KlaviyoAPIError.internalError("Invalid url string. API URL: ")) diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt similarity index 100% rename from Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt rename to Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt similarity index 100% rename from Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt rename to Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt similarity index 100% rename from Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt rename to Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt similarity index 100% rename from Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt rename to Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt similarity index 100% rename from Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt rename to Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt similarity index 100% rename from Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt rename to Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt similarity index 100% rename from Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt rename to Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt diff --git a/Tests/KlaviyoSwiftTests/EncodableTests.swift b/Tests/KlaviyoSwiftTests/EncodableTests.swift index fe30d828..60654bfe 100644 --- a/Tests/KlaviyoSwiftTests/EncodableTests.swift +++ b/Tests/KlaviyoSwiftTests/EncodableTests.swift @@ -2,59 +2,26 @@ // EncodableTests.swift // // -// Created by Noah Durell on 11/14/22. +// Created by Ajay Subramanya on 8/15/24. // +import Foundation + +@testable import KlaviyoCore @testable import KlaviyoSwift -import KlaviyoCore import SnapshotTesting import XCTest final class EncodableTests: XCTestCase { let testEncoder = KlaviyoEnvironment.encoder - override func setUpWithError() throws { - environment = KlaviyoEnvironment.test() - testEncoder.outputFormatting = .prettyPrinted.union(.sortedKeys) - } - - func testProfilePayload() throws { - let profile = Profile.test - let payload = CreateProfilePayload(data: profile.toAPIModel(anonymousId: "foo")) - assertSnapshot(matching: payload, as: .json(KlaviyoEnvironment.encoder)) - } - - func testEventPayloadWithoutMetadata() throws { - let event = Event.test - let createEventPayload = CreateEventPayload(data: CreateEventPayload.Event(name: event.metric.name.value, anonymousId: "anon-id")) - assertSnapshot(matching: createEventPayload, as: .json(KlaviyoEnvironment.encoder)) - } - - func testTokenPayload() throws { - let tokenPayload = PushTokenPayload( - pushToken: "foo", - enablement: "AUTHORIZED", - background: "AVAILABLE", - profile: ProfilePayload(email: "foo", phoneNumber: "foo", anonymousId: "foo")) - assertSnapshot(matching: tokenPayload, as: .json(KlaviyoEnvironment.encoder)) - } - - func testUnregisterTokenPayload() throws { - let tokenPayload = UnregisterPushTokenPayload( - pushToken: "foo", - email: "foo", - phoneNumber: "foo", - anonymousId: "foo") - assertSnapshot(matching: tokenPayload, as: .json) - } - func testKlaviyoState() throws { let tokenPayload = PushTokenPayload( pushToken: "foo", enablement: "AUTHORIZED", background: "AVAILABLE", profile: ProfilePayload(email: "foo", phoneNumber: "foo", anonymousId: "foo")) - let request = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload), uuid: environment.uuid().uuidString) let klaviyoState = KlaviyoState( email: "foo", anonymousId: "foo", @@ -68,14 +35,4 @@ final class EncodableTests: XCTestCase { requestsInFlight: [request]) assertSnapshot(matching: klaviyoState, as: .json) } - - func testKlaviyoRequest() throws { - let tokenPayload = PushTokenPayload( - pushToken: "foo", - enablement: "AUTHORIZED", - background: "AVAILABLE", - profile: ProfilePayload(email: "foo", phoneNumber: "foo", anonymousId: "foo")) - let request = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) - assertSnapshot(matching: request, as: .json) - } } diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json index ef713322..1059cbbf 100644 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json @@ -4,17 +4,17 @@ "phoneNumber" : "foo", "pushTokenData" : { "deviceData" : { - "app_build" : "1", - "app_id" : "com.klaviyo.fooapp", - "app_name" : "FooApp", - "app_version" : "1.2.3", - "device_id" : "fe-fi-fo-fum", - "device_model" : "jPhone 1,1", + "app_build" : "22189", + "app_id" : "com.apple.dt.xctest.tool", + "app_name" : "xctest", + "app_version" : "15.0", + "device_id" : "278E698D-7E37-477E-BD83-60BB4FCF404E", + "device_model" : "arm64", "environment" : "debug", "klaviyo_sdk" : "swift", - "manufacturer" : "Orange", + "manufacturer" : "Apple", "os_name" : "iOS", - "os_version" : "1.1.1", + "os_version" : "17.0.0", "sdk_version" : "3.2.0" }, "pushBackground" : "AVAILABLE", @@ -31,17 +31,17 @@ "attributes" : { "background" : "AVAILABLE", "device_metadata" : { - "app_build" : "1", - "app_id" : "com.klaviyo.fooapp", - "app_name" : "FooApp", - "app_version" : "1.2.3", - "device_id" : "fe-fi-fo-fum", - "device_model" : "jPhone 1,1", + "app_build" : "22189", + "app_id" : "com.apple.dt.xctest.tool", + "app_name" : "xctest", + "app_version" : "15.0", + "device_id" : "278E698D-7E37-477E-BD83-60BB4FCF404E", + "device_model" : "arm64", "environment" : "debug", "klaviyo_sdk" : "swift", - "manufacturer" : "Orange", + "manufacturer" : "Apple", "os_name" : "iOS", - "os_version" : "1.1.1", + "os_version" : "17.0.0", "sdk_version" : "3.2.0" }, "enablement_status" : "AUTHORIZED", @@ -65,7 +65,7 @@ } } }, - "uuid" : "00000000-0000-0000-0000-000000000001" + "uuid" : "7C626F6D-0239-4049-B0E6-67038C781106" } ] } \ No newline at end of file diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt b/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt deleted file mode 100644 index d415f44e..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt +++ /dev/null @@ -1,22 +0,0 @@ -▿ KlaviyoAPIError - ▿ internalRequestError: KlaviyoAPIError - ▿ dataEncodingError: KlaviyoRequest - - apiKey: "foo" - ▿ endpoint: KlaviyoEndpoint - ▿ createProfile: CreateProfilePayload - ▿ data: ProfilePayload - ▿ attributes: Attributes - - anonymousId: "foo" - - email: Optional.none - - externalId: Optional.none - - firstName: Optional.none - - image: Optional.none - - lastName: Optional.none - - location: Optional.none - - organization: Optional.none - - phoneNumber: Optional.none - ▿ properties: [:] - - value: 0 key/value pairs - - title: Optional.none - - type: "profile" - - uuid: "00000000-0000-0000-0000-000000000001" From de80de0f511f0f20df42728f04e8e4db7df67fc6 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Mon, 19 Aug 2024 11:08:12 -0500 Subject: [PATCH 34/72] some test clean up --- Tests/KlaviyoCoreTests/TestUtils.swift | 5 +- Tests/KlaviyoSwiftTests/EncodableTests.swift | 4 +- .../KlaviyoSwiftTests/KlaviyoTestUtils.swift | 4 -- Tests/KlaviyoSwiftTests/TestData.swift | 21 +----- .../EncodableTests/testKlaviyoState.1.json | 72 +------------------ 5 files changed, 8 insertions(+), 98 deletions(-) diff --git a/Tests/KlaviyoCoreTests/TestUtils.swift b/Tests/KlaviyoCoreTests/TestUtils.swift index a212c6e0..15f58638 100644 --- a/Tests/KlaviyoCoreTests/TestUtils.swift +++ b/Tests/KlaviyoCoreTests/TestUtils.swift @@ -5,6 +5,7 @@ // Created by Ajay Subramanya on 8/15/24. // +import AnyCodable import Combine import Foundation import KlaviyoCore @@ -160,7 +161,7 @@ extension PushTokenPayload { pushToken: "foo", enablement: "AUTHORIZED", background: "AVAILABLE", - profile: ProfilePayload(anonymousId: "anon-id")) + profile: ProfilePayload(properties: AnyCodable([:]), anonymousId: "anon-id")) } extension ProfilePayload { @@ -183,6 +184,6 @@ extension ProfilePayload { organization: "Blobco", title: "Jelly", image: "foo", - location: location, + location: location, properties: AnyCodable([:]), anonymousId: "foo") } diff --git a/Tests/KlaviyoSwiftTests/EncodableTests.swift b/Tests/KlaviyoSwiftTests/EncodableTests.swift index 60654bfe..14cb7d2b 100644 --- a/Tests/KlaviyoSwiftTests/EncodableTests.swift +++ b/Tests/KlaviyoSwiftTests/EncodableTests.swift @@ -26,13 +26,13 @@ final class EncodableTests: XCTestCase { email: "foo", anonymousId: "foo", phoneNumber: "foo", - pushTokenData: .init( + pushTokenData: KlaviyoState.PushTokenData( pushToken: "foo", pushEnablement: .authorized, pushBackground: .available, deviceData: .init(context: environment.appContextInfo())), queue: [request], requestsInFlight: [request]) - assertSnapshot(matching: klaviyoState, as: .json) + assertSnapshot(matching: klaviyoState, as: .json(KlaviyoEnvironment.encoder)) } } diff --git a/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift b/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift index 1bfee09e..ac7c5aa8 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift @@ -11,10 +11,6 @@ import XCTest import CombineSchedulers import KlaviyoCore -enum FakeFileError: Error { - case fake -} - let ARCHIVED_RETURNED_DATA = Data() extension ArchiverClient { diff --git a/Tests/KlaviyoSwiftTests/TestData.swift b/Tests/KlaviyoSwiftTests/TestData.swift index b086fd53..10224b79 100644 --- a/Tests/KlaviyoSwiftTests/TestData.swift +++ b/Tests/KlaviyoSwiftTests/TestData.swift @@ -83,7 +83,7 @@ extension Profile { title: "Jelly", image: "foo", location: .test, - properties: SAMPLE_PROPERTIES) + properties: [:]) } extension Profile.Location { @@ -114,30 +114,13 @@ extension Event { "Device Manufacturer": "Orange", "Device Model": "jPhone 1,1" ] as [String: Any] - static let test = Self(name: .CustomEvent("blob"), properties: SAMPLE_PROPERTIES, time: KlaviyoEnvironment.test().date()) + static let test = Self(name: .CustomEvent("blob"), properties: nil, time: KlaviyoEnvironment.test().date()) } extension Event.Metric { static let test = Self(name: .CustomEvent("blob")) } -extension CreateEventPayload { - static let test = CreateEventPayload(data: Event(name: "test")) -} - -extension URLResponse { - static let non200Response = HTTPURLResponse(url: TEST_URL, statusCode: 500, httpVersion: nil, headerFields: nil)! - static let validResponse = HTTPURLResponse(url: TEST_URL, statusCode: 200, httpVersion: nil, headerFields: nil)! -} - -extension PushTokenPayload { - static let test = PushTokenPayload( - pushToken: "foo", - enablement: "AUTHORIZED", - background: "AVAILABLE", - profile: ProfilePayload(anonymousId: "anon-id")) -} - extension KlaviyoState { static let test = KlaviyoState(apiKey: "foo", email: "test@test.com", diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json index 1059cbbf..6ab2a369 100644 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json @@ -1,71 +1 @@ -{ - "anonymousId" : "foo", - "email" : "foo", - "phoneNumber" : "foo", - "pushTokenData" : { - "deviceData" : { - "app_build" : "22189", - "app_id" : "com.apple.dt.xctest.tool", - "app_name" : "xctest", - "app_version" : "15.0", - "device_id" : "278E698D-7E37-477E-BD83-60BB4FCF404E", - "device_model" : "arm64", - "environment" : "debug", - "klaviyo_sdk" : "swift", - "manufacturer" : "Apple", - "os_name" : "iOS", - "os_version" : "17.0.0", - "sdk_version" : "3.2.0" - }, - "pushBackground" : "AVAILABLE", - "pushEnablement" : "AUTHORIZED", - "pushToken" : "foo" - }, - "queue" : [ - { - "apiKey" : "foo", - "endpoint" : { - "registerPushToken" : { - "_0" : { - "data" : { - "attributes" : { - "background" : "AVAILABLE", - "device_metadata" : { - "app_build" : "22189", - "app_id" : "com.apple.dt.xctest.tool", - "app_name" : "xctest", - "app_version" : "15.0", - "device_id" : "278E698D-7E37-477E-BD83-60BB4FCF404E", - "device_model" : "arm64", - "environment" : "debug", - "klaviyo_sdk" : "swift", - "manufacturer" : "Apple", - "os_name" : "iOS", - "os_version" : "17.0.0", - "sdk_version" : "3.2.0" - }, - "enablement_status" : "AUTHORIZED", - "platform" : "ios", - "profile" : { - "data" : { - "attributes" : { - "anonymous_id" : "foo", - "email" : "foo", - "phone_number" : "foo", - "properties" : null - }, - "type" : "profile" - } - }, - "token" : "foo", - "vendor" : "APNs" - }, - "type" : "push-token" - } - } - } - }, - "uuid" : "7C626F6D-0239-4049-B0E6-67038C781106" - } - ] -} \ No newline at end of file +{"anonymousId":"foo","email":"foo","pushTokenData":{"pushBackground":"AVAILABLE","pushToken":"foo","pushEnablement":"AUTHORIZED","deviceData":{"os_version":"17.0.0","app_version":"15.0","environment":"debug","klaviyo_sdk":"swift","device_model":"arm64","app_id":"com.apple.dt.xctest.tool","app_build":"22189","sdk_version":"3.2.0","device_id":"278E698D-7E37-477E-BD83-60BB4FCF404E","os_name":"iOS","manufacturer":"Apple","app_name":"xctest"}},"phoneNumber":"foo","queue":[{"apiKey":"foo","uuid":"294B9706-602D-4E5A-B34E-27A08F88AC14","endpoint":{"registerPushToken":{"_0":{"data":{"attributes":{"background":"AVAILABLE","device_metadata":{"app_name":"xctest","app_id":"com.apple.dt.xctest.tool","environment":"debug","device_model":"arm64","os_name":"iOS","device_id":"278E698D-7E37-477E-BD83-60BB4FCF404E","sdk_version":"3.2.0","os_version":"17.0.0","manufacturer":"Apple","app_version":"15.0","app_build":"22189","klaviyo_sdk":"swift"},"platform":"ios","vendor":"APNs","profile":{"data":{"attributes":{"email":"foo","phone_number":"foo","anonymous_id":"foo","properties":null},"type":"profile"}},"token":"foo","enablement_status":"AUTHORIZED"},"type":"push-token"}}}}}]} \ No newline at end of file From 8551f78128f4cf0fca387d199fa0fcef1bdd9d4b Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Mon, 19 Aug 2024 11:20:06 -0500 Subject: [PATCH 35/72] fixed couple tests --- .../KlaviyoSwift/Models/ProfileAPIExtension.swift | 12 +++++++++--- Tests/KlaviyoCoreTests/FileUtilsTests.swift | 1 - Tests/KlaviyoCoreTests/TestUtils.swift | 5 +++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift b/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift index f652a12b..0d39edd9 100644 --- a/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift +++ b/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift @@ -8,6 +8,12 @@ import Foundation import KlaviyoCore +extension String { + fileprivate func returnNilIfEmpty() -> String? { + isEmpty ? nil : self + } +} + extension Profile { func toAPIModel( email: String? = nil, @@ -15,9 +21,9 @@ extension Profile { externalId: String? = nil, anonymousId: String) -> ProfilePayload { ProfilePayload( - email: email, - phoneNumber: phoneNumber, - externalId: externalId, + email: email ?? self.email?.returnNilIfEmpty(), + phoneNumber: phoneNumber ?? self.phoneNumber?.returnNilIfEmpty(), + externalId: externalId ?? self.externalId?.returnNilIfEmpty(), firstName: firstName, lastName: lastName, organization: organization, diff --git a/Tests/KlaviyoCoreTests/FileUtilsTests.swift b/Tests/KlaviyoCoreTests/FileUtilsTests.swift index a3554169..3251bafe 100644 --- a/Tests/KlaviyoCoreTests/FileUtilsTests.swift +++ b/Tests/KlaviyoCoreTests/FileUtilsTests.swift @@ -5,7 +5,6 @@ // Created by Noah Durell on 9/29/22. // -@testable import KlaviyoSwift import KlaviyoCore import XCTest diff --git a/Tests/KlaviyoCoreTests/TestUtils.swift b/Tests/KlaviyoCoreTests/TestUtils.swift index 15f58638..a43de4f1 100644 --- a/Tests/KlaviyoCoreTests/TestUtils.swift +++ b/Tests/KlaviyoCoreTests/TestUtils.swift @@ -161,7 +161,7 @@ extension PushTokenPayload { pushToken: "foo", enablement: "AUTHORIZED", background: "AVAILABLE", - profile: ProfilePayload(properties: AnyCodable([:]), anonymousId: "anon-id")) + profile: ProfilePayload(properties: [:], anonymousId: "anon-id")) } extension ProfilePayload { @@ -184,6 +184,7 @@ extension ProfilePayload { organization: "Blobco", title: "Jelly", image: "foo", - location: location, properties: AnyCodable([:]), + location: location, + properties: [:], anonymousId: "foo") } From ef55c305a73b6e984344c73a61067463169584ba Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Mon, 19 Aug 2024 16:55:52 -0500 Subject: [PATCH 36/72] fixed 2 more tests --- Sources/KlaviyoCore/KlaviyoEnvironment.swift | 2 +- Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Sources/KlaviyoCore/KlaviyoEnvironment.swift b/Sources/KlaviyoCore/KlaviyoEnvironment.swift index 1478ba8a..b79aa1e3 100644 --- a/Sources/KlaviyoCore/KlaviyoEnvironment.swift +++ b/Sources/KlaviyoCore/KlaviyoEnvironment.swift @@ -114,7 +114,7 @@ public struct KlaviyoEnvironment { public var klaviyoAPI: KlaviyoAPI public var timer: (Double) -> AnyPublisher - static var production = KlaviyoEnvironment( + public static var production = KlaviyoEnvironment( archiverClient: ArchiverClient.production, fileClient: FileClient.production, dataFromUrl: { url in try Data(contentsOf: url) }, diff --git a/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift b/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift index b964d496..29224e48 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift @@ -130,8 +130,10 @@ final class KlaviyoStateTests: XCTestCase { let event = Event.test let createEventPayload = CreateEventPayload(data: CreateEventPayload.Event(name: event.metric.name.value)) let eventRequest = KlaviyoRequest(apiKey: "foo", endpoint: .createEvent(createEventPayload)) + let profile = Profile.test let payload = CreateProfilePayload(data: profile.toAPIModel(anonymousId: "foo")) + let profileRequest = KlaviyoRequest(apiKey: "foo", endpoint: .createProfile(payload)) let tokenPayload = PushTokenPayload( pushToken: "foo", @@ -139,9 +141,12 @@ final class KlaviyoStateTests: XCTestCase { background: "AVAILABLE", profile: ProfilePayload(email: "foo", phoneNumber: "foo", anonymousId: "foo")) let tokenRequest = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) - let state = KlaviyoState(apiKey: "key", queue: [tokenRequest, profileRequest, eventRequest]) - let encodedState = try environment.encodeJSON(AnyEncodable(state)) - let decodedState: KlaviyoState = try environment.decoder.decode(encodedState) + + let state = KlaviyoState(apiKey: "key", queue: [tokenRequest]) + + let encodedState = try KlaviyoEnvironment.production.encodeJSON(AnyEncodable(state)) + let decodedState: KlaviyoState = try KlaviyoEnvironment.production.decoder.decode(encodedState) + XCTAssertEqual(decodedState, state) } From 7af6d8a8efebd5640db80bc01ba2985c350573f4 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Mon, 19 Aug 2024 17:30:42 -0500 Subject: [PATCH 37/72] fixed the gnarly any codeable test --- Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift | 2 +- Sources/KlaviyoCore/Models/APIModels/ProfilePayload.swift | 2 +- Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift b/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift index dd3c7fe3..2f6be5bd 100644 --- a/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift +++ b/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift @@ -57,7 +57,7 @@ public struct CreateEventPayload: Equatable, Codable { time: Date? = nil, uniqueId: String? = nil) { metric = Metric(name: name) - self.properties = AnyCodable(properties) + self.properties = AnyCodable(properties ?? [:]) self.value = value self.time = time ?? environment.date() self.uniqueId = uniqueId ?? environment.uuid().uuidString diff --git a/Sources/KlaviyoCore/Models/APIModels/ProfilePayload.swift b/Sources/KlaviyoCore/Models/APIModels/ProfilePayload.swift index 25a6cb85..2711a771 100644 --- a/Sources/KlaviyoCore/Models/APIModels/ProfilePayload.swift +++ b/Sources/KlaviyoCore/Models/APIModels/ProfilePayload.swift @@ -59,7 +59,7 @@ public struct ProfilePayload: Equatable, Codable { self.title = title self.image = image self.location = location - self.properties = AnyCodable(properties) + self.properties = AnyCodable(properties ?? [:]) self.anonymousId = anonymousId } diff --git a/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift b/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift index 29224e48..1feb3d7b 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift @@ -142,7 +142,7 @@ final class KlaviyoStateTests: XCTestCase { profile: ProfilePayload(email: "foo", phoneNumber: "foo", anonymousId: "foo")) let tokenRequest = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload)) - let state = KlaviyoState(apiKey: "key", queue: [tokenRequest]) + let state = KlaviyoState(apiKey: "key", queue: [tokenRequest, eventRequest, profileRequest]) let encodedState = try KlaviyoEnvironment.production.encodeJSON(AnyEncodable(state)) let decodedState: KlaviyoState = try KlaviyoEnvironment.production.decoder.decode(encodedState) From e89fd6a6811145c44a00f95cbb6962ea82f796be Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Mon, 19 Aug 2024 17:46:35 -0500 Subject: [PATCH 38/72] more tests passing --- .../EncodableTests/testEventPayloadWithoutMetadata.1.json | 8 ++++++-- .../EncodableTests/testKlaviyoRequest.1.json | 4 +++- .../EncodableTests/testProfilePayload.1.json | 4 +++- .../__Snapshots__/EncodableTests/testTokenPayload.1.json | 4 +++- .../EncodableTests/testUnregisterTokenPayload.1.json | 4 +++- .../__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt | 4 ++-- Tests/KlaviyoSwiftTests/EncodableTests.swift | 4 ++-- .../__Snapshots__/EncodableTests/testKlaviyoState.1.json | 2 +- 8 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json index 436e0823..69b4464d 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json @@ -13,12 +13,16 @@ "data" : { "attributes" : { "anonymous_id" : "anon-id", - "properties" : null + "properties" : { + + } }, "type" : "profile" } }, - "properties" : null, + "properties" : { + + }, "time" : "2009-02-13T23:31:30Z", "unique_id" : "00000000-0000-0000-0000-000000000001" }, diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json index 0902a3b9..cc8253d1 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json @@ -28,7 +28,9 @@ "anonymous_id" : "foo", "email" : "foo", "phone_number" : "foo", - "properties" : null + "properties" : { + + } }, "type" : "profile" } diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json index 31137db8..c8491d66 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json @@ -20,7 +20,9 @@ }, "organization" : "Blobco", "phone_number" : "+15555555555", - "properties" : null, + "properties" : { + + }, "title" : "Jelly" }, "type" : "profile" diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json index 0e64c3ab..28dd3b60 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json @@ -24,7 +24,9 @@ "anonymous_id" : "foo", "email" : "foo", "phone_number" : "foo", - "properties" : null + "properties" : { + + } }, "type" : "profile" } diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json index f65ceca1..e4e5fdab 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json @@ -8,7 +8,9 @@ "anonymous_id" : "foo", "email" : "foo", "phone_number" : "foo", - "properties" : null + "properties" : { + + } }, "type" : "profile" } diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt index 0c9910e5..ae591ad9 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt +++ b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt @@ -41,8 +41,8 @@ - some: "Blobco" ▿ phoneNumber: Optional - some: "+15555555555" - ▿ properties: nil - - value: (0 elements) + ▿ properties: [:] + - value: 0 key/value pairs ▿ title: Optional - some: "Jelly" - type: "profile" diff --git a/Tests/KlaviyoSwiftTests/EncodableTests.swift b/Tests/KlaviyoSwiftTests/EncodableTests.swift index 14cb7d2b..20ca3c0b 100644 --- a/Tests/KlaviyoSwiftTests/EncodableTests.swift +++ b/Tests/KlaviyoSwiftTests/EncodableTests.swift @@ -21,7 +21,7 @@ final class EncodableTests: XCTestCase { enablement: "AUTHORIZED", background: "AVAILABLE", profile: ProfilePayload(email: "foo", phoneNumber: "foo", anonymousId: "foo")) - let request = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload), uuid: environment.uuid().uuidString) + let request = KlaviyoRequest(apiKey: "foo", endpoint: .registerPushToken(tokenPayload), uuid: KlaviyoEnvironment.test().uuid().uuidString) let klaviyoState = KlaviyoState( email: "foo", anonymousId: "foo", @@ -30,7 +30,7 @@ final class EncodableTests: XCTestCase { pushToken: "foo", pushEnablement: .authorized, pushBackground: .available, - deviceData: .init(context: environment.appContextInfo())), + deviceData: .init(context: KlaviyoEnvironment.test().appContextInfo())), queue: [request], requestsInFlight: [request]) assertSnapshot(matching: klaviyoState, as: .json(KlaviyoEnvironment.encoder)) diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json index 6ab2a369..fa2f893a 100644 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json @@ -1 +1 @@ -{"anonymousId":"foo","email":"foo","pushTokenData":{"pushBackground":"AVAILABLE","pushToken":"foo","pushEnablement":"AUTHORIZED","deviceData":{"os_version":"17.0.0","app_version":"15.0","environment":"debug","klaviyo_sdk":"swift","device_model":"arm64","app_id":"com.apple.dt.xctest.tool","app_build":"22189","sdk_version":"3.2.0","device_id":"278E698D-7E37-477E-BD83-60BB4FCF404E","os_name":"iOS","manufacturer":"Apple","app_name":"xctest"}},"phoneNumber":"foo","queue":[{"apiKey":"foo","uuid":"294B9706-602D-4E5A-B34E-27A08F88AC14","endpoint":{"registerPushToken":{"_0":{"data":{"attributes":{"background":"AVAILABLE","device_metadata":{"app_name":"xctest","app_id":"com.apple.dt.xctest.tool","environment":"debug","device_model":"arm64","os_name":"iOS","device_id":"278E698D-7E37-477E-BD83-60BB4FCF404E","sdk_version":"3.2.0","os_version":"17.0.0","manufacturer":"Apple","app_version":"15.0","app_build":"22189","klaviyo_sdk":"swift"},"platform":"ios","vendor":"APNs","profile":{"data":{"attributes":{"email":"foo","phone_number":"foo","anonymous_id":"foo","properties":null},"type":"profile"}},"token":"foo","enablement_status":"AUTHORIZED"},"type":"push-token"}}}}}]} \ No newline at end of file +{"phoneNumber":"foo","queue":[{"apiKey":"foo","uuid":"00000000-0000-0000-0000-000000000001","endpoint":{"registerPushToken":{"_0":{"data":{"type":"push-token","attributes":{"token":"foo","enablement_status":"AUTHORIZED","platform":"ios","background":"AVAILABLE","profile":{"data":{"attributes":{"anonymous_id":"foo","phone_number":"foo","email":"foo","properties":{}},"type":"profile"}},"device_metadata":{"klaviyo_sdk":"swift","os_version":"17.0.0","device_id":"278E698D-7E37-477E-BD83-60BB4FCF404E","app_id":"com.apple.dt.xctest.tool","os_name":"iOS","manufacturer":"Apple","app_build":"22189","sdk_version":"3.2.0","environment":"debug","app_name":"xctest","app_version":"15.0","device_model":"arm64"},"vendor":"APNs"}}}}}}],"email":"foo","anonymousId":"foo","pushTokenData":{"pushBackground":"AVAILABLE","deviceData":{"device_id":"fe-fi-fo-fum","manufacturer":"Orange","os_version":"1.1.1","app_name":"FooApp","app_version":"1.2.3","app_build":"1","device_model":"jPhone 1,1","app_id":"com.klaviyo.fooapp","environment":"debug","sdk_version":"3.2.0","klaviyo_sdk":"swift","os_name":"iOS"},"pushToken":"foo","pushEnablement":"AUTHORIZED"}} \ No newline at end of file From 5c02cb534b1ecfd5a12754a4694d1bf8074c79dc Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Tue, 20 Aug 2024 11:15:01 -0500 Subject: [PATCH 39/72] updating comments --- .../KlaviyoCore/Models/KlaviyoAPIError.swift | 2 +- Sources/KlaviyoSwift/Models/Error.swift | 2 +- Sources/KlaviyoSwift/Models/Event.swift | 2 +- Sources/KlaviyoSwift/Models/Profile.swift | 2 +- .../Models/ProfileAPIExtension.swift | 2 +- .../testEventPayloadWithoutMetadata.1.json | 31 ------------ .../EncodableTests/testKlaviyoRequest.1.json | 47 ------------------ .../EncodableTests/testProfilePayload.1.json | 30 ------------ .../EncodableTests/testTokenPayload.1.json | 39 --------------- .../testUnregisterTokenPayload.1.json | 23 --------- .../KlaviyoAPITests/testEncodingError.1.txt | 49 ------------------- .../testInvalidStatusCode.1.txt | 4 -- .../KlaviyoAPITests/testInvalidURL.1.txt | 1 - .../KlaviyoAPITests/testNetworkError.1.txt | 2 - .../testSuccessfulResponseWithEvent.1.txt | 20 -------- .../testSuccessfulResponseWithEvent.2.txt | 1 - .../testSuccessfulResponseWithProfile.1.txt | 20 -------- .../testSuccessfulResponseWithProfile.2.txt | 1 - ...testSuccessfulResponseWithStoreToken.1.txt | 20 -------- ...testSuccessfulResponseWithStoreToken.2.txt | 1 - .../testCreateEmphemeralSesionHeaders.1.txt | 23 --------- .../testDefaultUserAgent.1.txt | 1 - .../testSessionDataTask.1.txt | 1 - .../testSessionDataTask.2.txt | 2 - .../EncodableTests/testKlaviyoState.1.json | 1 - 25 files changed, 5 insertions(+), 322 deletions(-) delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.2.txt delete mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json diff --git a/Sources/KlaviyoCore/Models/KlaviyoAPIError.swift b/Sources/KlaviyoCore/Models/KlaviyoAPIError.swift index 88135176..e633c9ac 100644 --- a/Sources/KlaviyoCore/Models/KlaviyoAPIError.swift +++ b/Sources/KlaviyoCore/Models/KlaviyoAPIError.swift @@ -1,5 +1,5 @@ // -// File.swift +// KlaviyoAPIError.swift // // // Created by Ajay Subramanya on 8/8/24. diff --git a/Sources/KlaviyoSwift/Models/Error.swift b/Sources/KlaviyoSwift/Models/Error.swift index 05e1a174..a419cbc5 100644 --- a/Sources/KlaviyoSwift/Models/Error.swift +++ b/Sources/KlaviyoSwift/Models/Error.swift @@ -1,5 +1,5 @@ // -// File.swift +// Error.swift // // // Created by Ajay Subramanya on 8/6/24. diff --git a/Sources/KlaviyoSwift/Models/Event.swift b/Sources/KlaviyoSwift/Models/Event.swift index 3bb8d282..3b7efb4d 100644 --- a/Sources/KlaviyoSwift/Models/Event.swift +++ b/Sources/KlaviyoSwift/Models/Event.swift @@ -1,5 +1,5 @@ // -// File.swift +// Event.swift // // // Created by Ajay Subramanya on 8/6/24. diff --git a/Sources/KlaviyoSwift/Models/Profile.swift b/Sources/KlaviyoSwift/Models/Profile.swift index fb3a9944..60791d7a 100644 --- a/Sources/KlaviyoSwift/Models/Profile.swift +++ b/Sources/KlaviyoSwift/Models/Profile.swift @@ -1,5 +1,5 @@ // -// File.swift +// Profile.swift // // // Created by Ajay Subramanya on 8/6/24. diff --git a/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift b/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift index 0d39edd9..caa32f79 100644 --- a/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift +++ b/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift @@ -1,5 +1,5 @@ // -// File.swift +// ProfileAPIExtension.swift // // // Created by Ajay Subramanya on 8/6/24. diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json deleted file mode 100644 index 69b4464d..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "data" : { - "attributes" : { - "metric" : { - "data" : { - "attributes" : { - "name" : "test" - }, - "type" : "metric" - } - }, - "profile" : { - "data" : { - "attributes" : { - "anonymous_id" : "anon-id", - "properties" : { - - } - }, - "type" : "profile" - } - }, - "properties" : { - - }, - "time" : "2009-02-13T23:31:30Z", - "unique_id" : "00000000-0000-0000-0000-000000000001" - }, - "type" : "event" - } -} diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json deleted file mode 100644 index cc8253d1..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "apiKey" : "foo", - "endpoint" : { - "registerPushToken" : { - "_0" : { - "data" : { - "attributes" : { - "background" : "AVAILABLE", - "device_metadata" : { - "app_build" : "1", - "app_id" : "com.klaviyo.fooapp", - "app_name" : "FooApp", - "app_version" : "1.2.3", - "device_id" : "fe-fi-fo-fum", - "device_model" : "jPhone 1,1", - "environment" : "debug", - "klaviyo_sdk" : "swift", - "manufacturer" : "Orange", - "os_name" : "iOS", - "os_version" : "1.1.1", - "sdk_version" : "3.2.0" - }, - "enablement_status" : "AUTHORIZED", - "platform" : "ios", - "profile" : { - "data" : { - "attributes" : { - "anonymous_id" : "foo", - "email" : "foo", - "phone_number" : "foo", - "properties" : { - - } - }, - "type" : "profile" - } - }, - "token" : "foo", - "vendor" : "APNs" - }, - "type" : "push-token" - } - } - } - }, - "uuid" : "00000000-0000-0000-0000-000000000001" -} diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json deleted file mode 100644 index c8491d66..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "data" : { - "attributes" : { - "anonymous_id" : "foo", - "email" : "blobemail", - "external_id" : "blobid", - "first_name" : "Blob", - "image" : "foo", - "last_name" : "Junior", - "location" : { - "address1" : "blob", - "address2" : "blob", - "city" : "blob city", - "country" : "Blobland", - "latitude" : 1, - "longitude" : 1, - "region" : "BL", - "timezone" : "EST", - "zip" : "0BLOB" - }, - "organization" : "Blobco", - "phone_number" : "+15555555555", - "properties" : { - - }, - "title" : "Jelly" - }, - "type" : "profile" - } -} diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json deleted file mode 100644 index 28dd3b60..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "data" : { - "attributes" : { - "background" : "AVAILABLE", - "device_metadata" : { - "app_build" : "1", - "app_id" : "com.klaviyo.fooapp", - "app_name" : "FooApp", - "app_version" : "1.2.3", - "device_id" : "fe-fi-fo-fum", - "device_model" : "jPhone 1,1", - "environment" : "debug", - "klaviyo_sdk" : "swift", - "manufacturer" : "Orange", - "os_name" : "iOS", - "os_version" : "1.1.1", - "sdk_version" : "3.2.0" - }, - "enablement_status" : "AUTHORIZED", - "platform" : "ios", - "profile" : { - "data" : { - "attributes" : { - "anonymous_id" : "foo", - "email" : "foo", - "phone_number" : "foo", - "properties" : { - - } - }, - "type" : "profile" - } - }, - "token" : "foo", - "vendor" : "APNs" - }, - "type" : "push-token" - } -} diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json deleted file mode 100644 index e4e5fdab..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "data" : { - "attributes" : { - "platform" : "ios", - "profile" : { - "data" : { - "attributes" : { - "anonymous_id" : "foo", - "email" : "foo", - "phone_number" : "foo", - "properties" : { - - } - }, - "type" : "profile" - } - }, - "token" : "foo", - "vendor" : "APNs" - }, - "type" : "push-token-unregister" - } -} diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt deleted file mode 100644 index ae591ad9..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt +++ /dev/null @@ -1,49 +0,0 @@ -▿ KlaviyoAPIError - ▿ internalRequestError: KlaviyoAPIError - ▿ dataEncodingError: KlaviyoRequest - - apiKey: "foo" - ▿ endpoint: KlaviyoEndpoint - ▿ createProfile: CreateProfilePayload - ▿ data: ProfilePayload - ▿ attributes: Attributes - - anonymousId: "foo" - ▿ email: Optional - - some: "blobemail" - ▿ externalId: Optional - - some: "blobid" - ▿ firstName: Optional - - some: "Blob" - ▿ image: Optional - - some: "foo" - ▿ lastName: Optional - - some: "Junior" - ▿ location: Optional - ▿ some: Location - ▿ address1: Optional - - some: "blob" - ▿ address2: Optional - - some: "blob" - ▿ city: Optional - - some: "blob city" - ▿ country: Optional - - some: "Blobland" - ▿ latitude: Optional - - some: 1.0 - ▿ longitude: Optional - - some: 1.0 - ▿ region: Optional - - some: "BL" - ▿ timezone: Optional - - some: "EST" - ▿ zip: Optional - - some: "0BLOB" - ▿ organization: Optional - - some: "Blobco" - ▿ phoneNumber: Optional - - some: "+15555555555" - ▿ properties: [:] - - value: 0 key/value pairs - ▿ title: Optional - - some: "Jelly" - - type: "profile" - - uuid: "00000000-0000-0000-0000-000000000001" diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt deleted file mode 100644 index ccb902f1..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt +++ /dev/null @@ -1,4 +0,0 @@ -▿ KlaviyoAPIError - ▿ httpError: (2 elements) - - .0: 500 - - .1: 0 bytes diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt deleted file mode 100644 index da5e392f..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt +++ /dev/null @@ -1 +0,0 @@ -internalRequestError(KlaviyoCore.KlaviyoAPIError.internalError("Invalid url string. API URL: ")) diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt deleted file mode 100644 index 8c73f9ae..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt +++ /dev/null @@ -1,2 +0,0 @@ -▿ KlaviyoAPIError - - networkError: Error Domain=network error Code=0 "(null)" diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt deleted file mode 100644 index fafe69a5..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt +++ /dev/null @@ -1,20 +0,0 @@ -▿ dead_beef/client/events/?company_id=foo - ▿ url: Optional - - some: dead_beef/client/events/?company_id=foo - - cachePolicy: 0 - - timeoutInterval: 60.0 - - mainDocumentURL: Optional.none - - networkServiceType: NSURLRequestNetworkServiceType.NSURLRequestNetworkServiceType - - allowsCellularAccess: true - ▿ httpMethod: Optional - - some: "POST" - ▿ allHTTPHeaderFields: Optional> - ▿ some: 1 key/value pair - ▿ (2 elements) - - key: "X-Klaviyo-Attempt-Count" - - value: "0/50" - ▿ httpBody: Optional - - some: 0 bytes - - httpBodyStream: Optional.none - - httpShouldHandleCookies: true - - httpShouldUsePipelining: false diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt deleted file mode 100644 index eff1843b..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt +++ /dev/null @@ -1 +0,0 @@ -- 0 bytes diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt deleted file mode 100644 index 9563cd57..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt +++ /dev/null @@ -1,20 +0,0 @@ -▿ dead_beef/client/profiles/?company_id=foo - ▿ url: Optional - - some: dead_beef/client/profiles/?company_id=foo - - cachePolicy: 0 - - timeoutInterval: 60.0 - - mainDocumentURL: Optional.none - - networkServiceType: NSURLRequestNetworkServiceType.NSURLRequestNetworkServiceType - - allowsCellularAccess: true - ▿ httpMethod: Optional - - some: "POST" - ▿ allHTTPHeaderFields: Optional> - ▿ some: 1 key/value pair - ▿ (2 elements) - - key: "X-Klaviyo-Attempt-Count" - - value: "0/50" - ▿ httpBody: Optional - - some: 0 bytes - - httpBodyStream: Optional.none - - httpShouldHandleCookies: true - - httpShouldUsePipelining: false diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt deleted file mode 100644 index eff1843b..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt +++ /dev/null @@ -1 +0,0 @@ -- 0 bytes diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt deleted file mode 100644 index b80d173f..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt +++ /dev/null @@ -1,20 +0,0 @@ -▿ dead_beef/client/push-tokens/?company_id=foo - ▿ url: Optional - - some: dead_beef/client/push-tokens/?company_id=foo - - cachePolicy: 0 - - timeoutInterval: 60.0 - - mainDocumentURL: Optional.none - - networkServiceType: NSURLRequestNetworkServiceType.NSURLRequestNetworkServiceType - - allowsCellularAccess: true - ▿ httpMethod: Optional - - some: "POST" - ▿ allHTTPHeaderFields: Optional> - ▿ some: 1 key/value pair - ▿ (2 elements) - - key: "X-Klaviyo-Attempt-Count" - - value: "0/50" - ▿ httpBody: Optional - - some: 0 bytes - - httpBodyStream: Optional.none - - httpShouldHandleCookies: true - - httpShouldUsePipelining: false diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt deleted file mode 100644 index eff1843b..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt +++ /dev/null @@ -1 +0,0 @@ -- 0 bytes diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt deleted file mode 100644 index 19cd06cb..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt +++ /dev/null @@ -1,23 +0,0 @@ -▿ Optional> - ▿ some: 6 key/value pairs - ▿ (2 elements) - - key: "Accept-Encoding" - ▿ value: 3 elements - - "br" - - "gzip" - - "deflate" - ▿ (2 elements) - - key: "User-Agent" - - value: "FooApp/1.2.3 (com.klaviyo.fooapp; build:1; iOS 1.1.1) klaviyo-ios/3.2.0" - ▿ (2 elements) - - key: "X-Klaviyo-Mobile" - - value: "1" - ▿ (2 elements) - - key: "accept" - - value: "application/json" - ▿ (2 elements) - - key: "content-type" - - value: "application/json" - ▿ (2 elements) - - key: "revision" - - value: "2023-07-15" diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt deleted file mode 100644 index 40374900..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt +++ /dev/null @@ -1 +0,0 @@ -- "FooApp/1.2.3 (com.klaviyo.fooapp; build:1; iOS 1.1.1) klaviyo-ios/3.2.0" diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt deleted file mode 100644 index eff1843b..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt +++ /dev/null @@ -1 +0,0 @@ -- 0 bytes diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.2.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.2.txt deleted file mode 100644 index 2d85f9c7..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.2.txt +++ /dev/null @@ -1,2 +0,0 @@ -- { URL: fake_url } { Status Code: 200, Headers { -} } diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json deleted file mode 100644 index fa2f893a..00000000 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json +++ /dev/null @@ -1 +0,0 @@ -{"phoneNumber":"foo","queue":[{"apiKey":"foo","uuid":"00000000-0000-0000-0000-000000000001","endpoint":{"registerPushToken":{"_0":{"data":{"type":"push-token","attributes":{"token":"foo","enablement_status":"AUTHORIZED","platform":"ios","background":"AVAILABLE","profile":{"data":{"attributes":{"anonymous_id":"foo","phone_number":"foo","email":"foo","properties":{}},"type":"profile"}},"device_metadata":{"klaviyo_sdk":"swift","os_version":"17.0.0","device_id":"278E698D-7E37-477E-BD83-60BB4FCF404E","app_id":"com.apple.dt.xctest.tool","os_name":"iOS","manufacturer":"Apple","app_build":"22189","sdk_version":"3.2.0","environment":"debug","app_name":"xctest","app_version":"15.0","device_model":"arm64"},"vendor":"APNs"}}}}}}],"email":"foo","anonymousId":"foo","pushTokenData":{"pushBackground":"AVAILABLE","deviceData":{"device_id":"fe-fi-fo-fum","manufacturer":"Orange","os_version":"1.1.1","app_name":"FooApp","app_version":"1.2.3","app_build":"1","device_model":"jPhone 1,1","app_id":"com.klaviyo.fooapp","environment":"debug","sdk_version":"3.2.0","klaviyo_sdk":"swift","os_name":"iOS"},"pushToken":"foo","pushEnablement":"AUTHORIZED"}} \ No newline at end of file From d8fd5b5183880abb855deb2c18a94aaad1da2b22 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Tue, 20 Aug 2024 11:55:11 -0500 Subject: [PATCH 40/72] made value for event public --- Sources/KlaviyoSwift/Models/Event.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/KlaviyoSwift/Models/Event.swift b/Sources/KlaviyoSwift/Models/Event.swift index 3b7efb4d..db8ba72e 100644 --- a/Sources/KlaviyoSwift/Models/Event.swift +++ b/Sources/KlaviyoSwift/Models/Event.swift @@ -85,7 +85,7 @@ public struct Event: Equatable { } extension Event.EventName { - var value: String { + public var value: String { switch self { case .OpenedPush: return "$opened_push" case .OpenedAppMetric: return "Opened App" From be5f2f8480b9bb29f65c3b7d1ac6543958118333 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 22 Aug 2024 14:57:40 -0500 Subject: [PATCH 41/72] fixed importing cocoapods --- Examples/KlaviyoSwiftExamples/CocoapodsExample/Podfile.lock | 2 +- KlaviyoSwift.podspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/KlaviyoSwiftExamples/CocoapodsExample/Podfile.lock b/Examples/KlaviyoSwiftExamples/CocoapodsExample/Podfile.lock index a76b87a0..b92f66b2 100644 --- a/Examples/KlaviyoSwiftExamples/CocoapodsExample/Podfile.lock +++ b/Examples/KlaviyoSwiftExamples/CocoapodsExample/Podfile.lock @@ -21,4 +21,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 0e01c771c2241e458c117e9ad0fc5219a42e2cf3 -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.2 diff --git a/KlaviyoSwift.podspec b/KlaviyoSwift.podspec index e9963e2c..4eba5d7c 100644 --- a/KlaviyoSwift.podspec +++ b/KlaviyoSwift.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7' s.platform = :ios s.ios.deployment_target = '13.0' - s.source_files = 'Sources/KlaviyoSwift/**/*.swift' + s.source_files = 'Sources/**/**/*.swift' s.resource_bundles = {"KlaviyoSwift" => ["Sources/KlaviyoSwift/PrivacyInfo.xcprivacy"]} s.dependency 'AnyCodable-FlightSchool' end From 5ec484d20818494574dc60fc479a11e138d1eae1 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 22 Aug 2024 15:08:09 -0500 Subject: [PATCH 42/72] moved run time warning log to logging client --- Sources/KlaviyoCore/KlaviyoEnvironment.swift | 23 -------------------- Sources/KlaviyoCore/Utils/LoggerClient.swift | 22 +++++++++++++++++++ 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/Sources/KlaviyoCore/KlaviyoEnvironment.swift b/Sources/KlaviyoCore/KlaviyoEnvironment.swift index b79aa1e3..3770a702 100644 --- a/Sources/KlaviyoCore/KlaviyoEnvironment.swift +++ b/Sources/KlaviyoCore/KlaviyoEnvironment.swift @@ -9,9 +9,6 @@ import AnyCodable import Combine import Foundation import UIKit -#if canImport(os) -import os -#endif public var environment = KlaviyoEnvironment.production @@ -186,23 +183,3 @@ public struct DataDecoder { try jsonDecoder.decode(T.self, from: data) } } - -@usableFromInline -@inline(__always) -func runtimeWarn( - _ message: @autoclosure () -> String, - category: String? = __klaviyoSwiftName, - file: StaticString? = nil, - line: UInt? = nil) { - #if DEBUG - let message = message() - let category = category ?? "Runtime Warning" - #if canImport(os) - os_log( - .fault, - log: OSLog(subsystem: "com.apple.runtime-issues", category: category), - "%@", - message) - #endif - #endif -} diff --git a/Sources/KlaviyoCore/Utils/LoggerClient.swift b/Sources/KlaviyoCore/Utils/LoggerClient.swift index 9e28667a..09c337df 100644 --- a/Sources/KlaviyoCore/Utils/LoggerClient.swift +++ b/Sources/KlaviyoCore/Utils/LoggerClient.swift @@ -6,7 +6,9 @@ // import Foundation +#if canImport(os) import os +#endif public struct LoggerClient { public init(error: @escaping (String) -> Void) { @@ -16,3 +18,23 @@ public struct LoggerClient { public var error: (String) -> Void public static let production = Self(error: { message in os_log("%{public}s", type: .error, message) }) } + +@usableFromInline +@inline(__always) +func runtimeWarn( + _ message: @autoclosure () -> String, + category: String? = __klaviyoSwiftName, + file: StaticString? = nil, + line: UInt? = nil) { + #if DEBUG + let message = message() + let category = category ?? "Runtime Warning" + #if canImport(os) + os_log( + .fault, + log: OSLog(subsystem: "com.apple.runtime-issues", category: category), + "%@", + message) + #endif + #endif +} From ad6fa529ee004dcb666ca69c2516f146bea4ea14 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 22 Aug 2024 15:16:22 -0500 Subject: [PATCH 43/72] updating package to not expose kvyo core --- Package.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Package.swift b/Package.swift index e27d82d7..1e616680 100644 --- a/Package.swift +++ b/Package.swift @@ -7,9 +7,6 @@ let package = Package( name: "klaviyo-swift-sdk", platforms: [.iOS(.v13)], products: [ - .library( - name: "KlaviyoCore", - targets: ["KlaviyoCore"]), .library( name: "KlaviyoSwift", targets: ["KlaviyoSwift"]), From b761853a1cd44e24281461bc30f78c8aee11af9b Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 22 Aug 2024 15:25:55 -0500 Subject: [PATCH 44/72] ran swift formt locally --- Examples/KlaviyoSwiftExamples/Shared/AppDelegate.swift | 3 +-- .../KlaviyoSwiftExamples/Shared/DebugViewController.swift | 2 +- Sources/KlaviyoCore/Networking/KlaviyoAPI.swift | 1 - Sources/KlaviyoCore/Networking/SDKRequestIterator.swift | 3 +-- Sources/KlaviyoCore/Utils/ArchivalUtils.swift | 2 +- Sources/KlaviyoSwift/Models/LifecycleEventsExtension.swift | 2 +- .../StateManagement/APIRequestErrorHandling.swift | 2 +- Sources/KlaviyoSwift/StateManagement/StateManagement.swift | 4 ++-- Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift | 2 +- 9 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Examples/KlaviyoSwiftExamples/Shared/AppDelegate.swift b/Examples/KlaviyoSwiftExamples/Shared/AppDelegate.swift index f3d48055..f4ebb702 100644 --- a/Examples/KlaviyoSwiftExamples/Shared/AppDelegate.swift +++ b/Examples/KlaviyoSwiftExamples/Shared/AppDelegate.swift @@ -10,7 +10,7 @@ import KlaviyoSwift import UIKit -@UIApplicationMain +@main class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: - Private members @@ -156,7 +156,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { case .checkout: // this is where we could present the checkout view break - case .debug: // sending debug should show the deeplink URL in code let debugViewController = DebugViewController() diff --git a/Examples/KlaviyoSwiftExamples/Shared/DebugViewController.swift b/Examples/KlaviyoSwiftExamples/Shared/DebugViewController.swift index fed6dcdd..7bd9df5d 100644 --- a/Examples/KlaviyoSwiftExamples/Shared/DebugViewController.swift +++ b/Examples/KlaviyoSwiftExamples/Shared/DebugViewController.swift @@ -1,5 +1,5 @@ // -// ViewController.swift +// DebugViewController.swift // SPMExample // // Created by Ajay Subramanya on 2/28/23. diff --git a/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift b/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift index ec8e29ba..b6f0cd89 100644 --- a/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift +++ b/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift @@ -73,5 +73,4 @@ public struct KlaviyoAPI { // For internal testing use only public static var requestHandler: (KlaviyoRequest, URLRequest?, RequestStatus) -> Void = { _, _, _ in } - } diff --git a/Sources/KlaviyoCore/Networking/SDKRequestIterator.swift b/Sources/KlaviyoCore/Networking/SDKRequestIterator.swift index 3d650082..4e6d2c8f 100644 --- a/Sources/KlaviyoCore/Networking/SDKRequestIterator.swift +++ b/Sources/KlaviyoCore/Networking/SDKRequestIterator.swift @@ -1,5 +1,5 @@ // -// File.swift +// SDKRequestIterator.swift // // // Created by Noah Durell on 2/13/23. @@ -128,7 +128,6 @@ public struct SDKRequest: Identifiable, Equatable { } } - public enum RequestStatus { public enum RequestError: Error { case requestFailed(Error) diff --git a/Sources/KlaviyoCore/Utils/ArchivalUtils.swift b/Sources/KlaviyoCore/Utils/ArchivalUtils.swift index 4bf44edd..cd9c79e3 100644 --- a/Sources/KlaviyoCore/Utils/ArchivalUtils.swift +++ b/Sources/KlaviyoCore/Utils/ArchivalUtils.swift @@ -1,5 +1,5 @@ // -// ArchiveUtils.swift +// ArchivalUtils.swift // KlaviyoSwift // // Created by Noah Durell on 9/26/22. diff --git a/Sources/KlaviyoSwift/Models/LifecycleEventsExtension.swift b/Sources/KlaviyoSwift/Models/LifecycleEventsExtension.swift index bc1eda67..13105afe 100644 --- a/Sources/KlaviyoSwift/Models/LifecycleEventsExtension.swift +++ b/Sources/KlaviyoSwift/Models/LifecycleEventsExtension.swift @@ -1,5 +1,5 @@ // -// LifeCycleEventsExtension.swift +// LifecycleEventsExtension.swift // // // Created by Ajay Subramanya on 8/13/24. diff --git a/Sources/KlaviyoSwift/StateManagement/APIRequestErrorHandling.swift b/Sources/KlaviyoSwift/StateManagement/APIRequestErrorHandling.swift index 0cd4fef0..2aa88ade 100644 --- a/Sources/KlaviyoSwift/StateManagement/APIRequestErrorHandling.swift +++ b/Sources/KlaviyoSwift/StateManagement/APIRequestErrorHandling.swift @@ -8,7 +8,7 @@ import Foundation import KlaviyoCore -struct ErrorHandlingConstants { +enum ErrorHandlingConstants { static let maxRetries = 50 static let maxBackoff = 60 * 3 // 3 minutes } diff --git a/Sources/KlaviyoSwift/StateManagement/StateManagement.swift b/Sources/KlaviyoSwift/StateManagement/StateManagement.swift index 833407bc..5a607712 100644 --- a/Sources/KlaviyoSwift/StateManagement/StateManagement.swift +++ b/Sources/KlaviyoSwift/StateManagement/StateManagement.swift @@ -1,5 +1,5 @@ // -// KlaviyoStateManagement.swift +// StateManagement.swift // // Klaviyo Swift SDK // @@ -478,7 +478,7 @@ struct KlaviyoReducer: ReducerProtocol { return .none case let .resetStateAndDequeue(request, invalidFields): - invalidFields.forEach { invalidField in + for invalidField in invalidFields { switch invalidField { case .email: state.email = nil diff --git a/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift b/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift index 0cb5553f..ecfb03bd 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift @@ -1,5 +1,5 @@ // -// File.swift +// KlaviyoSDKTests.swift // // // Created by Noah Durell on 2/21/23. From 8ce581d3cded1d211b0f1e8c6290d395a31cf89a Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Thu, 22 Aug 2024 15:46:24 -0500 Subject: [PATCH 45/72] trying to make opened push private --- Sources/KlaviyoSwift/Models/Event.swift | 1 + Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift | 2 +- Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift | 2 +- Tests/KlaviyoSwiftTests/StateManagementTests.swift | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/KlaviyoSwift/Models/Event.swift b/Sources/KlaviyoSwift/Models/Event.swift index db8ba72e..07843712 100644 --- a/Sources/KlaviyoSwift/Models/Event.swift +++ b/Sources/KlaviyoSwift/Models/Event.swift @@ -11,6 +11,7 @@ import KlaviyoCore public struct Event: Equatable { public enum EventName: Equatable { + @_spi(KlaviyoPrivate) case OpenedPush case OpenedAppMetric case ViewedProductMetric diff --git a/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift b/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift index ecfb03bd..66d6bad1 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift @@ -5,7 +5,7 @@ // Created by Noah Durell on 2/21/23. // -@testable import KlaviyoSwift +@testable @_spi(KlaviyoPrivate) import KlaviyoSwift import Foundation import KlaviyoCore import XCTest diff --git a/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift b/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift index eefb98eb..59fcb5b4 100644 --- a/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift +++ b/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift @@ -5,7 +5,7 @@ // Created by Noah Durell on 12/15/22. // -@testable import KlaviyoSwift +@testable @_spi(KlaviyoPrivate) import KlaviyoSwift import Foundation import KlaviyoCore import XCTest diff --git a/Tests/KlaviyoSwiftTests/StateManagementTests.swift b/Tests/KlaviyoSwiftTests/StateManagementTests.swift index cb7c161a..7abaab0f 100644 --- a/Tests/KlaviyoSwiftTests/StateManagementTests.swift +++ b/Tests/KlaviyoSwiftTests/StateManagementTests.swift @@ -5,7 +5,7 @@ // Created by Noah Durell on 12/6/22. // -@testable import KlaviyoSwift +@testable @_spi(KlaviyoPrivate) import KlaviyoSwift import AnyCodable import Combine import Foundation From dd98d49cdcf8ce68220d8c45eff51b5b85849a54 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Fri, 23 Aug 2024 11:59:44 -0500 Subject: [PATCH 46/72] some review comments --- Sources/KlaviyoCore/AppContextInfo.swift | 12 ++++++------ Sources/KlaviyoCore/AppLifeCycleEvents.swift | 4 ++-- Sources/KlaviyoSwift/Models/Event.swift | 10 +++++----- .../Models/LifecycleEventsExtension.swift | 2 +- .../KlaviyoSwift/Models/ProfileAPIExtension.swift | 4 ++-- .../APIRequestErrorHandlingTests.swift | 12 ++++++------ 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Sources/KlaviyoCore/AppContextInfo.swift b/Sources/KlaviyoCore/AppContextInfo.swift index 98e8bdde..001eb616 100644 --- a/Sources/KlaviyoCore/AppContextInfo.swift +++ b/Sources/KlaviyoCore/AppContextInfo.swift @@ -8,13 +8,13 @@ import Foundation import UIKit public struct AppContextInfo { - private let info = Bundle.main.infoDictionary - public static let defaultExecutable: String = (Bundle.main.infoDictionary?["CFBundleExecutable"] as? String) ?? + private static let info = Bundle.main.infoDictionary + public static let defaultExecutable: String = (info?["CFBundleExecutable"] as? String) ?? (ProcessInfo.processInfo.arguments.first?.split(separator: "/").last.map(String.init)) ?? "Unknown" - public static let defaultBundleId: String = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String ?? "Unknown" - public static let defaultAppVersion: String = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown" - public static let defaultAppBuild: String = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown" - public static let defaultAppName: String = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "Unknown" + public static let defaultBundleId: String = info?["CFBundleIdentifier"] as? String ?? "Unknown" + public static let defaultAppVersion: String = info?["CFBundleShortVersionString"] as? String ?? "Unknown" + public static let defaultAppBuild: String = info?["CFBundleVersion"] as? String ?? "Unknown" + public static let defaultAppName: String = info?["CFBundleName"] as? String ?? "Unknown" public static let defaultOSVersion = ProcessInfo.processInfo.operatingSystemVersion public static let defaultManufacturer = "Apple" public static let defaultOSName = "iOS" diff --git a/Sources/KlaviyoCore/AppLifeCycleEvents.swift b/Sources/KlaviyoCore/AppLifeCycleEvents.swift index 36b48f45..f8959174 100644 --- a/Sources/KlaviyoCore/AppLifeCycleEvents.swift +++ b/Sources/KlaviyoCore/AppLifeCycleEvents.swift @@ -15,7 +15,7 @@ public enum LifeCycleErrors: Error { public enum LifeCycleEvents { case terminated - case forgrounded + case foregrounded case backgrounded case reachabilityChanged(status: Reachability.NetworkStatus) } @@ -39,7 +39,7 @@ public struct AppLifeCycleEvents { environment.emitDeveloperWarning("failure to start reachability notifier") } }) - .map { _ in LifeCycleEvents.forgrounded } + .map { _ in LifeCycleEvents.foregrounded } let backgrounded = environment .notificationCenterPublisher(UIApplication.didEnterBackgroundNotification) .handleEvents(receiveOutput: { _ in diff --git a/Sources/KlaviyoSwift/Models/Event.swift b/Sources/KlaviyoSwift/Models/Event.swift index 07843712..17380208 100644 --- a/Sources/KlaviyoSwift/Models/Event.swift +++ b/Sources/KlaviyoSwift/Models/Event.swift @@ -47,7 +47,7 @@ public struct Event: Equatable { } private let _properties: AnyCodable - public var time: Date + public let time: Date public let value: Double? public let uniqueId: String let identifiers: Identifiers? @@ -56,13 +56,13 @@ public struct Event: Equatable { properties: [String: Any]? = nil, identifiers: Identifiers? = nil, value: Double? = nil, - time: Date? = nil, - uniqueId: String? = nil) { + time: Date = environment.date(), + uniqueId: String = environment.uuid().uuidString) { metric = .init(name: name) _properties = AnyCodable(properties ?? [:]) - self.time = time ?? environment.date() + self.time = time self.value = value - self.uniqueId = uniqueId ?? environment.uuid().uuidString + self.uniqueId = uniqueId self.identifiers = identifiers } diff --git a/Sources/KlaviyoSwift/Models/LifecycleEventsExtension.swift b/Sources/KlaviyoSwift/Models/LifecycleEventsExtension.swift index 13105afe..0bc16ffd 100644 --- a/Sources/KlaviyoSwift/Models/LifecycleEventsExtension.swift +++ b/Sources/KlaviyoSwift/Models/LifecycleEventsExtension.swift @@ -13,7 +13,7 @@ extension LifeCycleEvents { switch self { case .terminated: return .stop - case .forgrounded: + case .foregrounded: return .start case .backgrounded: return .stop diff --git a/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift b/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift index caa32f79..c91fe35a 100644 --- a/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift +++ b/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift @@ -21,7 +21,7 @@ extension Profile { externalId: String? = nil, anonymousId: String) -> ProfilePayload { ProfilePayload( - email: email ?? self.email?.returnNilIfEmpty(), + email: email ?? self.email, phoneNumber: phoneNumber ?? self.phoneNumber?.returnNilIfEmpty(), externalId: externalId ?? self.externalId?.returnNilIfEmpty(), firstName: firstName, @@ -45,7 +45,7 @@ extension Profile.Location { latitude: latitude, longitude: longitude, region: region, - zip: self.zip, + zip: zip, timezone: timezone) } } diff --git a/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift b/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift index 22b2cdc5..56adc017 100644 --- a/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift +++ b/Tests/KlaviyoSwiftTests/APIRequestErrorHandlingTests.swift @@ -1,9 +1,9 @@ -//// -//// APIRequestErrorHandlingTests.swift -//// State management tests related to api request error handling. -//// -//// Created by Noah Durell on 12/15/22. -//// +// +// APIRequestErrorHandlingTests.swift +// State management tests related to api request error handling. +// +// Created by Noah Durell on 12/15/22. +// @testable import KlaviyoSwift import Foundation From 810de7e7f98457565e9cd211e448bf9e613ef77a Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Fri, 23 Aug 2024 12:06:29 -0500 Subject: [PATCH 47/72] Update Sources/KlaviyoSwift/Models/Event.swift Co-authored-by: Andrew Balmer --- Sources/KlaviyoSwift/Models/Event.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/KlaviyoSwift/Models/Event.swift b/Sources/KlaviyoSwift/Models/Event.swift index 17380208..8b204b62 100644 --- a/Sources/KlaviyoSwift/Models/Event.swift +++ b/Sources/KlaviyoSwift/Models/Event.swift @@ -68,7 +68,7 @@ public struct Event: Equatable { /// Create a new event to track a profile's activity, the SDK will associate the event with any identified/anonymous profile in the SDK state /// - Parameters: - /// - name: Name of the event. Must be less than 128 characters., pick from `Event.EventName` which can also contain custom events + /// - name: Name of the event. Must be less than 128 characters., pick from ``Event.EventName`` which can also contain custom events /// - properties: Properties of this event. /// - value: A numeric, monetary value to associate with this event. For example, the dollar amount of a purchase. /// - uniqueId: A unique identifier for an event From 72b237c169f836b5c3654153e2ae8dba941614c2 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Fri, 23 Aug 2024 14:41:14 -0500 Subject: [PATCH 48/72] added a test --- .../KlaviyoSwiftTests/KlaviyoModelsTest.swift | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Tests/KlaviyoSwiftTests/KlaviyoModelsTest.swift diff --git a/Tests/KlaviyoSwiftTests/KlaviyoModelsTest.swift b/Tests/KlaviyoSwiftTests/KlaviyoModelsTest.swift new file mode 100644 index 00000000..1ff77e79 --- /dev/null +++ b/Tests/KlaviyoSwiftTests/KlaviyoModelsTest.swift @@ -0,0 +1,52 @@ +// +// KlaviyoModelsTest.swift +// +// +// Created by Ajay Subramanya on 8/23/24. +// + +@testable import KlaviyoSwift +import Foundation +import XCTest + +class KlaviyoModelsTest: XCTestCase { + func testProfileModelConvertsToAPIModel() { + let profile = Profile( + email: "walter.white@breakingbad.com", + externalId: "999", + firstName: "Walter", + lastName: "White", + organization: "Walter White Inc.", + title: "Lead chemist", + image: "https://www.breakingbad.com/walter.png", + location: Profile.Location( + address1: "1 main st", + city: "Albuquerque", + country: "USA", + zip: "42000", + timezone: "MDT"), + properties: ["order amount": "a lot of money"]) + let anonymousId = "C10H15N" + let apiProfile = profile.toAPIModel(anonymousId: anonymousId) + + XCTAssertEqual(apiProfile.attributes.email, profile.email) + XCTAssertEqual(apiProfile.attributes.phoneNumber, profile.phoneNumber) + XCTAssertEqual(apiProfile.attributes.externalId, profile.externalId) + XCTAssertEqual(apiProfile.attributes.firstName, profile.firstName) + XCTAssertEqual(apiProfile.attributes.lastName, profile.lastName) + XCTAssertEqual(apiProfile.attributes.organization, profile.organization) + XCTAssertEqual(apiProfile.attributes.title, profile.title) + XCTAssertEqual(apiProfile.attributes.image, profile.image) + XCTAssertEqual(apiProfile.attributes.location?.address1, profile.location?.address1) + XCTAssertEqual(apiProfile.attributes.location?.city, profile.location?.city) + XCTAssertEqual(apiProfile.attributes.location?.country, profile.location?.country) + XCTAssertEqual(apiProfile.attributes.location?.zip, profile.location?.zip) + XCTAssertEqual(apiProfile.attributes.location?.timezone, profile.location?.timezone) + + let apiProps = apiProfile.attributes.properties.value as! [String: Any] + let orderAmount = apiProps["order amount"] as! String + + XCTAssertEqual(orderAmount, profile.properties["order amount"] as! String) + XCTAssertEqual(apiProfile.attributes.anonymousId, anonymousId) + } +} From 04b880594818332300d97cfa23fafc6e473713fb Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Fri, 23 Aug 2024 15:30:57 -0500 Subject: [PATCH 49/72] adding more tests --- .../Models/ProfileAPIExtension.swift | 2 +- .../KlaviyoSwiftTests/KlaviyoModelsTest.swift | 45 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift b/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift index c91fe35a..0fa3c7ac 100644 --- a/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift +++ b/Sources/KlaviyoSwift/Models/ProfileAPIExtension.swift @@ -21,7 +21,7 @@ extension Profile { externalId: String? = nil, anonymousId: String) -> ProfilePayload { ProfilePayload( - email: email ?? self.email, + email: email ?? self.email?.returnNilIfEmpty(), phoneNumber: phoneNumber ?? self.phoneNumber?.returnNilIfEmpty(), externalId: externalId ?? self.externalId?.returnNilIfEmpty(), firstName: firstName, diff --git a/Tests/KlaviyoSwiftTests/KlaviyoModelsTest.swift b/Tests/KlaviyoSwiftTests/KlaviyoModelsTest.swift index 1ff77e79..ad9f0214 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoModelsTest.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoModelsTest.swift @@ -13,6 +13,7 @@ class KlaviyoModelsTest: XCTestCase { func testProfileModelConvertsToAPIModel() { let profile = Profile( email: "walter.white@breakingbad.com", + phoneNumber: "1800-better-call-saul", externalId: "999", firstName: "Walter", lastName: "White", @@ -49,4 +50,48 @@ class KlaviyoModelsTest: XCTestCase { XCTAssertEqual(orderAmount, profile.properties["order amount"] as! String) XCTAssertEqual(apiProfile.attributes.anonymousId, anonymousId) } + + func testProfileWithNoIdsModelConvertsToAPIModel() { + let profile = Profile( + firstName: "Walter", + lastName: "White") + let anonymousId = "C10H15N" + let apiProfile = profile.toAPIModel( + email: "walter.white@breakingbad.com", + phoneNumber: "1800-better-call-saul", + externalId: "999", + anonymousId: anonymousId) + XCTAssertNil(profile.email) + XCTAssertNil(profile.phoneNumber) + XCTAssertNil(profile.externalId) + XCTAssertEqual(apiProfile.attributes.email, "walter.white@breakingbad.com") + XCTAssertEqual(apiProfile.attributes.phoneNumber, "1800-better-call-saul") + XCTAssertEqual(apiProfile.attributes.externalId, "999") + XCTAssertEqual(apiProfile.attributes.firstName, profile.firstName) + XCTAssertEqual(apiProfile.attributes.lastName, profile.lastName) + XCTAssertEqual(apiProfile.attributes.anonymousId, anonymousId) + } + + func testEmptyStringIdsConvertToNil() { + let profile = Profile( + firstName: "Walter", + lastName: "White") + let anonymousId = "C10H15N" + let apiProfile = profile.toAPIModel( + email: " ", + phoneNumber: " ", + externalId: " ", + anonymousId: anonymousId) + XCTAssertNil(profile.email) + XCTAssertNil(profile.phoneNumber) + XCTAssertNil(profile.externalId) + + XCTAssertNil(apiProfile.attributes.email) + XCTAssertNil(apiProfile.attributes.phoneNumber) + XCTAssertNil(apiProfile.attributes.externalId) + + XCTAssertEqual(apiProfile.attributes.firstName, profile.firstName) + XCTAssertEqual(apiProfile.attributes.lastName, profile.lastName) + XCTAssertEqual(apiProfile.attributes.anonymousId, anonymousId) + } } From 4ece6fcecad6dbbcf706202d7cf0628f4bc193c5 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Fri, 23 Aug 2024 16:29:04 -0500 Subject: [PATCH 50/72] added some tessts --- Tests/KlaviyoSwiftTests/KlaviyoModelsTest.swift | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Tests/KlaviyoSwiftTests/KlaviyoModelsTest.swift b/Tests/KlaviyoSwiftTests/KlaviyoModelsTest.swift index ad9f0214..690a72e4 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoModelsTest.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoModelsTest.swift @@ -74,22 +74,18 @@ class KlaviyoModelsTest: XCTestCase { func testEmptyStringIdsConvertToNil() { let profile = Profile( + email: "", + phoneNumber: "", + externalId: "", firstName: "Walter", lastName: "White") let anonymousId = "C10H15N" let apiProfile = profile.toAPIModel( - email: " ", - phoneNumber: " ", - externalId: " ", anonymousId: anonymousId) - XCTAssertNil(profile.email) - XCTAssertNil(profile.phoneNumber) - XCTAssertNil(profile.externalId) XCTAssertNil(apiProfile.attributes.email) XCTAssertNil(apiProfile.attributes.phoneNumber) XCTAssertNil(apiProfile.attributes.externalId) - XCTAssertEqual(apiProfile.attributes.firstName, profile.firstName) XCTAssertEqual(apiProfile.attributes.lastName, profile.lastName) XCTAssertEqual(apiProfile.attributes.anonymousId, anonymousId) From 9a4fb37a385676b77e4df6ee443dda2623082846 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Fri, 23 Aug 2024 16:35:29 -0500 Subject: [PATCH 51/72] updated tests --- Sources/KlaviyoSwift/Models/Event.swift | 1 - .../testEventPayloadWithoutMetadata.1.json | 31 ++++++++++++ .../EncodableTests/testKlaviyoRequest.1.json | 47 ++++++++++++++++++ .../EncodableTests/testProfilePayload.1.json | 30 ++++++++++++ .../EncodableTests/testTokenPayload.1.json | 39 +++++++++++++++ .../testUnregisterTokenPayload.1.json | 23 +++++++++ .../KlaviyoAPITests/testEncodingError.1.txt | 49 +++++++++++++++++++ .../testInvalidStatusCode.1.txt | 4 ++ .../KlaviyoAPITests/testInvalidURL.1.txt | 1 + .../KlaviyoAPITests/testNetworkError.1.txt | 2 + .../testSuccessfulResponseWithEvent.1.txt | 20 ++++++++ .../testSuccessfulResponseWithEvent.2.txt} | 0 .../testSuccessfulResponseWithProfile.1.txt | 20 ++++++++ .../testSuccessfulResponseWithProfile.2.txt | 1 + ...testSuccessfulResponseWithStoreToken.1.txt | 20 ++++++++ ...testSuccessfulResponseWithStoreToken.2.txt | 1 + .../testCreateEmphemeralSesionHeaders.1.txt | 0 .../testDefaultUserAgent.1.txt | 0 .../testSessionDataTask.1.txt | 1 + .../testSessionDataTask.2.txt | 0 Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift | 2 +- .../StateManagementEdgeCaseTests.swift | 2 +- .../StateManagementTests.swift | 2 +- .../EncodableTests/testKlaviyoState.1.json | 1 + 24 files changed, 293 insertions(+), 4 deletions(-) create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt rename Tests/{KlaviyoSwiftTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt => KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt} (100%) create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt (100%) rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt (100%) create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt rename Tests/{KlaviyoSwiftTests => KlaviyoCoreTests}/__Snapshots__/NetworkSessionTests/testSessionDataTask.2.txt (100%) create mode 100644 Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json diff --git a/Sources/KlaviyoSwift/Models/Event.swift b/Sources/KlaviyoSwift/Models/Event.swift index 8b204b62..e403a07b 100644 --- a/Sources/KlaviyoSwift/Models/Event.swift +++ b/Sources/KlaviyoSwift/Models/Event.swift @@ -11,7 +11,6 @@ import KlaviyoCore public struct Event: Equatable { public enum EventName: Equatable { - @_spi(KlaviyoPrivate) case OpenedPush case OpenedAppMetric case ViewedProductMetric diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json new file mode 100644 index 00000000..69b4464d --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json @@ -0,0 +1,31 @@ +{ + "data" : { + "attributes" : { + "metric" : { + "data" : { + "attributes" : { + "name" : "test" + }, + "type" : "metric" + } + }, + "profile" : { + "data" : { + "attributes" : { + "anonymous_id" : "anon-id", + "properties" : { + + } + }, + "type" : "profile" + } + }, + "properties" : { + + }, + "time" : "2009-02-13T23:31:30Z", + "unique_id" : "00000000-0000-0000-0000-000000000001" + }, + "type" : "event" + } +} diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json new file mode 100644 index 00000000..cc8253d1 --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json @@ -0,0 +1,47 @@ +{ + "apiKey" : "foo", + "endpoint" : { + "registerPushToken" : { + "_0" : { + "data" : { + "attributes" : { + "background" : "AVAILABLE", + "device_metadata" : { + "app_build" : "1", + "app_id" : "com.klaviyo.fooapp", + "app_name" : "FooApp", + "app_version" : "1.2.3", + "device_id" : "fe-fi-fo-fum", + "device_model" : "jPhone 1,1", + "environment" : "debug", + "klaviyo_sdk" : "swift", + "manufacturer" : "Orange", + "os_name" : "iOS", + "os_version" : "1.1.1", + "sdk_version" : "3.2.0" + }, + "enablement_status" : "AUTHORIZED", + "platform" : "ios", + "profile" : { + "data" : { + "attributes" : { + "anonymous_id" : "foo", + "email" : "foo", + "phone_number" : "foo", + "properties" : { + + } + }, + "type" : "profile" + } + }, + "token" : "foo", + "vendor" : "APNs" + }, + "type" : "push-token" + } + } + } + }, + "uuid" : "00000000-0000-0000-0000-000000000001" +} diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json new file mode 100644 index 00000000..c8491d66 --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json @@ -0,0 +1,30 @@ +{ + "data" : { + "attributes" : { + "anonymous_id" : "foo", + "email" : "blobemail", + "external_id" : "blobid", + "first_name" : "Blob", + "image" : "foo", + "last_name" : "Junior", + "location" : { + "address1" : "blob", + "address2" : "blob", + "city" : "blob city", + "country" : "Blobland", + "latitude" : 1, + "longitude" : 1, + "region" : "BL", + "timezone" : "EST", + "zip" : "0BLOB" + }, + "organization" : "Blobco", + "phone_number" : "+15555555555", + "properties" : { + + }, + "title" : "Jelly" + }, + "type" : "profile" + } +} diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json new file mode 100644 index 00000000..28dd3b60 --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json @@ -0,0 +1,39 @@ +{ + "data" : { + "attributes" : { + "background" : "AVAILABLE", + "device_metadata" : { + "app_build" : "1", + "app_id" : "com.klaviyo.fooapp", + "app_name" : "FooApp", + "app_version" : "1.2.3", + "device_id" : "fe-fi-fo-fum", + "device_model" : "jPhone 1,1", + "environment" : "debug", + "klaviyo_sdk" : "swift", + "manufacturer" : "Orange", + "os_name" : "iOS", + "os_version" : "1.1.1", + "sdk_version" : "3.2.0" + }, + "enablement_status" : "AUTHORIZED", + "platform" : "ios", + "profile" : { + "data" : { + "attributes" : { + "anonymous_id" : "foo", + "email" : "foo", + "phone_number" : "foo", + "properties" : { + + } + }, + "type" : "profile" + } + }, + "token" : "foo", + "vendor" : "APNs" + }, + "type" : "push-token" + } +} diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json new file mode 100644 index 00000000..e4e5fdab --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json @@ -0,0 +1,23 @@ +{ + "data" : { + "attributes" : { + "platform" : "ios", + "profile" : { + "data" : { + "attributes" : { + "anonymous_id" : "foo", + "email" : "foo", + "phone_number" : "foo", + "properties" : { + + } + }, + "type" : "profile" + } + }, + "token" : "foo", + "vendor" : "APNs" + }, + "type" : "push-token-unregister" + } +} diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt new file mode 100644 index 00000000..ae591ad9 --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testEncodingError.1.txt @@ -0,0 +1,49 @@ +▿ KlaviyoAPIError + ▿ internalRequestError: KlaviyoAPIError + ▿ dataEncodingError: KlaviyoRequest + - apiKey: "foo" + ▿ endpoint: KlaviyoEndpoint + ▿ createProfile: CreateProfilePayload + ▿ data: ProfilePayload + ▿ attributes: Attributes + - anonymousId: "foo" + ▿ email: Optional + - some: "blobemail" + ▿ externalId: Optional + - some: "blobid" + ▿ firstName: Optional + - some: "Blob" + ▿ image: Optional + - some: "foo" + ▿ lastName: Optional + - some: "Junior" + ▿ location: Optional + ▿ some: Location + ▿ address1: Optional + - some: "blob" + ▿ address2: Optional + - some: "blob" + ▿ city: Optional + - some: "blob city" + ▿ country: Optional + - some: "Blobland" + ▿ latitude: Optional + - some: 1.0 + ▿ longitude: Optional + - some: 1.0 + ▿ region: Optional + - some: "BL" + ▿ timezone: Optional + - some: "EST" + ▿ zip: Optional + - some: "0BLOB" + ▿ organization: Optional + - some: "Blobco" + ▿ phoneNumber: Optional + - some: "+15555555555" + ▿ properties: [:] + - value: 0 key/value pairs + ▿ title: Optional + - some: "Jelly" + - type: "profile" + - uuid: "00000000-0000-0000-0000-000000000001" diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt new file mode 100644 index 00000000..ccb902f1 --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidStatusCode.1.txt @@ -0,0 +1,4 @@ +▿ KlaviyoAPIError + ▿ httpError: (2 elements) + - .0: 500 + - .1: 0 bytes diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt new file mode 100644 index 00000000..da5e392f --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt @@ -0,0 +1 @@ +internalRequestError(KlaviyoCore.KlaviyoAPIError.internalError("Invalid url string. API URL: ")) diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt new file mode 100644 index 00000000..8c73f9ae --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testNetworkError.1.txt @@ -0,0 +1,2 @@ +▿ KlaviyoAPIError + - networkError: Error Domain=network error Code=0 "(null)" diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt new file mode 100644 index 00000000..fafe69a5 --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.1.txt @@ -0,0 +1,20 @@ +▿ dead_beef/client/events/?company_id=foo + ▿ url: Optional + - some: dead_beef/client/events/?company_id=foo + - cachePolicy: 0 + - timeoutInterval: 60.0 + - mainDocumentURL: Optional.none + - networkServiceType: NSURLRequestNetworkServiceType.NSURLRequestNetworkServiceType + - allowsCellularAccess: true + ▿ httpMethod: Optional + - some: "POST" + ▿ allHTTPHeaderFields: Optional> + ▿ some: 1 key/value pair + ▿ (2 elements) + - key: "X-Klaviyo-Attempt-Count" + - value: "0/50" + ▿ httpBody: Optional + - some: 0 bytes + - httpBodyStream: Optional.none + - httpShouldHandleCookies: true + - httpShouldUsePipelining: false diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt similarity index 100% rename from Tests/KlaviyoSwiftTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt rename to Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithEvent.2.txt diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt new file mode 100644 index 00000000..9563cd57 --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.1.txt @@ -0,0 +1,20 @@ +▿ dead_beef/client/profiles/?company_id=foo + ▿ url: Optional + - some: dead_beef/client/profiles/?company_id=foo + - cachePolicy: 0 + - timeoutInterval: 60.0 + - mainDocumentURL: Optional.none + - networkServiceType: NSURLRequestNetworkServiceType.NSURLRequestNetworkServiceType + - allowsCellularAccess: true + ▿ httpMethod: Optional + - some: "POST" + ▿ allHTTPHeaderFields: Optional> + ▿ some: 1 key/value pair + ▿ (2 elements) + - key: "X-Klaviyo-Attempt-Count" + - value: "0/50" + ▿ httpBody: Optional + - some: 0 bytes + - httpBodyStream: Optional.none + - httpShouldHandleCookies: true + - httpShouldUsePipelining: false diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt new file mode 100644 index 00000000..eff1843b --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithProfile.2.txt @@ -0,0 +1 @@ +- 0 bytes diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt new file mode 100644 index 00000000..b80d173f --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.1.txt @@ -0,0 +1,20 @@ +▿ dead_beef/client/push-tokens/?company_id=foo + ▿ url: Optional + - some: dead_beef/client/push-tokens/?company_id=foo + - cachePolicy: 0 + - timeoutInterval: 60.0 + - mainDocumentURL: Optional.none + - networkServiceType: NSURLRequestNetworkServiceType.NSURLRequestNetworkServiceType + - allowsCellularAccess: true + ▿ httpMethod: Optional + - some: "POST" + ▿ allHTTPHeaderFields: Optional> + ▿ some: 1 key/value pair + ▿ (2 elements) + - key: "X-Klaviyo-Attempt-Count" + - value: "0/50" + ▿ httpBody: Optional + - some: 0 bytes + - httpBodyStream: Optional.none + - httpShouldHandleCookies: true + - httpShouldUsePipelining: false diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt new file mode 100644 index 00000000..eff1843b --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testSuccessfulResponseWithStoreToken.2.txt @@ -0,0 +1 @@ +- 0 bytes diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt similarity index 100% rename from Tests/KlaviyoSwiftTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt rename to Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt similarity index 100% rename from Tests/KlaviyoSwiftTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt rename to Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt new file mode 100644 index 00000000..eff1843b --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.1.txt @@ -0,0 +1 @@ +- 0 bytes diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.2.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.2.txt similarity index 100% rename from Tests/KlaviyoSwiftTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.2.txt rename to Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testSessionDataTask.2.txt diff --git a/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift b/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift index 66d6bad1..ecfb03bd 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoSDKTests.swift @@ -5,7 +5,7 @@ // Created by Noah Durell on 2/21/23. // -@testable @_spi(KlaviyoPrivate) import KlaviyoSwift +@testable import KlaviyoSwift import Foundation import KlaviyoCore import XCTest diff --git a/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift b/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift index 59fcb5b4..eefb98eb 100644 --- a/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift +++ b/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift @@ -5,7 +5,7 @@ // Created by Noah Durell on 12/15/22. // -@testable @_spi(KlaviyoPrivate) import KlaviyoSwift +@testable import KlaviyoSwift import Foundation import KlaviyoCore import XCTest diff --git a/Tests/KlaviyoSwiftTests/StateManagementTests.swift b/Tests/KlaviyoSwiftTests/StateManagementTests.swift index 7abaab0f..cb7c161a 100644 --- a/Tests/KlaviyoSwiftTests/StateManagementTests.swift +++ b/Tests/KlaviyoSwiftTests/StateManagementTests.swift @@ -5,7 +5,7 @@ // Created by Noah Durell on 12/6/22. // -@testable @_spi(KlaviyoPrivate) import KlaviyoSwift +@testable import KlaviyoSwift import AnyCodable import Combine import Foundation diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json new file mode 100644 index 00000000..3a356244 --- /dev/null +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json @@ -0,0 +1 @@ +{"anonymousId":"foo","pushTokenData":{"deviceData":{"klaviyo_sdk":"swift","os_name":"iOS","os_version":"1.1.1","environment":"debug","device_id":"fe-fi-fo-fum","manufacturer":"Orange","sdk_version":"3.2.0","device_model":"jPhone 1,1","app_version":"1.2.3","app_id":"com.klaviyo.fooapp","app_name":"FooApp","app_build":"1"},"pushBackground":"AVAILABLE","pushToken":"foo","pushEnablement":"AUTHORIZED"},"phoneNumber":"foo","queue":[{"uuid":"00000000-0000-0000-0000-000000000001","apiKey":"foo","endpoint":{"registerPushToken":{"_0":{"data":{"type":"push-token","attributes":{"vendor":"APNs","enablement_status":"AUTHORIZED","token":"foo","device_metadata":{"device_model":"jPhone 1,1","os_name":"iOS","os_version":"1.1.1","device_id":"fe-fi-fo-fum","sdk_version":"3.2.0","manufacturer":"Orange","app_name":"FooApp","app_version":"1.2.3","app_build":"1","environment":"debug","klaviyo_sdk":"swift","app_id":"com.klaviyo.fooapp"},"platform":"ios","background":"AVAILABLE","profile":{"data":{"type":"profile","attributes":{"phone_number":"foo","email":"foo","anonymous_id":"foo","properties":{}}}}}}}}}}],"email":"foo"} \ No newline at end of file From ef83d7706a11e488cb9437d81813d7b32276b048 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Tue, 27 Aug 2024 11:52:47 -0500 Subject: [PATCH 52/72] fixed one more test, two more to go --- Tests/KlaviyoSwiftTests/EncodableTests.swift | 5 ++ .../EncodableTests/testKlaviyoState.1.json | 74 ++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/Tests/KlaviyoSwiftTests/EncodableTests.swift b/Tests/KlaviyoSwiftTests/EncodableTests.swift index 20ca3c0b..c5961ce1 100644 --- a/Tests/KlaviyoSwiftTests/EncodableTests.swift +++ b/Tests/KlaviyoSwiftTests/EncodableTests.swift @@ -15,6 +15,11 @@ import XCTest final class EncodableTests: XCTestCase { let testEncoder = KlaviyoEnvironment.encoder + override func setUpWithError() throws { + environment = KlaviyoEnvironment.test() + testEncoder.outputFormatting = .prettyPrinted.union(.sortedKeys) + } + func testKlaviyoState() throws { let tokenPayload = PushTokenPayload( pushToken: "foo", diff --git a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json index 3a356244..edb7e4c7 100644 --- a/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json +++ b/Tests/KlaviyoSwiftTests/__Snapshots__/EncodableTests/testKlaviyoState.1.json @@ -1 +1,73 @@ -{"anonymousId":"foo","pushTokenData":{"deviceData":{"klaviyo_sdk":"swift","os_name":"iOS","os_version":"1.1.1","environment":"debug","device_id":"fe-fi-fo-fum","manufacturer":"Orange","sdk_version":"3.2.0","device_model":"jPhone 1,1","app_version":"1.2.3","app_id":"com.klaviyo.fooapp","app_name":"FooApp","app_build":"1"},"pushBackground":"AVAILABLE","pushToken":"foo","pushEnablement":"AUTHORIZED"},"phoneNumber":"foo","queue":[{"uuid":"00000000-0000-0000-0000-000000000001","apiKey":"foo","endpoint":{"registerPushToken":{"_0":{"data":{"type":"push-token","attributes":{"vendor":"APNs","enablement_status":"AUTHORIZED","token":"foo","device_metadata":{"device_model":"jPhone 1,1","os_name":"iOS","os_version":"1.1.1","device_id":"fe-fi-fo-fum","sdk_version":"3.2.0","manufacturer":"Orange","app_name":"FooApp","app_version":"1.2.3","app_build":"1","environment":"debug","klaviyo_sdk":"swift","app_id":"com.klaviyo.fooapp"},"platform":"ios","background":"AVAILABLE","profile":{"data":{"type":"profile","attributes":{"phone_number":"foo","email":"foo","anonymous_id":"foo","properties":{}}}}}}}}}}],"email":"foo"} \ No newline at end of file +{ + "anonymousId" : "foo", + "email" : "foo", + "phoneNumber" : "foo", + "pushTokenData" : { + "deviceData" : { + "app_build" : "1", + "app_id" : "com.klaviyo.fooapp", + "app_name" : "FooApp", + "app_version" : "1.2.3", + "device_id" : "fe-fi-fo-fum", + "device_model" : "jPhone 1,1", + "environment" : "debug", + "klaviyo_sdk" : "swift", + "manufacturer" : "Orange", + "os_name" : "iOS", + "os_version" : "1.1.1", + "sdk_version" : "3.2.0" + }, + "pushBackground" : "AVAILABLE", + "pushEnablement" : "AUTHORIZED", + "pushToken" : "foo" + }, + "queue" : [ + { + "apiKey" : "foo", + "endpoint" : { + "registerPushToken" : { + "_0" : { + "data" : { + "attributes" : { + "background" : "AVAILABLE", + "device_metadata" : { + "app_build" : "1", + "app_id" : "com.klaviyo.fooapp", + "app_name" : "FooApp", + "app_version" : "1.2.3", + "device_id" : "fe-fi-fo-fum", + "device_model" : "jPhone 1,1", + "environment" : "debug", + "klaviyo_sdk" : "swift", + "manufacturer" : "Orange", + "os_name" : "iOS", + "os_version" : "1.1.1", + "sdk_version" : "3.2.0" + }, + "enablement_status" : "AUTHORIZED", + "platform" : "ios", + "profile" : { + "data" : { + "attributes" : { + "anonymous_id" : "foo", + "email" : "foo", + "phone_number" : "foo", + "properties" : { + + } + }, + "type" : "profile" + } + }, + "token" : "foo", + "vendor" : "APNs" + }, + "type" : "push-token" + } + } + } + }, + "uuid" : "00000000-0000-0000-0000-000000000001" + } + ] +} \ No newline at end of file From ff51050d292dd4c88bd456be8635809a8254061e Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Tue, 27 Aug 2024 14:00:55 -0500 Subject: [PATCH 53/72] fixed all tests --- .../StateManagementEdgeCaseTests.swift | 1 + .../StateManagementTests.swift | 1 + Tests/KlaviyoSwiftTests/TestData.swift | 17 +++++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift b/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift index eefb98eb..cea1c8b0 100644 --- a/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift +++ b/Tests/KlaviyoSwiftTests/StateManagementEdgeCaseTests.swift @@ -14,6 +14,7 @@ class StateManagementEdgeCaseTests: XCTestCase { @MainActor override func setUp() async throws { environment = KlaviyoEnvironment.test() + klaviyoSwiftEnvironment = KlaviyoSwiftEnvironment.test() } // MARK: - initialization diff --git a/Tests/KlaviyoSwiftTests/StateManagementTests.swift b/Tests/KlaviyoSwiftTests/StateManagementTests.swift index cb7c161a..a5f85ac0 100644 --- a/Tests/KlaviyoSwiftTests/StateManagementTests.swift +++ b/Tests/KlaviyoSwiftTests/StateManagementTests.swift @@ -16,6 +16,7 @@ class StateManagementTests: XCTestCase { @MainActor override func setUp() async throws { environment = KlaviyoEnvironment.test() + klaviyoSwiftEnvironment = KlaviyoSwiftEnvironment.test() } // MARK: - Initialization diff --git a/Tests/KlaviyoSwiftTests/TestData.swift b/Tests/KlaviyoSwiftTests/TestData.swift index 10224b79..9e83c167 100644 --- a/Tests/KlaviyoSwiftTests/TestData.swift +++ b/Tests/KlaviyoSwiftTests/TestData.swift @@ -7,6 +7,7 @@ import Foundation @_spi(KlaviyoPrivate) @testable import KlaviyoSwift +import Combine import KlaviyoCore let TEST_API_KEY = "fake-key" @@ -183,3 +184,19 @@ let TEST_FAILURE_JSON_INVALID_EMAIL = """ ] } """ + +extension KlaviyoSwiftEnvironment { + static let testStore = Store(initialState: KlaviyoState(queue: []), reducer: KlaviyoReducer()) + + static let test = { + KlaviyoSwiftEnvironment(send: { action in + testStore.send(action) + }, state: { + KlaviyoSwiftEnvironment.testStore.state.value + }, statePublisher: { + Just(INITIALIZED_TEST_STATE()).eraseToAnyPublisher() + }, stateChangePublisher: { + Empty().eraseToAnyPublisher() + }) + } +} From 42259048380db1aa4c3a1e7e28e7ea9886dffa4b Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Tue, 27 Aug 2024 16:33:05 -0500 Subject: [PATCH 54/72] adding private api for setting RN SDK name and version --- Sources/KlaviyoCore/KlaviyoEnvironment.swift | 13 +++++++++++-- .../Models/APIModels/CreateEventPayload.swift | 4 ++-- Sources/KlaviyoCore/Networking/KlaviyoAPI.swift | 9 +++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Sources/KlaviyoCore/KlaviyoEnvironment.swift b/Sources/KlaviyoCore/KlaviyoEnvironment.swift index 3770a702..b078a7cb 100644 --- a/Sources/KlaviyoCore/KlaviyoEnvironment.swift +++ b/Sources/KlaviyoCore/KlaviyoEnvironment.swift @@ -37,7 +37,9 @@ public struct KlaviyoEnvironment { timeZone: @escaping () -> String, appContextInfo: @escaping () -> AppContextInfo, klaviyoAPI: KlaviyoAPI, - timer: @escaping (Double) -> AnyPublisher) { + timer: @escaping (Double) -> AnyPublisher, + SDKName: String, + SDKVersion: String) { self.archiverClient = archiverClient self.fileClient = fileClient self.dataFromUrl = dataFromUrl @@ -62,6 +64,8 @@ public struct KlaviyoEnvironment { self.appContextInfo = appContextInfo self.klaviyoAPI = klaviyoAPI self.timer = timer + self.SDKName = SDKName + self.SDKVersion = SDKVersion } static let productionHost = "https://a.klaviyo.com" @@ -111,6 +115,9 @@ public struct KlaviyoEnvironment { public var klaviyoAPI: KlaviyoAPI public var timer: (Double) -> AnyPublisher + public var SDKName: String + public var SDKVersion: String + public static var production = KlaviyoEnvironment( archiverClient: ArchiverClient.production, fileClient: FileClient.production, @@ -156,7 +163,9 @@ public struct KlaviyoEnvironment { Timer.publish(every: interval, on: .main, in: .default) .autoconnect() .eraseToAnyPublisher() - }) + }, + SDKName: __klaviyoSwiftName, + SDKVersion: __klaviyoSwiftVersion) } public var networkSession: NetworkSession! diff --git a/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift b/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift index 2f6be5bd..030876f8 100644 --- a/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift +++ b/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift @@ -120,8 +120,8 @@ extension Dictionary where Key == String, Value == Any { "Device Model": context.deviceModel, "OS Name": context.osName, "OS Version": context.osVersion, - "SDK Name": __klaviyoSwiftName, - "SDK Version": __klaviyoSwiftVersion, + "SDK Name": environment.SDKName, + "SDK Version": environment.SDKVersion, "App Name": context.appName, "App ID": context.bundleId, "App Version": context.appVersion, diff --git a/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift b/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift index b6f0cd89..c1c16d6f 100644 --- a/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift +++ b/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift @@ -13,6 +13,15 @@ public func setKlaviyoAPIURL(url: String) { environment.apiURL = url } +@_spi(KlaviyoPrivate) +public func setKlaviyoSDKNameAndVersion(name: String, version: String) { + print("name = \(name) | version = \(version)") + environment.SDKName = name + environment.SDKVersion = version + + print("name = \(environment.SDKName) | version = \(environment.SDKVersion)") +} + public struct KlaviyoAPI { public var send: (KlaviyoRequest, Int) async -> Result From 63c4f08af8cc6fce68a73da144b89bd855cbd5cc Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 28 Aug 2024 11:30:42 -0500 Subject: [PATCH 55/72] updating models --- Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift | 4 ++-- Sources/KlaviyoCore/Utils/LoggerClient.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift b/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift index 244ef0f5..0a9726e9 100644 --- a/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift +++ b/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift @@ -104,8 +104,8 @@ public struct PushTokenPayload: Equatable, Codable { appVersion = context.appVersion appBuild = context.appBuild environment = context.environment - klaviyoSdk = __klaviyoSwiftName - sdkVersion = __klaviyoSwiftVersion + klaviyoSdk = KlaviyoCore.environment.SDKName + sdkVersion = KlaviyoCore.environment.SDKVersion } } } diff --git a/Sources/KlaviyoCore/Utils/LoggerClient.swift b/Sources/KlaviyoCore/Utils/LoggerClient.swift index 09c337df..49da89b3 100644 --- a/Sources/KlaviyoCore/Utils/LoggerClient.swift +++ b/Sources/KlaviyoCore/Utils/LoggerClient.swift @@ -23,7 +23,7 @@ public struct LoggerClient { @inline(__always) func runtimeWarn( _ message: @autoclosure () -> String, - category: String? = __klaviyoSwiftName, + category: String? = environment.SDKName, file: StaticString? = nil, line: UInt? = nil) { #if DEBUG From 6846c5be3130ce4d549fbfbfa8087b17efc8acb4 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 28 Aug 2024 11:36:40 -0500 Subject: [PATCH 56/72] removed print statements --- Sources/KlaviyoCore/Networking/KlaviyoAPI.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift b/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift index c1c16d6f..ba377f3f 100644 --- a/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift +++ b/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift @@ -15,11 +15,8 @@ public func setKlaviyoAPIURL(url: String) { @_spi(KlaviyoPrivate) public func setKlaviyoSDKNameAndVersion(name: String, version: String) { - print("name = \(name) | version = \(version)") environment.SDKName = name environment.SDKVersion = version - - print("name = \(environment.SDKName) | version = \(environment.SDKVersion)") } public struct KlaviyoAPI { From da496360ce601781f835272dfdd15fcad897b489 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya Date: Wed, 28 Aug 2024 15:58:32 -0500 Subject: [PATCH 57/72] made certaint things internal --- Sources/KlaviyoCore/AppContextInfo.swift | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Sources/KlaviyoCore/AppContextInfo.swift b/Sources/KlaviyoCore/AppContextInfo.swift index 001eb616..336e656d 100644 --- a/Sources/KlaviyoCore/AppContextInfo.swift +++ b/Sources/KlaviyoCore/AppContextInfo.swift @@ -32,23 +32,23 @@ public struct AppContextInfo { private static let deviceIdStoreKey = "_klaviyo_device_id" - public let executable: String - public let bundleId: String - public let appVersion: String - public let appBuild: String - public let appName: String - public let version: OperatingSystemVersion - public let osName: String - public let manufacturer: String - public let deviceModel: String - public let deviceId: String - public let environment: String + let executable: String + let bundleId: String + let appVersion: String + let appBuild: String + let appName: String + let version: OperatingSystemVersion + let osName: String + let manufacturer: String + let deviceModel: String + let deviceId: String + let environment: String - public var osVersion: String { + var osVersion: String { "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)" } - public var osVersionName: String { + var osVersionName: String { "\(osName) \(osVersion)" } From 877b173d7c4f02726066469e40f7b0a30b4319a9 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Thu, 29 Aug 2024 10:17:20 -0500 Subject: [PATCH 58/72] moved to serperate file --- .../KlaviyoCore/Networking/KlaviyoAPI.swift | 11 -------- .../Networking/NetworkSession.swift | 2 +- .../Networking/PrivateMethods.swift | 28 +++++++++++++++++++ Tests/KlaviyoCoreTests/TestUtils.swift | 4 ++- .../KlaviyoSwiftTests/KlaviyoTestUtils.swift | 4 ++- 5 files changed, 35 insertions(+), 14 deletions(-) create mode 100644 Sources/KlaviyoCore/Networking/PrivateMethods.swift diff --git a/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift b/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift index ba377f3f..135b76a6 100644 --- a/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift +++ b/Sources/KlaviyoCore/Networking/KlaviyoAPI.swift @@ -8,17 +8,6 @@ import AnyCodable import Foundation -@_spi(KlaviyoPrivate) -public func setKlaviyoAPIURL(url: String) { - environment.apiURL = url -} - -@_spi(KlaviyoPrivate) -public func setKlaviyoSDKNameAndVersion(name: String, version: String) { - environment.SDKName = name - environment.SDKVersion = version -} - public struct KlaviyoAPI { public var send: (KlaviyoRequest, Int) async -> Result diff --git a/Sources/KlaviyoCore/Networking/NetworkSession.swift b/Sources/KlaviyoCore/Networking/NetworkSession.swift index 647cf55b..0a7398c5 100644 --- a/Sources/KlaviyoCore/Networking/NetworkSession.swift +++ b/Sources/KlaviyoCore/Networking/NetworkSession.swift @@ -35,7 +35,7 @@ public struct NetworkSession { public static let defaultUserAgent = { () -> String in let appContext = environment.appContextInfo() - let klaivyoSDKVersion = "klaviyo-ios/\(__klaviyoSwiftVersion)" + let klaivyoSDKVersion = "klaviyo-ios/\(environment.SDKVersion)" return "\(appContext.executable)/\(appContext.appVersion) (\(appContext.bundleId); build:\(appContext.appBuild); \(appContext.osVersionName)) \(klaivyoSDKVersion)" }() diff --git a/Sources/KlaviyoCore/Networking/PrivateMethods.swift b/Sources/KlaviyoCore/Networking/PrivateMethods.swift new file mode 100644 index 00000000..d5d305e2 --- /dev/null +++ b/Sources/KlaviyoCore/Networking/PrivateMethods.swift @@ -0,0 +1,28 @@ +// +// File.swift +// +// +// Created by Ajay Subramanya on 8/29/24. +// + +import Foundation + +@_spi(KlaviyoPrivate) +/// Used to override the URL that the SDK will interface for network activity +/// This is used internally to test the SDK against different backends, DO NOT use this in your apps. +/// - Parameter url: the backing url to use for setting API +public func setKlaviyoAPIURL(url: String) { + environment.apiURL = url +} + +@_spi(KlaviyoPrivate) +/// Used to set the SDK name and version. +/// This is mainly used by Klaviyo's react native SDK to override native platform values +/// DO NOT overrrifde this in yout apps as it will lead to network errors as our backend will not accept unsupported values here and your network requests will fail. +/// - Parameters: +/// - name: The name of the SDK +/// - version: The version of the SDK +public func setKlaviyoSDKNameAndVersion(name: String, version: String) { + environment.SDKName = name + environment.SDKVersion = version +} diff --git a/Tests/KlaviyoCoreTests/TestUtils.swift b/Tests/KlaviyoCoreTests/TestUtils.swift index a43de4f1..7a1dcb49 100644 --- a/Tests/KlaviyoCoreTests/TestUtils.swift +++ b/Tests/KlaviyoCoreTests/TestUtils.swift @@ -94,7 +94,9 @@ extension KlaviyoEnvironment { timeZone: { "EST" }, appContextInfo: { AppContextInfo.test }, klaviyoAPI: KlaviyoAPI.test(), - timer: { _ in Just(Date()).eraseToAnyPublisher() }) + timer: { _ in Just(Date()).eraseToAnyPublisher() }, + SDKName: __klaviyoSwiftName, + SDKVersion: __klaviyoSwiftVersion) } } diff --git a/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift b/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift index ac7c5aa8..431ae048 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift @@ -50,7 +50,9 @@ extension KlaviyoEnvironment { timeZone: { "EST" }, appContextInfo: { AppContextInfo.test }, klaviyoAPI: KlaviyoAPI.test(), - timer: { _ in Just(Date()).eraseToAnyPublisher() }) + timer: { _ in Just(Date()).eraseToAnyPublisher() }, + SDKName: __klaviyoSwiftName, + SDKVersion: __klaviyoSwiftVersion) } } From f07834c547fa9f8f0dcde65385a6acbbcf80222c Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Thu, 29 Aug 2024 10:40:21 -0500 Subject: [PATCH 59/72] consolidated to one method --- .../Networking/PrivateMethods.swift | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/Sources/KlaviyoCore/Networking/PrivateMethods.swift b/Sources/KlaviyoCore/Networking/PrivateMethods.swift index d5d305e2..7b47e4fc 100644 --- a/Sources/KlaviyoCore/Networking/PrivateMethods.swift +++ b/Sources/KlaviyoCore/Networking/PrivateMethods.swift @@ -8,21 +8,20 @@ import Foundation @_spi(KlaviyoPrivate) -/// Used to override the URL that the SDK will interface for network activity -/// This is used internally to test the SDK against different backends, DO NOT use this in your apps. -/// - Parameter url: the backing url to use for setting API -public func setKlaviyoAPIURL(url: String) { - environment.apiURL = url -} +/// Used to override SDK defailts for INTERNAL USE ONLY +/// - Parameter url: The URL to use for Klaviyo client APIs, This is used internally to test the SDK against different backends, DO NOT use this in your apps. +/// - Parameter name: The name of the SDK, defaults to swift but react native will pass it's own name. DO NOT override this in your apps as our backend will not accept unsupported values here and your network requests will fail. +/// - Parameter version: The version of the swift SDK default to hard coded values here but react native will pass it's own values here. DO NOT override this in your apps. +public func overrideSDKDefaults(url: String? = nil, name: String? = nil, version: String? = nil) { + if let url = url { + environment.apiURL = url + } -@_spi(KlaviyoPrivate) -/// Used to set the SDK name and version. -/// This is mainly used by Klaviyo's react native SDK to override native platform values -/// DO NOT overrrifde this in yout apps as it will lead to network errors as our backend will not accept unsupported values here and your network requests will fail. -/// - Parameters: -/// - name: The name of the SDK -/// - version: The version of the SDK -public func setKlaviyoSDKNameAndVersion(name: String, version: String) { - environment.SDKName = name - environment.SDKVersion = version + if let name = name { + environment.SDKName = name + } + + if let version = version { + environment.SDKVersion = version + } } From 7b1fee1f6754a1087ca23467575a04b30aca7d75 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:04:46 -0500 Subject: [PATCH 60/72] using closures --- Sources/KlaviyoCore/KlaviyoEnvironment.swift | 18 +++++++++--------- .../Models/APIModels/PushTokenPayload.swift | 4 ++-- .../Networking/KlaviyoRequest.swift | 6 +++--- .../Networking/PrivateMethods.swift | 6 +++--- Sources/KlaviyoCore/Utils/LoggerClient.swift | 2 +- Tests/KlaviyoCoreTests/KlaviyoAPITests.swift | 2 +- Tests/KlaviyoCoreTests/TestUtils.swift | 6 +++--- .../testCreateEmphemeralSesionHeaders.1.txt | 2 +- .../testDefaultUserAgent.1.txt | 2 +- Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift | 6 +++--- 10 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Sources/KlaviyoCore/KlaviyoEnvironment.swift b/Sources/KlaviyoCore/KlaviyoEnvironment.swift index b078a7cb..9acc1e59 100644 --- a/Sources/KlaviyoCore/KlaviyoEnvironment.swift +++ b/Sources/KlaviyoCore/KlaviyoEnvironment.swift @@ -29,7 +29,7 @@ public struct KlaviyoEnvironment { raiseFatalError: @escaping (String) -> Void, emitDeveloperWarning: @escaping (String) -> Void, networkSession: @escaping () -> NetworkSession, - apiURL: String, + apiURL: @escaping () -> String, encodeJSON: @escaping (AnyEncodable) throws -> Data, decoder: DataDecoder, uuid: @escaping () -> UUID, @@ -38,8 +38,8 @@ public struct KlaviyoEnvironment { appContextInfo: @escaping () -> AppContextInfo, klaviyoAPI: KlaviyoAPI, timer: @escaping (Double) -> AnyPublisher, - SDKName: String, - SDKVersion: String) { + SDKName: @escaping () -> String, + SDKVersion: @escaping () -> String) { self.archiverClient = archiverClient self.fileClient = fileClient self.dataFromUrl = dataFromUrl @@ -105,7 +105,7 @@ public struct KlaviyoEnvironment { public var emitDeveloperWarning: (String) -> Void public var networkSession: () -> NetworkSession - public var apiURL: String + public var apiURL: () -> String public var encodeJSON: (AnyEncodable) throws -> Data public var decoder: DataDecoder public var uuid: () -> UUID @@ -115,8 +115,8 @@ public struct KlaviyoEnvironment { public var klaviyoAPI: KlaviyoAPI public var timer: (Double) -> AnyPublisher - public var SDKName: String - public var SDKVersion: String + public var SDKName: () -> String + public var SDKVersion: () -> String public static var production = KlaviyoEnvironment( archiverClient: ArchiverClient.production, @@ -151,7 +151,7 @@ public struct KlaviyoEnvironment { }, emitDeveloperWarning: { runtimeWarn($0) }, networkSession: createNetworkSession, - apiURL: KlaviyoEnvironment.productionHost, + apiURL: { KlaviyoEnvironment.productionHost }, encodeJSON: { encodable in try encoder.encode(encodable) }, decoder: DataDecoder.production, uuid: { UUID() }, @@ -164,8 +164,8 @@ public struct KlaviyoEnvironment { .autoconnect() .eraseToAnyPublisher() }, - SDKName: __klaviyoSwiftName, - SDKVersion: __klaviyoSwiftVersion) + SDKName: { __klaviyoSwiftName }, + SDKVersion: { __klaviyoSwiftVersion }) } public var networkSession: NetworkSession! diff --git a/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift b/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift index 0a9726e9..7a3e356a 100644 --- a/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift +++ b/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift @@ -104,8 +104,8 @@ public struct PushTokenPayload: Equatable, Codable { appVersion = context.appVersion appBuild = context.appBuild environment = context.environment - klaviyoSdk = KlaviyoCore.environment.SDKName - sdkVersion = KlaviyoCore.environment.SDKVersion + klaviyoSdk = KlaviyoCore.environment.SDKName() + sdkVersion = KlaviyoCore.environment.SDKVersion() } } } diff --git a/Sources/KlaviyoCore/Networking/KlaviyoRequest.swift b/Sources/KlaviyoCore/Networking/KlaviyoRequest.swift index cedd4070..ac36f024 100644 --- a/Sources/KlaviyoCore/Networking/KlaviyoRequest.swift +++ b/Sources/KlaviyoCore/Networking/KlaviyoRequest.swift @@ -24,7 +24,7 @@ public struct KlaviyoRequest: Equatable, Codable { public func urlRequest(_ attemptNumber: Int = 1) throws -> URLRequest { guard let url = url else { - throw KlaviyoAPIError.internalError("Invalid url string. API URL: \(environment.apiURL)") + throw KlaviyoAPIError.internalError("Invalid url string. API URL: \(environment.apiURL())") } var request = URLRequest(url: url) // We only support post right now @@ -41,8 +41,8 @@ public struct KlaviyoRequest: Equatable, Codable { var url: URL? { switch endpoint { case .createProfile, .createEvent, .registerPushToken, .unregisterPushToken: - if !environment.apiURL.isEmpty { - return URL(string: "\(environment.apiURL)/\(path)/?company_id=\(apiKey)") + if !environment.apiURL().isEmpty { + return URL(string: "\(environment.apiURL())/\(path)/?company_id=\(apiKey)") } return nil } diff --git a/Sources/KlaviyoCore/Networking/PrivateMethods.swift b/Sources/KlaviyoCore/Networking/PrivateMethods.swift index 7b47e4fc..e7913645 100644 --- a/Sources/KlaviyoCore/Networking/PrivateMethods.swift +++ b/Sources/KlaviyoCore/Networking/PrivateMethods.swift @@ -14,14 +14,14 @@ import Foundation /// - Parameter version: The version of the swift SDK default to hard coded values here but react native will pass it's own values here. DO NOT override this in your apps. public func overrideSDKDefaults(url: String? = nil, name: String? = nil, version: String? = nil) { if let url = url { - environment.apiURL = url + environment.apiURL = { url } } if let name = name { - environment.SDKName = name + environment.SDKName = { name } } if let version = version { - environment.SDKVersion = version + environment.SDKVersion = { version } } } diff --git a/Sources/KlaviyoCore/Utils/LoggerClient.swift b/Sources/KlaviyoCore/Utils/LoggerClient.swift index 49da89b3..5f2829e4 100644 --- a/Sources/KlaviyoCore/Utils/LoggerClient.swift +++ b/Sources/KlaviyoCore/Utils/LoggerClient.swift @@ -23,7 +23,7 @@ public struct LoggerClient { @inline(__always) func runtimeWarn( _ message: @autoclosure () -> String, - category: String? = environment.SDKName, + category: String? = environment.SDKName(), file: StaticString? = nil, line: UInt? = nil) { #if DEBUG diff --git a/Tests/KlaviyoCoreTests/KlaviyoAPITests.swift b/Tests/KlaviyoCoreTests/KlaviyoAPITests.swift index 3857408f..34993b78 100644 --- a/Tests/KlaviyoCoreTests/KlaviyoAPITests.swift +++ b/Tests/KlaviyoCoreTests/KlaviyoAPITests.swift @@ -18,7 +18,7 @@ final class KlaviyoAPITests: XCTestCase { } func testInvalidURL() async throws { - environment.apiURL = "" + environment.apiURL = { "" } await sendAndAssert(with: KlaviyoRequest( apiKey: "foo", diff --git a/Tests/KlaviyoCoreTests/TestUtils.swift b/Tests/KlaviyoCoreTests/TestUtils.swift index 7a1dcb49..a729b58d 100644 --- a/Tests/KlaviyoCoreTests/TestUtils.swift +++ b/Tests/KlaviyoCoreTests/TestUtils.swift @@ -86,7 +86,7 @@ extension KlaviyoEnvironment { raiseFatalError: { _ in }, emitDeveloperWarning: { _ in }, networkSession: { NetworkSession.test() }, - apiURL: "dead_beef", + apiURL: { "dead_beef" }, encodeJSON: { _ in TEST_RETURN_DATA }, decoder: DataDecoder(jsonDecoder: TestJSONDecoder()), uuid: { UUID(uuidString: "00000000-0000-0000-0000-000000000001")! }, @@ -95,8 +95,8 @@ extension KlaviyoEnvironment { appContextInfo: { AppContextInfo.test }, klaviyoAPI: KlaviyoAPI.test(), timer: { _ in Just(Date()).eraseToAnyPublisher() }, - SDKName: __klaviyoSwiftName, - SDKVersion: __klaviyoSwiftVersion) + SDKName: { __klaviyoSwiftName }, + SDKVersion: { __klaviyoSwiftVersion }) } } diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt index 19cd06cb..6caaad22 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt +++ b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt @@ -8,7 +8,7 @@ - "deflate" ▿ (2 elements) - key: "User-Agent" - - value: "FooApp/1.2.3 (com.klaviyo.fooapp; build:1; iOS 1.1.1) klaviyo-ios/3.2.0" + - value: "FooApp/1.2.3 (com.klaviyo.fooapp; build:1; iOS 1.1.1) klaviyo-ios/(Function)" ▿ (2 elements) - key: "X-Klaviyo-Mobile" - value: "1" diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt index 40374900..c8ec1ed4 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt +++ b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt @@ -1 +1 @@ -- "FooApp/1.2.3 (com.klaviyo.fooapp; build:1; iOS 1.1.1) klaviyo-ios/3.2.0" +- "FooApp/1.2.3 (com.klaviyo.fooapp; build:1; iOS 1.1.1) klaviyo-ios/(Function)" diff --git a/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift b/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift index 431ae048..0b7d7257 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoTestUtils.swift @@ -42,7 +42,7 @@ extension KlaviyoEnvironment { raiseFatalError: { _ in }, emitDeveloperWarning: { _ in }, networkSession: { NetworkSession.test() }, - apiURL: "dead_beef", + apiURL: { "dead_beef" }, encodeJSON: { _ in TEST_RETURN_DATA }, decoder: DataDecoder(jsonDecoder: TestJSONDecoder()), uuid: { UUID(uuidString: "00000000-0000-0000-0000-000000000001")! }, @@ -51,8 +51,8 @@ extension KlaviyoEnvironment { appContextInfo: { AppContextInfo.test }, klaviyoAPI: KlaviyoAPI.test(), timer: { _ in Just(Date()).eraseToAnyPublisher() }, - SDKName: __klaviyoSwiftName, - SDKVersion: __klaviyoSwiftVersion) + SDKName: { __klaviyoSwiftName }, + SDKVersion: { __klaviyoSwiftVersion }) } } From 13cb284f8513922144299bae4f2646585c7d951a Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:08:37 -0500 Subject: [PATCH 61/72] SDK to sdk in naming --- Sources/KlaviyoCore/KlaviyoEnvironment.swift | 8 ++++---- .../KlaviyoCore/Models/APIModels/CreateEventPayload.swift | 4 ++-- .../KlaviyoCore/Models/APIModels/PushTokenPayload.swift | 4 ++-- Sources/KlaviyoCore/Networking/NetworkSession.swift | 2 +- Sources/KlaviyoCore/Networking/PrivateMethods.swift | 4 ++-- Sources/KlaviyoCore/Utils/LoggerClient.swift | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/KlaviyoCore/KlaviyoEnvironment.swift b/Sources/KlaviyoCore/KlaviyoEnvironment.swift index 9acc1e59..da3232eb 100644 --- a/Sources/KlaviyoCore/KlaviyoEnvironment.swift +++ b/Sources/KlaviyoCore/KlaviyoEnvironment.swift @@ -64,8 +64,8 @@ public struct KlaviyoEnvironment { self.appContextInfo = appContextInfo self.klaviyoAPI = klaviyoAPI self.timer = timer - self.SDKName = SDKName - self.SDKVersion = SDKVersion + sdkName = SDKName + sdkVersion = SDKVersion } static let productionHost = "https://a.klaviyo.com" @@ -115,8 +115,8 @@ public struct KlaviyoEnvironment { public var klaviyoAPI: KlaviyoAPI public var timer: (Double) -> AnyPublisher - public var SDKName: () -> String - public var SDKVersion: () -> String + public var sdkName: () -> String + public var sdkVersion: () -> String public static var production = KlaviyoEnvironment( archiverClient: ArchiverClient.production, diff --git a/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift b/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift index 030876f8..57816d8c 100644 --- a/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift +++ b/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift @@ -120,8 +120,8 @@ extension Dictionary where Key == String, Value == Any { "Device Model": context.deviceModel, "OS Name": context.osName, "OS Version": context.osVersion, - "SDK Name": environment.SDKName, - "SDK Version": environment.SDKVersion, + "SDK Name": environment.sdkName, + "SDK Version": environment.sdkVersion, "App Name": context.appName, "App ID": context.bundleId, "App Version": context.appVersion, diff --git a/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift b/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift index 7a3e356a..441f5c88 100644 --- a/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift +++ b/Sources/KlaviyoCore/Models/APIModels/PushTokenPayload.swift @@ -104,8 +104,8 @@ public struct PushTokenPayload: Equatable, Codable { appVersion = context.appVersion appBuild = context.appBuild environment = context.environment - klaviyoSdk = KlaviyoCore.environment.SDKName() - sdkVersion = KlaviyoCore.environment.SDKVersion() + klaviyoSdk = KlaviyoCore.environment.sdkName() + sdkVersion = KlaviyoCore.environment.sdkVersion() } } } diff --git a/Sources/KlaviyoCore/Networking/NetworkSession.swift b/Sources/KlaviyoCore/Networking/NetworkSession.swift index 0a7398c5..59e70580 100644 --- a/Sources/KlaviyoCore/Networking/NetworkSession.swift +++ b/Sources/KlaviyoCore/Networking/NetworkSession.swift @@ -35,7 +35,7 @@ public struct NetworkSession { public static let defaultUserAgent = { () -> String in let appContext = environment.appContextInfo() - let klaivyoSDKVersion = "klaviyo-ios/\(environment.SDKVersion)" + let klaivyoSDKVersion = "klaviyo-ios/\(environment.sdkVersion())" return "\(appContext.executable)/\(appContext.appVersion) (\(appContext.bundleId); build:\(appContext.appBuild); \(appContext.osVersionName)) \(klaivyoSDKVersion)" }() diff --git a/Sources/KlaviyoCore/Networking/PrivateMethods.swift b/Sources/KlaviyoCore/Networking/PrivateMethods.swift index e7913645..5a036308 100644 --- a/Sources/KlaviyoCore/Networking/PrivateMethods.swift +++ b/Sources/KlaviyoCore/Networking/PrivateMethods.swift @@ -18,10 +18,10 @@ public func overrideSDKDefaults(url: String? = nil, name: String? = nil, version } if let name = name { - environment.SDKName = { name } + environment.sdkName = { name } } if let version = version { - environment.SDKVersion = { version } + environment.sdkVersion = { version } } } diff --git a/Sources/KlaviyoCore/Utils/LoggerClient.swift b/Sources/KlaviyoCore/Utils/LoggerClient.swift index 5f2829e4..24778205 100644 --- a/Sources/KlaviyoCore/Utils/LoggerClient.swift +++ b/Sources/KlaviyoCore/Utils/LoggerClient.swift @@ -23,7 +23,7 @@ public struct LoggerClient { @inline(__always) func runtimeWarn( _ message: @autoclosure () -> String, - category: String? = environment.SDKName(), + category: String? = environment.sdkName(), file: StaticString? = nil, line: UInt? = nil) { #if DEBUG From 4fa5d2d77d42cee8ba6a4a94b3ad5bb996db69b4 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:12:25 -0500 Subject: [PATCH 62/72] better comments --- Sources/KlaviyoCore/Networking/PrivateMethods.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/KlaviyoCore/Networking/PrivateMethods.swift b/Sources/KlaviyoCore/Networking/PrivateMethods.swift index 5a036308..cff5ac5e 100644 --- a/Sources/KlaviyoCore/Networking/PrivateMethods.swift +++ b/Sources/KlaviyoCore/Networking/PrivateMethods.swift @@ -7,11 +7,11 @@ import Foundation -@_spi(KlaviyoPrivate) /// Used to override SDK defailts for INTERNAL USE ONLY /// - Parameter url: The URL to use for Klaviyo client APIs, This is used internally to test the SDK against different backends, DO NOT use this in your apps. /// - Parameter name: The name of the SDK, defaults to swift but react native will pass it's own name. DO NOT override this in your apps as our backend will not accept unsupported values here and your network requests will fail. /// - Parameter version: The version of the swift SDK default to hard coded values here but react native will pass it's own values here. DO NOT override this in your apps. +@_spi(KlaviyoPrivate) public func overrideSDKDefaults(url: String? = nil, name: String? = nil, version: String? = nil) { if let url = url { environment.apiURL = { url } From ae86e6948dbc62ccdb2f0c7bc5bad72eaab127ed Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:14:16 -0500 Subject: [PATCH 63/72] better comments --- Sources/KlaviyoCore/Networking/PrivateMethods.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/KlaviyoCore/Networking/PrivateMethods.swift b/Sources/KlaviyoCore/Networking/PrivateMethods.swift index cff5ac5e..d52c5791 100644 --- a/Sources/KlaviyoCore/Networking/PrivateMethods.swift +++ b/Sources/KlaviyoCore/Networking/PrivateMethods.swift @@ -12,6 +12,7 @@ import Foundation /// - Parameter name: The name of the SDK, defaults to swift but react native will pass it's own name. DO NOT override this in your apps as our backend will not accept unsupported values here and your network requests will fail. /// - Parameter version: The version of the swift SDK default to hard coded values here but react native will pass it's own values here. DO NOT override this in your apps. @_spi(KlaviyoPrivate) +@available(*, deprecated, message: "This function is for internal use only, and should NOT be used in production applications") public func overrideSDKDefaults(url: String? = nil, name: String? = nil, version: String? = nil) { if let url = url { environment.apiURL = { url } From d1bafc0044714116f91e2841c3956b411d6a05bd Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:06:27 -0500 Subject: [PATCH 64/72] using compact map --- Sources/KlaviyoCore/AppLifeCycleEvents.swift | 3 +-- .../testCreateEmphemeralSesionHeaders.1.txt | 2 +- .../NetworkSessionTests/testDefaultUserAgent.1.txt | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Sources/KlaviyoCore/AppLifeCycleEvents.swift b/Sources/KlaviyoCore/AppLifeCycleEvents.swift index f8959174..175c36f7 100644 --- a/Sources/KlaviyoCore/AppLifeCycleEvents.swift +++ b/Sources/KlaviyoCore/AppLifeCycleEvents.swift @@ -49,8 +49,7 @@ public struct AppLifeCycleEvents { // The below is a bit convoluted since network status can be nil. let reachability = environment .notificationCenterPublisher(ReachabilityChangedNotification) - .map { _ in - // TODO: compact map isn't working, need to fix + .compactMap { _ in let status = environment.reachabilityStatus() ?? .reachableViaWWAN return LifeCycleEvents.reachabilityChanged(status: status) } diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt index 6caaad22..19cd06cb 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt +++ b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testCreateEmphemeralSesionHeaders.1.txt @@ -8,7 +8,7 @@ - "deflate" ▿ (2 elements) - key: "User-Agent" - - value: "FooApp/1.2.3 (com.klaviyo.fooapp; build:1; iOS 1.1.1) klaviyo-ios/(Function)" + - value: "FooApp/1.2.3 (com.klaviyo.fooapp; build:1; iOS 1.1.1) klaviyo-ios/3.2.0" ▿ (2 elements) - key: "X-Klaviyo-Mobile" - value: "1" diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt index c8ec1ed4..40374900 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt +++ b/Tests/KlaviyoCoreTests/__Snapshots__/NetworkSessionTests/testDefaultUserAgent.1.txt @@ -1 +1 @@ -- "FooApp/1.2.3 (com.klaviyo.fooapp; build:1; iOS 1.1.1) klaviyo-ios/(Function)" +- "FooApp/1.2.3 (com.klaviyo.fooapp; build:1; iOS 1.1.1) klaviyo-ios/3.2.0" From 9db9460cc48600350611200844bf67e267cbac69 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:23:08 -0500 Subject: [PATCH 65/72] removed a line in test --- Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift b/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift index 1feb3d7b..8c656358 100644 --- a/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift +++ b/Tests/KlaviyoSwiftTests/KlaviyoStateTests.swift @@ -120,7 +120,6 @@ final class KlaviyoStateTests: XCTestCase { queue: [], requestsInFlight: [])) } -// environment.decoder = DataDecoder(jsonDecoder: KlaviyoEnvironment.decoder) let state = loadKlaviyoStateFromDisk(apiKey: "foo") assertSnapshot(matching: state, as: .dump) From 4273043c891865adc7327f186f2d015ebc5aef9d Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:40:19 -0500 Subject: [PATCH 66/72] added a line back in --- Tests/KlaviyoCoreTests/ArchivalUtilsTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/KlaviyoCoreTests/ArchivalUtilsTests.swift b/Tests/KlaviyoCoreTests/ArchivalUtilsTests.swift index 228073f4..d4074acb 100644 --- a/Tests/KlaviyoCoreTests/ArchivalUtilsTests.swift +++ b/Tests/KlaviyoCoreTests/ArchivalUtilsTests.swift @@ -106,6 +106,7 @@ class ArchivalSystemTest: XCTestCase { let TEST_URL = filePathForData(apiKey: "foo", data: "people") override func setUpWithError() throws { + environment = KlaviyoEnvironment.production try? FileManager.default.removeItem(atPath: TEST_URL.path) } From b4c72d00c6b6b2fa3bcf63b871b5257bca584bf0 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:51:41 -0500 Subject: [PATCH 67/72] mibnor fix --- .../Models/APIModels/CreateEventPayload.swift | 4 +-- .../testEventPayloadWithoutMetadata.1.json | 31 ------------------- 2 files changed, 2 insertions(+), 33 deletions(-) delete mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json diff --git a/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift b/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift index 57816d8c..f7e21d41 100644 --- a/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift +++ b/Sources/KlaviyoCore/Models/APIModels/CreateEventPayload.swift @@ -120,8 +120,8 @@ extension Dictionary where Key == String, Value == Any { "Device Model": context.deviceModel, "OS Name": context.osName, "OS Version": context.osVersion, - "SDK Name": environment.sdkName, - "SDK Version": environment.sdkVersion, + "SDK Name": environment.sdkName(), + "SDK Version": environment.sdkVersion(), "App Name": context.appName, "App ID": context.bundleId, "App Version": context.appVersion, diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json deleted file mode 100644 index 69b4464d..00000000 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "data" : { - "attributes" : { - "metric" : { - "data" : { - "attributes" : { - "name" : "test" - }, - "type" : "metric" - } - }, - "profile" : { - "data" : { - "attributes" : { - "anonymous_id" : "anon-id", - "properties" : { - - } - }, - "type" : "profile" - } - }, - "properties" : { - - }, - "time" : "2009-02-13T23:31:30Z", - "unique_id" : "00000000-0000-0000-0000-000000000001" - }, - "type" : "event" - } -} From b0f2533712987ebd7a25498659d25f37d4101c1c Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Tue, 3 Sep 2024 16:09:33 -0500 Subject: [PATCH 68/72] fixed tests --- .../testEventPayloadWithoutMetadata.1.json | 31 +++++++++++++++++++ .../StateManagementTests.swift | 3 -- 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json new file mode 100644 index 00000000..69b4464d --- /dev/null +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json @@ -0,0 +1,31 @@ +{ + "data" : { + "attributes" : { + "metric" : { + "data" : { + "attributes" : { + "name" : "test" + }, + "type" : "metric" + } + }, + "profile" : { + "data" : { + "attributes" : { + "anonymous_id" : "anon-id", + "properties" : { + + } + }, + "type" : "profile" + } + }, + "properties" : { + + }, + "time" : "2009-02-13T23:31:30Z", + "unique_id" : "00000000-0000-0000-0000-000000000001" + }, + "type" : "event" + } +} diff --git a/Tests/KlaviyoSwiftTests/StateManagementTests.swift b/Tests/KlaviyoSwiftTests/StateManagementTests.swift index 39884eed..b2ef5156 100644 --- a/Tests/KlaviyoSwiftTests/StateManagementTests.swift +++ b/Tests/KlaviyoSwiftTests/StateManagementTests.swift @@ -592,9 +592,6 @@ class StateManagementTests: XCTestCase { $0.pendingRequests = [KlaviyoState.PendingRequest.event(event)] } - // TODO: this passes if I comment the below line in state management - // `.merge(with: klaviyoSwiftEnvironment.stateChangePublisher().eraseToEffect())` - // fails with - An effect returned for this action is still running. It must complete before the end of the test. … await store.send(.completeInitialization(initialState)) { $0.pendingRequests = [] $0.initalizationState = .initialized From f94176de23dd13930a55fb3b9a8262755345337d Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Tue, 3 Sep 2024 21:55:35 -0500 Subject: [PATCH 69/72] commiting snapshots without verify --- .../EncodableTests/testEventPayloadWithoutMetadata.1.json | 2 +- .../__Snapshots__/EncodableTests/testKlaviyoRequest.1.json | 2 +- .../__Snapshots__/EncodableTests/testProfilePayload.1.json | 2 +- .../__Snapshots__/EncodableTests/testTokenPayload.1.json | 2 +- .../EncodableTests/testUnregisterTokenPayload.1.json | 2 +- .../__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json index 69b4464d..3f2a80de 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json @@ -28,4 +28,4 @@ }, "type" : "event" } -} +} \ No newline at end of file diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json index cc8253d1..4e9b45c7 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testKlaviyoRequest.1.json @@ -44,4 +44,4 @@ } }, "uuid" : "00000000-0000-0000-0000-000000000001" -} +} \ No newline at end of file diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json index c8491d66..8a051b50 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testProfilePayload.1.json @@ -27,4 +27,4 @@ }, "type" : "profile" } -} +} \ No newline at end of file diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json index 28dd3b60..8935058a 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testTokenPayload.1.json @@ -36,4 +36,4 @@ }, "type" : "push-token" } -} +} \ No newline at end of file diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json index e4e5fdab..74ed5677 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testUnregisterTokenPayload.1.json @@ -20,4 +20,4 @@ }, "type" : "push-token-unregister" } -} +} \ No newline at end of file diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt index da5e392f..675c1934 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt +++ b/Tests/KlaviyoCoreTests/__Snapshots__/KlaviyoAPITests/testInvalidURL.1.txt @@ -1 +1 @@ -internalRequestError(KlaviyoCore.KlaviyoAPIError.internalError("Invalid url string. API URL: ")) +internalRequestError(KlaviyoCore.KlaviyoAPIError.internalError("Invalid url string. API URL: ")) \ No newline at end of file From 62a51c4beeeb6f70203b8930220c917507edc227 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:08:35 -0500 Subject: [PATCH 70/72] excluding core snapshots --- .pre-commit-config.yaml | 2 +- Tests/KlaviyoCoreTests/KlaviyoAPITests.swift | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 62239b1d..988885de 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -exclude: Tests/KlaviyoSwiftTests/__Snapshots__ +exclude: Tests/.*/__Snapshots__ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.3.0 diff --git a/Tests/KlaviyoCoreTests/KlaviyoAPITests.swift b/Tests/KlaviyoCoreTests/KlaviyoAPITests.swift index 34993b78..2245b924 100644 --- a/Tests/KlaviyoCoreTests/KlaviyoAPITests.swift +++ b/Tests/KlaviyoCoreTests/KlaviyoAPITests.swift @@ -12,8 +12,6 @@ import XCTest @MainActor final class KlaviyoAPITests: XCTestCase { override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - environment = KlaviyoEnvironment.test() } From 1d8e5abc6fe4d3fd8e8221c0892b658fd3d3803e Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:42:28 -0500 Subject: [PATCH 71/72] added properties to event test --- Tests/KlaviyoCoreTests/EncodableTests.swift | 12 ++++++++++-- ...Metadata.1.json => testEventPayload.1.json} | 18 +++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) rename Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/{testEventPayloadWithoutMetadata.1.json => testEventPayload.1.json} (52%) diff --git a/Tests/KlaviyoCoreTests/EncodableTests.swift b/Tests/KlaviyoCoreTests/EncodableTests.swift index 5814a3a1..c6042fd4 100644 --- a/Tests/KlaviyoCoreTests/EncodableTests.swift +++ b/Tests/KlaviyoCoreTests/EncodableTests.swift @@ -22,8 +22,16 @@ final class EncodableTests: XCTestCase { assertSnapshot(matching: payload, as: .json(KlaviyoEnvironment.encoder)) } - func testEventPayloadWithoutMetadata() throws { - let createEventPayload = CreateEventPayload(data: CreateEventPayload.Event(name: "test", anonymousId: "anon-id")) + func testEventPayload() throws { + let SAMPLE_PROPERTIES = [ + "blob": "blob", + "stuff": 2, + "hello": [ + "sub": "dict" + ] + ] as [String: Any] + let payloadData = CreateEventPayload.Event(name: "test", properties: SAMPLE_PROPERTIES, anonymousId: "anon-id") + let createEventPayload = CreateEventPayload(data: payloadData) assertSnapshot(matching: createEventPayload, as: .json(KlaviyoEnvironment.encoder)) } diff --git a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayload.1.json similarity index 52% rename from Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json rename to Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayload.1.json index 3f2a80de..0fe6338e 100644 --- a/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayloadWithoutMetadata.1.json +++ b/Tests/KlaviyoCoreTests/__Snapshots__/EncodableTests/testEventPayload.1.json @@ -21,7 +21,23 @@ } }, "properties" : { - + "App Build" : "1", + "App ID" : "com.klaviyo.fooapp", + "App Name" : "FooApp", + "App Version" : "1.2.3", + "blob" : "blob", + "Device ID" : "fe-fi-fo-fum", + "Device Manufacturer" : "Orange", + "Device Model" : "jPhone 1,1", + "hello" : { + "sub" : "dict" + }, + "OS Name" : "iOS", + "OS Version" : "1.1.1", + "Push Token" : "", + "SDK Name" : "swift", + "SDK Version" : "3.2.0", + "stuff" : 2 }, "time" : "2009-02-13T23:31:30Z", "unique_id" : "00000000-0000-0000-0000-000000000001" From 66c110ff2417793aee73cb50270f33dd3aa8ba36 Mon Sep 17 00:00:00 2001 From: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:43:22 -0500 Subject: [PATCH 72/72] added properties to event test --- Tests/KlaviyoCoreTests/EncodableTests.swift | 7 ------- Tests/KlaviyoCoreTests/TestUtils.swift | 8 ++++++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Tests/KlaviyoCoreTests/EncodableTests.swift b/Tests/KlaviyoCoreTests/EncodableTests.swift index c6042fd4..a88cc7e8 100644 --- a/Tests/KlaviyoCoreTests/EncodableTests.swift +++ b/Tests/KlaviyoCoreTests/EncodableTests.swift @@ -23,13 +23,6 @@ final class EncodableTests: XCTestCase { } func testEventPayload() throws { - let SAMPLE_PROPERTIES = [ - "blob": "blob", - "stuff": 2, - "hello": [ - "sub": "dict" - ] - ] as [String: Any] let payloadData = CreateEventPayload.Event(name: "test", properties: SAMPLE_PROPERTIES, anonymousId: "anon-id") let createEventPayload = CreateEventPayload(data: payloadData) assertSnapshot(matching: createEventPayload, as: .json(KlaviyoEnvironment.encoder)) diff --git a/Tests/KlaviyoCoreTests/TestUtils.swift b/Tests/KlaviyoCoreTests/TestUtils.swift index 6a2df64b..4f1c0c78 100644 --- a/Tests/KlaviyoCoreTests/TestUtils.swift +++ b/Tests/KlaviyoCoreTests/TestUtils.swift @@ -61,6 +61,14 @@ let TEST_FAILURE_JSON_INVALID_EMAIL = """ } """ +let SAMPLE_PROPERTIES = [ + "blob": "blob", + "stuff": 2, + "hello": [ + "sub": "dict" + ] +] as [String: Any] + extension ArchiverClient { static let test = ArchiverClient( archivedData: { _, _ in ARCHIVED_RETURNED_DATA },