From c2be80aba11411c3b39023e4865eaba8ef47273c Mon Sep 17 00:00:00 2001 From: Nan Date: Mon, 22 Apr 2024 12:01:37 -0700 Subject: [PATCH 01/13] remove properties model reference from Update User request * We don't need the reference to the property model for an Update User request, and this reference has gone unused so far * We don't hydrate from the response as the response is not the full user, just the property that was updated. * The request still has a reference to the identity model it is operating on --- .../Executors/OSPropertyOperationExecutor.swift | 11 ----------- .../Source/Requests/OSRequestUpdateProperties.swift | 9 +-------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift index eea593d9e..b464ce8fd 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift @@ -61,10 +61,6 @@ class OSPropertyOperationExecutor: OSOperationExecutor { if var updateRequestQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_PROPERTIES_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY, defaultValue: []) as? [OSRequestUpdateProperties] { // Hook each uncached Request to the model in the store for (index, request) in updateRequestQueue.enumerated().reversed() { - // 0. Hook up the properties model if its the current user's so it can hydrate - if let propertiesModel = OneSignalUserManagerImpl.sharedInstance.propertiesModelStore.getModel(modelId: request.modelToUpdate.modelId) { - request.modelToUpdate = propertiesModel - } if let identityModel = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: request.identityModel.modelId) { // 1. The identity model exist in the store, set it to be the Request's models request.identityModel = identityModel @@ -103,16 +99,10 @@ class OSPropertyOperationExecutor: OSOperationExecutor { OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSPropertyOperationExecutor processDeltaQueue with queue: \(self.deltaQueue)") } for delta in self.deltaQueue { - guard let model = delta.model as? OSPropertiesModel else { - // Log error - continue - } - let request = OSRequestUpdateProperties( properties: [delta.property: delta.value], deltas: nil, refreshDeviceMetadata: false, // Sort this out. - modelToUpdate: model, identityModel: OneSignalUserManagerImpl.sharedInstance.user.identityModel // TODO: Make sure this is ok ) self.updateRequestQueue.append(request) @@ -204,7 +194,6 @@ extension OSPropertyOperationExecutor { properties: [:], deltas: propertiesDeltas.jsonRepresentation(), refreshDeviceMetadata: refreshDeviceMetadata, - modelToUpdate: propertiesModel, identityModel: identityModel) if sendImmediately { diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift index 1ad4b1de1..7f84f4c18 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift @@ -34,9 +34,6 @@ class OSRequestUpdateProperties: OneSignalRequest, OSUserRequest { return stringDescription } - // TODO: does updating properties even have a response in which we need to hydrate from? Then we can get rid of modelToUpdate - // Yes we may, if we cleared local state - var modelToUpdate: OSPropertiesModel var identityModel: OSIdentityModel // TODO: Decide if addPushSubscriptionIdToAdditionalHeadersIfNeeded should block. @@ -55,8 +52,7 @@ class OSRequestUpdateProperties: OneSignalRequest, OSUserRequest { } } - init(properties: [String: Any], deltas: [String: Any]?, refreshDeviceMetadata: Bool?, modelToUpdate: OSPropertiesModel, identityModel: OSIdentityModel) { - self.modelToUpdate = modelToUpdate + init(properties: [String: Any], deltas: [String: Any]?, refreshDeviceMetadata: Bool?, identityModel: OSIdentityModel) { self.identityModel = identityModel self.stringDescription = "" super.init() @@ -79,7 +75,6 @@ class OSRequestUpdateProperties: OneSignalRequest, OSUserRequest { } func encode(with coder: NSCoder) { - coder.encode(modelToUpdate, forKey: "modelToUpdate") coder.encode(identityModel, forKey: "identityModel") coder.encode(parameters, forKey: "parameters") coder.encode(method.rawValue, forKey: "method") // Encodes as String @@ -88,7 +83,6 @@ class OSRequestUpdateProperties: OneSignalRequest, OSUserRequest { required init?(coder: NSCoder) { guard - let modelToUpdate = coder.decodeObject(forKey: "modelToUpdate") as? OSPropertiesModel, let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, let parameters = coder.decodeObject(forKey: "parameters") as? [String: Any], @@ -97,7 +91,6 @@ class OSRequestUpdateProperties: OneSignalRequest, OSUserRequest { // Log error return nil } - self.modelToUpdate = modelToUpdate self.identityModel = identityModel self.stringDescription = "" super.init() From f06201336182032f0f9aea11912ac7e560da46c7 Mon Sep 17 00:00:00 2001 From: Nan Date: Sun, 28 Apr 2024 23:55:46 -0700 Subject: [PATCH 02/13] [nits] Update string descriptions of Requests --- .../OneSignalUser/Source/Requests/OSRequestFetchUser.swift | 4 ++-- .../Source/Requests/OSRequestUpdateProperties.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestFetchUser.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestFetchUser.swift index 92c7ddd0b..f9f1fe210 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestFetchUser.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestFetchUser.swift @@ -58,7 +58,7 @@ class OSRequestFetchUser: OneSignalRequest, OSUserRequest { self.aliasLabel = aliasLabel self.aliasId = aliasId self.onNewSession = onNewSession - self.stringDescription = "OSRequestFetchUser with aliasLabel: \(aliasLabel) aliasId: \(aliasId)" + self.stringDescription = "" super.init() self.method = GET _ = prepareForExecution() // sets the path property @@ -88,7 +88,7 @@ class OSRequestFetchUser: OneSignalRequest, OSUserRequest { self.aliasLabel = aliasLabel self.aliasId = aliasId self.onNewSession = coder.decodeBool(forKey: "onNewSession") - self.stringDescription = "OSRequestFetchUser with aliasLabel: \(aliasLabel) aliasId: \(aliasId)" + self.stringDescription = "" super.init() self.method = HTTPMethod(rawValue: rawMethod) self.timestamp = timestamp diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift index 7f84f4c18..dc91e6d9d 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift @@ -54,7 +54,7 @@ class OSRequestUpdateProperties: OneSignalRequest, OSUserRequest { init(properties: [String: Any], deltas: [String: Any]?, refreshDeviceMetadata: Bool?, identityModel: OSIdentityModel) { self.identityModel = identityModel - self.stringDescription = "" + self.stringDescription = "" super.init() var propertiesObject = properties From f9ce88e83c7dcad3f4f09eb194aeef24a5d7d468 Mon Sep 17 00:00:00 2001 From: Nan Date: Sat, 27 Apr 2024 21:32:30 -0700 Subject: [PATCH 03/13] [tests] add waiting for background threads to run * OneSignalCoreMocks now has XCTest dependency * It uses a waiter to allow the test to wait for any async calls to run, because we don't have control over all threads. For example, a test may need to wait for operation repo to run everything in the dispatch queue --- .../OneSignal.xcodeproj/project.pbxproj | 264 +++++++++++++++--- .../OneSignalCoreMocks.swift | 7 + 2 files changed, 226 insertions(+), 45 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj index 60cd1a807..d49570558 100644 --- a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj +++ b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -86,6 +86,8 @@ 3C7A39C12B7BED900082665E /* OneSignalCoreMocks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CC0639A2B6D7A8C002BB07F /* OneSignalCoreMocks.framework */; }; 3C7A39C22B7BED900082665E /* OneSignalCoreMocks.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3CC0639A2B6D7A8C002BB07F /* OneSignalCoreMocks.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3C7A39DC2B7C1C580082665E /* UNUserNotificationCenterOverrider.m in Sources */ = {isa = PBXBuildFile; fileRef = 4529DEE61FA82CDC00CEAB1D /* UNUserNotificationCenterOverrider.m */; }; + 3C87066D2BDE05B8000D8CD2 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C7A39D42B7C18EE0082665E /* XCTest.framework */; platformFilter = ios; }; + 3C87066E2BDE05B8000D8CD2 /* XCTest.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3C7A39D42B7C18EE0082665E /* XCTest.framework */; platformFilter = ios; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3C8E6DF928A6D89E0031E48A /* OSOperationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8E6DF828A6D89E0031E48A /* OSOperationExecutor.swift */; }; 3C8E6DFF28AB09AE0031E48A /* OSPropertyOperationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8E6DFE28AB09AE0031E48A /* OSPropertyOperationExecutor.swift */; }; 3C8E6E0128AC0BA10031E48A /* OSIdentityOperationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8E6E0028AC0BA10031E48A /* OSIdentityOperationExecutor.swift */; }; @@ -899,6 +901,7 @@ dstSubfolderSpec = 10; files = ( 3CEE93582B7C78FE008440BD /* OneSignalCore.framework in Embed Frameworks */, + 3C87066E2BDE05B8000D8CD2 /* XCTest.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -1371,6 +1374,7 @@ buildActionMask = 2147483647; files = ( 3CEE93572B7C78FD008440BD /* OneSignalCore.framework in Frameworks */, + 3C87066D2BDE05B8000D8CD2 /* XCTest.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3939,7 +3943,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -3947,8 +3955,9 @@ PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.OneSignalOSCore; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -4000,7 +4009,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -4057,7 +4070,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; @@ -4120,7 +4137,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; @@ -4179,7 +4200,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; @@ -4390,7 +4415,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; @@ -4453,7 +4482,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; @@ -4512,7 +4545,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; @@ -4739,7 +4776,11 @@ "$(PROJECT_DIR)", ); IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); OTHER_CFLAGS = "-fembed-bitcode"; OTHER_LDFLAGS = ""; PRODUCT_NAME = OneSignal; @@ -4771,7 +4812,11 @@ INFOPLIST_FILE = OneSignalFramework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MACH_O_TYPE = mh_dylib; MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = ""; @@ -4808,7 +4853,11 @@ HEADER_SEARCH_PATHS = $CONFIGURATION_TEMP_DIR/UnitTests.build/DerivedSources; INFOPLIST_FILE = UnitTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "-fembed-bitcode"; @@ -4907,7 +4956,11 @@ "$(PROJECT_DIR)", ); IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); OTHER_CFLAGS = "-fembed-bitcode"; OTHER_LDFLAGS = ""; PRODUCT_NAME = OneSignal; @@ -4939,7 +4992,11 @@ INFOPLIST_FILE = OneSignalFramework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MACH_O_TYPE = mh_dylib; MTL_ENABLE_DEBUG_INFO = NO; ONLY_ACTIVE_ARCH = NO; @@ -4977,7 +5034,11 @@ HEADER_SEARCH_PATHS = $CONFIGURATION_TEMP_DIR/UnitTests.build/DerivedSources; INFOPLIST_FILE = UnitTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "-fembed-bitcode"; @@ -5076,7 +5137,11 @@ "$(PROJECT_DIR)", ); IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); OTHER_CFLAGS = "-fembed-bitcode"; OTHER_LDFLAGS = ""; PRODUCT_NAME = OneSignal; @@ -5108,7 +5173,11 @@ INFOPLIST_FILE = OneSignalFramework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MACH_O_TYPE = mh_dylib; MTL_ENABLE_DEBUG_INFO = NO; ONLY_ACTIVE_ARCH = NO; @@ -5146,7 +5215,11 @@ HEADER_SEARCH_PATHS = $CONFIGURATION_TEMP_DIR/UnitTests.build/DerivedSources; INFOPLIST_FILE = UnitTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "-fembed-bitcode"; @@ -5234,7 +5307,11 @@ INFOPLIST_FILE = UnitTestApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 13.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = NO; @@ -5292,7 +5369,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -5351,7 +5432,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -5411,7 +5496,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -5468,7 +5557,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -5525,7 +5618,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -5533,8 +5630,9 @@ PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.OneSignalUser; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -5585,7 +5683,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -5645,7 +5747,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -5709,7 +5815,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -5765,7 +5875,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -5827,7 +5941,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -5883,7 +6001,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -5944,7 +6066,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2021 Hiptic. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -5999,7 +6125,11 @@ INFOPLIST_KEY_NSPrincipalClass = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; @@ -6061,7 +6191,11 @@ INFOPLIST_KEY_NSPrincipalClass = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; @@ -6120,7 +6254,11 @@ INFOPLIST_KEY_NSPrincipalClass = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; @@ -6176,7 +6314,11 @@ INFOPLIST_KEY_NSPrincipalClass = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; @@ -6238,7 +6380,11 @@ INFOPLIST_KEY_NSPrincipalClass = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; @@ -6297,7 +6443,11 @@ INFOPLIST_KEY_NSPrincipalClass = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; @@ -6357,7 +6507,11 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OneSignalOSCoreFramework/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); OTHER_CFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = com.onesignal.OneSignalOSCore; PRODUCT_NAME = OneSignalOSCore; @@ -6402,7 +6556,11 @@ INFOPLIST_FILE = UnitTestApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 13.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; OTHER_CFLAGS = "-fembed-bitcode"; @@ -6451,7 +6609,11 @@ INFOPLIST_FILE = UnitTestApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 13.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = NO; @@ -6504,7 +6666,11 @@ INFOPLIST_KEY_NSPrincipalClass = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -6565,7 +6731,11 @@ INFOPLIST_KEY_NSPrincipalClass = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -6622,7 +6792,11 @@ INFOPLIST_KEY_NSPrincipalClass = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; diff --git a/iOS_SDK/OneSignalSDK/OneSignalCoreMocks/OneSignalCoreMocks.swift b/iOS_SDK/OneSignalSDK/OneSignalCoreMocks/OneSignalCoreMocks.swift index 587eb102b..176ca3a94 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalCoreMocks/OneSignalCoreMocks.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalCoreMocks/OneSignalCoreMocks.swift @@ -22,6 +22,7 @@ import Foundation import OneSignalCore +import XCTest @objc public class OneSignalCoreMocks: NSObject { @@ -43,4 +44,10 @@ public class OneSignalCoreMocks: NSObject { sharedUserDefaults.removeObject(forKey: key) } } + + /** Wait specified number of seconds for any async methods to run */ + public static func waitForBackgroundThreads(seconds: Double) { + let expectation = XCTestExpectation(description: "Wait for \(seconds) seconds") + _ = XCTWaiter.wait(for: [expectation], timeout: seconds) + } } From 7ab2abe2158aae7fede638b084bc475880690947 Mon Sep 17 00:00:00 2001 From: Nan Date: Sun, 28 Apr 2024 13:42:27 -0700 Subject: [PATCH 04/13] [tests] add some user mocking --- .../OneSignal.xcodeproj/project.pbxproj | 8 ++++++++ .../OneSignalUserMocks/MockUserDefines.swift | 5 +++++ .../OneSignalUserMocks/MockUserRequests.swift | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 iOS_SDK/OneSignalSDK/OneSignalUserMocks/MockUserDefines.swift create mode 100644 iOS_SDK/OneSignalSDK/OneSignalUserMocks/MockUserRequests.swift diff --git a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj index d49570558..8b5865fa8 100644 --- a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj +++ b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj @@ -88,6 +88,8 @@ 3C7A39DC2B7C1C580082665E /* UNUserNotificationCenterOverrider.m in Sources */ = {isa = PBXBuildFile; fileRef = 4529DEE61FA82CDC00CEAB1D /* UNUserNotificationCenterOverrider.m */; }; 3C87066D2BDE05B8000D8CD2 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C7A39D42B7C18EE0082665E /* XCTest.framework */; platformFilter = ios; }; 3C87066E2BDE05B8000D8CD2 /* XCTest.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3C7A39D42B7C18EE0082665E /* XCTest.framework */; platformFilter = ios; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3C8706702BDE0957000D8CD2 /* MockUserRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C87066F2BDE0957000D8CD2 /* MockUserRequests.swift */; }; + 3C8706722BDEE076000D8CD2 /* MockUserDefines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8706712BDEE076000D8CD2 /* MockUserDefines.swift */; }; 3C8E6DF928A6D89E0031E48A /* OSOperationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8E6DF828A6D89E0031E48A /* OSOperationExecutor.swift */; }; 3C8E6DFF28AB09AE0031E48A /* OSPropertyOperationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8E6DFE28AB09AE0031E48A /* OSPropertyOperationExecutor.swift */; }; 3C8E6E0128AC0BA10031E48A /* OSIdentityOperationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8E6E0028AC0BA10031E48A /* OSIdentityOperationExecutor.swift */; }; @@ -971,6 +973,8 @@ 3C4F9E4328A4466C009F453A /* OSOperationRepo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSOperationRepo.swift; sourceTree = ""; }; 3C5117162B15C31E00563465 /* OSUserState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSUserState.swift; sourceTree = ""; }; 3C7A39D42B7C18EE0082665E /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 3C87066F2BDE0957000D8CD2 /* MockUserRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserRequests.swift; sourceTree = ""; }; + 3C8706712BDEE076000D8CD2 /* MockUserDefines.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserDefines.swift; sourceTree = ""; }; 3C8E6DF828A6D89E0031E48A /* OSOperationExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSOperationExecutor.swift; sourceTree = ""; }; 3C8E6DFE28AB09AE0031E48A /* OSPropertyOperationExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSPropertyOperationExecutor.swift; sourceTree = ""; }; 3C8E6E0028AC0BA10031E48A /* OSIdentityOperationExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSIdentityOperationExecutor.swift; sourceTree = ""; }; @@ -1726,6 +1730,8 @@ isa = PBXGroup; children = ( 3CC063DF2B6D7F2A002BB07F /* OneSignalUserMocks.h */, + 3C87066F2BDE0957000D8CD2 /* MockUserRequests.swift */, + 3C8706712BDEE076000D8CD2 /* MockUserDefines.swift */, 3CC063E52B6D7F96002BB07F /* OneSignalUserMocks.swift */, ); path = OneSignalUserMocks; @@ -3364,6 +3370,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3C8706702BDE0957000D8CD2 /* MockUserRequests.swift in Sources */, + 3C8706722BDEE076000D8CD2 /* MockUserDefines.swift in Sources */, 3CC063E62B6D7F96002BB07F /* OneSignalUserMocks.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/iOS_SDK/OneSignalSDK/OneSignalUserMocks/MockUserDefines.swift b/iOS_SDK/OneSignalSDK/OneSignalUserMocks/MockUserDefines.swift new file mode 100644 index 000000000..452c4c192 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUserMocks/MockUserDefines.swift @@ -0,0 +1,5 @@ +public let anonUserOSID = "test_anon_user_onesignal_id" +public let userA_OSID = "test_user_a_onesignal_id" +public let userA_EUID = "test_user_a_external_id" +public let userB_OSID = "test_user_b_onesignal_id" +public let userB_EUID = "test_user_b_external_id" diff --git a/iOS_SDK/OneSignalSDK/OneSignalUserMocks/MockUserRequests.swift b/iOS_SDK/OneSignalSDK/OneSignalUserMocks/MockUserRequests.swift new file mode 100644 index 000000000..fbc57ee59 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUserMocks/MockUserRequests.swift @@ -0,0 +1,18 @@ +import OneSignalCore + +public class MockUserRequests { + + public static func testIdentityPayload(onesignalId: String, externalId: String?) -> [String: [String: String]] { + var aliases = [OS_ONESIGNAL_ID: onesignalId] + aliases[OS_EXTERNAL_ID] = externalId // only add if non-nil + return [ + "identity": aliases + ] + } + + public static func testPropertiesPayload(properties: [String: Any]) -> [String: Any] { + return [ + "properties": properties + ] + } +} From 9caff5c7deb6ca5added4e82f7c3b93f079fc8e7 Mon Sep 17 00:00:00 2001 From: Nan Date: Sun, 28 Apr 2024 13:49:22 -0700 Subject: [PATCH 05/13] [tests] extend NSDictionary for testing * We will need to know if a particular payload exists in a dictionary to confirm what requests are sending --- .../OneSignal.xcodeproj/project.pbxproj | 12 ++++++++++ .../Extensions/NSDictionary+UnitTests.swift | 24 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 iOS_SDK/OneSignalSDK/OneSignalCoreMocks/Extensions/NSDictionary+UnitTests.swift diff --git a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj index 8b5865fa8..b4b0e4fed 100644 --- a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj +++ b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj @@ -90,6 +90,7 @@ 3C87066E2BDE05B8000D8CD2 /* XCTest.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3C7A39D42B7C18EE0082665E /* XCTest.framework */; platformFilter = ios; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3C8706702BDE0957000D8CD2 /* MockUserRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C87066F2BDE0957000D8CD2 /* MockUserRequests.swift */; }; 3C8706722BDEE076000D8CD2 /* MockUserDefines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8706712BDEE076000D8CD2 /* MockUserDefines.swift */; }; + 3C8706762BDEED75000D8CD2 /* NSDictionary+UnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8706752BDEED75000D8CD2 /* NSDictionary+UnitTests.swift */; }; 3C8E6DF928A6D89E0031E48A /* OSOperationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8E6DF828A6D89E0031E48A /* OSOperationExecutor.swift */; }; 3C8E6DFF28AB09AE0031E48A /* OSPropertyOperationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8E6DFE28AB09AE0031E48A /* OSPropertyOperationExecutor.swift */; }; 3C8E6E0128AC0BA10031E48A /* OSIdentityOperationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8E6E0028AC0BA10031E48A /* OSIdentityOperationExecutor.swift */; }; @@ -975,6 +976,7 @@ 3C7A39D42B7C18EE0082665E /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 3C87066F2BDE0957000D8CD2 /* MockUserRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserRequests.swift; sourceTree = ""; }; 3C8706712BDEE076000D8CD2 /* MockUserDefines.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserDefines.swift; sourceTree = ""; }; + 3C8706752BDEED75000D8CD2 /* NSDictionary+UnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDictionary+UnitTests.swift"; sourceTree = ""; }; 3C8E6DF828A6D89E0031E48A /* OSOperationExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSOperationExecutor.swift; sourceTree = ""; }; 3C8E6DFE28AB09AE0031E48A /* OSPropertyOperationExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSPropertyOperationExecutor.swift; sourceTree = ""; }; 3C8E6E0028AC0BA10031E48A /* OSIdentityOperationExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSIdentityOperationExecutor.swift; sourceTree = ""; }; @@ -1676,6 +1678,14 @@ path = Source; sourceTree = ""; }; + 3C8706742BDEED53000D8CD2 /* Extensions */ = { + isa = PBXGroup; + children = ( + 3C8706752BDEED75000D8CD2 /* NSDictionary+UnitTests.swift */, + ); + path = Extensions; + sourceTree = ""; + }; 3C9AD6BA2B2284AB00BC1540 /* Executors */ = { isa = PBXGroup; children = ( @@ -1709,6 +1719,7 @@ 3CC0639B2B6D7A8D002BB07F /* OneSignalCoreMocks */ = { isa = PBXGroup; children = ( + 3C8706742BDEED53000D8CD2 /* Extensions */, 3CC0639C2B6D7A8D002BB07F /* OneSignalCoreMocks.h */, 3CC063B32B6D7BA2002BB07F /* OneSignalCoreMocks.swift */, 3CC063B12B6D7AD8002BB07F /* MockOneSignalClient.swift */, @@ -3353,6 +3364,7 @@ buildActionMask = 2147483647; files = ( 3CC063B22B6D7AD8002BB07F /* MockOneSignalClient.swift in Sources */, + 3C8706762BDEED75000D8CD2 /* NSDictionary+UnitTests.swift in Sources */, 3CC063B42B6D7BA2002BB07F /* OneSignalCoreMocks.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/iOS_SDK/OneSignalSDK/OneSignalCoreMocks/Extensions/NSDictionary+UnitTests.swift b/iOS_SDK/OneSignalSDK/OneSignalCoreMocks/Extensions/NSDictionary+UnitTests.swift new file mode 100644 index 000000000..44a185e88 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalCoreMocks/Extensions/NSDictionary+UnitTests.swift @@ -0,0 +1,24 @@ +extension NSDictionary { + func contains(key: String, value: Any) -> Bool { + guard let dictVal = self[key] else { + return false + } + + return equals(dictVal, value) + } + + func contains(_ dict: [String: Any]) -> Bool { + for (key, value) in dict { + if !contains(key: key, value: value) { + return false + } + } + return true + } + + private func equals(_ x: Any, _ y: Any) -> Bool { + guard x is AnyHashable else { return false } + guard y is AnyHashable else { return false } + return (x as! AnyHashable) == (y as! AnyHashable) + } +} From f235b8bfe3aa3fd62f5be6b34d1e9d112013afc9 Mon Sep 17 00:00:00 2001 From: Nan Date: Sun, 28 Apr 2024 23:36:08 -0700 Subject: [PATCH 06/13] [tests] add assert helpers to Mock Client * Adds additional property to track if all requests have been handled by mock responses set * Adds method to use in asserts to confirm if there is only one executed request that contains the payload provided, and the url matches the path provided.f --- .../MockOneSignalClient.swift | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalCoreMocks/MockOneSignalClient.swift b/iOS_SDK/OneSignalSDK/OneSignalCoreMocks/MockOneSignalClient.swift index 163bf465a..6096ebed4 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalCoreMocks/MockOneSignalClient.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalCoreMocks/MockOneSignalClient.swift @@ -40,6 +40,8 @@ public class MockOneSignalClient: NSObject, IOneSignalClient { var shouldUseProvisionalAuthorization = false // new in iOS 12 (aka Direct to History) var remoteParamsOutcomes: [String: Any] = [:] + public var allRequestsHandled = true + /** May add to or change this default remote params response*/ public func getRemoteParamsResponse() -> [String: Any] { return remoteParamsResponse ?? [ @@ -70,7 +72,7 @@ public class MockOneSignalClient: NSObject, IOneSignalClient { // Temp. method to log info while building unit tests @objc public func logSelfInfo() { - print("🧪 MockOneSignalClient with executionQueue \(executionQueue)") + print("🧪 MockOneSignalClient with executedRequests \(executedRequests)") } public func reset() { @@ -115,6 +117,7 @@ public class MockOneSignalClient: NSObject, IOneSignalClient { if (mockResponses[String(describing: request)]) != nil { successBlock(mockResponses[String(describing: request)]) } else { + allRequestsHandled = false print("🧪 cannot find a mock response for request: \(request)") } } @@ -137,3 +140,34 @@ public class MockOneSignalClient: NSObject, IOneSignalClient { mockResponses[request] = response } } + +// MARK: - Asserts + +extension MockOneSignalClient { + /** + Checks if there is only one executed request that contains the payload provided, and the url matches the path provided. + */ + public func onlyOneRequest(contains path: String, contains payload: [String: Any]) -> Bool { + var found = false + + for request in executedRequests { + guard let params = request.parameters as? NSDictionary else { + continue + } + + if params.contains(payload) { + if request.path == path { + guard !found else { + // False if more than 1 request satisfies both requirements + return false + } + found = true + } else { + return false + } + } + } + + return found + } +} From bde68d1ed3db5053843e61a72aca11aa6e897401 Mon Sep 17 00:00:00 2001 From: Nan Date: Sun, 28 Apr 2024 23:28:27 -0700 Subject: [PATCH 07/13] [tests] update schemes for testing * Change OneSignalUserTests build configs to "Test" * Add OS_TEST preprocessor macro (Test) to OneSignalOSCore which is needed for the reduced flush interval --- iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj | 1 + .../xcshareddata/xcschemes/OneSignalUserTests.xcscheme | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj index b4b0e4fed..d79bc30d3 100644 --- a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj +++ b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj @@ -6524,6 +6524,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_TESTABILITY = YES; + GCC_PREPROCESSOR_DEFINITIONS = OS_TEST; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OneSignalOSCoreFramework/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; diff --git a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/xcshareddata/xcschemes/OneSignalUserTests.xcscheme b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/xcshareddata/xcschemes/OneSignalUserTests.xcscheme index a953a4573..bd94aea75 100644 --- a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/xcshareddata/xcschemes/OneSignalUserTests.xcscheme +++ b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/xcshareddata/xcschemes/OneSignalUserTests.xcscheme @@ -7,7 +7,7 @@ buildImplicitDependencies = "YES"> Date: Sun, 28 Apr 2024 23:39:19 -0700 Subject: [PATCH 08/13] [tests] Add test for switching users with adding tags * Starts with anon user, log into 2 users via Identify User and Create User * Add tags to each user --- .../OneSignalUserTests.swift | 81 ++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift b/iOS_SDK/OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift index d8111396c..4d48b7c23 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift @@ -43,7 +43,7 @@ final class OneSignalUserTests: XCTestCase { } override func tearDownWithError() throws { - // TODO: Need to clear all data between tests for user manager, models, etc. + // TODO: Need to clear all data between tests for client, user manager, models, etc. OneSignalCoreMocks.clearUserDefaults() OneSignalUserMocks.reset() } @@ -137,4 +137,83 @@ final class OneSignalUserTests: XCTestCase { identityModel.clearData() } } + + func testSwitchUser_sendsCorrectTags() throws { + /* Setup */ + + let client = MockOneSignalClient() + + // 1. Set up mock responses for the anonymous user + let anonCreateResponse = MockUserRequests.testIdentityPayload(onesignalId: anonUserOSID, externalId: nil) + + client.setMockResponseForRequest( + request: "", + response: anonCreateResponse) + + // 2. Set up mock responses for User A + let tagsUserA = ["tag_a": "value_a"] + let createUserA = MockUserRequests.testIdentityPayload(onesignalId: userA_OSID, externalId: userA_EUID) + let tagsResponseUserA = MockUserRequests.testPropertiesPayload(properties: ["tags": tagsUserA]) + + client.setMockResponseForRequest( + request: "", + response: createUserA + ) + client.setMockResponseForRequest( + request: "", + response: createUserA + ) + client.setMockResponseForRequest( + request: "", + response: tagsResponseUserA + ) + + // 3. Set up mock responses for User B + let tagsUserB = ["tag_b": "value_b"] + let createUserB = MockUserRequests.testIdentityPayload(onesignalId: userB_OSID, externalId: userB_EUID) + let tagsResponseUserB = MockUserRequests.testPropertiesPayload(properties: ["tags": tagsUserB]) + + client.setMockResponseForRequest( + request: "", + response: createUserB + ) + client.setMockResponseForRequest( + request: "", + response: createUserB + ) + client.setMockResponseForRequest( + request: "", + response: tagsResponseUserB) + + OneSignalCoreImpl.setSharedClient(client) + + /* When */ + + // 1. Login to user A and add tag + OneSignalUserManagerImpl.sharedInstance.login(externalId: userA_EUID, token: nil) + OneSignalUserManagerImpl.sharedInstance.addTag(key: "tag_a", value: "value_a") + + // 2. Login to user B and add tag + OneSignalUserManagerImpl.sharedInstance.login(externalId: userB_EUID, token: nil) + OneSignalUserManagerImpl.sharedInstance.addTag(key: "tag_b", value: "value_b") + + // 3. Run background threads + OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5) + + /* Then */ + + // Assert that every request SDK makes has a response set, and is handled + XCTAssertTrue(client.allRequestsHandled) + + // Assert there is only one request containing these tags and they are sent to userA + XCTAssertTrue(client.onlyOneRequest( + contains: "apps/test-app-id/users/by/onesignal_id/\(userA_OSID)", + contains: ["properties": ["tags": tagsUserA]]) + ) + // Assert there is only one request containing these tags and they are sent to userB + XCTAssertTrue(client.onlyOneRequest( + contains: "apps/test-app-id/users/by/onesignal_id/\(userB_OSID)", + contains: ["properties": ["tags": tagsUserB]]) + ) + } } From 88e31f58920c96bad02b3184b383e73f1baa3643 Mon Sep 17 00:00:00 2001 From: Nan Date: Wed, 24 Apr 2024 17:46:32 -0700 Subject: [PATCH 09/13] Add Identity Model ID to Deltas to track which user * The model on a Delta for property change is the properties model. However, the identity is needed to send the request. * We have been setting the identity model as the current user's when these Deltas become Requests. * However, the timing can be buggy when we flush Deltas when users change. The Operation Repo and Property Executor use a dispatch queue so by the time the request is made, the user may have changed. --- .../OneSignalSDK/OneSignalOSCore/Source/OSDelta.swift | 10 ++++++++-- .../Source/OSIdentityModelStoreListener.swift | 1 + .../Source/OSPropertiesModelStoreListener.swift | 1 + .../Source/OSSubscriptionModelStoreListener.swift | 3 +++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSDelta.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSDelta.swift index 39220214d..9cbba04e3 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSDelta.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSDelta.swift @@ -26,6 +26,7 @@ */ import Foundation +import OneSignalCore // TODO: Known Issue: Since these don't carry the app_id, it may have changed by the time Deltas become Requests, if app_id changes. // All requests requiring unique ID's will effectively be dropped. @@ -34,6 +35,7 @@ open class OSDelta: NSObject, NSCoding { public let name: String public let deltaId: String public let timestamp: Date + public let identityModelId: String public var model: OSModel public let property: String public let value: Any @@ -42,10 +44,11 @@ open class OSDelta: NSObject, NSCoding { return "" } - public init(name: String, model: OSModel, property: String, value: Any) { + public init(name: String, identityModelId: String, model: OSModel, property: String, value: Any) { self.name = name self.deltaId = UUID().uuidString self.timestamp = Date() + self.identityModelId = identityModelId self.model = model self.property = property self.value = value @@ -55,6 +58,7 @@ open class OSDelta: NSObject, NSCoding { coder.encode(name, forKey: "name") coder.encode(deltaId, forKey: "deltaId") coder.encode(timestamp, forKey: "timestamp") + coder.encode(identityModelId, forKey: "identityModelId") coder.encode(model, forKey: "model") coder.encode(property, forKey: "property") coder.encode(value, forKey: "value") @@ -64,17 +68,19 @@ open class OSDelta: NSObject, NSCoding { guard let name = coder.decodeObject(forKey: "name") as? String, let deltaId = coder.decodeObject(forKey: "deltaId") as? String, let timestamp = coder.decodeObject(forKey: "timestamp") as? Date, + let identityModelId = coder.decodeObject(forKey: "identityModelId") as? String, let model = coder.decodeObject(forKey: "model") as? OSModel, let property = coder.decodeObject(forKey: "property") as? String, let value = coder.decodeObject(forKey: "value") else { - // Log error + OneSignalLog.onesignalLog(.LL_ERROR, message: "Unable to init OSDelta from cache") return nil } self.name = name self.deltaId = deltaId self.timestamp = timestamp + self.identityModelId = identityModelId self.model = model self.property = property self.value = value diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModelStoreListener.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModelStoreListener.swift index 359050d20..4edabb321 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModelStoreListener.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModelStoreListener.swift @@ -60,6 +60,7 @@ class OSIdentityModelStoreListener: OSModelStoreListener { return OSDelta( name: name, + identityModelId: args.model.modelId, model: args.model, property: args.property, value: args.newValue diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertiesModelStoreListener.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertiesModelStoreListener.swift index 8d4ef768b..7db8b6d39 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertiesModelStoreListener.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertiesModelStoreListener.swift @@ -47,6 +47,7 @@ class OSPropertiesModelStoreListener: OSModelStoreListener { func getUpdateModelDelta(_ args: OSModelChangedArgs) -> OSDelta? { return OSDelta( name: OS_UPDATE_PROPERTIES_DELTA, + identityModelId: OneSignalUserManagerImpl.sharedInstance.user.identityModel.modelId, model: args.model, property: args.property, value: args.newValue diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionModelStoreListener.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionModelStoreListener.swift index 79183a893..6ffe8c9ac 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionModelStoreListener.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionModelStoreListener.swift @@ -39,6 +39,7 @@ class OSSubscriptionModelStoreListener: OSModelStoreListener { func getAddModelDelta(_ model: OSSubscriptionModel) -> OSDelta? { return OSDelta( name: OS_ADD_SUBSCRIPTION_DELTA, + identityModelId: OneSignalUserManagerImpl.sharedInstance.user.identityModel.modelId, model: model, property: model.type.rawValue, // push, email, sms value: model.address ?? "" @@ -51,6 +52,7 @@ class OSSubscriptionModelStoreListener: OSModelStoreListener { func getRemoveModelDelta(_ model: OSSubscriptionModel) -> OSDelta? { return OSDelta( name: OS_REMOVE_SUBSCRIPTION_DELTA, + identityModelId: OneSignalUserManagerImpl.sharedInstance.user.identityModel.modelId, model: model, property: model.type.rawValue, // push, email, sms value: model.address ?? "" @@ -60,6 +62,7 @@ class OSSubscriptionModelStoreListener: OSModelStoreListener { func getUpdateModelDelta(_ args: OSModelChangedArgs) -> OSDelta? { return OSDelta( name: OS_UPDATE_SUBSCRIPTION_DELTA, + identityModelId: OneSignalUserManagerImpl.sharedInstance.user.identityModel.modelId, model: args.model, property: args.property, value: args.newValue From f9cf3640b8da908fcb92f072a8b640d35b5021f1 Mon Sep 17 00:00:00 2001 From: Nan Date: Wed, 24 Apr 2024 17:32:08 -0700 Subject: [PATCH 10/13] Have an Identity Model Repo for models still being used * Introduce OSIdentityModelRepo managed by the User Manager where a dictionary of identity models live * This will hold identity models added when users change * When requests are uncached, their identity models will also be added as they are still being used. * This is a lightweight option over modifying the Identity Model Store (which is an option if we wish to use the model store to manage all identity models) --- .../OneSignal.xcodeproj/project.pbxproj | 4 ++ .../Source/OSIdentityModelRepo.swift | 55 +++++++++++++++++++ .../Source/OneSignalUserManagerImpl.swift | 13 +++++ .../OneSignalUserMocks.swift | 9 ++- 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModelRepo.swift diff --git a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj index d79bc30d3..dc27505fb 100644 --- a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj +++ b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj @@ -68,6 +68,7 @@ 3C14E3A12AFAE461006ED053 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3C14E3A02AFAE461006ED053 /* PrivacyInfo.xcprivacy */; }; 3C14E3A42AFAE54C006ED053 /* OneSignalSwiftInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC08AFF2947D4E900C81DA3 /* OneSignalSwiftInterface.swift */; }; 3C24B0EC2BD09D7A0052E771 /* OneSignalCoreObjCTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C24B0EB2BD09D7A0052E771 /* OneSignalCoreObjCTests.m */; }; + 3C277D7E2BD76E0000857606 /* OSIdentityModelRepo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C277D7D2BD76E0000857606 /* OSIdentityModelRepo.swift */; }; 3C2C7DC8288F3C020020F9AE /* OSSubscriptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2C7DC7288F3C020020F9AE /* OSSubscriptionModel.swift */; }; 3C2D8A5928B4C4E300BE41F6 /* OSDelta.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2D8A5828B4C4E300BE41F6 /* OSDelta.swift */; }; 3C44673E296D099D0039A49E /* OneSignalMobileProvision.m in Sources */ = {isa = PBXBuildFile; fileRef = 912411FD1E73342200E41FD7 /* OneSignalMobileProvision.m */; }; @@ -963,6 +964,7 @@ 3C14E3A02AFAE461006ED053 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 3C24B0EA2BD09D790052E771 /* OneSignalCoreTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OneSignalCoreTests-Bridging-Header.h"; sourceTree = ""; }; 3C24B0EB2BD09D7A0052E771 /* OneSignalCoreObjCTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OneSignalCoreObjCTests.m; sourceTree = ""; }; + 3C277D7D2BD76E0000857606 /* OSIdentityModelRepo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSIdentityModelRepo.swift; sourceTree = ""; }; 3C2C7DC2288E007E0020F9AE /* UnitTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UnitTests-Bridging-Header.h"; sourceTree = ""; }; 3C2C7DC7288F3C020020F9AE /* OSSubscriptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSSubscriptionModel.swift; sourceTree = ""; }; 3C2D8A5828B4C4E300BE41F6 /* OSDelta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSDelta.swift; sourceTree = ""; }; @@ -1982,6 +1984,7 @@ DE69E1A9282ED8790090BB3D /* UnitTestApp-Bridging-Header.h */, 3C0EF49D28A1DBCB00E5434B /* OSUserInternalImpl.swift */, DE69E1AA282ED8790090BB3D /* OneSignalUserManagerImpl.swift */, + 3C277D7D2BD76E0000857606 /* OSIdentityModelRepo.swift */, 3C2C7DC7288F3C020020F9AE /* OSSubscriptionModel.swift */, 3CE92279289FA88B001B1062 /* OSIdentityModelStoreListener.swift */, 3CF8629D28A183F900776CA4 /* OSIdentityModel.swift */, @@ -3519,6 +3522,7 @@ 3CF862A028A1964F00776CA4 /* OSPropertiesModel.swift in Sources */, 3C8E6E0128AC0BA10031E48A /* OSIdentityOperationExecutor.swift in Sources */, 3CF862A228A197D200776CA4 /* OSPropertiesModelStoreListener.swift in Sources */, + 3C277D7E2BD76E0000857606 /* OSIdentityModelRepo.swift in Sources */, 3C9AD6C12B22886600BC1540 /* OSRequestUpdateSubscription.swift in Sources */, 3C0EF49E28A1DBCB00E5434B /* OSUserInternalImpl.swift in Sources */, 3C8E6DFF28AB09AE0031E48A /* OSPropertyOperationExecutor.swift in Sources */, diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModelRepo.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModelRepo.swift new file mode 100644 index 000000000..781fe4e8f --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModelRepo.swift @@ -0,0 +1,55 @@ +/* + Modified MIT License + + Copyright 2024 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import Foundation + +/** + This class stores all Identity Models that are being used during an app session. + Its purpose is to manage the instances for all referencing objects. + The models are built up on each new cold start, so no caching occurs. + + When are Identity Models added to this repo? + 1. When the User Manager starts, and the Identity Model is loaded from cache. + 2. When users switch and new Identity Models are created. + 3. Identity Models are added when requests are uncached. + */ +class OSIdentityModelRepo { + let lock = NSLock() + var models: [String: OSIdentityModel] = [:] + + func add(model: OSIdentityModel) { + lock.withLock { + models[model.modelId] = model + } + } + + func get(modelId: String) -> OSIdentityModel? { + lock.withLock { + return models[modelId] + } + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift index 92d161abd..2a605bc26 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift @@ -118,6 +118,8 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { @objc public let pushSubscriptionImpl: OSPushSubscriptionImpl + var identityModelRepo = OSIdentityModelRepo() + private var hasCalledStart = false private var jwtExpiredHandler: OSJwtExpiredHandler? @@ -211,12 +213,14 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { let pushSubscription = pushSubscriptionModelStore.getModels()[OS_PUSH_SUBSCRIPTION_MODEL_KEY] { hasCachedUser = true _user = OSUserInternalImpl(identityModel: identityModel, propertiesModel: propertiesModel, pushSubscriptionModel: pushSubscription) + addIdentityModelToRepo(identityModel) OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OneSignalUserManager.start called, loaded the user from cache.") } // TODO: Update the push sub model with any new state from NotificationsManager // Setup the executors + // The OSUserExecutor has to run first, before other executors OSUserExecutor.start() OSOperationRepo.sharedInstance.start() @@ -253,6 +257,14 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { } } + func addIdentityModelToRepo(_ model: OSIdentityModel) { + self.identityModelRepo.add(model: model) + } + + func getIdentityModel(_ modelId: String) -> OSIdentityModel? { + return identityModelRepo.get(modelId: modelId) + } + @objc public func login(externalId: String, token: String?) { guard !OneSignalConfigManager.shouldAwaitAppIdAndLogMissingPrivacyConsent(forMethod: nil) else { @@ -429,6 +441,7 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { let identityModel = OSIdentityModel(aliases: aliases, changeNotifier: OSEventProducer()) self.identityModelStore.add(id: OS_IDENTITY_MODEL_KEY, model: identityModel, hydrating: false) + self.addIdentityModelToRepo(identityModel) let propertiesModel = OSPropertiesModel(changeNotifier: OSEventProducer()) self.propertiesModelStore.add(id: OS_PROPERTIES_MODEL_KEY, model: propertiesModel, hydrating: false) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUserMocks/OneSignalUserMocks.swift b/iOS_SDK/OneSignalSDK/OneSignalUserMocks/OneSignalUserMocks.swift index 229a6e27d..e0eb6facd 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUserMocks/OneSignalUserMocks.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUserMocks/OneSignalUserMocks.swift @@ -42,7 +42,6 @@ public class OneSignalUserMocks: NSObject { public static func resetStaticUserExecutor() { OSUserExecutor.userRequestQueue.removeAll() OSUserExecutor.transferSubscriptionRequestQueue.removeAll() - OSUserExecutor.identityModels.removeAll() } /** @@ -51,6 +50,8 @@ public class OneSignalUserMocks: NSObject { This is adapting as more data needs to be considered and reset... */ public static func resetUserManager() { + OneSignalUserManagerImpl.sharedInstance.identityModelRepo.reset() + OneSignalUserManagerImpl.sharedInstance.identityModelStore.clearModelsFromStore() OneSignalUserManagerImpl.sharedInstance.propertiesModelStore.clearModelsFromStore() OneSignalUserManagerImpl.sharedInstance.subscriptionModelStore.clearModelsFromStore() @@ -72,3 +73,9 @@ public class OneSignalUserMocks: NSObject { OSOperationRepo.sharedInstance.addExecutor(subscriptionExecutor) } } + +extension OSIdentityModelRepo { + func reset() { + self.models = [:] + } +} From a0df1966abdaac0955ceefef0f3d3ca45a2fb3ba Mon Sep 17 00:00:00 2001 From: Nan Date: Wed, 24 Apr 2024 17:36:49 -0700 Subject: [PATCH 11/13] Uncaching in executors will use the Identity Model Repo * When Requests are uncached in the executors, hook them up to the same identity model instance via the Identity Model Repo --- .../OSIdentityOperationExecutor.swift | 33 ++++++----- .../OSPropertyOperationExecutor.swift | 29 +++++----- .../OSSubscriptionOperationExecutor.swift | 43 ++++++++------ .../Source/Executors/OSUserExecutor.swift | 57 +++++++++---------- 4 files changed, 87 insertions(+), 75 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSIdentityOperationExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSIdentityOperationExecutor.swift index fd4674e9f..3811c0077 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSIdentityOperationExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSIdentityOperationExecutor.swift @@ -40,11 +40,12 @@ class OSIdentityOperationExecutor: OSOperationExecutor { if var deltaQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_IDENTITY_EXECUTOR_DELTA_QUEUE_KEY, defaultValue: []) as? [OSDelta] { // Hook each uncached Delta to the model in the store for (index, delta) in deltaQueue.enumerated().reversed() { - if let modelInStore = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: delta.model.modelId) { - // The model exists in the store, set it to be the Delta's model + if let modelInStore = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(delta.model.modelId) { + // The model exists in the repo, set it to be the Delta's model delta.model = modelInStore } else { // The model does not exist, drop this Delta + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSIdentityOperationExecutor.init dropped \(delta)") deltaQueue.remove(at: index) } } @@ -59,14 +60,15 @@ class OSIdentityOperationExecutor: OSOperationExecutor { if var addRequestQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, defaultValue: []) as? [OSRequestAddAliases] { // Hook each uncached Request to the model in the store for (index, request) in addRequestQueue.enumerated().reversed() { - if let identityModel = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: request.identityModel.modelId) { - // 1. The model exists in the store, so set it to be the Request's models + if let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(request.identityModel.modelId) { + // 1. The model exists in the repo, so set it to be the Request's models request.identityModel = identityModel - } else if let identityModel = OSUserExecutor.identityModels[request.identityModel.modelId] { - // 2. The model exists in the user executor - request.identityModel = identityModel - } else if !request.prepareForExecution() { - // 3. The models do not exist AND this request cannot be sent, drop this Request + } else if request.prepareForExecution() { + // 2. The request can be sent, add the model to the repo + OneSignalUserManagerImpl.sharedInstance.addIdentityModelToRepo(request.identityModel) + } else { + // 3. The model do not exist AND this request cannot be sent, drop this Request + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSIdentityOperationExecutor.init dropped \(request)") addRequestQueue.remove(at: index) } } @@ -79,14 +81,15 @@ class OSIdentityOperationExecutor: OSOperationExecutor { if var removeRequestQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_IDENTITY_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, defaultValue: []) as? [OSRequestRemoveAlias] { // Hook each uncached Request to the model in the store for (index, request) in removeRequestQueue.enumerated().reversed() { - if let identityModel = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: request.identityModel.modelId) { - // 1. The model exists in the store, so set it to be the Request's model + if let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(request.identityModel.modelId) { + // 1. The model exists in the repo, so set it to be the Request's model request.identityModel = identityModel - } else if let identityModel = OSUserExecutor.identityModels[request.identityModel.modelId] { - // 2. The model exists in the user executor - request.identityModel = identityModel - } else if !request.prepareForExecution() { + } else if request.prepareForExecution() { + // 2. The request can be sent, add the model to the repo + OneSignalUserManagerImpl.sharedInstance.addIdentityModelToRepo(request.identityModel) + } else { // 3. The model does not exist AND this request cannot be sent, drop this Request + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSIdentityOperationExecutor.init dropped \(request)") removeRequestQueue.remove(at: index) } } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift index b464ce8fd..7de21b0df 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift @@ -40,13 +40,9 @@ class OSPropertyOperationExecutor: OSOperationExecutor { // Read unfinished deltas from cache, if any... // Note that we should only have deltas for the current user as old ones are flushed.. if var deltaQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_PROPERTIES_EXECUTOR_DELTA_QUEUE_KEY, defaultValue: []) as? [OSDelta] { - // Hook each uncached Delta to the model in the store for (index, delta) in deltaQueue.enumerated().reversed() { - if let modelInStore = OneSignalUserManagerImpl.sharedInstance.propertiesModelStore.getModel(modelId: delta.model.modelId) { - // 1. The model exists in the properties model store, set it to be the Delta's model - delta.model = modelInStore - } else { - // 2. The model does not exist, drop this Delta + if OneSignalUserManagerImpl.sharedInstance.getIdentityModel(delta.identityModelId) == nil { + // The identity model does not exist, drop this Delta OneSignalLog.onesignalLog(.LL_WARN, message: "OSPropertyOperationExecutor.init dropped: \(delta)") deltaQueue.remove(at: index) } @@ -61,13 +57,13 @@ class OSPropertyOperationExecutor: OSOperationExecutor { if var updateRequestQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_PROPERTIES_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY, defaultValue: []) as? [OSRequestUpdateProperties] { // Hook each uncached Request to the model in the store for (index, request) in updateRequestQueue.enumerated().reversed() { - if let identityModel = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: request.identityModel.modelId) { - // 1. The identity model exist in the store, set it to be the Request's models - request.identityModel = identityModel - } else if let identityModel = OSUserExecutor.identityModels[request.identityModel.modelId] { - // 2. The model exists in the user executor + if let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(request.identityModel.modelId) { + // 1. The identity model exist in the repo, set it to be the Request's model request.identityModel = identityModel - } else if !request.prepareForExecution() { + } else if request.prepareForExecution() { + // 2. The request can be sent, add the model to the repo + OneSignalUserManagerImpl.sharedInstance.addIdentityModelToRepo(request.identityModel) + } else { // 3. The identitymodel do not exist AND this request cannot be sent, drop this Request OneSignalLog.onesignalLog(.LL_WARN, message: "OSPropertyOperationExecutor.init dropped: \(request)") updateRequestQueue.remove(at: index) @@ -99,11 +95,18 @@ class OSPropertyOperationExecutor: OSOperationExecutor { OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSPropertyOperationExecutor processDeltaQueue with queue: \(self.deltaQueue)") } for delta in self.deltaQueue { + guard let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(delta.identityModelId) + else { + // drop this delta + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSPropertyOperationExecutor.processDeltaQueue dropped: \(delta)") + continue + } + let request = OSRequestUpdateProperties( properties: [delta.property: delta.value], deltas: nil, refreshDeviceMetadata: false, // Sort this out. - identityModel: OneSignalUserManagerImpl.sharedInstance.user.identityModel // TODO: Make sure this is ok + identityModel: identityModel ) self.updateRequestQueue.append(request) } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSSubscriptionOperationExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSSubscriptionOperationExecutor.swift index 499705bad..4718de69f 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSSubscriptionOperationExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSSubscriptionOperationExecutor.swift @@ -47,6 +47,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { delta.model = modelInStore } else { // The model does not exist, drop this Delta + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSSubscriptionOperationExecutor.init dropped \(delta)") deltaQueue.remove(at: index) } } @@ -75,14 +76,15 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { subscriptionModels[request.subscriptionModel.modelId] = request.subscriptionModel } // 2. Hook up the identity model - if let identityModel = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: request.identityModel.modelId) { - // a. The model exist in the store + if let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(request.identityModel.modelId) { + // a. The model exist in the repo request.identityModel = identityModel - } else if let identityModel = OSUserExecutor.identityModels[request.identityModel.modelId] { - // b. The model exist in the user executor - request.identityModel = identityModel - } else if !request.prepareForExecution() { - // The model do not exist AND this request cannot be sent, drop this Request + } else if request.prepareForExecution() { + // b. The request can be sent, add the model to the repo + OneSignalUserManagerImpl.sharedInstance.addIdentityModelToRepo(request.identityModel) + } else { + // c. The model do not exist AND this request cannot be sent, drop this Request + OneSignalLog.onesignalLog(.LL_WARN, message: "OSSubscriptionOperationExecutor.init dropped: \(request)") continue } requestQueue.append(request) @@ -104,6 +106,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { request.subscriptionModel = subscriptionModel } else if !request.prepareForExecution() { // 3. The model does not exist AND this request cannot be sent, drop this Request + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSSubscriptionOperationExecutor.init dropped \(request)") removeRequestQueue.remove(at: index) } } @@ -124,6 +127,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { request.subscriptionModel = subscriptionModel } else if !request.prepareForExecution() { // 3. The models do not exist AND this request cannot be sent, drop this Request + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSSubscriptionOperationExecutor.init dropped \(request)") updateRequestQueue.remove(at: index) } } @@ -161,29 +165,34 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSSubscriptionOperationExecutor processDeltaQueue with queue: \(deltaQueue)") } for delta in deltaQueue { - guard let model = delta.model as? OSSubscriptionModel else { - // Log error + guard let subModel = delta.model as? OSSubscriptionModel + else { + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSSubscriptionOperationExecutor.processDeltaQueue dropped \(delta)") continue } switch delta.name { case OS_ADD_SUBSCRIPTION_DELTA: - let request = OSRequestCreateSubscription( - subscriptionModel: model, - identityModel: OneSignalUserManagerImpl.sharedInstance.user.identityModel // TODO: Make sure this is ok - ) - addRequestQueue.append(request) - + // Only create the request if the identity model exists + if let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(delta.identityModelId) { + let request = OSRequestCreateSubscription( + subscriptionModel: subModel, + identityModel: identityModel + ) + addRequestQueue.append(request) + } else { + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSSubscriptionOperationExecutor.processDeltaQueue dropped \(delta)") + } case OS_REMOVE_SUBSCRIPTION_DELTA: let request = OSRequestDeleteSubscription( - subscriptionModel: model + subscriptionModel: subModel ) removeRequestQueue.append(request) case OS_UPDATE_SUBSCRIPTION_DELTA: let request = OSRequestUpdateSubscription( subscriptionObject: [delta.property: delta.value], - subscriptionModel: model + subscriptionModel: subModel ) updateRequestQueue.append(request) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift index 0bb0ee47f..3d69e2f02 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift @@ -36,7 +36,6 @@ import OneSignalOSCore class OSUserExecutor { static var userRequestQueue: [OSUserRequest] = [] static var transferSubscriptionRequestQueue: [OSRequestTransferSubscription] = [] - static var identityModels: [String: OSIdentityModel] = [:] // Read in requests from the cache, do not read in FetchUser requests as this is not needed. static func start() { @@ -47,58 +46,48 @@ class OSUserExecutor { // Hook each uncached Request to the right model reference for request in cachedRequestQueue { if request.isKind(of: OSRequestFetchIdentityBySubscription.self), let req = request as? OSRequestFetchIdentityBySubscription { - if let identityModel = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: req.identityModel.modelId) { - // 1. The model exist in the store, set it to be the Request's model - req.identityModel = identityModel - } else if let identityModel = identityModels[req.identityModel.modelId] { - // 2. The model exists in the dict of identityModels already processed to use + if let identityModel = getIdentityModel(req.identityModel.modelId) { + // 1. The model exist in the repo, set it to be the Request's model + // It is the current user or the model has already been processed req.identityModel = identityModel } else { - // 3. The models do not exist, use the model on the request, and add to dict. - identityModels[req.identityModel.modelId] = req.identityModel + // 2. The model do not exist, use the model on the request, and add to repo. + addIdentityModel(req.identityModel) } userRequestQueue.append(req) } else if request.isKind(of: OSRequestCreateUser.self), let req = request as? OSRequestCreateUser { - if let identityModel = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: req.identityModel.modelId) { - // 1. The model exist in the store, set it to be the Request's model - req.identityModel = identityModel - } else if let identityModel = identityModels[req.identityModel.modelId] { - // 2. The model exists in the dict of identityModels already processed to use + if let identityModel = getIdentityModel(req.identityModel.modelId) { + // 1. The model exist in the repo, set it to be the Request's model req.identityModel = identityModel } else { - // 3. The models do not exist, use the model on the request, and add to dict. - identityModels[req.identityModel.modelId] = req.identityModel + // 2. The models do not exist, use the model on the request, and add to repo. + addIdentityModel(req.identityModel) } userRequestQueue.append(req) } else if request.isKind(of: OSRequestIdentifyUser.self), let req = request as? OSRequestIdentifyUser { - if let identityModelToIdentify = identityModels[req.identityModelToIdentify.modelId], - let identityModelToUpdate = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: req.identityModelToUpdate.modelId) { - // 1. A model exist in the dict and a model exist in the store, set it to be the Request's models - req.identityModelToIdentify = identityModelToIdentify - req.identityModelToUpdate = identityModelToUpdate - } else if let identityModelToIdentify = identityModels[req.identityModelToIdentify.modelId], - let identityModelToUpdate = identityModels[req.identityModelToUpdate.modelId] { - // 2. The two models exist in the dict, set it to be the Request's models + if let identityModelToIdentify = getIdentityModel(req.identityModelToIdentify.modelId), + let identityModelToUpdate = getIdentityModel(req.identityModelToUpdate.modelId) { + // 1. Both models exist in the repo, set it to be the Request's models req.identityModelToIdentify = identityModelToIdentify req.identityModelToUpdate = identityModelToUpdate - } else if let identityModelToIdentify = identityModels[req.identityModelToIdentify.modelId], - identityModels[req.identityModelToUpdate.modelId] == nil { - // 3. A model is in the dict, the other model does not exist + } else if let identityModelToIdentify = getIdentityModel(req.identityModelToIdentify.modelId), + getIdentityModel(req.identityModelToUpdate.modelId) == nil { + // 2. A model is in the repo, the other model does not exist req.identityModelToIdentify = identityModelToIdentify - identityModels[req.identityModelToUpdate.modelId] = req.identityModelToUpdate + addIdentityModel(req.identityModelToUpdate) } else { - // 4. Both models don't exist yet + // 3. Both models don't exist yet // Drop the request if the identityModelToIdentify does not already exist AND the request is missing OSID // Otherwise, this request will forever fail `prepareForExecution` and block pending requests such as recovery calls to `logout` or `login` guard request.prepareForExecution() else { OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor.start() dropped: \(request)") continue } - identityModels[req.identityModelToIdentify.modelId] = req.identityModelToIdentify - identityModels[req.identityModelToUpdate.modelId] = req.identityModelToUpdate + addIdentityModel(req.identityModelToIdentify) + addIdentityModel(req.identityModelToUpdate) } userRequestQueue.append(req) } @@ -128,6 +117,14 @@ class OSUserExecutor { executePendingRequests() } + static private func getIdentityModel(_ modelId: String) -> OSIdentityModel? { + return OneSignalUserManagerImpl.sharedInstance.getIdentityModel(modelId) + } + + static private func addIdentityModel(_ model: OSIdentityModel) { + OneSignalUserManagerImpl.sharedInstance.addIdentityModelToRepo(model) + } + static func appendToQueue(_ request: OSUserRequest) { if request.isKind(of: OSRequestTransferSubscription.self), let req = request as? OSRequestTransferSubscription { self.transferSubscriptionRequestQueue.append(req) From 975076e50a9578f6f19c045f89b4fa20e60a3cba Mon Sep 17 00:00:00 2001 From: Nan Date: Mon, 29 Apr 2024 00:05:18 -0700 Subject: [PATCH 12/13] [nits] ran swiftlint --- iOS_SDK/OneSignalSDK/OneSignalCoreMocks/.swiftlint.yml | 4 ++++ .../OneSignalSDK/OneSignalUser/Source/OSIdentityModel.swift | 2 +- .../OneSignalUser/Source/Requests/OSRequestCreateUser.swift | 2 +- .../Source/Requests/OSRequestUpdateProperties.swift | 2 +- .../OneSignalUser/Source/Requests/OSUserRequest.swift | 2 +- iOS_SDK/OneSignalSDK/OneSignalUserMocks/.swiftlint.yml | 2 ++ .../OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift | 2 +- 7 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 iOS_SDK/OneSignalSDK/OneSignalCoreMocks/.swiftlint.yml create mode 100644 iOS_SDK/OneSignalSDK/OneSignalUserMocks/.swiftlint.yml diff --git a/iOS_SDK/OneSignalSDK/OneSignalCoreMocks/.swiftlint.yml b/iOS_SDK/OneSignalSDK/OneSignalCoreMocks/.swiftlint.yml new file mode 100644 index 000000000..9d2d81d60 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalCoreMocks/.swiftlint.yml @@ -0,0 +1,4 @@ +# in tests, we may want to force cast and throw any errors +disabled_rules: + - force_cast + - identifier_name diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModel.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModel.swift index 194e6f16f..6e70b5057 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModel.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModel.swift @@ -86,7 +86,7 @@ class OSIdentityModel: OSModel { } self.set(property: "aliases", newValue: aliases) } - + /** Called to clear the model's data in preparation for hydration via a fetch user call. */ diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift index b356bcd49..dbf8ed914 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift @@ -48,7 +48,7 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { OneSignalLog.onesignalLog(.LL_DEBUG, message: "Cannot generate the create user request due to null app ID.") return false } - let _ = self.addPushSubscriptionIdToAdditionalHeaders() + _ = self.addPushSubscriptionIdToAdditionalHeaders() self.addJWTHeader(identityModel: identityModel) self.path = "apps/\(appId)/users" // The pushSub doesn't need to have a token. diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift index dc91e6d9d..91290cead 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift @@ -41,7 +41,7 @@ class OSRequestUpdateProperties: OneSignalRequest, OSUserRequest { func prepareForExecution() -> Bool { if let onesignalId = identityModel.onesignalId, let appId = OneSignalConfigManager.getAppId() { - let _ = self.addPushSubscriptionIdToAdditionalHeaders() + _ = self.addPushSubscriptionIdToAdditionalHeaders() self.addJWTHeader(identityModel: identityModel) self.path = "apps/\(appId)/users/by/\(OS_ONESIGNAL_ID)/\(onesignalId)" return true diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSUserRequest.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSUserRequest.swift index 43057203e..f6020c140 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSUserRequest.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSUserRequest.swift @@ -41,7 +41,7 @@ internal extension OneSignalRequest { // additionalHeaders["Authorization"] = "Bearer \(token)" // self.additionalHeaders = additionalHeaders } - + /** Returns if the `OneSignal-Subscription-Id` header was added successfully. */ func addPushSubscriptionIdToAdditionalHeaders() -> Bool { if let pushSubscriptionId = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionId { diff --git a/iOS_SDK/OneSignalSDK/OneSignalUserMocks/.swiftlint.yml b/iOS_SDK/OneSignalSDK/OneSignalUserMocks/.swiftlint.yml new file mode 100644 index 000000000..9cbe9dd8f --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUserMocks/.swiftlint.yml @@ -0,0 +1,2 @@ +disabled_rules: + - identifier_name diff --git a/iOS_SDK/OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift b/iOS_SDK/OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift index 4d48b7c23..c3b5d3ce5 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift @@ -201,7 +201,7 @@ final class OneSignalUserTests: XCTestCase { OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5) /* Then */ - + // Assert that every request SDK makes has a response set, and is handled XCTAssertTrue(client.allRequestsHandled) From ef9d2c695f2bdf399a55d4cdfb28d4bd8c0d66ff Mon Sep 17 00:00:00 2001 From: Nan Date: Mon, 29 Apr 2024 13:28:53 -0700 Subject: [PATCH 13/13] [tests] add more to reset User Manager state between tests * Also move its reset methods into an extension --- .../Source/OSModelStoreListener.swift | 2 +- .../Source/OneSignalUserManagerImpl.swift | 4 +- .../OneSignalUserMocks.swift | 53 ++++++++++--------- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModelStoreListener.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModelStoreListener.swift index d6297fbd3..540dcca40 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModelStoreListener.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModelStoreListener.swift @@ -47,7 +47,7 @@ extension OSModelStoreListener { store.changeSubscription.subscribe(self) } - func close() { + public func close() { store.changeSubscription.unsubscribe(self) } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift index 2a605bc26..f2d971be4 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift @@ -120,7 +120,7 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { var identityModelRepo = OSIdentityModelRepo() - private var hasCalledStart = false + var hasCalledStart = false private var jwtExpiredHandler: OSJwtExpiredHandler? @@ -139,7 +139,7 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { return user } - private var _user: OSUserInternal? + var _user: OSUserInternal? // This is a user instance to operate on when there is no app_id and/or privacy consent yet, effectively no-op. // The models are not added to any model stores. diff --git a/iOS_SDK/OneSignalSDK/OneSignalUserMocks/OneSignalUserMocks.swift b/iOS_SDK/OneSignalSDK/OneSignalUserMocks/OneSignalUserMocks.swift index e0eb6facd..cedfb6868 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUserMocks/OneSignalUserMocks.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUserMocks/OneSignalUserMocks.swift @@ -36,46 +36,47 @@ public class OneSignalUserMocks: NSObject { public static func reset() { resetStaticUserExecutor() - resetUserManager() + // TODO: Reset Operation Repo first + // OSCoreMocks.resetOperationRepo() + OneSignalUserManagerImpl.sharedInstance.reset() } public static func resetStaticUserExecutor() { OSUserExecutor.userRequestQueue.removeAll() OSUserExecutor.transferSubscriptionRequestQueue.removeAll() } +} + +extension OSIdentityModelRepo { + func reset() { + self.models = [:] + } +} - /** +extension OneSignalUserManagerImpl { + /** User Manager needs to reset between tests until we dependency inject the User Manager. For example, executors it owns may have cached requests or deltas that would have carried over. This is adapting as more data needs to be considered and reset... */ - public static func resetUserManager() { - OneSignalUserManagerImpl.sharedInstance.identityModelRepo.reset() - - OneSignalUserManagerImpl.sharedInstance.identityModelStore.clearModelsFromStore() - OneSignalUserManagerImpl.sharedInstance.propertiesModelStore.clearModelsFromStore() - OneSignalUserManagerImpl.sharedInstance.subscriptionModelStore.clearModelsFromStore() - OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModelStore.clearModelsFromStore() + func reset() { + identityModelRepo.reset() - let propertyExecutor = OSPropertyOperationExecutor() - let identityExecutor = OSIdentityOperationExecutor() - let subscriptionExecutor = OSSubscriptionOperationExecutor() + // Model store listeners unsubscribe to their models + // User Manager start() will subscribe them + identityModelStoreListener.close() + propertiesModelStoreListener.close() + subscriptionModelStoreListener.close() + pushSubscriptionModelStoreListener.close() - OneSignalUserManagerImpl.sharedInstance.propertyExecutor = propertyExecutor - OneSignalUserManagerImpl.sharedInstance.identityExecutor = identityExecutor - OneSignalUserManagerImpl.sharedInstance.subscriptionExecutor = subscriptionExecutor + // Executor instances do no need to be reset, they are initailized in start() - // TODO: Reset Operation Repo first - // OSCoreMocks.resetOperationRepo() + identityModelStore.clearModelsFromStore() + propertiesModelStore.clearModelsFromStore() + subscriptionModelStore.clearModelsFromStore() + pushSubscriptionModelStore.clearModelsFromStore() - OSOperationRepo.sharedInstance.addExecutor(identityExecutor) - OSOperationRepo.sharedInstance.addExecutor(propertyExecutor) - OSOperationRepo.sharedInstance.addExecutor(subscriptionExecutor) - } -} - -extension OSIdentityModelRepo { - func reset() { - self.models = [:] + _user = nil + hasCalledStart = false } }