From 486ff38097801bf9b4333b87ab78086f80f6062f Mon Sep 17 00:00:00 2001 From: "justin.fiedler" Date: Tue, 6 Feb 2024 19:02:41 +0000 Subject: [PATCH 1/9] fix: migrate storage to instanceName-apiKey to isolate by instance --- Sources/Amplitude/Amplitude.swift | 17 +++++++++++++++++ Sources/Amplitude/Configuration.swift | 10 +++++++--- .../Amplitude/Storages/PersistentStorage.swift | 8 ++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Sources/Amplitude/Amplitude.swift b/Sources/Amplitude/Amplitude.swift index ffd9bc89..4b8fbef4 100644 --- a/Sources/Amplitude/Amplitude.swift +++ b/Sources/Amplitude/Amplitude.swift @@ -41,6 +41,7 @@ public class Amplitude { migrateApiKeyStorages() migrateDefaultInstanceStorages() + migrateInstanceOnlyStorages() if configuration.migrateLegacyData { RemnantDataMigration(self).execute() @@ -361,4 +362,20 @@ public class Amplitude { StoragePrefixMigration(source: legacyIdentifyStorage, destination: persistentIdentifyStorage, logger: logger).execute() } } + + private func migrateInstanceOnlyStorages() { + let instanceName = configuration.getNormalizeInstanceName() + + if let persistentStorage = configuration.storageProvider as? PersistentStorage { + let instanceOnlyEventPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-storage-\(instanceName)" + let instanceNameOnlyStorage = PersistentStorage(storagePrefix: instanceOnlyEventPrefix) + StoragePrefixMigration(source: instanceNameOnlyStorage, destination: persistentStorage, logger: logger).execute() + } + + if let persistentIdentifyStorage = configuration.identifyStorageProvider as? PersistentStorage { + let instanceOnlyIdentifyPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-identify-\(instanceName)" + let instanceNameOnlyIdentifyStorage = PersistentStorage(storagePrefix: instanceOnlyIdentifyPrefix) + StoragePrefixMigration(source: instanceNameOnlyIdentifyStorage, destination: persistentIdentifyStorage, logger: logger).execute() + } + } } diff --git a/Sources/Amplitude/Configuration.swift b/Sources/Amplitude/Configuration.swift index 4046c3d7..4694488a 100644 --- a/Sources/Amplitude/Configuration.swift +++ b/Sources/Amplitude/Configuration.swift @@ -62,7 +62,7 @@ public class Configuration { identifyBatchIntervalMillis: Int = Constants.Configuration.IDENTIFY_BATCH_INTERVAL_MILLIS, migrateLegacyData: Bool = true ) { - let normalizedInstanceName = instanceName == "" ? Constants.Configuration.DEFAULT_INSTANCE : instanceName + let normalizedInstanceName = getNormalizeInstanceName() self.apiKey = apiKey self.flushQueueSize = flushQueueSize @@ -70,9 +70,9 @@ public class Configuration { self.instanceName = normalizedInstanceName self.optOut = optOut self.storageProvider = storageProvider - ?? PersistentStorage(storagePrefix: "storage-\(normalizedInstanceName)") + ?? PersistentStorage(storagePrefix: PersistentStorage.getEventStoragePrefix(apiKey, normalizedInstanceName)) self.identifyStorageProvider = identifyStorageProvider - ?? PersistentStorage(storagePrefix: "identify-\(normalizedInstanceName)") + ?? PersistentStorage(storagePrefix: PersistentStorage.getIdentifyStoragePrefix(apiKey, normalizedInstanceName)) self.logLevel = logLevel self.loggerProvider = loggerProvider self.minIdLength = minIdLength @@ -100,4 +100,8 @@ public class Configuration { && minTimeBetweenSessionsMillis > 0 && (minIdLength == nil || minIdLength! > 0) } + + internal func getNormalizeInstanceName() -> String { + return self.instanceName == "" ? Constants.Configuration.DEFAULT_INSTANCE : self.instanceName + } } diff --git a/Sources/Amplitude/Storages/PersistentStorage.swift b/Sources/Amplitude/Storages/PersistentStorage.swift index cde30c1b..db4f15ef 100644 --- a/Sources/Amplitude/Storages/PersistentStorage.swift +++ b/Sources/Amplitude/Storages/PersistentStorage.swift @@ -10,6 +10,14 @@ import Foundation class PersistentStorage: Storage { typealias EventBlock = URL + static internal func getEventStoragePrefix(_ apiKey: String, _ instanceName: String) -> String { + return "storage-\(instanceName)-\(apiKey)" + } + + static internal func getIdentifyStoragePrefix(_ apiKey: String, _ instanceName: String) -> String { + return "identify-\(instanceName)-\(apiKey)" + } + let storagePrefix: String let userDefaults: UserDefaults? let fileManager: FileManager From 2c10a1af91ed12b838488a5994aa03108af0558e Mon Sep 17 00:00:00 2001 From: "justin.fiedler" Date: Tue, 6 Feb 2024 19:43:53 +0000 Subject: [PATCH 2/9] fix: only migration instance storage for sandboxed apps --- Sources/Amplitude/Amplitude.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Sources/Amplitude/Amplitude.swift b/Sources/Amplitude/Amplitude.swift index 4b8fbef4..7571ae27 100644 --- a/Sources/Amplitude/Amplitude.swift +++ b/Sources/Amplitude/Amplitude.swift @@ -362,10 +362,14 @@ public class Amplitude { StoragePrefixMigration(source: legacyIdentifyStorage, destination: persistentIdentifyStorage, logger: logger).execute() } } - + private func migrateInstanceOnlyStorages() { - let instanceName = configuration.getNormalizeInstanceName() + // Only migrate sandboxed apps to avoid potential data polution + if (!SandboxHelper().isSandboxEnabled()) { + return; + } + let instanceName = configuration.getNormalizeInstanceName() if let persistentStorage = configuration.storageProvider as? PersistentStorage { let instanceOnlyEventPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-storage-\(instanceName)" let instanceNameOnlyStorage = PersistentStorage(storagePrefix: instanceOnlyEventPrefix) From b2acfbe80c66dcfe8f28028972af2c654730bafb Mon Sep 17 00:00:00 2001 From: "justin.fiedler" Date: Tue, 6 Feb 2024 19:49:37 +0000 Subject: [PATCH 3/9] chore: code clean up --- Sources/Amplitude/Amplitude.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Amplitude/Amplitude.swift b/Sources/Amplitude/Amplitude.swift index 7571ae27..d1ebfc6e 100644 --- a/Sources/Amplitude/Amplitude.swift +++ b/Sources/Amplitude/Amplitude.swift @@ -364,11 +364,11 @@ public class Amplitude { } private func migrateInstanceOnlyStorages() { - // Only migrate sandboxed apps to avoid potential data polution + // Only migrate sandboxed apps to avoid potential data pollution if (!SandboxHelper().isSandboxEnabled()) { return; } - + let instanceName = configuration.getNormalizeInstanceName() if let persistentStorage = configuration.storageProvider as? PersistentStorage { let instanceOnlyEventPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-storage-\(instanceName)" From 72b9378df295068981f71f8a1890da8d3adc9434 Mon Sep 17 00:00:00 2001 From: "justin.fiedler" Date: Wed, 7 Feb 2024 19:20:17 +0000 Subject: [PATCH 4/9] chore: add STORAGE_VERSION to userdefaults to keep track of migration status --- Sources/Amplitude/Amplitude.swift | 58 ++++++++++++++----- Sources/Amplitude/Configuration.swift | 8 ++- .../Storages/PersistentStorage.swift | 4 +- Sources/Amplitude/Types.swift | 17 ++++++ 4 files changed, 67 insertions(+), 20 deletions(-) diff --git a/Sources/Amplitude/Amplitude.swift b/Sources/Amplitude/Amplitude.swift index d1ebfc6e..65ca4a7b 100644 --- a/Sources/Amplitude/Amplitude.swift +++ b/Sources/Amplitude/Amplitude.swift @@ -41,11 +41,10 @@ public class Amplitude { migrateApiKeyStorages() migrateDefaultInstanceStorages() - migrateInstanceOnlyStorages() - - if configuration.migrateLegacyData { + if (configuration.migrateLegacyData && getStorageVersion() < .INSTANCE_NAME_AND_API_KEY) { RemnantDataMigration(self).execute() } + migrateInstanceOnlyStorages() if let deviceId: String? = configuration.storageProvider.read(key: .DEVICE_ID) { state.deviceId = deviceId @@ -334,7 +333,17 @@ public class Amplitude { } } + private func getStorageVersion() -> PersistentStorageVersion { + let storageVersionInt: Int? = configuration.storageProvider.read(key: .STORAGE_VERSION) + let storageVersion: PersistentStorageVersion = (storageVersionInt == nil) ? PersistentStorageVersion.NO_VERSION : PersistentStorageVersion(rawValue: storageVersionInt!)! + return storageVersion + } + private func migrateApiKeyStorages() { + if (getStorageVersion() >= PersistentStorageVersion.API_KEY) { + return; + } + configuration.loggerProvider.error(message: "Running migrateApiKeyStorages") if let persistentStorage = configuration.storageProvider as? PersistentStorage { let apiKeyStorage = PersistentStorage(storagePrefix: "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-\(configuration.apiKey)") StoragePrefixMigration(source: apiKeyStorage, destination: persistentStorage, logger: logger).execute() @@ -347,10 +356,11 @@ public class Amplitude { } private func migrateDefaultInstanceStorages() { - if configuration.instanceName != Constants.Configuration.DEFAULT_INSTANCE { + if (getStorageVersion() >= PersistentStorageVersion.INSTANCE_NAME || + configuration.instanceName != Constants.Configuration.DEFAULT_INSTANCE) { return } - + configuration.loggerProvider.error(message: "Running migrateDefaultInstanceStorages") let legacyDefaultInstanceName = "default_instance" if let persistentStorage = configuration.storageProvider as? PersistentStorage { let legacyStorage = PersistentStorage(storagePrefix: "storage-\(legacyDefaultInstanceName)") @@ -364,22 +374,38 @@ public class Amplitude { } private func migrateInstanceOnlyStorages() { - // Only migrate sandboxed apps to avoid potential data pollution - if (!SandboxHelper().isSandboxEnabled()) { + if (getStorageVersion() >= .INSTANCE_NAME_AND_API_KEY) { return; } + configuration.loggerProvider.error(message: "Running migrateInstanceOnlyStorages") - let instanceName = configuration.getNormalizeInstanceName() - if let persistentStorage = configuration.storageProvider as? PersistentStorage { - let instanceOnlyEventPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-storage-\(instanceName)" - let instanceNameOnlyStorage = PersistentStorage(storagePrefix: instanceOnlyEventPrefix) - StoragePrefixMigration(source: instanceNameOnlyStorage, destination: persistentStorage, logger: logger).execute() + // Only migrate sandboxed apps to avoid potential data pollution + if (SandboxHelper().isSandboxEnabled()) { + let instanceName = configuration.getNormalizeInstanceName() + if let persistentStorage = configuration.storageProvider as? PersistentStorage { + let instanceOnlyEventPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-storage-\(instanceName)" + let instanceNameOnlyStorage = PersistentStorage(storagePrefix: instanceOnlyEventPrefix) + StoragePrefixMigration(source: instanceNameOnlyStorage, destination: persistentStorage, logger: logger).execute() + } + + if let persistentIdentifyStorage = configuration.identifyStorageProvider as? PersistentStorage { + let instanceOnlyIdentifyPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-identify-\(instanceName)" + let instanceNameOnlyIdentifyStorage = PersistentStorage(storagePrefix: instanceOnlyIdentifyPrefix) + StoragePrefixMigration(source: instanceNameOnlyIdentifyStorage, destination: persistentIdentifyStorage, logger: logger).execute() + } + } else { + configuration.loggerProvider.debug(message: "Skipping migrateInstanceOnlyStorages in non-sandboxed app") } - if let persistentIdentifyStorage = configuration.identifyStorageProvider as? PersistentStorage { - let instanceOnlyIdentifyPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-identify-\(instanceName)" - let instanceNameOnlyIdentifyStorage = PersistentStorage(storagePrefix: instanceOnlyIdentifyPrefix) - StoragePrefixMigration(source: instanceNameOnlyIdentifyStorage, destination: persistentIdentifyStorage, logger: logger).execute() + do { + // Store the current storage version + try configuration.storageProvider.write( + key: .STORAGE_VERSION, + value: PersistentStorageVersion.INSTANCE_NAME_AND_API_KEY.rawValue as Int + ) + configuration.loggerProvider.debug(message: "Updated STORAGE_VERSION to .INSTANCE_NAME_AND_API_KEY") + } catch { + configuration.loggerProvider.error(message: "Unable to set STORAGE_VERSION in storageProvider during migration") } } } diff --git a/Sources/Amplitude/Configuration.swift b/Sources/Amplitude/Configuration.swift index 4694488a..470ce8f7 100644 --- a/Sources/Amplitude/Configuration.swift +++ b/Sources/Amplitude/Configuration.swift @@ -62,7 +62,7 @@ public class Configuration { identifyBatchIntervalMillis: Int = Constants.Configuration.IDENTIFY_BATCH_INTERVAL_MILLIS, migrateLegacyData: Bool = true ) { - let normalizedInstanceName = getNormalizeInstanceName() + let normalizedInstanceName = Configuration.getNormalizeInstanceName(instanceName) self.apiKey = apiKey self.flushQueueSize = flushQueueSize @@ -100,8 +100,12 @@ public class Configuration { && minTimeBetweenSessionsMillis > 0 && (minIdLength == nil || minIdLength! > 0) } + + private class func getNormalizeInstanceName(_ instanceName: String) -> String { + return instanceName == "" ? Constants.Configuration.DEFAULT_INSTANCE : instanceName + } internal func getNormalizeInstanceName() -> String { - return self.instanceName == "" ? Constants.Configuration.DEFAULT_INSTANCE : self.instanceName + return Configuration.getNormalizeInstanceName(self.instanceName) } } diff --git a/Sources/Amplitude/Storages/PersistentStorage.swift b/Sources/Amplitude/Storages/PersistentStorage.swift index db4f15ef..0e409ef8 100644 --- a/Sources/Amplitude/Storages/PersistentStorage.swift +++ b/Sources/Amplitude/Storages/PersistentStorage.swift @@ -13,11 +13,11 @@ class PersistentStorage: Storage { static internal func getEventStoragePrefix(_ apiKey: String, _ instanceName: String) -> String { return "storage-\(instanceName)-\(apiKey)" } - + static internal func getIdentifyStoragePrefix(_ apiKey: String, _ instanceName: String) -> String { return "identify-\(instanceName)-\(apiKey)" } - + let storagePrefix: String let userDefaults: UserDefaults? let fileManager: FileManager diff --git a/Sources/Amplitude/Types.swift b/Sources/Amplitude/Types.swift index 2cd0669f..eab33645 100644 --- a/Sources/Amplitude/Types.swift +++ b/Sources/Amplitude/Types.swift @@ -74,6 +74,23 @@ public enum StorageKey: String, CaseIterable { case DEVICE_ID = "device_id" case APP_BUILD = "app_build" case APP_VERSION = "app_version" + // The version of PersistentStorage, used for data migrations + // Value should be a PersistentStorageVersion value + // Note the first version is 2, which corresponds to instanceName-apiKey based storage + case STORAGE_VERSION = "storage_version" +} + +public enum PersistentStorageVersion: Int, Comparable { + public static func < (lhs: PersistentStorageVersion, rhs: PersistentStorageVersion) -> Bool { + return lhs.rawValue < rhs.rawValue + } + + case NO_VERSION = -1 + // Note that versioning was added after these storage changes (0, 1) + case API_KEY = 0 + case INSTANCE_NAME = 1 + // This is the first version (2) we set a value in storageProvider.read(.StorageVersion) + case INSTANCE_NAME_AND_API_KEY = 2 } public protocol Logger { From 674d6c3e7a31543dc44e678ff123b459c9c00142 Mon Sep 17 00:00:00 2001 From: "justin.fiedler" Date: Wed, 7 Feb 2024 19:24:59 +0000 Subject: [PATCH 5/9] chore: lint fixes --- Sources/Amplitude/Amplitude.swift | 16 ++++++++-------- Sources/Amplitude/Configuration.swift | 2 +- Sources/Amplitude/Types.swift | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/Amplitude/Amplitude.swift b/Sources/Amplitude/Amplitude.swift index 65ca4a7b..0d019cfd 100644 --- a/Sources/Amplitude/Amplitude.swift +++ b/Sources/Amplitude/Amplitude.swift @@ -41,7 +41,7 @@ public class Amplitude { migrateApiKeyStorages() migrateDefaultInstanceStorages() - if (configuration.migrateLegacyData && getStorageVersion() < .INSTANCE_NAME_AND_API_KEY) { + if configuration.migrateLegacyData && getStorageVersion() < .INSTANCE_NAME_AND_API_KEY { RemnantDataMigration(self).execute() } migrateInstanceOnlyStorages() @@ -340,8 +340,8 @@ public class Amplitude { } private func migrateApiKeyStorages() { - if (getStorageVersion() >= PersistentStorageVersion.API_KEY) { - return; + if getStorageVersion() >= PersistentStorageVersion.API_KEY { + return } configuration.loggerProvider.error(message: "Running migrateApiKeyStorages") if let persistentStorage = configuration.storageProvider as? PersistentStorage { @@ -356,8 +356,8 @@ public class Amplitude { } private func migrateDefaultInstanceStorages() { - if (getStorageVersion() >= PersistentStorageVersion.INSTANCE_NAME || - configuration.instanceName != Constants.Configuration.DEFAULT_INSTANCE) { + if getStorageVersion() >= PersistentStorageVersion.INSTANCE_NAME || + configuration.instanceName != Constants.Configuration.DEFAULT_INSTANCE { return } configuration.loggerProvider.error(message: "Running migrateDefaultInstanceStorages") @@ -374,13 +374,13 @@ public class Amplitude { } private func migrateInstanceOnlyStorages() { - if (getStorageVersion() >= .INSTANCE_NAME_AND_API_KEY) { - return; + if getStorageVersion() >= .INSTANCE_NAME_AND_API_KEY { + return } configuration.loggerProvider.error(message: "Running migrateInstanceOnlyStorages") // Only migrate sandboxed apps to avoid potential data pollution - if (SandboxHelper().isSandboxEnabled()) { + if SandboxHelper().isSandboxEnabled() { let instanceName = configuration.getNormalizeInstanceName() if let persistentStorage = configuration.storageProvider as? PersistentStorage { let instanceOnlyEventPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-storage-\(instanceName)" diff --git a/Sources/Amplitude/Configuration.swift b/Sources/Amplitude/Configuration.swift index 470ce8f7..c56c744c 100644 --- a/Sources/Amplitude/Configuration.swift +++ b/Sources/Amplitude/Configuration.swift @@ -104,7 +104,7 @@ public class Configuration { private class func getNormalizeInstanceName(_ instanceName: String) -> String { return instanceName == "" ? Constants.Configuration.DEFAULT_INSTANCE : instanceName } - + internal func getNormalizeInstanceName() -> String { return Configuration.getNormalizeInstanceName(self.instanceName) } diff --git a/Sources/Amplitude/Types.swift b/Sources/Amplitude/Types.swift index eab33645..6a69d0f6 100644 --- a/Sources/Amplitude/Types.swift +++ b/Sources/Amplitude/Types.swift @@ -84,7 +84,7 @@ public enum PersistentStorageVersion: Int, Comparable { public static func < (lhs: PersistentStorageVersion, rhs: PersistentStorageVersion) -> Bool { return lhs.rawValue < rhs.rawValue } - + case NO_VERSION = -1 // Note that versioning was added after these storage changes (0, 1) case API_KEY = 0 From c48ccd7f68f1916cb24a9ebb4330e4da8a3a0024 Mon Sep 17 00:00:00 2001 From: "justin.fiedler" Date: Thu, 8 Feb 2024 00:55:33 +0000 Subject: [PATCH 6/9] chore: add Configuration tests to check new storage paths --- Sources/Amplitude/Amplitude.swift | 8 ++-- .../Storages/PersistentStorage.swift | 4 +- Sources/Amplitude/Types.swift | 2 +- Tests/AmplitudeTests/ConfigurationTests.swift | 41 +++++++++++++++++++ 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Sources/Amplitude/Amplitude.swift b/Sources/Amplitude/Amplitude.swift index 0d019cfd..0dbbb1ee 100644 --- a/Sources/Amplitude/Amplitude.swift +++ b/Sources/Amplitude/Amplitude.swift @@ -41,7 +41,7 @@ public class Amplitude { migrateApiKeyStorages() migrateDefaultInstanceStorages() - if configuration.migrateLegacyData && getStorageVersion() < .INSTANCE_NAME_AND_API_KEY { + if configuration.migrateLegacyData && getStorageVersion() < .API_KEY_AND_INSTANCE_NAME { RemnantDataMigration(self).execute() } migrateInstanceOnlyStorages() @@ -374,7 +374,7 @@ public class Amplitude { } private func migrateInstanceOnlyStorages() { - if getStorageVersion() >= .INSTANCE_NAME_AND_API_KEY { + if getStorageVersion() >= .API_KEY_AND_INSTANCE_NAME { return } configuration.loggerProvider.error(message: "Running migrateInstanceOnlyStorages") @@ -401,9 +401,9 @@ public class Amplitude { // Store the current storage version try configuration.storageProvider.write( key: .STORAGE_VERSION, - value: PersistentStorageVersion.INSTANCE_NAME_AND_API_KEY.rawValue as Int + value: PersistentStorageVersion.API_KEY_AND_INSTANCE_NAME.rawValue as Int ) - configuration.loggerProvider.debug(message: "Updated STORAGE_VERSION to .INSTANCE_NAME_AND_API_KEY") + configuration.loggerProvider.debug(message: "Updated STORAGE_VERSION to .API_KEY_AND_INSTANCE_NAME") } catch { configuration.loggerProvider.error(message: "Unable to set STORAGE_VERSION in storageProvider during migration") } diff --git a/Sources/Amplitude/Storages/PersistentStorage.swift b/Sources/Amplitude/Storages/PersistentStorage.swift index 0e409ef8..22eeceee 100644 --- a/Sources/Amplitude/Storages/PersistentStorage.swift +++ b/Sources/Amplitude/Storages/PersistentStorage.swift @@ -11,11 +11,11 @@ class PersistentStorage: Storage { typealias EventBlock = URL static internal func getEventStoragePrefix(_ apiKey: String, _ instanceName: String) -> String { - return "storage-\(instanceName)-\(apiKey)" + return "storage-\(apiKey)-\(instanceName)" } static internal func getIdentifyStoragePrefix(_ apiKey: String, _ instanceName: String) -> String { - return "identify-\(instanceName)-\(apiKey)" + return "identify-\(apiKey)-\(instanceName)" } let storagePrefix: String diff --git a/Sources/Amplitude/Types.swift b/Sources/Amplitude/Types.swift index 6a69d0f6..71989c9d 100644 --- a/Sources/Amplitude/Types.swift +++ b/Sources/Amplitude/Types.swift @@ -90,7 +90,7 @@ public enum PersistentStorageVersion: Int, Comparable { case API_KEY = 0 case INSTANCE_NAME = 1 // This is the first version (2) we set a value in storageProvider.read(.StorageVersion) - case INSTANCE_NAME_AND_API_KEY = 2 + case API_KEY_AND_INSTANCE_NAME = 2 } public protocol Logger { diff --git a/Tests/AmplitudeTests/ConfigurationTests.swift b/Tests/AmplitudeTests/ConfigurationTests.swift index 70d611f8..e39eb770 100644 --- a/Tests/AmplitudeTests/ConfigurationTests.swift +++ b/Tests/AmplitudeTests/ConfigurationTests.swift @@ -47,4 +47,45 @@ final class ConfigurationTests: XCTestCase { configuration = Configuration(apiKey: apiKey, minIdLength: 0) XCTAssertFalse(configuration.isValid()) } + + func testStorageByApiKeyAndInstanceName() throws { + let configuration = Configuration(apiKey: "migration-api-key") + + let expectedStoragePostfix = "\(configuration.apiKey)-\(configuration.getNormalizeInstanceName())" + + let eventsStorage = configuration.storageProvider as? PersistentStorage + let eventStorageUrl = eventsStorage != nil + ? eventsStorage?.getEventsStorageDirectory(createDirectory: false).absoluteString + : "" + + let identifyStorage = configuration.storageProvider as? PersistentStorage + let identifyStorageUrl = identifyStorage != nil + ? identifyStorage?.getEventsStorageDirectory(createDirectory: false).absoluteString + : "" + + XCTAssertTrue(eventStorageUrl?.contains(expectedStoragePostfix) ?? false) + XCTAssertTrue(identifyStorageUrl?.contains(expectedStoragePostfix) ?? false) + } + + func testStorageByApiKeyAndInstanceNameWithCustomInstanceName() throws { + let configuration = Configuration( + apiKey: "migration-api-key", + instanceName: "test-instance" + ) + + let expectedStoragePostfix = "\(configuration.apiKey)-\(configuration.getNormalizeInstanceName())" + + let eventsStorage = configuration.storageProvider as? PersistentStorage + let eventStorageUrl = eventsStorage != nil + ? eventsStorage?.getEventsStorageDirectory(createDirectory: false).absoluteString + : "" + + let identifyStorage = configuration.storageProvider as? PersistentStorage + let identifyStorageUrl = identifyStorage != nil + ? identifyStorage?.getEventsStorageDirectory(createDirectory: false).absoluteString + : "" + + XCTAssertTrue(eventStorageUrl?.contains(expectedStoragePostfix) ?? false) + XCTAssertTrue(identifyStorageUrl?.contains(expectedStoragePostfix) ?? false) + } } From 9327a75a1dd4b1aecb7b18da47bd8f1bac667b2d Mon Sep 17 00:00:00 2001 From: "justin.fiedler" Date: Fri, 9 Feb 2024 20:53:13 +0000 Subject: [PATCH 7/9] chore: added tests to check migrations in sandboxed and non-sandboxed environments --- Sources/Amplitude/Amplitude.swift | 50 ++++-- .../Migration/StoragePrefixMigration.swift | 7 +- Tests/AmplitudeTests/AmplitudeTests.swift | 167 +++++++++++++++++- .../Supports/TestUtilities.swift | 12 ++ 4 files changed, 215 insertions(+), 21 deletions(-) diff --git a/Sources/Amplitude/Amplitude.swift b/Sources/Amplitude/Amplitude.swift index 0dbbb1ee..67748e5f 100644 --- a/Sources/Amplitude/Amplitude.swift +++ b/Sources/Amplitude/Amplitude.swift @@ -343,7 +343,7 @@ public class Amplitude { if getStorageVersion() >= PersistentStorageVersion.API_KEY { return } - configuration.loggerProvider.error(message: "Running migrateApiKeyStorages") + configuration.loggerProvider.debug(message: "Running migrateApiKeyStorages") if let persistentStorage = configuration.storageProvider as? PersistentStorage { let apiKeyStorage = PersistentStorage(storagePrefix: "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-\(configuration.apiKey)") StoragePrefixMigration(source: apiKeyStorage, destination: persistentStorage, logger: logger).execute() @@ -360,7 +360,7 @@ public class Amplitude { configuration.instanceName != Constants.Configuration.DEFAULT_INSTANCE { return } - configuration.loggerProvider.error(message: "Running migrateDefaultInstanceStorages") + configuration.loggerProvider.debug(message: "Running migrateDefaultInstanceStorages") let legacyDefaultInstanceName = "default_instance" if let persistentStorage = configuration.storageProvider as? PersistentStorage { let legacyStorage = PersistentStorage(storagePrefix: "storage-\(legacyDefaultInstanceName)") @@ -373,28 +373,38 @@ public class Amplitude { } } - private func migrateInstanceOnlyStorages() { + internal func migrateInstanceOnlyStorages() { if getStorageVersion() >= .API_KEY_AND_INSTANCE_NAME { + configuration.loggerProvider.debug(message: "Skipping migrateInstanceOnlyStorages based on STORAGE_VERSION") return } - configuration.loggerProvider.error(message: "Running migrateInstanceOnlyStorages") + configuration.loggerProvider.debug(message: "Running migrateInstanceOnlyStorages") + let skipEventMigration = !isSandboxEnabled() // Only migrate sandboxed apps to avoid potential data pollution - if SandboxHelper().isSandboxEnabled() { - let instanceName = configuration.getNormalizeInstanceName() - if let persistentStorage = configuration.storageProvider as? PersistentStorage { - let instanceOnlyEventPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-storage-\(instanceName)" - let instanceNameOnlyStorage = PersistentStorage(storagePrefix: instanceOnlyEventPrefix) - StoragePrefixMigration(source: instanceNameOnlyStorage, destination: persistentStorage, logger: logger).execute() - } + if skipEventMigration { + configuration.loggerProvider.debug(message: "Skipping event migration in non-sandboxed app. Transfering UserDefaults only.") + } + + let instanceName = configuration.getNormalizeInstanceName() + if let persistentStorage = configuration.storageProvider as? PersistentStorage { + let instanceOnlyEventPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-storage-\(instanceName)" + let instanceNameOnlyStorage = PersistentStorage(storagePrefix: instanceOnlyEventPrefix) + StoragePrefixMigration( + source: instanceNameOnlyStorage, + destination: persistentStorage, + logger: logger + ).execute(skipEventFiles: skipEventMigration) + } - if let persistentIdentifyStorage = configuration.identifyStorageProvider as? PersistentStorage { - let instanceOnlyIdentifyPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-identify-\(instanceName)" - let instanceNameOnlyIdentifyStorage = PersistentStorage(storagePrefix: instanceOnlyIdentifyPrefix) - StoragePrefixMigration(source: instanceNameOnlyIdentifyStorage, destination: persistentIdentifyStorage, logger: logger).execute() - } - } else { - configuration.loggerProvider.debug(message: "Skipping migrateInstanceOnlyStorages in non-sandboxed app") + if let persistentIdentifyStorage = configuration.identifyStorageProvider as? PersistentStorage { + let instanceOnlyIdentifyPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-identify-\(instanceName)" + let instanceNameOnlyIdentifyStorage = PersistentStorage(storagePrefix: instanceOnlyIdentifyPrefix) + StoragePrefixMigration( + source: instanceNameOnlyIdentifyStorage, + destination: persistentIdentifyStorage, + logger: logger + ).execute(skipEventFiles: skipEventMigration) } do { @@ -408,4 +418,8 @@ public class Amplitude { configuration.loggerProvider.error(message: "Unable to set STORAGE_VERSION in storageProvider during migration") } } + + internal func isSandboxEnabled() -> Bool { + return SandboxHelper().isSandboxEnabled(); + } } diff --git a/Sources/Amplitude/Migration/StoragePrefixMigration.swift b/Sources/Amplitude/Migration/StoragePrefixMigration.swift index aea27a54..cbf58a28 100644 --- a/Sources/Amplitude/Migration/StoragePrefixMigration.swift +++ b/Sources/Amplitude/Migration/StoragePrefixMigration.swift @@ -11,12 +11,15 @@ class StoragePrefixMigration { self.logger = logger } - func execute() { + func execute(skipEventFiles: Bool = false) { if source.storagePrefix == destination.storagePrefix { return } - moveSourceEventFilesToDestination() + if (!skipEventFiles) { + moveSourceEventFilesToDestination() + } + moveUserDefaults() } diff --git a/Tests/AmplitudeTests/AmplitudeTests.swift b/Tests/AmplitudeTests/AmplitudeTests.swift index 03d3ace3..735c7b25 100644 --- a/Tests/AmplitudeTests/AmplitudeTests.swift +++ b/Tests/AmplitudeTests/AmplitudeTests.swift @@ -324,7 +324,172 @@ final class AmplitudeTests: XCTestCase { ]) } - func getDictionary(_ props: [String: Any?]) -> NSDictionary { + func testMigrationToApiKeyAndInstanceNameStorage() throws { + let legacyUserId = "legacy-user-id"; + let config = Configuration( + apiKey: "amp-migration-api-key", + // don't transfer any events + flushQueueSize: 1000, + flushIntervalMillis: 99999, + logLevel: LogLevelEnum.DEBUG, + defaultTracking: DefaultTrackingOptions.NONE + ) + + // Create storages using instance name only + let legacyEventStorage = PersistentStorage(storagePrefix: "storage-\(config.getNormalizeInstanceName())") + let legacyIdentityStorage = PersistentStorage(storagePrefix: "identify-\(config.getNormalizeInstanceName())") + + print("Creating legacy Amplitude instance") + // Init Amplitude using legacy storage + let legacyStorageAmplitude = FakeAmplitudeWithNoApiAndInstanceNameMigration(configuration: Configuration( + apiKey: config.apiKey, + flushQueueSize: config.flushQueueSize, + flushIntervalMillis: config.flushIntervalMillis, + storageProvider: legacyEventStorage, + identifyStorageProvider: legacyIdentityStorage, + logLevel: config.logLevel, + defaultTracking: config.defaultTracking + )) + + let legacyDeviceId = legacyStorageAmplitude.getDeviceId() + + // set userId + legacyStorageAmplitude.setUserId(userId: legacyUserId) + XCTAssertEqual(legacyUserId, legacyStorageAmplitude.getUserId()) + + // track events to legacy storage + legacyStorageAmplitude.identify(identify: Identify().set(property: "user-prop", value: true)) + legacyStorageAmplitude.track(event: BaseEvent(eventType: "Legacy Storage Event")) + + guard let legacyEventFiles: [URL]? = legacyEventStorage.read(key: StorageKey.EVENTS) else { return } + + var legacyEventsString = "" + legacyEventFiles?.forEach { file in + legacyEventsString = legacyEventStorage.getEventsString(eventBlock: file) ?? "" + print(legacyEventsString) + } + + XCTAssertEqual(legacyEventFiles?.count ?? 0, 1) + + let amplitude = Amplitude(configuration: config) + let deviceId = amplitude.getDeviceId() + let userId = amplitude.getUserId() + + guard let eventFiles: [URL]? = amplitude.storage.read(key: StorageKey.EVENTS) else { return } + + var eventsString = "" + eventFiles?.forEach { file in + eventsString = legacyEventStorage.getEventsString(eventBlock: file) ?? "" + print(eventsString) + } + + XCTAssertEqual(legacyDeviceId != nil, true) + XCTAssertEqual(deviceId != nil, true) + XCTAssertEqual(legacyDeviceId, deviceId) + + XCTAssertEqual(legacyUserId, userId) + + XCTAssertNotNil(legacyEventsString) + + #if os(macOS) + // We don't want to transfer event data in non-sanboxed apps + XCTAssertFalse(amplitude.isSandboxEnabled()) + XCTAssertEqual(eventFiles?.count ?? 0, 0) + #else + XCTAssertTrue(eventsString != "") + XCTAssertEqual(legacyEventsString, eventsString) + XCTAssertEqual(eventFiles?.count ?? 0, 1) + #endif + + // clear storage + amplitude.storage.reset() + amplitude.identifyStorage.reset() + legacyStorageAmplitude.storage.reset() + legacyStorageAmplitude.identifyStorage.reset() + } + + #if os(macOS) + func testMigrationToApiKeyAndInstanceNameStorageMacSandboxEnabled() throws { + let legacyUserId = "legacy-user-id"; + let config = Configuration( + apiKey: "amp-mac-migration-api-key", + // don't transfer any events + flushQueueSize: 1000, + flushIntervalMillis: 99999, + logLevel: LogLevelEnum.DEBUG, + defaultTracking: DefaultTrackingOptions.NONE + ) + + // Create storages using instance name only + let legacyEventStorage = FakePersistentStorageAppSandboxEnabled(storagePrefix: "storage-\(config.getNormalizeInstanceName())") + let legacyIdentityStorage = FakePersistentStorageAppSandboxEnabled(storagePrefix: "identify-\(config.getNormalizeInstanceName())") + + print("Creating legacy Amplitude instance") + // Init Amplitude using legacy storage + let legacyStorageAmplitude = FakeAmplitudeWithNoApiAndInstanceNameMigration(configuration: Configuration( + apiKey: config.apiKey, + flushQueueSize: config.flushQueueSize, + flushIntervalMillis: config.flushIntervalMillis, + storageProvider: legacyEventStorage, + identifyStorageProvider: legacyIdentityStorage, + logLevel: config.logLevel, + defaultTracking: config.defaultTracking + )) + + let legacyDeviceId = legacyStorageAmplitude.getDeviceId() + + // set userId + legacyStorageAmplitude.setUserId(userId: legacyUserId) + XCTAssertEqual(legacyUserId, legacyStorageAmplitude.getUserId()) + + // track events to legacy storage + legacyStorageAmplitude.identify(identify: Identify().set(property: "user-prop", value: true)) + legacyStorageAmplitude.track(event: BaseEvent(eventType: "Legacy Storage Event")) + + guard let legacyEventFiles: [URL]? = legacyEventStorage.read(key: StorageKey.EVENTS) else { return } + + var legacyEventsString = "" + legacyEventFiles?.forEach { file in + legacyEventsString = legacyEventStorage.getEventsString(eventBlock: file) ?? "" + print(legacyEventsString) + } + + XCTAssertEqual(legacyEventFiles?.count ?? 0, 1) + + let amplitude = FakeAmplitudeWithSandboxEnabled(configuration: config) + let deviceId = amplitude.getDeviceId() + let userId = amplitude.getUserId() + + guard let eventFiles: [URL]? = amplitude.storage.read(key: StorageKey.EVENTS) else { return } + + var eventsString = "" + eventFiles?.forEach { file in + eventsString = legacyEventStorage.getEventsString(eventBlock: file) ?? "" + print(eventsString) + } + + XCTAssertEqual(legacyDeviceId != nil, true) + XCTAssertEqual(deviceId != nil, true) + XCTAssertEqual(legacyDeviceId, deviceId) + + XCTAssertEqual(legacyUserId, userId) + + XCTAssertNotNil(legacyEventsString) + + // Transfer event data in sandboxed apps + XCTAssertTrue(eventsString == "") + XCTAssertNotEqual(legacyEventsString, eventsString) + XCTAssertEqual(eventFiles?.count ?? 0, 0) + + // clear storage + amplitude.storage.reset() + amplitude.identifyStorage.reset() + legacyStorageAmplitude.storage.reset() + legacyStorageAmplitude.identifyStorage.reset() + } + #endif + + private func getDictionary(_ props: [String: Any?]) -> NSDictionary { return NSDictionary(dictionary: props as [AnyHashable: Any]) } } diff --git a/Tests/AmplitudeTests/Supports/TestUtilities.swift b/Tests/AmplitudeTests/Supports/TestUtilities.swift index 134c53a6..98ece6c8 100644 --- a/Tests/AmplitudeTests/Supports/TestUtilities.swift +++ b/Tests/AmplitudeTests/Supports/TestUtilities.swift @@ -260,3 +260,15 @@ class FakePersistentStorageAppSandboxEnabled: PersistentStorage { return true } } + +class FakeAmplitudeWithNoApiAndInstanceNameMigration: Amplitude { + override func migrateInstanceOnlyStorages() { + // do nothing + } +} + +class FakeAmplitudeWithSandboxEnabled: Amplitude { + override internal func isSandboxEnabled() -> Bool { + return true + } +} From 525c8133717720d2b10a0da39b218660295a0c1d Mon Sep 17 00:00:00 2001 From: "justin.fiedler" Date: Fri, 9 Feb 2024 21:06:50 +0000 Subject: [PATCH 8/9] chore: lint fixes --- Sources/Amplitude/Amplitude.swift | 8 ++++---- Sources/Amplitude/Migration/StoragePrefixMigration.swift | 4 ++-- Tests/AmplitudeTests/AmplitudeTests.swift | 8 ++++---- Tests/AmplitudeTests/Supports/TestUtilities.swift | 3 +-- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Sources/Amplitude/Amplitude.swift b/Sources/Amplitude/Amplitude.swift index 7f4d4d84..686c04a3 100644 --- a/Sources/Amplitude/Amplitude.swift +++ b/Sources/Amplitude/Amplitude.swift @@ -388,14 +388,14 @@ public class Amplitude { if skipEventMigration { configuration.loggerProvider.debug(message: "Skipping event migration in non-sandboxed app. Transfering UserDefaults only.") } - + let instanceName = configuration.getNormalizeInstanceName() if let persistentStorage = configuration.storageProvider as? PersistentStorage { let instanceOnlyEventPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-storage-\(instanceName)" let instanceNameOnlyStorage = PersistentStorage(storagePrefix: instanceOnlyEventPrefix) StoragePrefixMigration( source: instanceNameOnlyStorage, - destination: persistentStorage, + destination: persistentStorage, logger: logger ).execute(skipEventFiles: skipEventMigration) } @@ -421,8 +421,8 @@ public class Amplitude { configuration.loggerProvider.error(message: "Unable to set STORAGE_VERSION in storageProvider during migration") } } - + internal func isSandboxEnabled() -> Bool { - return SandboxHelper().isSandboxEnabled(); + return SandboxHelper().isSandboxEnabled() } } diff --git a/Sources/Amplitude/Migration/StoragePrefixMigration.swift b/Sources/Amplitude/Migration/StoragePrefixMigration.swift index cbf58a28..91ce11e8 100644 --- a/Sources/Amplitude/Migration/StoragePrefixMigration.swift +++ b/Sources/Amplitude/Migration/StoragePrefixMigration.swift @@ -16,10 +16,10 @@ class StoragePrefixMigration { return } - if (!skipEventFiles) { + if !skipEventFiles { moveSourceEventFilesToDestination() } - + moveUserDefaults() } diff --git a/Tests/AmplitudeTests/AmplitudeTests.swift b/Tests/AmplitudeTests/AmplitudeTests.swift index 8bbec8c7..f2d58623 100644 --- a/Tests/AmplitudeTests/AmplitudeTests.swift +++ b/Tests/AmplitudeTests/AmplitudeTests.swift @@ -325,7 +325,7 @@ final class AmplitudeTests: XCTestCase { } func testMigrationToApiKeyAndInstanceNameStorage() throws { - let legacyUserId = "legacy-user-id"; + let legacyUserId = "legacy-user-id" let config = Configuration( apiKey: "amp-migration-api-key", // don't transfer any events @@ -341,7 +341,7 @@ final class AmplitudeTests: XCTestCase { print("Creating legacy Amplitude instance") // Init Amplitude using legacy storage - let legacyStorageAmplitude = FakeAmplitudeWithNoApiAndInstanceNameMigration(configuration: Configuration( + let legacyStorageAmplitude = FakeAmplitudeWithNoInstNameOnlyMigration(configuration: Configuration( apiKey: config.apiKey, flushQueueSize: config.flushQueueSize, flushIntervalMillis: config.flushIntervalMillis, @@ -410,7 +410,7 @@ final class AmplitudeTests: XCTestCase { #if os(macOS) func testMigrationToApiKeyAndInstanceNameStorageMacSandboxEnabled() throws { - let legacyUserId = "legacy-user-id"; + let legacyUserId = "legacy-user-id" let config = Configuration( apiKey: "amp-mac-migration-api-key", // don't transfer any events @@ -426,7 +426,7 @@ final class AmplitudeTests: XCTestCase { print("Creating legacy Amplitude instance") // Init Amplitude using legacy storage - let legacyStorageAmplitude = FakeAmplitudeWithNoApiAndInstanceNameMigration(configuration: Configuration( + let legacyStorageAmplitude = FakeAmplitudeWithNoInstNameOnlyMigration(configuration: Configuration( apiKey: config.apiKey, flushQueueSize: config.flushQueueSize, flushIntervalMillis: config.flushIntervalMillis, diff --git a/Tests/AmplitudeTests/Supports/TestUtilities.swift b/Tests/AmplitudeTests/Supports/TestUtilities.swift index 7dc28407..d16bd923 100644 --- a/Tests/AmplitudeTests/Supports/TestUtilities.swift +++ b/Tests/AmplitudeTests/Supports/TestUtilities.swift @@ -263,8 +263,7 @@ class FakePersistentStorageAppSandboxEnabled: PersistentStorage { } } - -class FakeAmplitudeWithNoApiAndInstanceNameMigration: Amplitude { +class FakeAmplitudeWithNoInstNameOnlyMigration: Amplitude { override func migrateInstanceOnlyStorages() { // do nothing } From 5e32747c9c9984b890d70f35cba8bb58edcf154e Mon Sep 17 00:00:00 2001 From: "justin.fiedler" Date: Fri, 9 Feb 2024 22:01:49 +0000 Subject: [PATCH 9/9] chore: PR feedback updates (code cleanup) --- Sources/Amplitude/Types.swift | 2 +- Tests/AmplitudeTests/AmplitudeTests.swift | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Sources/Amplitude/Types.swift b/Sources/Amplitude/Types.swift index ba0ee2bc..388c785c 100644 --- a/Sources/Amplitude/Types.swift +++ b/Sources/Amplitude/Types.swift @@ -76,7 +76,7 @@ public enum StorageKey: String, CaseIterable { case APP_VERSION = "app_version" // The version of PersistentStorage, used for data migrations // Value should be a PersistentStorageVersion value - // Note the first version is 2, which corresponds to instanceName-apiKey based storage + // Note the first version is 2, which corresponds to apiKey-instanceName based storage case STORAGE_VERSION = "storage_version" } diff --git a/Tests/AmplitudeTests/AmplitudeTests.swift b/Tests/AmplitudeTests/AmplitudeTests.swift index f2d58623..3d8da841 100644 --- a/Tests/AmplitudeTests/AmplitudeTests.swift +++ b/Tests/AmplitudeTests/AmplitudeTests.swift @@ -339,7 +339,6 @@ final class AmplitudeTests: XCTestCase { let legacyEventStorage = PersistentStorage(storagePrefix: "storage-\(config.getNormalizeInstanceName())") let legacyIdentityStorage = PersistentStorage(storagePrefix: "identify-\(config.getNormalizeInstanceName())") - print("Creating legacy Amplitude instance") // Init Amplitude using legacy storage let legacyStorageAmplitude = FakeAmplitudeWithNoInstNameOnlyMigration(configuration: Configuration( apiKey: config.apiKey, @@ -366,7 +365,6 @@ final class AmplitudeTests: XCTestCase { var legacyEventsString = "" legacyEventFiles?.forEach { file in legacyEventsString = legacyEventStorage.getEventsString(eventBlock: file) ?? "" - print(legacyEventsString) } XCTAssertEqual(legacyEventFiles?.count ?? 0, 1) @@ -380,7 +378,6 @@ final class AmplitudeTests: XCTestCase { var eventsString = "" eventFiles?.forEach { file in eventsString = legacyEventStorage.getEventsString(eventBlock: file) ?? "" - print(eventsString) } XCTAssertEqual(legacyDeviceId != nil, true) @@ -424,7 +421,6 @@ final class AmplitudeTests: XCTestCase { let legacyEventStorage = FakePersistentStorageAppSandboxEnabled(storagePrefix: "storage-\(config.getNormalizeInstanceName())") let legacyIdentityStorage = FakePersistentStorageAppSandboxEnabled(storagePrefix: "identify-\(config.getNormalizeInstanceName())") - print("Creating legacy Amplitude instance") // Init Amplitude using legacy storage let legacyStorageAmplitude = FakeAmplitudeWithNoInstNameOnlyMigration(configuration: Configuration( apiKey: config.apiKey, @@ -451,7 +447,6 @@ final class AmplitudeTests: XCTestCase { var legacyEventsString = "" legacyEventFiles?.forEach { file in legacyEventsString = legacyEventStorage.getEventsString(eventBlock: file) ?? "" - print(legacyEventsString) } XCTAssertEqual(legacyEventFiles?.count ?? 0, 1) @@ -465,7 +460,6 @@ final class AmplitudeTests: XCTestCase { var eventsString = "" eventFiles?.forEach { file in eventsString = legacyEventStorage.getEventsString(eventBlock: file) ?? "" - print(eventsString) } XCTAssertEqual(legacyDeviceId != nil, true)