diff --git a/.github/workflows/on_push_workflow.yml b/.github/workflows/on_push_workflow.yml index 91a77aee..c8497206 100644 --- a/.github/workflows/on_push_workflow.yml +++ b/.github/workflows/on_push_workflow.yml @@ -17,7 +17,7 @@ jobs: run: pod install --no-repo-update --verbose shell: bash - name: Build And Test - run: xcodebuild -workspace ~/work/ios-emarsys-sdk/ios-emarsys-sdk/EmarsysSDK.xcworkspace -scheme Tests -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 15 Pro,OS=17.5' -derivedDataPath ~/tmp test -quiet + run: xcodebuild -workspace ~/work/ios-emarsys-sdk/ios-emarsys-sdk/EmarsysSDK.xcworkspace -scheme Tests -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 15 Pro Max,OS=17.5' -derivedDataPath ~/tmp test -quiet shell: bash - name: Trigger Sample App Build uses: peter-evans/repository-dispatch@v1 diff --git a/Emarsys Sample/Emarsys-Sample.xcodeproj/project.pbxproj b/Emarsys Sample/Emarsys-Sample.xcodeproj/project.pbxproj index 5652a0bd..ae35d519 100644 --- a/Emarsys Sample/Emarsys-Sample.xcodeproj/project.pbxproj +++ b/Emarsys Sample/Emarsys-Sample.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -654,7 +654,7 @@ buildSettings = { CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 4J5FXBB97U; @@ -668,7 +668,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.emarsys.EmarsysSample.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = EmarsysSampleNotificationServiceProvProfDev; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = EmarsysSampleNotificationServiceProvProf; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; @@ -827,7 +827,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; CODE_SIGN_ENTITLEMENTS = "Emarsys-Sample/Emarsys-Sample.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_ASSET_PATHS = "\"Emarsys-Sample/Preview Content\""; @@ -842,7 +842,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.emarsys.EmarsysSample; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = EmarsysSampleProvProfDev; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = EmarsysSampleProvProf; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; diff --git a/Emarsys Sample/Emarsys-Sample/Views/DashboardView.swift b/Emarsys Sample/Emarsys-Sample/Views/DashboardView.swift index 17bd258e..65f4dcf1 100644 --- a/Emarsys Sample/Emarsys-Sample/Views/DashboardView.swift +++ b/Emarsys Sample/Emarsys-Sample/Views/DashboardView.swift @@ -191,7 +191,13 @@ struct DashboardView: View { UserDefaults.standard.set(nil, forKey: ConfigUserDefaultsKey.applicationCode.rawValue) UserDefaults.standard.set(nil, forKey: ConfigUserDefaultsKey.contactFieldId.rawValue) } - + Emarsys.config.changeMerchantId(merchantId: self.loginData.merchantId) { error in + if (error == nil) { + self.showMessage(successful: true) + } else { + self.showMessage(successful: true) + } + } self.showSetupChangeMessage = true } } diff --git a/Emarsys Sample/Emarsys-Sample/Views/MobileEngageView.swift b/Emarsys Sample/Emarsys-Sample/Views/MobileEngageView.swift index 32344868..66335fb9 100644 --- a/Emarsys Sample/Emarsys-Sample/Views/MobileEngageView.swift +++ b/Emarsys Sample/Emarsys-Sample/Views/MobileEngageView.swift @@ -115,7 +115,7 @@ struct MobileEngageView: View { func convertEventPayloadToJson() -> Dictionary? { var eventAttributes: [String: String]? - if let attributes = self.customEventPayload { + if let attributes = self.customEventPayload, !attributes.isEmpty { if let data = attributes.data(using: .utf8) { do { eventAttributes = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] diff --git a/EmarsysSDK.xcodeproj/project.pbxproj b/EmarsysSDK.xcodeproj/project.pbxproj index 0839d048..608984e7 100644 --- a/EmarsysSDK.xcodeproj/project.pbxproj +++ b/EmarsysSDK.xcodeproj/project.pbxproj @@ -222,7 +222,13 @@ 37460FE8E5129D15F541D35F /* PRERequestContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 37460FC10842C0A13863CF72 /* PRERequestContextTests.m */; }; 574DD0E9273283F8005F2750 /* EMSGeofenceTrigger.h in Headers */ = {isa = PBXBuildFile; fileRef = 574DD0E8273283F8005F2750 /* EMSGeofenceTrigger.h */; }; 574DD0EB2732843D005F2750 /* EMSGeofence.m in Sources */ = {isa = PBXBuildFile; fileRef = 574DD0EA2732843D005F2750 /* EMSGeofence.m */; }; + 5D3394D52C50E83A00E617E1 /* NSString+EMSCore.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D3394D42C50E83A00E617E1 /* NSString+EMSCore.m */; }; + 5D3394D62C50E83A00E617E1 /* NSString+EMSCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D3394D32C50E83A00E617E1 /* NSString+EMSCore.h */; }; + 5D3E118A2C77265B00D26BB1 /* EMSCompletionBlockProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D3E11892C77265B00D26BB1 /* EMSCompletionBlockProvider.m */; }; + 5D3E118B2C77265B00D26BB1 /* EMSCompletionBlockProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D3E11882C77265B00D26BB1 /* EMSCompletionBlockProvider.h */; }; 5D7EEE092BF4B4AB000E1558 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 5D7EEE082BF4B4AB000E1558 /* PrivacyInfo.xcprivacy */; }; + 5DF6FD1A2C6F392C0038790E /* EMSTestColumnInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DF6FD192C6F392C0038790E /* EMSTestColumnInfo.m */; }; + 5DF6FD1D2C6F39710038790E /* EMSTestColumnInfoMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DF6FD1C2C6F39710038790E /* EMSTestColumnInfoMapper.m */; }; 642590867F1D9665A69405B2 /* FakeDisplayedIAMRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6425981445EC441D5025D02A /* FakeDisplayedIAMRepository.swift */; }; 6425912A1561D7A4F6765FFF /* EMSAppEventActionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 64259B9AFE68A274724F14B2 /* EMSAppEventActionTests.m */; }; 642591319BA14CA36E2171CE /* EMSActionFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 642594C59C5EFF459584A660 /* EMSActionFactory.h */; }; @@ -337,9 +343,9 @@ 8A6BEA8323E86B480066B13C /* EMSRESTClientCompletionProxyProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A6BE8E723E86B480066B13C /* EMSRESTClientCompletionProxyProtocol.h */; }; 8A6BEA8423E86B480066B13C /* EMSCoreCompletionHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A6BE8E823E86B480066B13C /* EMSCoreCompletionHandler.h */; }; 8A6BEA8523E86B480066B13C /* EMSUUIDProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A6BE8EA23E86B480066B13C /* EMSUUIDProvider.h */; }; - 8A6BEA8623E86B480066B13C /* EMSCompletionBlockProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A6BE8EB23E86B480066B13C /* EMSCompletionBlockProvider.h */; }; + 8A6BEA8623E86B480066B13C /* EMSCompletionProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A6BE8EB23E86B480066B13C /* EMSCompletionProvider.h */; }; 8A6BEA8723E86B480066B13C /* EMSTimestampProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A6BE8EC23E86B480066B13C /* EMSTimestampProvider.m */; }; - 8A6BEA8823E86B480066B13C /* EMSCompletionBlockProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A6BE8ED23E86B480066B13C /* EMSCompletionBlockProvider.m */; }; + 8A6BEA8823E86B480066B13C /* EMSCompletionProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A6BE8ED23E86B480066B13C /* EMSCompletionProvider.m */; }; 8A6BEA8923E86B480066B13C /* EMSUUIDProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A6BE8EE23E86B480066B13C /* EMSUUIDProvider.m */; }; 8A6BEA8A23E86B480066B13C /* EMSTimestampProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A6BE8EF23E86B480066B13C /* EMSTimestampProvider.h */; }; 8A6BEA8B23E86B480066B13C /* MEExperimental.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A6BE8F123E86B480066B13C /* MEExperimental.m */; }; @@ -885,7 +891,16 @@ 37460FC4B8A95C2256E40EC6 /* EMSShardTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = EMSShardTests.m; path = Shard/EMSShardTests.m; sourceTree = ""; }; 574DD0E8273283F8005F2750 /* EMSGeofenceTrigger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMSGeofenceTrigger.h; sourceTree = ""; }; 574DD0EA2732843D005F2750 /* EMSGeofence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EMSGeofence.m; sourceTree = ""; }; + 5D15F9E92C75E90F0023AAC1 /* EMSSession+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "EMSSession+Tests.h"; sourceTree = ""; }; + 5D3394D32C50E83A00E617E1 /* NSString+EMSCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+EMSCore.h"; sourceTree = ""; }; + 5D3394D42C50E83A00E617E1 /* NSString+EMSCore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+EMSCore.m"; sourceTree = ""; }; + 5D3E11882C77265B00D26BB1 /* EMSCompletionBlockProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EMSCompletionBlockProvider.h; sourceTree = ""; }; + 5D3E11892C77265B00D26BB1 /* EMSCompletionBlockProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EMSCompletionBlockProvider.m; sourceTree = ""; }; 5D7EEE082BF4B4AB000E1558 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 5DF6FD182C6F392C0038790E /* EMSTestColumnInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EMSTestColumnInfo.h; sourceTree = ""; }; + 5DF6FD192C6F392C0038790E /* EMSTestColumnInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EMSTestColumnInfo.m; sourceTree = ""; }; + 5DF6FD1B2C6F39710038790E /* EMSTestColumnInfoMapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EMSTestColumnInfoMapper.h; sourceTree = ""; }; + 5DF6FD1C2C6F39710038790E /* EMSTestColumnInfoMapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EMSTestColumnInfoMapper.m; sourceTree = ""; }; 6425900619382B0A3A232868 /* EMSActionProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMSActionProtocol.h; sourceTree = ""; }; 6425906591104A1F55339EEB /* EMSMobileEngageNullSafeBodyParserTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EMSMobileEngageNullSafeBodyParserTests.m; sourceTree = ""; }; 642590A6317E018602B6AF71 /* EMSGeofenceInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMSGeofenceInternal.h; sourceTree = ""; }; @@ -1003,9 +1018,9 @@ 8A6BE8E723E86B480066B13C /* EMSRESTClientCompletionProxyProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMSRESTClientCompletionProxyProtocol.h; sourceTree = ""; }; 8A6BE8E823E86B480066B13C /* EMSCoreCompletionHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMSCoreCompletionHandler.h; sourceTree = ""; }; 8A6BE8EA23E86B480066B13C /* EMSUUIDProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMSUUIDProvider.h; sourceTree = ""; }; - 8A6BE8EB23E86B480066B13C /* EMSCompletionBlockProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMSCompletionBlockProvider.h; sourceTree = ""; }; + 8A6BE8EB23E86B480066B13C /* EMSCompletionProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMSCompletionProvider.h; sourceTree = ""; }; 8A6BE8EC23E86B480066B13C /* EMSTimestampProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EMSTimestampProvider.m; sourceTree = ""; }; - 8A6BE8ED23E86B480066B13C /* EMSCompletionBlockProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EMSCompletionBlockProvider.m; sourceTree = ""; }; + 8A6BE8ED23E86B480066B13C /* EMSCompletionProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EMSCompletionProvider.m; sourceTree = ""; }; 8A6BE8EE23E86B480066B13C /* EMSUUIDProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EMSUUIDProvider.m; sourceTree = ""; }; 8A6BE8EF23E86B480066B13C /* EMSTimestampProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMSTimestampProvider.h; sourceTree = ""; }; 8A6BE8F123E86B480066B13C /* MEExperimental.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MEExperimental.m; sourceTree = ""; }; @@ -1277,7 +1292,6 @@ 8A6BEA4A23E86B480066B13C /* EMSEmarsysRequestFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMSEmarsysRequestFactory.h; sourceTree = ""; }; 8A6BEA4C23E86B480066B13C /* EMSDeepLinkInternal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EMSDeepLinkInternal.m; sourceTree = ""; }; 8A6BEA4D23E86B480066B13C /* EMSLoggingDeepLinkInternal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EMSLoggingDeepLinkInternal.m; sourceTree = ""; }; - 8A6BEA4E23E86B480066B13C /* EMSLoggingDeepLinkInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EMSLoggingDeepLinkInternal.h; path = ../EmarsysSDK/DeepLink/EMSLoggingDeepLinkInternal.h; sourceTree = ""; }; 8A6BEA4F23E86B480066B13C /* EMSDeepLinkInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EMSDeepLinkInternal.h; sourceTree = ""; }; 8A6BEA5023E86B480066B13C /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8A84D9A72546F3E20043BA4A /* EmarsysSDKTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "EmarsysSDKTests-Bridging-Header.h"; sourceTree = ""; }; @@ -1670,6 +1684,10 @@ 32C6F9B35971E5720FCAB213 /* EMSSQLiteHelper+Test.h */, 37460D08A2B28531BABDD7A8 /* Triggers */, 8A602294219C6EF4000080FC /* EMSSQLStatementFactoryTests.m */, + 5DF6FD182C6F392C0038790E /* EMSTestColumnInfo.h */, + 5DF6FD192C6F392C0038790E /* EMSTestColumnInfo.m */, + 5DF6FD1B2C6F39710038790E /* EMSTestColumnInfoMapper.h */, + 5DF6FD1C2C6F39710038790E /* EMSTestColumnInfoMapper.m */, ); path = Database; sourceTree = ""; @@ -1880,6 +1898,7 @@ 5DF894CA2A812AED00803F2C /* Private */ = { isa = PBXGroup; children = ( + 5D3394D32C50E83A00E617E1 /* NSString+EMSCore.h */, 8A6BE8AE23E86B480066B13C /* EMSOperationQueue.h */, 32C6F4D808CD54C437521E7B /* EMSQueueDelegator.h */, 32C6FB547AB9C97BDC4F354A /* EMSDispatchWaiter.h */, @@ -1916,7 +1935,7 @@ 8A6BE8E723E86B480066B13C /* EMSRESTClientCompletionProxyProtocol.h */, 8A6BE8E823E86B480066B13C /* EMSCoreCompletionHandler.h */, 8A6BE8EA23E86B480066B13C /* EMSUUIDProvider.h */, - 8A6BE8EB23E86B480066B13C /* EMSCompletionBlockProvider.h */, + 8A6BE8EB23E86B480066B13C /* EMSCompletionProvider.h */, 8A6BE8EF23E86B480066B13C /* EMSTimestampProvider.h */, F2A2154E820F2B973BB32EBF /* EMSRandomProvider.h */, 8A6BE8F223E86B480066B13C /* MEExperimental.h */, @@ -2090,7 +2109,7 @@ 8A6BEA4F23E86B480066B13C /* EMSDeepLinkInternal.h */, 32C6F02CE3E640F757B02675 /* EMSEventHandlerProtocolBlockConverter.h */, 8A6BEA4A23E86B480066B13C /* EMSEmarsysRequestFactory.h */, - 8A6BEA4E23E86B480066B13C /* EMSLoggingDeepLinkInternal.h */, + 5D3E11882C77265B00D26BB1 /* EMSCompletionBlockProvider.h */, ); path = Private; sourceTree = ""; @@ -2187,13 +2206,6 @@ path = Recommendations; sourceTree = ""; }; - 64259FAF9CA8825C6DDA2CC5 /* Parser */ = { - isa = PBXGroup; - children = ( - ); - path = Parser; - sourceTree = ""; - }; 88C68566D4B863A6407A0E21 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -2248,7 +2260,6 @@ 8A6BE91323E86B480066B13C /* EMSRequestManager.m */, 8A6BE91423E86B480066B13C /* EMSAuthentication.m */, 8A6BE91523E86B480066B13C /* Log */, - 8A6BE92C23E86B480066B13C /* Mappers */, 8A6BE92F23E86B480066B13C /* Validators */, 8A6BE93223E86B480066B13C /* Worker */, 8A6BE93923E86B480066B13C /* Categories */, @@ -2257,7 +2268,6 @@ 8A6BE95223E86B480066B13C /* EMSDeviceInfo.m */, 32C6F4FF6863B651D96B8BB4 /* EMSCrypto.m */, 64259AA03BC495443C2E54AA /* Router */, - 64259FAF9CA8825C6DDA2CC5 /* Parser */, ); path = Core; sourceTree = ""; @@ -2355,9 +2365,10 @@ isa = PBXGroup; children = ( 8A6BE8EC23E86B480066B13C /* EMSTimestampProvider.m */, - 8A6BE8ED23E86B480066B13C /* EMSCompletionBlockProvider.m */, + 8A6BE8ED23E86B480066B13C /* EMSCompletionProvider.m */, 8A6BE8EE23E86B480066B13C /* EMSUUIDProvider.m */, F2A2153700FA19467DB3BBF3 /* EMSRandomProvider.m */, + 5D3E11892C77265B00D26BB1 /* EMSCompletionBlockProvider.m */, ); path = Providers; sourceTree = ""; @@ -2436,13 +2447,6 @@ path = LogEntry; sourceTree = ""; }; - 8A6BE92C23E86B480066B13C /* Mappers */ = { - isa = PBXGroup; - children = ( - ); - path = Mappers; - sourceTree = ""; - }; 8A6BE92F23E86B480066B13C /* Validators */ = { isa = PBXGroup; children = ( @@ -2471,6 +2475,7 @@ 8A6BE94723E86B480066B13C /* NSDictionary+EMSCore.m */, 8A6BE94923E86B480066B13C /* NSURLRequest+EMSCore.m */, FB8A35934808E998DA538E1C /* NSHTTPURLResponse+EMSCore.m */, + 5D3394D42C50E83A00E617E1 /* NSString+EMSCore.m */, ); path = Categories; sourceTree = ""; @@ -2991,6 +2996,7 @@ 3746044C8EBC88A514308371 /* EMSWaiter.h */, 32C6F40774A36CCD6E9A29D0 /* EmarsysTestUtils.m */, 32C6FD10D7BB83AE2C297792 /* EmarsysTestUtils.h */, + 5D15F9E92C75E90F0023AAC1 /* EMSSession+Tests.h */, ); path = Utils; sourceTree = ""; @@ -3280,10 +3286,12 @@ 8A6BEB6D23E86B490066B13C /* EMSViewControllerProvider.h in Headers */, 8A6BEAD423E86B480066B13C /* NSMutableDictionary+EMSCore.h in Headers */, 8A6BEAEB23E86B480066B13C /* EMSLogic.h in Headers */, + 5D3E118B2C77265B00D26BB1 /* EMSCompletionBlockProvider.h in Headers */, 8A6BEB7323E86B490066B13C /* EMSMainWindowProvider.h in Headers */, 8A6BEA7523E86B480066B13C /* EMSShardRepository.h in Headers */, 8A6BEAF523E86B480066B13C /* EMSConfigProtocol.h in Headers */, 8A6BEBA223E86B490066B13C /* EMSRemoteConfigResponseMapper.h in Headers */, + 5D3394D62C50E83A00E617E1 /* NSString+EMSCore.h in Headers */, 8A6BEB3823E86B490066B13C /* EMSMobileEngageV3Internal.h in Headers */, 8A6BEA7B23E86B480066B13C /* EMSRequestManager.h in Headers */, 8A6BEB0323E86B480066B13C /* EMSProduct+Emarsys.h in Headers */, @@ -3371,7 +3379,7 @@ 8A6BEB6923E86B490066B13C /* MEInAppTrackingProtocol.h in Headers */, 8A6BEB8B23E86B490066B13C /* EMSMobileEngageRefreshTokenCompletionProxy.h in Headers */, 8A6BEB5123E86B490066B13C /* EMSPushV3Internal.h in Headers */, - 8A6BEA8623E86B480066B13C /* EMSCompletionBlockProvider.h in Headers */, + 8A6BEA8623E86B480066B13C /* EMSCompletionProvider.h in Headers */, 8A6BEA9623E86B480066B13C /* EMSRequestModel.h in Headers */, 8A6BEAE123E86B480066B13C /* EMSPushNotificationProtocol.h in Headers */, 8A6BEB8023E86B490066B13C /* EMSNotificationService+Actions.h in Headers */, @@ -3755,6 +3763,7 @@ 166B3F4D2119A840006CD35C /* EMSShardTests.m in Sources */, 166B3F4E2119A840006CD35C /* EMSSQLiteHelperTests.m in Sources */, 8A602295219C6EF4000080FC /* EMSSQLStatementFactoryTests.m in Sources */, + 5DF6FD1D2C6F39710038790E /* EMSTestColumnInfoMapper.m in Sources */, 166B3F562119A840006CD35C /* MEIAMButtonClickedTests.m in Sources */, 8AFCA45221393D640052FEFA /* EMSPredictInternalTests.m in Sources */, 166B3F572119A840006CD35C /* MEIAMCloseTests.m in Sources */, @@ -3839,6 +3848,7 @@ 32C6FE0A6DE8D6BA043FAFEC /* EMSDeviceInfoV3ClientInternalTests.m in Sources */, 32C6FD6BDF2339C16189B100 /* EMSDeviceInfoClientV3InternalIntegrationTests.m in Sources */, 32C6F4CD0CC8E3A6770013C1 /* EMSConfigTests.m in Sources */, + 5DF6FD1A2C6F392C0038790E /* EMSTestColumnInfo.m in Sources */, 32C6FA1F0B2342FB70614F36 /* BuilderTests.m in Sources */, 32C6FD891660AA75348DBA5C /* MEExperimentalTests.m in Sources */, 32C6F17935310941C4279011 /* FakeFlipperFeature.m in Sources */, @@ -3955,6 +3965,7 @@ 8A6BEA6E23E86B480066B13C /* EMSQueryOldestRowSpecification.m in Sources */, 8A6BEBA623E86B490066B13C /* EMSConfigInternal.m in Sources */, 8A6BEB3423E86B490066B13C /* EMSLoggingMobileEngageInternal.m in Sources */, + 5D3E118A2C77265B00D26BB1 /* EMSCompletionBlockProvider.m in Sources */, 8A6BEABD23E86B480066B13C /* EMSMethodNotAllowed.m in Sources */, 8A6BEA8723E86B480066B13C /* EMSTimestampProvider.m in Sources */, 8A6BEB6223E86B490066B13C /* MEIAMButtonClicked.m in Sources */, @@ -4026,8 +4037,9 @@ 8A6BEB9023E86B490066B13C /* EMSCompletionProxyFactory.m in Sources */, 8A6BEB8523E86B490066B13C /* EMSNotificationService+Attachment.m in Sources */, 8A6BEB6F23E86B490066B13C /* EMSSceneProvider.m in Sources */, - 8A6BEA8823E86B480066B13C /* EMSCompletionBlockProvider.m in Sources */, + 8A6BEA8823E86B480066B13C /* EMSCompletionProvider.m in Sources */, 8A04DF332668C62F00FC4862 /* EMSAppEventActionModel.m in Sources */, + 5D3394D52C50E83A00E617E1 /* NSString+EMSCore.m in Sources */, 8A6BEA6223E86B480066B13C /* EMSCountMapper.m in Sources */, 8A6BEB2823E86B490066B13C /* EMSDeviceInfo+MEClientPayload.m in Sources */, 8A6BEADA23E86B480066B13C /* EMSScheduler.m in Sources */, diff --git a/Sources/Core/Batch/EMSBatchingShardTrigger.m b/Sources/Core/Batch/EMSBatchingShardTrigger.m index b7cff273..147d6afb 100644 --- a/Sources/Core/Batch/EMSBatchingShardTrigger.m +++ b/Sources/Core/Batch/EMSBatchingShardTrigger.m @@ -79,4 +79,4 @@ - (void)trigger { } } -@end \ No newline at end of file +@end diff --git a/Sources/Core/Categories/NSString+EMSCore.m b/Sources/Core/Categories/NSString+EMSCore.m new file mode 100644 index 00000000..834eb0cf --- /dev/null +++ b/Sources/Core/Categories/NSString+EMSCore.m @@ -0,0 +1,15 @@ +//// +// +// Copyright © 2024 Emarsys-Technologies Kft. All rights reserved. +// + +#import "NSString+EMSCore.h" + +@implementation NSString (EMSCore) + +- (NSString *)percentEncode { + NSCharacterSet *allowedCharacters = [[NSCharacterSet characterSetWithCharactersInString:@"\"`;/?:^%#@&=$+{}<>,|\\ !'()*[]"] invertedSet]; + return [self stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; +} + +@end diff --git a/Sources/Core/Categories/NSURL+EMSCore.m b/Sources/Core/Categories/NSURL+EMSCore.m index d2f07273..d5ec49c4 100644 --- a/Sources/Core/Categories/NSURL+EMSCore.m +++ b/Sources/Core/Categories/NSURL+EMSCore.m @@ -2,6 +2,7 @@ // Copyright (c) 2018 Emarsys. All rights reserved. // #import "NSURL+EMSCore.h" +#import "NSString+EMSCore.h" @implementation NSURL (EMSCore) @@ -13,17 +14,14 @@ + (NSURL *)urlWithBaseUrl:(NSString *)urlString NSParameterAssert(url.scheme); NSParameterAssert(url.host); - NSCharacterSet *allowedCharacters = [[NSCharacterSet characterSetWithCharactersInString:@"\"`;/?:^%#@&=$+{}<>,|\\ !'()*[]"] invertedSet]; NSMutableString *fullUrl = [NSMutableString stringWithFormat:@"%@?", urlString]; [queryParameters enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) { [fullUrl appendFormat:@"%@=%@&", - [[NSString stringWithFormat:@"%@", - key] stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters], - [[NSString stringWithFormat:@"%@", - value] stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]]; + [[NSString stringWithFormat:@"%@", key] percentEncode], + [[NSString stringWithFormat:@"%@", value] percentEncode]]; }]; [fullUrl deleteCharactersInRange:NSMakeRange(fullUrl.length - 1, 1)]; return [NSURL URLWithString:fullUrl]; } -@end \ No newline at end of file +@end diff --git a/Sources/Core/Crypto/EMSCrypto.m b/Sources/Core/Crypto/EMSCrypto.m index 504e9156..79a4a2d1 100644 --- a/Sources/Core/Crypto/EMSCrypto.m +++ b/Sources/Core/Crypto/EMSCrypto.m @@ -76,6 +76,9 @@ - (BOOL)verifyContent:(NSData *)content EMSLog(logEntry, LogLevelDebug); } } + if (publicKey) { + CFRelease(publicKey); + } return result; } diff --git a/Sources/Core/Database/EMSRequestModelMapper.m b/Sources/Core/Database/EMSRequestModelMapper.m index ff5ade0b..a6e6a264 100644 --- a/Sources/Core/Database/EMSRequestModelMapper.m +++ b/Sources/Core/Database/EMSRequestModelMapper.m @@ -6,6 +6,9 @@ #import "EMSRequestModel.h" #import "NSDictionary+EMSCore.h" #import "EMSSchemaContract.h" +#import "EMSMacros.h" +#import "EMSCrashLog.h" +#import "EMSStatusLog.h" @interface EMSRequestModelMapper () @@ -14,6 +17,7 @@ - (NSData *)dataFromStatement:(sqlite3_stmt *)statement - (BOOL)isNotNull:(sqlite3_stmt *)statement atIndex:(int)index; +- (nullable NSString *)mapToString:(const unsigned char *)utf8String; @end @@ -21,9 +25,9 @@ @implementation EMSRequestModelMapper - (id)modelFromStatement:(sqlite3_stmt *)statement { - NSString *requestId = [NSString stringWithUTF8String:(const char *) sqlite3_column_text(statement, 0)]; - NSString *method = [NSString stringWithUTF8String:(const char *) sqlite3_column_text(statement, 1)]; - NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:(const char *) sqlite3_column_text(statement, 2)]]; + NSString *requestId = [self mapToString:sqlite3_column_text(statement, 0)]; + NSString *method = [self mapToString:sqlite3_column_text(statement, 1)]; + NSURL *url = [NSURL URLWithString:[self mapToString:sqlite3_column_text(statement, 2)]]; NSDictionary *headers; if ([self isNotNull:statement atIndex:3]) { headers = [NSDictionary dictionaryWithData:[self dataFromStatement:statement @@ -36,6 +40,19 @@ - (id)modelFromStatement:(sqlite3_stmt *)statement { } NSDate *timestamp = [NSDate dateWithTimeIntervalSince1970:sqlite3_column_double(statement, 5)]; NSTimeInterval expiry = sqlite3_column_double(statement, 6); + if (!requestId || !method || !url || !timestamp || !expiry) { + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"requestId"] = requestId; + parameters[@"method"] = method; + parameters[@"url"] = url; + parameters[@"timestamp"] = [timestamp description]; + parameters[@"expiry"] = @(expiry); + EMSStatusLog *logEntry = [[EMSStatusLog alloc] initWithClass:[self class] + sel:_cmd + parameters:[NSDictionary dictionaryWithDictionary:parameters] + status:nil]; + EMSLog(logEntry, LogLevelError); + } return [[EMSRequestModel alloc] initWithRequestId:requestId timestamp:timestamp expiry:expiry @@ -75,6 +92,14 @@ - (BOOL)isNotNull:(sqlite3_stmt *)statement return sqlite3_column_type(statement, index) != SQLITE_NULL; } +- (nullable NSString *)mapToString:(const unsigned char *)utf8String { + NSString *result = nil; + if (utf8String) { + result = [NSString stringWithUTF8String:(char *)utf8String]; + } + return result; +} + - (NSString *)tableName { return REQUEST_TABLE_NAME; } diff --git a/Sources/Core/Database/EMSSQLiteHelper.m b/Sources/Core/Database/EMSSQLiteHelper.m index 24d79aa4..06477998 100644 --- a/Sources/Core/Database/EMSSQLiteHelper.m +++ b/Sources/Core/Database/EMSSQLiteHelper.m @@ -8,81 +8,114 @@ #import "EMSSQLStatementFactory.h" #import "EMSDBTriggerProtocol.h" #import "EMSSQLiteHelperSchemaHandlerProtocol.h" +#import "EMSStatusLog.h" +#import "EMSMacros.h" + +typedef BOOL (^EMSSQLLiteTransactionBlock)(void); + +const char *kBeginTransactionSQL = "BEGIN TRANSACTION;"; +const char *kCommitTransactionSQL = "COMMIT;"; +const char *kRollbackTransactionSQL = "ROLLBACK;"; @interface EMSSQLiteHelper () @property(nonatomic, assign) sqlite3 *db; @property(nonatomic, strong) NSString *dbPath; @property(nonatomic, strong) NSMutableDictionary *triggers; +@property(nonatomic, strong) NSOperationQueue *operationQueue; + +- (BOOL)executeTransaction:(EMSSQLLiteTransactionBlock)transactionBlock; +- (void)beginTransaction; +- (void)commitTransaction; +- (void)rollbackTransaction; +- (void)logWithSel:(SEL)sel + sqlResult:(int)sqlResult + error:(nullable NSString *)error + sql:(NSString *)sql; @end @implementation EMSSQLiteHelper - (instancetype)initWithDatabasePath:(NSString *)path - schemaDelegate:(id )schemaDelegate { + schemaDelegate:(id )schemaDelegate + operationQueue:(NSOperationQueue *)operationQueue { if (self = [super init]) { _dbPath = path; _schemaHandler = schemaDelegate; _triggers = [NSMutableDictionary new]; + _operationQueue = operationQueue; } return self; } - (instancetype)initWithSqlite3Db:(sqlite3 *)db - schemaDelegate:(id )schemaDelegate { + schemaDelegate:(id )schemaDelegate + operationQueue:(NSOperationQueue *)operationQueue { if (self = [super init]) { _db = db; _schemaHandler = schemaDelegate; _triggers = [NSMutableDictionary new]; + _operationQueue = operationQueue; } return self; } - (int)version { NSParameterAssert(_db); - int version = 0; - sqlite3_stmt *statement; - int result = sqlite3_prepare_v2(_db, [@"PRAGMA user_version;" UTF8String], -1, &statement, nil); - if (result == SQLITE_OK) { - - int step = sqlite3_step(statement); - if (step == SQLITE_ROW) { - version = sqlite3_column_int(statement, 0); + __block int version = 0; + __weak typeof(self) weakSelf = self; + [weakSelf executeTransaction:^BOOL{ + sqlite3_stmt *statement; + int result = sqlite3_prepare_v2(weakSelf.db, [@"PRAGMA user_version;" UTF8String], -1, &statement, nil); + if (result == SQLITE_OK) { + int step = sqlite3_step(statement); + if (step == SQLITE_ROW) { + version = sqlite3_column_int(statement, 0); + } } sqlite3_finalize(statement); - } + return version > 0; + }]; return version; } - (void)open { - if (sqlite3_open([self.dbPath UTF8String], &_db) == SQLITE_OK) { - - int version = [self version]; - if (version == 0) { - [self.schemaHandler onCreateWithDbHelper:self]; - } else { - int newVersion = [self.schemaHandler schemaVersion]; - if (version < newVersion) { - [self.schemaHandler onUpgradeWithDbHelper:self - oldVersion:version - newVersion:newVersion]; + __weak typeof(self) weakSelf = self; + [self.operationQueue addOperationWithBlock:^{ + sqlite3_initialize(); + int sqlResult = sqlite3_open_v2([weakSelf.dbPath UTF8String], &self->_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); + if (sqlResult == SQLITE_OK) { + int version = [weakSelf version]; + if (version == 0) { + [weakSelf.schemaHandler onCreateWithDbHelper:weakSelf]; + } else { + int newVersion = [weakSelf.schemaHandler schemaVersion]; + if (version < newVersion) { + [self.schemaHandler onUpgradeWithDbHelper:weakSelf + oldVersion:version + newVersion:newVersion]; + } } } - } + }]; + [self.operationQueue waitUntilAllOperationsAreFinished]; } - (void)close { - sqlite3_close(_db); - _db = nil; + __weak typeof(self) weakSelf = self; + [self.operationQueue addOperationWithBlock:^{ + sqlite3_close_v2(weakSelf.db); + sqlite3_shutdown(); + }]; + [self.operationQueue waitUntilAllOperationsAreFinished]; } - (void)registerTriggerWithTableName:(NSString *)tableName triggerType:(EMSDBTriggerType *)triggerType triggerEvent:(EMSDBTriggerEvent *)triggerEvent trigger:(id )trigger { - EMSDBTriggerKey *triggerKey = [[EMSDBTriggerKey alloc] initWithTableName:tableName withEvent:triggerEvent withType:triggerType]; @@ -95,58 +128,87 @@ - (void)registerTriggerWithTableName:(NSString *)tableName } - (BOOL)executeCommand:(NSString *)command { - sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_db, [command UTF8String], -1, &statement, nil) == SQLITE_OK) { - int value = sqlite3_step(statement); - sqlite3_finalize(statement); - return value == SQLITE_ROW || value == SQLITE_DONE; - } - return NO; -} - -- (BOOL)execute:(NSString *)command - withBindBlock:(BindBlock)bindBlock { - sqlite3_stmt *statement; - int i = sqlite3_prepare_v2(_db, [command UTF8String], -1, &statement, nil); - if (i == SQLITE_OK) { - bindBlock(statement); - int value = sqlite3_step(statement); - sqlite3_finalize(statement); - return value == SQLITE_ROW || value == SQLITE_DONE; - } - return NO; + __weak typeof(self) weakSelf = self; + return [self executeTransaction:^BOOL{ + BOOL result = YES; + char *utf8Error; + int sqlResult = sqlite3_exec(weakSelf.db, [command UTF8String], NULL, NULL, &utf8Error); + if (sqlResult != SQLITE_OK) { + result = NO; + NSString *error = nil; + if (error != NULL) { + error = [NSString stringWithUTF8String:utf8Error]; + } + [self logWithSel:_cmd + sqlResult:sqlResult + error:error + sql:[NSString stringWithUTF8String:kCommitTransactionSQL]]; + } + return result; + }]; } - (BOOL)removeFromTable:(NSString *)tableName selection:(NSString *)where selectionArgs:(NSArray *)whereArgs { - NSString *sqlCommand; + NSString *sql; if (where == nil || [where isEqualToString:@""]) { - sqlCommand = [NSString stringWithFormat:@"DELETE FROM %@", + sql = [NSString stringWithFormat:@"DELETE FROM %@", tableName]; } else { - sqlCommand = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@", + sql = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@", tableName, where]; } - [self runTriggerWithTableName:tableName event:[EMSDBTriggerEvent deleteEvent] type:[EMSDBTriggerType beforeType]]; - - BOOL result = [self execute:sqlCommand - withBindBlock:^(sqlite3_stmt *statement) { - for (int i = 0; i < whereArgs.count; ++i) { - sqlite3_bind_text(statement, i + 1, [whereArgs[(NSUInteger) i] UTF8String], -1, SQLITE_TRANSIENT); - } - }]; + + __block BOOL success; + __weak typeof(self) weakSelf = self; + [self.operationQueue addOperationWithBlock:^{ + success = [weakSelf executeTransaction:^BOOL{ + BOOL result = YES; + sqlite3_stmt *statement; + int prepareResult = sqlite3_prepare_v2(weakSelf.db, [sql UTF8String], -1, &statement, NULL); + if (prepareResult == SQLITE_OK) { + if (whereArgs.count > 0) { + for (int i = 0; i < whereArgs.count; ++i) { + sqlite3_bind_text(statement, i + 1, [whereArgs[(NSUInteger) i] UTF8String], -1, SQLITE_TRANSIENT); + } + } + int stepResult = sqlite3_step(statement); + if (stepResult != SQLITE_DONE) { + result = NO; + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"sql"] = sql; + parameters[@"stepResult"] = @(stepResult); + EMSLog([[EMSStatusLog alloc] initWithClass:[weakSelf class] + sel:@selector(removeFromTable:selection:selectionArgs:) + parameters:parameters + status:nil], LogLevelError); + } + } else { + result = NO; + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"sql"] = sql; + parameters[@"prepareResult"] = @(prepareResult); + EMSLog([[EMSStatusLog alloc] initWithClass:[weakSelf class] + sel:@selector(removeFromTable:selection:selectionArgs:) + parameters:parameters + status:nil], LogLevelError); + } + sqlite3_finalize(statement); + return result; + }]; + }]; + [self.operationQueue waitUntilAllOperationsAreFinished]; [self runTriggerWithTableName:tableName event:[EMSDBTriggerEvent deleteEvent] type:[EMSDBTriggerType afterType]]; - - return result; + return success; } - (NSArray *)queryWithTable:(NSString *)tableName @@ -161,31 +223,95 @@ - (NSArray *)queryWithTable:(NSString *)tableName selection:selection orderBy:orderBy limit:limit]; - NSMutableArray *models = [NSMutableArray new]; - sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &statement, nil) == SQLITE_OK) { - if (selectionArgs && selectionArgs.count > 0) { - for (int i = 0; i < selectionArgs.count; ++i) { - sqlite3_bind_text(statement, i + 1, [selectionArgs[(NSUInteger) i] UTF8String], -1, SQLITE_TRANSIENT); + __block NSMutableArray *models = [NSMutableArray new]; + __weak typeof(self) weakSelf = self; + [self.operationQueue addOperationWithBlock:^{ + [weakSelf executeTransaction:^BOOL{ + BOOL result = YES; + sqlite3_stmt *statement; + int prepareResult = sqlite3_prepare_v2(weakSelf.db, [sql UTF8String], -1, &statement, NULL); + if (prepareResult == SQLITE_OK) { + if (selectionArgs && selectionArgs.count > 0) { + for (int i = 0; i < selectionArgs.count; ++i) { + sqlite3_bind_text(statement, i + 1, [selectionArgs[(NSUInteger) i] UTF8String], -1, SQLITE_TRANSIENT); + } + } + int stepResult; + do { + stepResult = sqlite3_step(statement); + if (stepResult == SQLITE_ROW) { + [models addObject:[mapper modelFromStatement:statement]]; + } else if (stepResult != SQLITE_DONE) { + result = NO; + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"sql"] = sql; + parameters[@"stepResult"] = @(stepResult); + EMSLog([[EMSStatusLog alloc] initWithClass:[weakSelf class] + sel:@selector(queryWithTable:selection:selectionArgs:orderBy:limit:mapper:) + parameters:parameters + status:nil], LogLevelError); + } + } while (stepResult == SQLITE_ROW); + } else { + result = NO; + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"sql"] = sql; + parameters[@"prepareResult"] = @(prepareResult); + EMSLog([[EMSStatusLog alloc] initWithClass:[weakSelf class] + sel:@selector(queryWithTable:selection:selectionArgs:orderBy:limit:mapper:) + parameters:parameters + status:nil], LogLevelError); } - } - while (sqlite3_step(statement) == SQLITE_ROW) { - [models addObject:[mapper modelFromStatement:statement]]; - } - sqlite3_finalize(statement); - } - + sqlite3_finalize(statement); + return result; + }]; + }]; + [self.operationQueue waitUntilAllOperationsAreFinished]; return [NSArray arrayWithArray:models]; } - (BOOL)insertModel:(id)model withQuery:(NSString *)insertSQL mapper:(id )mapper { - return [self execute:insertSQL - withBindBlock:^(sqlite3_stmt *statement) { - [mapper bindStatement:statement - fromModel:model]; - }]; + __block BOOL success; + __weak typeof(self) weakSelf = self; + [self.operationQueue addOperationWithBlock:^{ + success = [weakSelf executeTransaction:^BOOL{ + BOOL result = YES; + sqlite3_stmt *statement; + int prepareResult = sqlite3_prepare_v2(weakSelf.db, [insertSQL UTF8String], -1, &statement, NULL); + if (prepareResult == SQLITE_OK) { + [mapper bindStatement:statement + fromModel:model]; + int stepResult = sqlite3_step(statement); + if (stepResult != SQLITE_DONE) { + result = NO; + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"sql"] = insertSQL; + parameters[@"model"] = [model description]; + parameters[@"stepResult"] = @(stepResult); + EMSLog([[EMSStatusLog alloc] initWithClass:[weakSelf class] + sel:@selector(insertModel:withQuery:mapper:) + parameters:parameters + status:nil], LogLevelError); + } + } else { + result = NO; + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"sql"] = insertSQL; + parameters[@"model"] = [model description]; + parameters[@"prepareResult"] = @(prepareResult); + EMSLog([[EMSStatusLog alloc] initWithClass:[weakSelf class] + sel:@selector(insertModel:withQuery:mapper:) + parameters:parameters + status:nil], LogLevelError); + } + sqlite3_finalize(statement); + return result; + }]; + }]; + [self.operationQueue waitUntilAllOperationsAreFinished]; + return success; } - (BOOL)insertModel:(id)model @@ -208,16 +334,46 @@ - (BOOL)insertModel:(id)model - (NSArray *)executeQuery:(NSString *)query mapper:(id )mapper { - NSMutableArray *models = [NSMutableArray new]; - sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_db, [query UTF8String], -1, &statement, nil) == SQLITE_OK) { - while (sqlite3_step(statement) == SQLITE_ROW) { - [models addObject:[mapper modelFromStatement:statement]]; - } - sqlite3_finalize(statement); - return [NSArray arrayWithArray:models]; - } - return nil; + __block NSMutableArray *models = [NSMutableArray new]; + __weak typeof(self) weakSelf = self; + [self.operationQueue addOperationWithBlock:^{ + [weakSelf executeTransaction:^BOOL{ + BOOL result = YES; + sqlite3_stmt *statement; + int prepareResult = sqlite3_prepare_v2(weakSelf.db, [query UTF8String], -1, &statement, NULL); + if (prepareResult == SQLITE_OK) { + int stepResult; + do { + stepResult = sqlite3_step(statement); + if (stepResult == SQLITE_ROW) { + [models addObject:[mapper modelFromStatement:statement]]; + } else if (stepResult != SQLITE_DONE) { + result = NO; + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"sql"] = query; + parameters[@"stepResult"] = @(stepResult); + EMSLog([[EMSStatusLog alloc] initWithClass:[weakSelf class] + sel:@selector(executeQuery:mapper:) + parameters:parameters + status:nil], LogLevelError); + } + } while (stepResult == SQLITE_ROW); + } else { + result = NO; + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"sql"] = query; + parameters[@"prepareResult"] = @(prepareResult); + EMSLog([[EMSStatusLog alloc] initWithClass:[weakSelf class] + sel:@selector(executeQuery:mapper:) + parameters:parameters + status:nil], LogLevelError); + } + sqlite3_finalize(statement); + return result; + }]; + }]; + [self.operationQueue waitUntilAllOperationsAreFinished]; + return [NSArray arrayWithArray:models]; } - (NSString *)createInsertSql:(id )mapper { @@ -252,4 +408,74 @@ - (NSDictionary *)registeredTriggers { return [NSDictionary dictionaryWithDictionary:self.triggers]; } +- (BOOL)executeTransaction:(EMSSQLLiteTransactionBlock)transactionBlock { + [self beginTransaction]; + BOOL success = transactionBlock(); + if (success) { + [self commitTransaction]; + } else { + [self rollbackTransaction]; + } + return success; +} + +- (void)beginTransaction { + char *utf8Error; + int execResult = sqlite3_exec(self.db, kBeginTransactionSQL, NULL, NULL, &utf8Error); + if (execResult != SQLITE_OK) { + NSString *error = nil; + if (error != NULL) { + error = [NSString stringWithUTF8String:utf8Error]; + } + [self logWithSel:_cmd + sqlResult:execResult + error:error + sql:[NSString stringWithUTF8String:kBeginTransactionSQL]]; + } +} + +- (void)commitTransaction { + char *utf8Error; + int execResult = sqlite3_exec(self.db, kCommitTransactionSQL, NULL, NULL, &utf8Error); + if (execResult != SQLITE_OK) { + NSString *error = nil; + if (error != NULL) { + error = [NSString stringWithUTF8String:utf8Error]; + } + [self logWithSel:_cmd + sqlResult:execResult + error:error + sql:[NSString stringWithUTF8String:kCommitTransactionSQL]]; + } +} + +- (void)rollbackTransaction { + char *utf8Error; + int execResult = sqlite3_exec(self.db, kRollbackTransactionSQL, NULL, NULL, &utf8Error); + if (execResult != SQLITE_OK) { + NSString *error = nil; + if (error != NULL) { + error = [NSString stringWithUTF8String:utf8Error]; + } + [self logWithSel:_cmd + sqlResult:execResult + error:error + sql:[NSString stringWithUTF8String:kRollbackTransactionSQL]]; + } +} + +- (void)logWithSel:(SEL)sel + sqlResult:(int)sqlResult + error:(nullable NSString *)error + sql:(NSString *)sql { + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"sql"] = sql; + parameters[@"sqlResult"] = @(sqlResult); + parameters[@"error"] = error; + EMSLog([[EMSStatusLog alloc] initWithClass:[self class] + sel:sel + parameters:parameters + status:nil], LogLevelError); +} + @end diff --git a/Sources/Core/Database/EMSShardMapper.m b/Sources/Core/Database/EMSShardMapper.m index a3b69d6b..a7e4925b 100644 --- a/Sources/Core/Database/EMSShardMapper.m +++ b/Sources/Core/Database/EMSShardMapper.m @@ -6,6 +6,8 @@ #import "NSDictionary+EMSCore.h" #import "EMSShard.h" #import "EMSSchemaContract.h" +#import "EMSMacros.h" +#import "EMSStatusLog.h" @interface EMSShardMapper () @@ -15,14 +17,16 @@ - (NSData *)dataFromStatement:(sqlite3_stmt *)statement - (BOOL)isNotNull:(sqlite3_stmt *)statement atIndex:(int)index; +- (nullable NSString *)mapToString:(const unsigned char *)utf8String; + @end @implementation EMSShardMapper - (id)modelFromStatement:(sqlite3_stmt *)statement { - NSString *shardId = [NSString stringWithUTF8String:(const char *) sqlite3_column_text(statement, 0)]; - NSString *type = [NSString stringWithUTF8String:(const char *) sqlite3_column_text(statement, 1)]; + NSString *shardId = [self mapToString:sqlite3_column_text(statement, 0)]; + NSString *type = [self mapToString:sqlite3_column_text(statement, 1)]; NSDictionary *data; if ([self isNotNull:statement atIndex:2]) { data = [NSDictionary dictionaryWithData:[self dataFromStatement:statement @@ -33,6 +37,19 @@ - (id)modelFromStatement:(sqlite3_stmt *)statement { } NSDate *timestamp = [NSDate dateWithTimeIntervalSince1970:sqlite3_column_double(statement, 3)]; NSTimeInterval ttl = sqlite3_column_double(statement, 4); + if (!shardId || !type || !data || !timestamp || !ttl) { + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"shardId"] = shardId; + parameters[@"type"] = type; + parameters[@"data"] = data; + parameters[@"timestamp"] = [timestamp description]; + parameters[@"expiry"] = @(ttl); + EMSStatusLog *logEntry = [[EMSStatusLog alloc] initWithClass:[self class] + sel:_cmd + parameters:[NSDictionary dictionaryWithDictionary:parameters] + status:nil]; + EMSLog(logEntry, LogLevelError); + } return [[EMSShard alloc] initWithShardId:shardId type:type data:data @@ -75,5 +92,12 @@ - (NSUInteger)fieldCount { return 5; } +- (nullable NSString *)mapToString:(const unsigned char *)utf8String { + NSString *result = nil; + if (utf8String) { + result = [NSString stringWithUTF8String:(char *)utf8String]; + } + return result; +} @end diff --git a/Sources/Core/Models/EMSRequestModelBuilder.m b/Sources/Core/Models/EMSRequestModelBuilder.m index f81f6039..cca67ab7 100644 --- a/Sources/Core/Models/EMSRequestModelBuilder.m +++ b/Sources/Core/Models/EMSRequestModelBuilder.m @@ -5,6 +5,8 @@ #import "EMSRequestModelBuilder.h" #import "EMSTimestampProvider.h" #import "EMSUUIDProvider.h" +#import "EMSMacros.h" +#import "EMSStatusLog.h" @implementation EMSRequestModelBuilder @@ -43,6 +45,13 @@ - (EMSRequestModelBuilder *)setUrl:(NSString *)url { NSURL *urlToCheck = [NSURL URLWithString:url]; if (urlToCheck && urlToCheck.scheme && urlToCheck.host) { _requestUrl = urlToCheck; + } else { + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"url"] = url; + EMSLog([[EMSStatusLog alloc] initWithClass:[self class] + sel:_cmd + parameters:parameters + status:nil], LogLevelError); } return self; } @@ -63,6 +72,14 @@ - (EMSRequestModelBuilder *)setUrl:(NSString *)url } [components setQueryItems:queryItems]; _requestUrl = [components URL]; + } else { + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"url"] = url; + parameters[@"queryParameters"] = queryParameters; + EMSLog([[EMSStatusLog alloc] initWithClass:[self class] + sel:_cmd + parameters:parameters + status:nil], LogLevelError); } return self; } @@ -87,4 +104,4 @@ - (EMSRequestModelBuilder *)setExtras:(NSDictionary *)ex return self; } -@end \ No newline at end of file +@end diff --git a/Sources/Core/Networking/EMSCoreCompletionHandlerMiddleware.m b/Sources/Core/Networking/EMSCoreCompletionHandlerMiddleware.m index 548b6868..324b234b 100644 --- a/Sources/Core/Networking/EMSCoreCompletionHandlerMiddleware.m +++ b/Sources/Core/Networking/EMSCoreCompletionHandlerMiddleware.m @@ -33,7 +33,7 @@ - (EMSRESTClientCompletionBlock)completionBlock { __weak typeof(self) weakSelf = self; return ^(EMSRequestModel *requestModel, EMSResponseModel *responseModel, NSError *error) { - [weakSelf.operationQueue addOperationWithBlock:^{ + [weakSelf.operationQueue addOperationWithBlock:^{ if ([weakSelf shouldContinueWorkerWithResponseModel:responseModel error:error]) { [weakSelf.requestRepository remove:[[EMSFilterByValuesSpecification alloc] initWithValues:requestModel.requestIds @@ -95,4 +95,4 @@ - (NSUInteger)hash { return hash; } -@end \ No newline at end of file +@end diff --git a/Sources/Core/Providers/EMSCompletionBlockProvider.m b/Sources/Core/Providers/EMSCompletionBlockProvider.m index 50254852..89f568c1 100644 --- a/Sources/Core/Providers/EMSCompletionBlockProvider.m +++ b/Sources/Core/Providers/EMSCompletionBlockProvider.m @@ -1,9 +1,13 @@ +//// // -// Copyright (c) 2019 Emarsys. All rights reserved. +// Copyright © 2024 Emarsys-Technologies Kft. All rights reserved. // + #import "EMSCompletionBlockProvider.h" +#import +#import "EMSBlocks.h" -@interface EMSCompletionBlockProvider () +@interface EMSCompletionBlockProvider() @property(nonatomic, strong) NSOperationQueue *operationQueue; @@ -12,19 +16,19 @@ @interface EMSCompletionBlockProvider () @implementation EMSCompletionBlockProvider - (instancetype)initWithOperationQueue:(NSOperationQueue *)operationQueue { - NSParameterAssert(operationQueue); - if (self = [super init]) { _operationQueue = operationQueue; } return self; } -- (EMSCompletion)provideCompletion:(EMSCompletion)completionBlock { +- (EMSCompletionBlock)provideCompletionBlock:(EMSCompletionBlock)completionBlock { __weak typeof(self) weakSelf = self; - return ^{ - [weakSelf.operationQueue addOperationWithBlock:completionBlock]; + return ^(NSError * _Nullable error) { + [weakSelf.operationQueue addOperationWithBlock:^{ + completionBlock(error); + }]; }; } -@end \ No newline at end of file +@end diff --git a/Sources/Core/Providers/EMSCompletionProvider.m b/Sources/Core/Providers/EMSCompletionProvider.m new file mode 100644 index 00000000..825a6cc2 --- /dev/null +++ b/Sources/Core/Providers/EMSCompletionProvider.m @@ -0,0 +1,30 @@ +// +// Copyright (c) 2019 Emarsys. All rights reserved. +// +#import "EMSCompletionProvider.h" + +@interface EMSCompletionProvider () + +@property(nonatomic, strong) NSOperationQueue *operationQueue; + +@end + +@implementation EMSCompletionProvider + +- (instancetype)initWithOperationQueue:(NSOperationQueue *)operationQueue { + NSParameterAssert(operationQueue); + + if (self = [super init]) { + _operationQueue = operationQueue; + } + return self; +} + +- (EMSCompletion)provideCompletion:(EMSCompletion)completionBlock { + __weak typeof(self) weakSelf = self; + return ^{ + [weakSelf.operationQueue addOperationWithBlock:completionBlock]; + }; +} + +@end diff --git a/Sources/Core/Repository/RequestModel/EMSRequestModelRepository.m b/Sources/Core/Repository/RequestModel/EMSRequestModelRepository.m index 754fe940..64690fbe 100644 --- a/Sources/Core/Repository/RequestModel/EMSRequestModelRepository.m +++ b/Sources/Core/Repository/RequestModel/EMSRequestModelRepository.m @@ -28,16 +28,6 @@ - (instancetype)initWithDbHelper:(id )sqliteHelper if (self = [super init]) { _dbHelper = sqliteHelper; _mapper = [EMSRequestModelMapper new]; - _dbHelper = sqliteHelper; - [_dbHelper open]; - - __weak typeof(self) weakSelf = self; - [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification - object:nil - queue:operationQueue - usingBlock:^(NSNotification *note) { - [weakSelf.dbHelper close]; - }]; } return self; } diff --git a/Sources/Core/Repository/Shard/EMSShardRepository.m b/Sources/Core/Repository/Shard/EMSShardRepository.m index b24afc8d..616d894f 100644 --- a/Sources/Core/Repository/Shard/EMSShardRepository.m +++ b/Sources/Core/Repository/Shard/EMSShardRepository.m @@ -20,8 +20,6 @@ - (instancetype)initWithDbHelper:(EMSSQLiteHelper *)sqliteHelper { if (self = [super init]) { _dbHelper = sqliteHelper; _mapper = [EMSShardMapper new]; - _dbHelper = sqliteHelper; - [_dbHelper open]; } return self; } @@ -57,4 +55,4 @@ - (BOOL)isEmpty { return [count integerValue] == 0; } -@end \ No newline at end of file +@end diff --git a/Sources/Core/Store/EMSStorage.m b/Sources/Core/Store/EMSStorage.m index 9dda576a..1c9f48d7 100644 --- a/Sources/Core/Store/EMSStorage.m +++ b/Sources/Core/Store/EMSStorage.m @@ -11,17 +11,44 @@ @interface EMSStorage () @property(nonatomic, strong) NSUserDefaults *fallbackUserDefaults; @property(nonatomic, strong) NSOperationQueue *operationQueue; +- (NSMutableDictionary *)createQueryWithKey:(NSString *)key + accessGroup:(nullable NSString *)accessGroup; + +- (nullable NSData *)readValueForKey:(NSString *)key + withAccessGroup:(nullable NSString *)accessGroup; + +- (OSStatus)updateValue:(NSData *)value + forKey:(NSString *)key + withAccessGroup:(nullable NSString *)accessGroup; + +- (OSStatus)deleteValueForKey:(NSString *)key + withAccessGroup:(nullable NSString *)accessGroup; + +- (NSMutableDictionary *)appendAccessModifierToQuery:(NSMutableDictionary *)query; + +- (NSMutableDictionary *)appendResultAttributesToQuery:(NSMutableDictionary *)query; + +- (NSMutableDictionary *)appendValueToQuery:(NSMutableDictionary *)query + value:(NSData *)value; + +- (OSStatus)createValue:(NSData *)value + forKey:(NSString *)key + withAccessGroup:(nullable NSString *)accessGroup; + @end @implementation EMSStorage - (instancetype)initWithSuiteNames:(NSArray *)suiteNames - accessGroup:(nullable NSString *)accessGroup { + accessGroup:(nullable NSString *)accessGroup + operationQueue:(nonnull NSOperationQueue *)operationQueue { NSParameterAssert(suiteNames); + NSParameterAssert(operationQueue); if (self = [super init]) { + _operationQueue = operationQueue; NSMutableArray *mutableUserDefaults = [NSMutableArray new]; for (NSString *suiteName in suiteNames) { - + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:suiteName]; [mutableUserDefaults addObject:userDefaults]; } @@ -40,10 +67,12 @@ - (void)setData:(nullable NSData *)data if (status != errSecSuccess) { [self.fallbackUserDefaults setObject:data forKey:key]; - + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; - parameters[@"data"] = [[NSString alloc] initWithData:data - encoding:NSUTF8StringEncoding]; + if (data) { + parameters[@"data"] = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + } parameters[@"key"] = key; NSMutableDictionary *statusDict = [NSMutableDictionary dictionary]; statusDict[@"osStatus"] = @(status); @@ -51,7 +80,7 @@ - (void)setData:(nullable NSData *)data sel:_cmd parameters:[NSDictionary dictionaryWithDictionary:parameters] status:[NSDictionary dictionaryWithDictionary:statusDict]]; - EMSLog(logEntry, LogLevelDebug); + EMSLog(logEntry, LogLevelError); } } @@ -59,32 +88,26 @@ - (OSStatus)setData:(nullable NSData *)data forKey:(NSString *)key accessGroup:(nullable NSString *)accessGroup { NSParameterAssert(key); - NSMutableDictionary *mutableQuery = [NSMutableDictionary new]; - mutableQuery[(id) kSecAttrAccount] = key; - mutableQuery[(id) kSecClass] = (id) kSecClassGenericPassword; - mutableQuery[(id) kSecAttrAccessible] = (id) kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; - mutableQuery[(id) kSecValueData] = data; - - mutableQuery[(id) kSecAttrAccessGroup] = accessGroup; - - NSDictionary *query = [NSDictionary dictionaryWithDictionary:mutableQuery]; - - OSStatus status = [self storeInSecureStorageWithQuery:query]; - if (status == errSecDuplicateItem) { - NSMutableDictionary *mutableDeleteQuery = [query mutableCopy]; - [mutableDeleteQuery removeObjectForKey:(id) kSecAttrAccessible]; - NSDictionary *deleteQuery = [NSDictionary dictionaryWithDictionary:mutableDeleteQuery]; - - SecItemDelete((__bridge CFDictionaryRef) deleteQuery); - status = [self storeInSecureStorageWithQuery:query]; + OSStatus status = 0; + if (data) { + NSData *existingValue = [self readValueForKey:key + withAccessGroup:accessGroup]; + if (existingValue) { + status = [self updateValue:data + forKey:key + withAccessGroup:accessGroup]; + } else { + status = [self createValue:data + forKey:key + withAccessGroup:accessGroup]; + } + } else { + status = [self deleteValueForKey:key + withAccessGroup:accessGroup]; } return status; } -- (OSStatus)storeInSecureStorageWithQuery:(NSDictionary *)query { - return SecItemAdd((__bridge CFDictionaryRef) query, NULL); -} - - (void)setString:(nullable NSString *)string forKey:(NSString *)key { NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; @@ -140,7 +163,7 @@ - (void)setDictionary:(nullable NSDictionary *)dictionary status:statusDictionary]; EMSLog(log, LogLevelDebug); } - + [self setData:data forKey:key]; } @@ -151,7 +174,7 @@ - (nullable NSData *)dataForKey:(NSString *)key { if (!result) { for (NSUserDefaults *userDefaults in self.userDefaultsArray) { id userDefaultsValue = [userDefaults objectForKey:key]; - + if ([userDefaultsValue isKindOfClass:[NSString class]]) { userDefaultsValue = [userDefaultsValue dataUsingEncoding:NSUTF8StringEncoding]; } else if ([userDefaultsValue isKindOfClass:[NSNumber class]]) { @@ -206,54 +229,20 @@ - (nullable NSData *)dataForKey:(NSString *)key { } } } - + return result; } - (nullable NSData *)dataForKey:(NSString *)key accessGroup:(nullable NSString *)accessGroup { NSParameterAssert(key); - NSData *result; - NSMutableDictionary *mutableQuery = [NSMutableDictionary new]; - mutableQuery[(id) kSecClass] = (id) kSecClassGenericPassword; - mutableQuery[(id) kSecAttrAccount] = key; - mutableQuery[(id) kSecReturnData] = (id) kCFBooleanTrue; - mutableQuery[(id) kSecReturnAttributes] = (id) kCFBooleanTrue; - mutableQuery[(id) kSecAttrAccessible] = (id) kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; - - mutableQuery[(id) kSecAttrAccessGroup] = accessGroup; - - NSDictionary *query = [NSDictionary dictionaryWithDictionary:mutableQuery]; - - CFTypeRef resultRef = NULL; - OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &resultRef); - if (status == errSecSuccess) { - NSDictionary *resultDict = (__bridge NSDictionary *) resultRef; - NSString *returnedAccessGroup = resultDict[(id) kSecAttrAccessGroup]; - if ((!accessGroup && ![returnedAccessGroup isEqual:self.accessGroup]) || (accessGroup && [accessGroup isEqual:returnedAccessGroup])) { - result = resultDict[(id) kSecValueData]; - } - } else if (status != errSecItemNotFound) { - NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; - parameters[@"key"] = key; - NSMutableDictionary *statusDict = [NSMutableDictionary dictionary]; - statusDict[@"osStatus"] = @(status); - EMSStatusLog *logEntry = [[EMSStatusLog alloc] initWithClass:[self class] - sel:_cmd - parameters:[NSDictionary dictionaryWithDictionary:parameters] - status:[NSDictionary dictionaryWithDictionary:statusDict]]; - EMSLog(logEntry, LogLevelDebug); - } - - if (resultRef) { - CFRelease(resultRef); - } - return result; + return [self readValueForKey:key + withAccessGroup:accessGroup]; } - (nullable NSString *)stringForKey:(NSString *)key { NSData *data = [self dataForKey:key]; - + return data ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : nil; } @@ -261,9 +250,9 @@ - (nullable NSString *)stringForKey:(NSString *)key { - (nullable NSNumber *)numberForKey:(NSString *)key { NSData *data = [self dataForKey:key]; NSError *error; - NSNumber *result = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSNumber class] - fromData:data - error:&error]; + NSNumber *result = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithArray:@[[NSNull class], [NSNumber class], [NSString class], [NSArray class], [NSDictionary class]]] + fromData:data + error:&error];; if (error) { NSMutableDictionary *parameterDictionary = [NSMutableDictionary new]; parameterDictionary[@"key"] = key; @@ -284,9 +273,9 @@ - (nullable NSNumber *)numberForKey:(NSString *)key { - (nullable NSDictionary *)dictionaryForKey:(NSString *)key { NSData *data = [self dataForKey:key]; NSError *error; - NSDictionary *result = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSDictionary class] - fromData:data - error:&error]; + NSDictionary *result = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithArray:@[[NSNull class], [NSNumber class], [NSString class], [NSArray class], [NSDictionary class]]] + fromData:data + error:&error];; if (error) { NSMutableDictionary *parameterDictionary = [NSMutableDictionary new]; parameterDictionary[@"key"] = key; @@ -336,4 +325,106 @@ - (nullable NSData *)sharedDataForKey:(NSString *)key { return result; } -@end \ No newline at end of file +- (NSMutableDictionary *)createQueryWithKey:(NSString *)key + accessGroup:(nullable NSString *)accessGroup { + NSMutableDictionary *mutableQuery = [NSMutableDictionary new]; + mutableQuery[(id) kSecClass] = (id) kSecClassGenericPassword; + mutableQuery[(id) kSecAttrAccount] = key; + mutableQuery[(id) kSecAttrAccessGroup] = accessGroup; + return mutableQuery; +} + +- (NSMutableDictionary *)appendAccessModifierToQuery:(NSMutableDictionary *)query { + query[(id) kSecAttrAccessible] = (id) kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; + return query; +} + +- (NSMutableDictionary *)appendResultAttributesToQuery:(NSMutableDictionary *)query { + query[(id) kSecReturnData] = (id) kCFBooleanTrue; + query[(id) kSecReturnAttributes] = (id) kCFBooleanTrue; + return query; +} + +- (NSMutableDictionary *)appendValueToQuery:(NSMutableDictionary *)query + value:(NSData *)value { + query[(id) kSecValueData] = value; + return query; +} + +- (OSStatus)createValue:(NSData *)value + forKey:(NSString *)key + withAccessGroup:(nullable NSString *)accessGroup { + __block OSStatus result; + __weak typeof(self) weakSelf = self; + [self.operationQueue addOperationWithBlock:^{ + NSMutableDictionary *mutableQuery = [weakSelf createQueryWithKey:key + accessGroup:accessGroup]; + mutableQuery = [weakSelf appendAccessModifierToQuery:mutableQuery]; + mutableQuery = [weakSelf appendValueToQuery:mutableQuery + value:value]; + NSDictionary *query = [NSDictionary dictionaryWithDictionary:mutableQuery]; + result = SecItemAdd((__bridge CFDictionaryRef) query, NULL); + }]; + [self.operationQueue waitUntilAllOperationsAreFinished]; + return result; +} + +- (nullable NSData *)readValueForKey:(NSString *)key + withAccessGroup:(nullable NSString *)accessGroup { + __block NSData *result = nil; + __weak typeof(self) weakSelf = self; + [self.operationQueue addOperationWithBlock:^{ + NSMutableDictionary *mutableQuery = [weakSelf createQueryWithKey:key + accessGroup:accessGroup]; + mutableQuery = [weakSelf appendResultAttributesToQuery:mutableQuery]; + NSDictionary *query = [NSDictionary dictionaryWithDictionary:mutableQuery]; + + CFTypeRef resultRef = NULL; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &resultRef); + if (status == errSecSuccess) { + NSDictionary *resultDict = (__bridge NSDictionary *) resultRef; + NSString *returnedAccessGroup = resultDict[(id) kSecAttrAccessGroup]; + if ((!accessGroup && ![returnedAccessGroup isEqual:accessGroup]) || (accessGroup && [accessGroup isEqual:returnedAccessGroup])) { + result = resultDict[(id) kSecValueData]; + } + } + if (resultRef) { + CFRelease(resultRef); + } + }]; + [weakSelf.operationQueue waitUntilAllOperationsAreFinished]; + return result; +} + +- (OSStatus)updateValue:(NSData *)value + forKey:(NSString *)key + withAccessGroup:(nullable NSString *)accessGroup { + __block OSStatus result; + __weak typeof(self) weakSelf = self; + [self.operationQueue addOperationWithBlock:^{ + NSDictionary *query = [weakSelf createQueryWithKey:key + accessGroup:accessGroup]; + NSMutableDictionary *attributesDictionary = [NSMutableDictionary dictionary]; + attributesDictionary = [weakSelf appendValueToQuery:attributesDictionary + value:value]; + result = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) attributesDictionary); + }]; + [self.operationQueue waitUntilAllOperationsAreFinished]; + return result; +} + +- (OSStatus)deleteValueForKey:(NSString *)key + withAccessGroup:(nullable NSString *)accessGroup { + __block OSStatus result; + __weak typeof(self) weakSelf = self; + [self.operationQueue addOperationWithBlock:^{ + NSMutableDictionary *mutableQuery = [weakSelf createQueryWithKey:key + accessGroup:accessGroup]; + NSDictionary *query = [NSDictionary dictionaryWithDictionary:mutableQuery]; + result = SecItemDelete((__bridge CFDictionaryRef) query); + }]; + [self.operationQueue waitUntilAllOperationsAreFinished]; + return result; +} + +@end diff --git a/Sources/EmarsysSDK/AppStart/EMSAppStartBlockProvider.m b/Sources/EmarsysSDK/AppStart/EMSAppStartBlockProvider.m index 9654e741..89f5f61e 100644 --- a/Sources/EmarsysSDK/AppStart/EMSAppStartBlockProvider.m +++ b/Sources/EmarsysSDK/AppStart/EMSAppStartBlockProvider.m @@ -12,6 +12,8 @@ #import "EMSGeofenceInternal.h" #import "EMSSdkStateLogger.h" #import "EMSStatusLog.h" +#import "EMSSQLiteHelper.h" +#import "EMSCompletionBlockProvider.h" @interface EMSAppStartBlockProvider () @@ -23,6 +25,8 @@ @interface EMSAppStartBlockProvider () @property(nonatomic, strong) EMSGeofenceInternal *geofenceInternal; @property(nonatomic, strong) EMSSdkStateLogger *sdkStateLogger; @property(nonatomic, strong) EMSLogger *logger; +@property(nonatomic, strong) EMSSQLiteHelper *dbHelper; +@property(nonatomic, strong) EMSCompletionBlockProvider *completionBlockProvider; @end @@ -35,7 +39,9 @@ - (instancetype)initWithRequestManager:(EMSRequestManager *)requestManager configInternal:(EMSConfigInternal *)configInternal geofenceInternal:(EMSGeofenceInternal *)geofenceInternal sdkStateLogger:(EMSSdkStateLogger *)sdkStateLogger - logger:(EMSLogger *)logger { + logger:(EMSLogger *)logger + dbHelper:(EMSSQLiteHelper *)dbHelper + completionBlockProvider:(EMSCompletionBlockProvider *)completionBlockProvider { NSParameterAssert(requestManager); NSParameterAssert(requestFactory); NSParameterAssert(requestContext); @@ -44,6 +50,8 @@ - (instancetype)initWithRequestManager:(EMSRequestManager *)requestManager NSParameterAssert(geofenceInternal); NSParameterAssert(sdkStateLogger); NSParameterAssert(logger); + NSParameterAssert(dbHelper); + NSParameterAssert(completionBlockProvider); if (self = [super init]) { _requestManager = requestManager; _requestFactory = requestFactory; @@ -53,6 +61,8 @@ - (instancetype)initWithRequestManager:(EMSRequestManager *)requestManager _geofenceInternal = geofenceInternal; _sdkStateLogger = sdkStateLogger; _logger = logger; + _dbHelper = dbHelper; + _completionBlockProvider = completionBlockProvider; } return self; } @@ -67,8 +77,7 @@ - (MEHandlerBlock)createAppStartEventBlock { eventAttributes:nil eventType:EventTypeInternal]; [weakSelf.requestManager submitRequestModel:requestModel - withCompletionBlock:^(NSError *error) { - }]; + withCompletionBlock:nil]; } }; } @@ -84,11 +93,11 @@ - (MEHandlerBlock)createRemoteConfigEventBlock { __weak typeof(self) weakSelf = self; return ^{ if (![@[@"", @"0", @"null", @"nil"] containsObject:[self.requestContext.applicationCode lowercaseString]]) { - [weakSelf.configInternal refreshConfigFromRemoteConfigWithCompletionBlock:^(NSError *error) { - if ([self.logger logLevel] == LogLevelTrace) { - [self.sdkStateLogger log]; + [weakSelf.configInternal refreshConfigFromRemoteConfigWithCompletionBlock:[weakSelf.completionBlockProvider provideCompletionBlock:^(NSError *error) { + if ([weakSelf.logger logLevel] == LogLevelTrace) { + [weakSelf.sdkStateLogger log]; } - }]; + }]]; } else { NSLog(@"ApplicationCode is incorrect: %@", self.requestContext.applicationCode); EMSStatusLog *logEntry = [[EMSStatusLog alloc] initWithClass:[self class] @@ -107,5 +116,11 @@ - (MEHandlerBlock)createFetchGeofenceEventBlock { }; } +- (MEHandlerBlock)createDbCloseEventBlock { + __weak typeof(self) weakSelf = self; + return ^{ + [weakSelf.dbHelper close]; + }; +} -@end \ No newline at end of file +@end diff --git a/Sources/EmarsysSDK/AppStart/EMSWrapperChecker.m b/Sources/EmarsysSDK/AppStart/EMSWrapperChecker.m index eb49dad6..50b475d7 100644 --- a/Sources/EmarsysSDK/AppStart/EMSWrapperChecker.m +++ b/Sources/EmarsysSDK/AppStart/EMSWrapperChecker.m @@ -4,45 +4,62 @@ #import "EMSWrapperChecker.h" #import "EMSDispatchWaiter.h" +#import "EMSStorageProtocol.h" @interface EMSWrapperChecker () @property(nonatomic, strong) NSOperationQueue *queue; @property(nonatomic, strong) EMSDispatchWaiter *waiter; @property(nonatomic, strong) NSString *innerWrapper; +@property(nonatomic, strong) idstorage; @end @implementation EMSWrapperChecker - (instancetype)initWithOperationQueue:(NSOperationQueue *)queue - waiter:(EMSDispatchWaiter *)waiter { + waiter:(EMSDispatchWaiter *)waiter + storage:(id)storage { NSParameterAssert(queue); NSParameterAssert(waiter); + NSParameterAssert(storage); if (self = [super init]) { + _storage = storage; _queue = queue; _waiter = waiter; + _innerWrapper = [self.storage stringForKey:kInnerWrapperKey]; } return self; } +- (void)setInnerWrapper:(NSString *)innerWrapper { + _innerWrapper = innerWrapper; + [self.storage setString:innerWrapper + forKey:kInnerWrapperKey]; +} + - (NSString *)wrapper { if (!self.innerWrapper) { self.innerWrapper = @"none"; [self.waiter enter]; __weak typeof(self) weakSelf = self; - [[NSNotificationCenter defaultCenter] addObserverForName:@"EmarsysSDKWrapperExist" - object:nil - queue:self.queue - usingBlock:^(NSNotification *note) { - weakSelf.innerWrapper = note.object; - [weakSelf.waiter exit]; - }]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"EmarsysSDKWrapperCheckerNotification" - object:nil]; + __weak __block id observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"EmarsysSDKWrapperExist" + object:nil + queue:nil + usingBlock:^(NSNotification *note) { + [[NSNotificationCenter defaultCenter] removeObserver:observer]; + [weakSelf.queue addOperationWithBlock:^{ + weakSelf.innerWrapper = note.object; + [weakSelf.waiter exit]; + }]; + }]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:@"EmarsysSDKWrapperCheckerNotification" + object:nil]; + }); [self.waiter waitWithInterval:1]; } return self.innerWrapper; } -@end \ No newline at end of file +@end diff --git a/Sources/EmarsysSDK/DI/EMSDependencyContainer.m b/Sources/EmarsysSDK/DI/EMSDependencyContainer.m index 0cb2ae6e..85fce8c5 100644 --- a/Sources/EmarsysSDK/DI/EMSDependencyContainer.m +++ b/Sources/EmarsysSDK/DI/EMSDependencyContainer.m @@ -63,7 +63,7 @@ #import "EMSConfigInternal.h" #import "EMSXPResponseHandler.h" #import "EMSEmarsysRequestFactory.h" -#import "EMSCompletionBlockProvider.h" +#import "EMSCompletionProvider.h" #import "EMSRemoteConfigResponseMapper.h" #import "EMSValueProvider.h" #import "EMSSceneProvider.h" @@ -89,6 +89,7 @@ #import "EMSSdkStateLogger.h" #import "EMSInMemoryStorage.h" #import "EMSDeviceEventStateRequestMapper.h" +#import "EMSCompletionBlockProvider.h" #define DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"MEDB.db"] @@ -144,11 +145,16 @@ @interface EMSDependencyContainer () @property(nonatomic, strong) NSOperationQueue *publicApiOperationQueue; @property(nonatomic, strong) NSOperationQueue *coreOperationQueue; +@property(nonatomic, strong) NSOperationQueue *dbQueue; +@property(nonatomic, strong) NSOperationQueue *storageQueue; @property(nonatomic, strong) MEButtonClickRepository *buttonClickRepository; @property(nonatomic, copy) RouterLogicBlock mobileEngageRouterLogicBlock; @property(nonatomic, copy) RouterLogicBlock predictRouterLogicBlock; @property(nonatomic, strong) EMSSession *session; +@property(nonatomic, strong) NSURLSession *urlSession; +@property(nonatomic, strong) EMSWrapperChecker *wrapperChecker; +@property(nonatomic, strong) EMSCompletionBlockProvider *completionBlockProvider; - (void)initializeDependenciesWithConfig:(EMSConfig *)config; @@ -159,16 +165,14 @@ @implementation EMSDependencyContainer - (instancetype)initWithConfig:(EMSConfig *)config { if (self = [super init]) { _uuidProvider = [EMSUUIDProvider new]; - _publicApiOperationQueue = [EMSOperationQueue new]; - _publicApiOperationQueue.maxConcurrentOperationCount = 1; - _publicApiOperationQueue.qualityOfService = NSQualityOfServiceUserInitiated; - _publicApiOperationQueue.name = [NSString stringWithFormat:@"public_api_queue_%@", - [self.uuidProvider provideUUIDString]]; - _coreOperationQueue = [EMSOperationQueue new]; - _coreOperationQueue.maxConcurrentOperationCount = 1; - _coreOperationQueue.qualityOfService = NSQualityOfServiceUtility; - _coreOperationQueue.name = [NSString stringWithFormat:@"core_sdk_queue_%@", - [self.uuidProvider provideUUIDString]]; + _publicApiOperationQueue = [self createQueueWithName:@"public_api_queue" + qualityOfService:NSQualityOfServiceUserInitiated]; + _coreOperationQueue = [self createQueueWithName:@"core_sdk_queue" + qualityOfService:NSQualityOfServiceUtility]; + _dbQueue = [self createQueueWithName:@"emarsys_sdk_db_queue" + qualityOfService:NSQualityOfServiceUtility]; + _storageQueue = [self createQueueWithName:@"emarsys_sdk_storage_queue" + qualityOfService:NSQualityOfServiceUtility]; _predictDelegator = [EMSQueueDelegator alloc]; [self.predictDelegator setupWithQueue:self.publicApiOperationQueue @@ -256,10 +260,13 @@ - (void)initializeDependenciesWithConfig:(EMSConfig *)config { EMSRandomProvider *randomProvider = [EMSRandomProvider new]; EMSTimestampProvider *timestampProvider = [EMSTimestampProvider new]; + + _completionBlockProvider = [[EMSCompletionBlockProvider alloc] initWithOperationQueue:self.publicApiOperationQueue]; _suiteNames = @[@"com.emarsys.core", @"com.emarsys.predict", @"com.emarsys.mobileengage", @"com.emarsys.sdk"]; EMSStorage *storage = [[EMSStorage alloc] initWithSuiteNames:self.suiteNames - accessGroup:config.sharedKeychainAccessGroup]; + accessGroup:config.sharedKeychainAccessGroup + operationQueue:self.storageQueue]; _storage = [[EMSInMemoryStorage alloc] initWithStorage:storage]; EMSDeviceInfo *deviceInfo = [[EMSDeviceInfo alloc] initWithSDKVersion:EMARSYS_SDK_VERSION @@ -278,7 +285,9 @@ - (void)initializeDependenciesWithConfig:(EMSConfig *)config { deviceInfo:deviceInfo]; _notificationCenterManager = [[EMSNotificationCenterManager alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]]; _dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:self.dbQueue]; + [_dbHelper open]; MEDisplayedIAMRepository *displayedIAMRepository = [[MEDisplayedIAMRepository alloc] initWithDbHelper:self.dbHelper]; _buttonClickRepository = [[MEButtonClickRepository alloc] initWithDbHelper:self.dbHelper]; @@ -294,7 +303,7 @@ - (void)initializeDependenciesWithConfig:(EMSConfig *)config { sceneProvider:[[EMSSceneProvider alloc] initWithApplication:[UIApplication sharedApplication]]] mainWindowProvider:[[EMSMainWindowProvider alloc] initWithApplication:[UIApplication sharedApplication]] timestampProvider:timestampProvider - completionBlockProvider:[[EMSCompletionBlockProvider alloc] initWithOperationQueue:self.coreOperationQueue] + completionBlockProvider:[[EMSCompletionProvider alloc] initWithOperationQueue:self.coreOperationQueue] displayedIamRepository:displayedIAMRepository buttonClickRepository:self.buttonClickRepository operationQueue:self.coreOperationQueue]; @@ -304,8 +313,6 @@ - (void)initializeDependenciesWithConfig:(EMSConfig *)config { [self.iamDelegator proxyWithInstanceRouter:iamInstanceRouter]; - [_dbHelper open]; - EMSShardRepository *shardRepository = [[EMSShardRepository alloc] initWithDbHelper:self.dbHelper]; MERequestModelRepositoryFactory *requestRepositoryFactory = [[MERequestModelRepositoryFactory alloc] initWithInApp:self.iam requestContext:self.requestContext @@ -318,13 +325,16 @@ - (void)initializeDependenciesWithConfig:(EMSConfig *)config { _requestRepository = [requestRepositoryFactory createWithBatchCustomEventProcessing:YES]; + _wrapperChecker = [[EMSWrapperChecker alloc] initWithOperationQueue:self.coreOperationQueue + waiter:[EMSDispatchWaiter new] + storage:self.storage]; + _logger = [[EMSLogger alloc] initWithShardRepository:shardRepository opertaionQueue:self.coreOperationQueue timestampProvider:timestampProvider uuidProvider:self.uuidProvider storage:self.storage - wrapperChecker:[[EMSWrapperChecker alloc] initWithOperationQueue:self.coreOperationQueue - waiter:[EMSDispatchWaiter new]]]; + wrapperChecker:self.wrapperChecker]; [self.logger setConsoleLogLevels:config.enabledConsoleLogLevels]; EMSCompletionMiddleware *middleware = [self createMiddleware]; @@ -332,12 +342,14 @@ - (void)initializeDependenciesWithConfig:(EMSConfig *)config { NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; [sessionConfiguration setTimeoutIntervalForRequest:30.0]; [sessionConfiguration setHTTPCookieStorage:nil]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration - delegate:nil - delegateQueue:self.coreOperationQueue]; + _urlSession = [NSURLSession sessionWithConfiguration:sessionConfiguration + delegate:nil + delegateQueue:self.coreOperationQueue]; EMSActionFactory *onEventActionFactory = [[EMSActionFactory alloc] initWithApplication:application - mobileEngage:self.mobileEngage]; + mobileEngage:self.mobileEngage + userNotificationCenter:[UNUserNotificationCenter currentNotificationCenter] + operationQueue:self.coreOperationQueue]; EMSInstanceRouter *onEventActionRouter = [[EMSInstanceRouter alloc] initWithDefaultInstance:[[EMSOnEventActionInternal alloc] initWithActionFactory:onEventActionFactory] loggingInstance:[EMSLoggingOnEventActionInternal new] @@ -349,7 +361,6 @@ - (void)initializeDependenciesWithConfig:(EMSConfig *)config { EMSContactTokenResponseHandler *contactTokenResponseHandler = [[EMSContactTokenResponseHandler alloc] initWithRequestContext:self.requestContext endpoint:self.endpoint]; _responseHandlers = [NSMutableArray array]; - [self.dbHelper open]; [self.responseHandlers addObject:[[MEIAMResponseHandler alloc] initWithInApp:self.iam]]; [self.responseHandlers addObject:[[MEIAMCleanupResponseHandlerV3 alloc] initWithButtonClickRepository:self.buttonClickRepository displayIamRepository:displayedIAMRepository @@ -369,7 +380,7 @@ - (void)initializeDependenciesWithConfig:(EMSConfig *)config { endpoint:self.endpoint]]; [self.responseHandlers addObject:contactTokenResponseHandler]; - _restClient = [[EMSRESTClient alloc] initWithSession:session + _restClient = [[EMSRESTClient alloc] initWithSession:self.urlSession queue:self.coreOperationQueue timestampProvider:timestampProvider additionalHeaders:[MEDefaultHeaders additionalHeaders] @@ -424,22 +435,20 @@ - (void)initializeDependenciesWithConfig:(EMSConfig *)config { actionFactory:onEventActionFactory timestampProvider:timestampProvider]]; - if ([MEExperimental isFeatureEnabled:EMSInnerFeature.predict]) { - _predictTrigger = [[EMSBatchingShardTrigger alloc] initWithRepository:shardRepository - specification:[[EMSFilterByTypeSpecification alloc] initWitType:@"predict_%%" - column:SHARD_COLUMN_NAME_TYPE] - mapper:[[EMSPredictMapper alloc] initWithRequestContext:self.predictRequestContext - endpoint:self.endpoint] - chunker:[[EMSListChunker alloc] initWithChunkSize:1] - predicate:[[EMSCountPredicate alloc] initWithThreshold:1] - requestManager:self.requestManager - persistent:YES - connectionWatchdog:watchdog]; - [_dbHelper registerTriggerWithTableName:SHARD_TABLE_NAME - triggerType:EMSDBTriggerType.afterType - triggerEvent:EMSDBTriggerEvent.insertEvent - trigger:self.predictTrigger]; - } + _predictTrigger = [[EMSBatchingShardTrigger alloc] initWithRepository:shardRepository + specification:[[EMSFilterByTypeSpecification alloc] initWitType:@"predict_%%" + column:SHARD_COLUMN_NAME_TYPE] + mapper:[[EMSPredictMapper alloc] initWithRequestContext:self.predictRequestContext + endpoint:self.endpoint] + chunker:[[EMSListChunker alloc] initWithChunkSize:1] + predicate:[[EMSCountPredicate alloc] initWithThreshold:1] + requestManager:self.requestManager + persistent:YES + connectionWatchdog:watchdog]; + [_dbHelper registerTriggerWithTableName:SHARD_TABLE_NAME + triggerType:EMSDBTriggerType.afterType + triggerEvent:EMSDBTriggerEvent.insertEvent + trigger:self.predictTrigger]; _loggerTrigger = [[EMSBatchingShardTrigger alloc] initWithRepository:shardRepository specification:[[EMSFilterByTypeSpecification alloc] initWitType:@"log_%%" @@ -478,13 +487,16 @@ - (void)initializeDependenciesWithConfig:(EMSConfig *)config { requestManager:self.requestManager requestContext:self.requestContext storage:self.storage - session:self.session] + session:self.session + completionBlockProvider:self.completionBlockProvider] loggingInstance:self.loggingMobileEngage routerLogic:self.mobileEngageRouterLogicBlock]; [self.mobileEngageDelegator proxyWithInstanceRouter:mobileEngageRouter]; EMSActionFactory *actionFactory = [[EMSActionFactory alloc] initWithApplication:application - mobileEngage:self.mobileEngage]; + mobileEngage:self.mobileEngage + userNotificationCenter:[UNUserNotificationCenter currentNotificationCenter] + operationQueue:self.coreOperationQueue]; EMSDeepLinkInternal *deepLinkInternal = [[EMSDeepLinkInternal alloc] initWithRequestManager:self.requestManager requestFactory:self.requestFactory]; @@ -555,7 +567,7 @@ - (void)initializeDependenciesWithConfig:(EMSConfig *)config { endpoint:self.endpoint logger:self.logger crypto:[[EMSCrypto alloc] init] - queue:self.coreOperationQueue + coreQueue:self.coreOperationQueue waiter:[EMSDispatchWaiter new] deviceInfoClient:self.deviceInfoClient]; @@ -579,7 +591,9 @@ - (void)initializeDependenciesWithConfig:(EMSConfig *)config { meRequestContext:self.requestContext config:config storage:self.storage] - logger:self.logger]; + logger:self.logger + dbHelper:self.dbHelper + completionBlockProvider:self.completionBlockProvider]; [self.iam setInAppTracker:inAppInternal]; } @@ -599,4 +613,13 @@ - (EMSCompletionMiddleware *)createMiddleware { }; } +- (NSOperationQueue *)createQueueWithName:(NSString *)queueName + qualityOfService:(NSQualityOfService)qualityOfService { + NSOperationQueue *operationQueue = [EMSOperationQueue new]; + operationQueue.maxConcurrentOperationCount = 1; + operationQueue.qualityOfService = qualityOfService; + operationQueue.name = [NSString stringWithFormat:@"%@_%@", queueName, [self.uuidProvider provideUUIDString]]; + return operationQueue; +} + @end diff --git a/Sources/EmarsysSDK/Emarsys.m b/Sources/EmarsysSDK/Emarsys.m index c6209e38..7aeea377 100644 --- a/Sources/EmarsysSDK/Emarsys.m +++ b/Sources/EmarsysSDK/Emarsys.m @@ -162,6 +162,8 @@ + (void)registerAppStartBlock { forNotification:@"EmarsysSDKDidFinishSetupNotification"]; [notificationCenterManager addHandlerBlock:[appStartBlockProvider createRemoteConfigEventBlock] forNotification:@"EmarsysSDKDidFinishSetupNotification"]; + [notificationCenterManager addHandlerBlock:[appStartBlockProvider createDbCloseEventBlock] + forNotification:UIApplicationWillTerminateNotification]; } + (id )push { diff --git a/Sources/EmarsysSDK/Setup/EMSConfigInternal.m b/Sources/EmarsysSDK/Setup/EMSConfigInternal.m index 5e894ad5..6be78c63 100644 --- a/Sources/EmarsysSDK/Setup/EMSConfigInternal.m +++ b/Sources/EmarsysSDK/Setup/EMSConfigInternal.m @@ -38,7 +38,7 @@ @interface EMSConfigInternal () @property(nonatomic, strong) EMSLogger *logger; @property(nonatomic, strong) EMSRemoteConfigResponseMapper *remoteConfigResponseMapper; @property(nonatomic, strong) EMSCrypto *crypto; -@property(nonatomic, strong) NSOperationQueue *queue; +@property(nonatomic, strong) NSOperationQueue *coreQueue; @property(nonatomic, strong) EMSDispatchWaiter *waiter; @property(nonatomic, strong) EMSDeviceInfoV3ClientInternal *deviceInfoClient; @@ -57,7 +57,7 @@ - (instancetype)initWithRequestManager:(EMSRequestManager *)requestManager endpoint:(EMSEndpoint *)endpoint logger:(EMSLogger *)logger crypto:(EMSCrypto *)crypto - queue:(NSOperationQueue *)queue + coreQueue:(NSOperationQueue *)coreQueue waiter:(EMSDispatchWaiter *)waiter deviceInfoClient:(id )deviceInfoClient { NSParameterAssert(requestManager); @@ -71,7 +71,7 @@ - (instancetype)initWithRequestManager:(EMSRequestManager *)requestManager NSParameterAssert(endpoint); NSParameterAssert(logger); NSParameterAssert(crypto); - NSParameterAssert(queue); + NSParameterAssert(coreQueue); NSParameterAssert(waiter); NSParameterAssert(deviceInfoClient); if (self = [super init]) { @@ -86,7 +86,7 @@ - (instancetype)initWithRequestManager:(EMSRequestManager *)requestManager _endpoint = endpoint; _logger = logger; _crypto = crypto; - _queue = queue; + _coreQueue = coreQueue; _waiter = waiter; _deviceInfoClient = deviceInfoClient; } @@ -186,27 +186,28 @@ - (void)changeApplicationCode:(nullable NSString *)applicationCode NSData *pushToken = self.pushInternal.deviceToken; BOOL hasContactIdentification = self.meRequestContext.hasContactIdentification; __block NSError *error = nil; + __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - if (self.meRequestContext.applicationCode && pushToken) { - error = [self clearPushToken]; + if (weakSelf.meRequestContext.applicationCode && pushToken) { + error = [weakSelf clearPushToken]; } - if (!error && self.meRequestContext.applicationCode && [self.meRequestContext hasContactIdentification]) { - error = [self clearContact]; + if (!error && weakSelf.meRequestContext.applicationCode && [weakSelf.meRequestContext hasContactIdentification]) { + error = [weakSelf clearContact]; } if (!error) { - self.meRequestContext.applicationCode = applicationCode; + weakSelf.meRequestContext.applicationCode = applicationCode; if (applicationCode) { - error = [self sendDeviceInfo]; + error = [weakSelf sendDeviceInfo]; if (pushToken) { - error = [self sendPushToken:pushToken]; + error = [weakSelf sendPushToken:pushToken]; } if (!error && !hasContactIdentification) { - error = [self clearContact]; + error = [weakSelf clearContact]; } } } if (error) { - self.meRequestContext.applicationCode = nil; + weakSelf.meRequestContext.applicationCode = nil; } if (completionHandler) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -255,7 +256,9 @@ - (NSError *)synchronizeMethodWithRunnerBlock:(void (^)(EMSCompletionBlock compl result = error; [weakSelf.waiter exit]; }; - runnerBlock(completionBlock); + [self.coreQueue addOperationWithBlock:^{ + runnerBlock(completionBlock); + }]; } [self.waiter waitWithInterval:METHOD_TIMEOUT]; return result; @@ -270,7 +273,7 @@ - (void)changeMerchantId:(NSString *)merchantId self.preRequestContext.merchantId = merchantId; [self.waiter enter]; __weak typeof(self) weakSelf = self; - [self.queue addOperationWithBlock:^{ + [self.coreQueue addOperationWithBlock:^{ [weakSelf.waiter exit]; }]; [self.waiter waitWithInterval:5]; diff --git a/Sources/MobileEngage/IAM/MEInApp.m b/Sources/MobileEngage/IAM/MEInApp.m index ffda4840..406e0819 100644 --- a/Sources/MobileEngage/IAM/MEInApp.m +++ b/Sources/MobileEngage/IAM/MEInApp.m @@ -14,7 +14,7 @@ #import "MEJSBridge.h" #import "MEButtonClickRepository.h" #import "EMSMacros.h" -#import "EMSCompletionBlockProvider.h" +#import "EMSCompletionProvider.h" #import "EMSInAppLog.h" @interface MEInApp () @@ -26,7 +26,7 @@ @interface MEInApp () @property(nonatomic, strong) EMSWindowProvider *windowProvider; @property(nonatomic, strong) EMSIAMViewControllerProvider *iamViewControllerProvider; @property(nonatomic, strong) MEDisplayedIAMRepository *displayedIamRepository; -@property(nonatomic, strong) EMSCompletionBlockProvider *completionBlockProvider; +@property(nonatomic, strong) EMSCompletionProvider *completionBlockProvider; @property(nonatomic, strong) EMSEventHandlerBlock innerEventHandler; @property(nonatomic, strong) NSMutableArray *messages; @@ -43,7 +43,7 @@ @implementation MEInApp - (instancetype)initWithWindowProvider:(EMSWindowProvider *)windowProvider mainWindowProvider:(EMSMainWindowProvider *)mainWindowProvider timestampProvider:(EMSTimestampProvider *)timestampProvider - completionBlockProvider:(EMSCompletionBlockProvider *)completionBlockProvider + completionBlockProvider:(EMSCompletionProvider *)completionBlockProvider displayedIamRepository:(MEDisplayedIAMRepository *)displayedIamRepository buttonClickRepository:(MEButtonClickRepository *)buttonClickRepository operationQueue:(NSOperationQueue *)operationQueue { diff --git a/Sources/MobileEngage/IAM/MEJSBridge.m b/Sources/MobileEngage/IAM/MEJSBridge.m index 53fbbdae..fcb7df08 100644 --- a/Sources/MobileEngage/IAM/MEJSBridge.m +++ b/Sources/MobileEngage/IAM/MEJSBridge.m @@ -69,4 +69,4 @@ - (WKUserContentController *)userContentController { return userContentController; } -@end \ No newline at end of file +@end diff --git a/Sources/MobileEngage/IAM/UI/MEIAMViewController.m b/Sources/MobileEngage/IAM/UI/MEIAMViewController.m index b6e311bf..a0dc3e34 100644 --- a/Sources/MobileEngage/IAM/UI/MEIAMViewController.m +++ b/Sources/MobileEngage/IAM/UI/MEIAMViewController.m @@ -31,7 +31,7 @@ - (void)viewDidDisappear:(BOOL)animated { [self.webView stopLoading]; [self.webView setNavigationDelegate:nil]; [self.webView.scrollView setDelegate:nil]; - [self.webView.configuration.userContentController removeAllUserScripts]; + [self.webView.configuration.userContentController removeAllScriptMessageHandlers]; [self.webView.configuration setUserContentController:[WKUserContentController new]]; [self.webView removeFromSuperview]; self.webView = nil; diff --git a/Sources/MobileEngage/InboxV3/EMSInboxV3.m b/Sources/MobileEngage/InboxV3/EMSInboxV3.m index c654f969..e9cc1db5 100644 --- a/Sources/MobileEngage/InboxV3/EMSInboxV3.m +++ b/Sources/MobileEngage/InboxV3/EMSInboxV3.m @@ -43,22 +43,22 @@ - (void)fetchMessagesWithResultBlock:(EMSInboxMessageResultBlock)resultBlock { __weak typeof(self) weakSelf = self; [self.requestManager submitRequestModelNow:requestModel successBlock:^(NSString *requestId, EMSResponseModel *response) { - dispatch_async(dispatch_get_main_queue(), ^{ - EMSInboxResult *result = [weakSelf.inboxResultParser parseFromResponse:response]; - weakSelf.messages = result.messages; - if (resultBlock) { - resultBlock(result, nil); - } - }); - } + dispatch_async(dispatch_get_main_queue(), ^{ + EMSInboxResult *result = [weakSelf.inboxResultParser parseFromResponse:response]; + weakSelf.messages = result.messages; + if (resultBlock) { + resultBlock(result, nil); + } + }); + } errorBlock:^(NSString *requestId, NSError *error) { - weakSelf.messages = nil; - dispatch_async(dispatch_get_main_queue(), ^{ - if (resultBlock) { - resultBlock(nil, error); - } - }); - }]; + weakSelf.messages = nil; + dispatch_async(dispatch_get_main_queue(), ^{ + if (resultBlock) { + resultBlock(nil, error); + } + }); + }]; } - (void)addTag:(NSString *)tag @@ -75,22 +75,24 @@ - (void) addTag:(NSString *)tag NSParameterAssert(messageId); __weak typeof(self) weakSelf = self; [self updateTagWithConditionBlock:^BOOL(EMSMessage *message) { - return [message.id isEqualToString:messageId] && ![message.tags containsObject:[tag lowercaseString]]; - } runnerBlock:^{ - EMSRequestModel *requestModel = [weakSelf.requestFactory createEventRequestModelWithEventName:@"inbox:tag:add" - eventAttributes:@{ - @"messageId": messageId, - @"tag": [tag lowercaseString] - } - eventType:EventTypeInternal]; - [weakSelf.requestManager submitRequestModel:requestModel - withCompletionBlock:completionBlock]; - } + return [message.id isEqualToString:messageId] && ![message.tags containsObject:[tag lowercaseString]]; + } runnerBlock:^{ + EMSRequestModel *requestModel = [weakSelf.requestFactory createEventRequestModelWithEventName:@"inbox:tag:add" + eventAttributes:@{ + @"messageId": messageId, + @"tag": [tag lowercaseString] + } + eventType:EventTypeInternal]; + [weakSelf.requestManager submitRequestModel:requestModel + withCompletionBlock:completionBlock]; + } requestUnnecessaryBlock:^{ - if (completionBlock) { - completionBlock(nil); - } - }]; + dispatch_async(dispatch_get_main_queue(), ^{ + if (completionBlock) { + completionBlock(nil); + } + }); + }]; } - (void)removeTag:(NSString *)tag @@ -107,22 +109,24 @@ - (void)removeTag:(NSString *)tag NSParameterAssert(messageId); __weak typeof(self) weakSelf = self; [self updateTagWithConditionBlock:^BOOL(EMSMessage *message) { - return [message.id isEqualToString:messageId] && [message.tags containsObject:[tag lowercaseString]]; - } runnerBlock:^{ - EMSRequestModel *requestModel = [weakSelf.requestFactory createEventRequestModelWithEventName:@"inbox:tag:remove" - eventAttributes:@{ - @"messageId": messageId, - @"tag": [tag lowercaseString] - } - eventType:EventTypeInternal]; - [weakSelf.requestManager submitRequestModel:requestModel - withCompletionBlock:completionBlock]; - } + return [message.id isEqualToString:messageId] && [message.tags containsObject:[tag lowercaseString]]; + } runnerBlock:^{ + EMSRequestModel *requestModel = [weakSelf.requestFactory createEventRequestModelWithEventName:@"inbox:tag:remove" + eventAttributes:@{ + @"messageId": messageId, + @"tag": [tag lowercaseString] + } + eventType:EventTypeInternal]; + [weakSelf.requestManager submitRequestModel:requestModel + withCompletionBlock:completionBlock]; + } requestUnnecessaryBlock:^{ - if (completionBlock) { - completionBlock(nil); - } - }]; + dispatch_async(dispatch_get_main_queue(), ^{ + if (completionBlock) { + completionBlock(nil); + } + }); + }]; } - (void)updateTagWithConditionBlock:(EMSMessageConditionBlock)conditionBlock @@ -146,4 +150,4 @@ - (void)updateTagWithConditionBlock:(EMSMessageConditionBlock)conditionBlock } -@end \ No newline at end of file +@end diff --git a/Sources/MobileEngage/Internal/EMSMobileEngageV3Internal.m b/Sources/MobileEngage/Internal/EMSMobileEngageV3Internal.m index 015a6404..e77f85a5 100644 --- a/Sources/MobileEngage/Internal/EMSMobileEngageV3Internal.m +++ b/Sources/MobileEngage/Internal/EMSMobileEngageV3Internal.m @@ -8,6 +8,7 @@ #import "EMSStorage.h" #import "EMSSession.h" #import "EMSStorageProtocol.h" +#import "EMSCompletionBlockProvider.h" #define kEMSPushTokenKey @"EMSPushTokenKey" @@ -18,6 +19,7 @@ @interface EMSMobileEngageV3Internal () @property(nonatomic, strong) MERequestContext *requestContext; @property(nonatomic, strong) id storage; @property(nonatomic, strong) EMSSession *session; +@property(nonatomic, strong) EMSCompletionBlockProvider *completionBlockProvider; @end @@ -27,12 +29,14 @@ - (instancetype)initWithRequestFactory:(EMSRequestFactory *)requestFactory requestManager:(EMSRequestManager *)requestManager requestContext:(MERequestContext *)requestContext storage:(id)storage - session:(EMSSession *)session { + session:(EMSSession *)session + completionBlockProvider:(EMSCompletionBlockProvider *)completionBlockProvider { NSParameterAssert(requestFactory); NSParameterAssert(requestManager); NSParameterAssert(requestContext); NSParameterAssert(storage); NSParameterAssert(session); + NSParameterAssert(completionBlockProvider); if (self = [super init]) { _requestFactory = requestFactory; _requestManager = requestManager; @@ -40,6 +44,7 @@ - (instancetype)initWithRequestFactory:(EMSRequestFactory *)requestFactory _requestContext = requestContext; _storage = storage; _session = session; + _completionBlockProvider = completionBlockProvider; } return self; } @@ -82,11 +87,11 @@ - (void)sendContactRequestWithShouldRestartSession:(BOOL)shouldRestartSession EMSRequestModel *requestModel = [self.requestFactory createContactRequestModel]; __weak typeof(self) weakSelf = self; [self.requestManager submitRequestModel:requestModel - withCompletionBlock:^(NSError * _Nullable error) { + withCompletionBlock:[self.completionBlockProvider provideCompletionBlock:^(NSError * _Nullable error) { if (shouldRestartSession) { - [weakSelf.session stopSessionWithCompletionBlock:^(NSError * _Nullable error) { + [weakSelf.session stopSessionWithCompletionBlock:[weakSelf.completionBlockProvider provideCompletionBlock:^(NSError * _Nullable error) { [weakSelf.session startSessionWithCompletionBlock:completionBlock]; - }]; + }]]; } else { if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -94,7 +99,7 @@ - (void)sendContactRequestWithShouldRestartSession:(BOOL)shouldRestartSession }); } } - }]; + }]]; } - (void)clearContact { @@ -103,16 +108,16 @@ - (void)clearContact { - (void)clearContactWithCompletionBlock:(EMSCompletionBlock)completionBlock { __weak typeof(self) weakSelf = self; - [self.session stopSessionWithCompletionBlock:^(NSError * _Nullable error) { + [self.session stopSessionWithCompletionBlock:[weakSelf.completionBlockProvider provideCompletionBlock:^(NSError * _Nullable error) { [weakSelf.storage setData:nil forKey:kEMSPushTokenKey]; [weakSelf.requestContext reset]; [weakSelf setContactWithContactFieldId:nil contactFieldValue:nil - completionBlock:^(NSError * _Nullable error) { + completionBlock:[weakSelf.completionBlockProvider provideCompletionBlock:^(NSError * _Nullable error) { [weakSelf.session startSessionWithCompletionBlock:completionBlock]; - }]; - }]; + }]]; + }]]; } - (void)trackCustomEventWithName:(NSString *)eventName diff --git a/Sources/MobileEngage/Parser/EMSMobileEngageNullSafeBodyParser.m b/Sources/MobileEngage/Parser/EMSMobileEngageNullSafeBodyParser.m index 7cf701e3..c55523dd 100644 --- a/Sources/MobileEngage/Parser/EMSMobileEngageNullSafeBodyParser.m +++ b/Sources/MobileEngage/Parser/EMSMobileEngageNullSafeBodyParser.m @@ -28,11 +28,15 @@ - (instancetype)initWithEndpoint:(EMSEndpoint *)endpoint { - (BOOL)shouldParse:(EMSRequestModel *)requestModel responseBody:(NSData *)responseBody httpUrlResponse:(NSHTTPURLResponse *)httpUrlResponse { + NSString *dataText = [[NSString alloc] initWithData:responseBody + encoding:NSUTF8StringEncoding]; return [self.endpoint isMobileEngageUrl:requestModel.url.absoluteString] && ![self.endpoint isPushToInAppUrl:requestModel.url.absoluteString] && [httpUrlResponse isSuccess] && responseBody - && responseBody.length > 0; + && responseBody.length > 0 + && dataText && + ([dataText characterAtIndex:0] == '{' || [dataText characterAtIndex:0] == '['); } - (id)parseWithRequestModel:(EMSRequestModel *)requestModel @@ -112,4 +116,4 @@ - (void)sendLogWithRequestModel:(EMSRequestModel *)requestModel [self setShouldSendLog:NO]; } -@end \ No newline at end of file +@end diff --git a/Sources/MobileEngage/Push/Actions/EMSActionFactory.m b/Sources/MobileEngage/Push/Actions/EMSActionFactory.m index 1ae7dcba..4f8f98e1 100644 --- a/Sources/MobileEngage/Push/Actions/EMSActionFactory.m +++ b/Sources/MobileEngage/Push/Actions/EMSActionFactory.m @@ -14,6 +14,8 @@ @interface EMSActionFactory () @property(nonatomic, strong) UIApplication *application; +@property(nonatomic, strong) UNUserNotificationCenter *userNotificationCenter; +@property(nonatomic, strong) NSOperationQueue *operationQueue; @property(nonatomic, weak) id mobileEngage; @end @@ -21,12 +23,18 @@ @interface EMSActionFactory () @implementation EMSActionFactory - (instancetype)initWithApplication:(UIApplication *)application - mobileEngage:(id )mobileEngage { + mobileEngage:(id )mobileEngage + userNotificationCenter:(nonnull UNUserNotificationCenter *)userNotificationCenter + operationQueue:(nonnull NSOperationQueue *)operationQueue { NSParameterAssert(application); NSParameterAssert(mobileEngage); + NSParameterAssert(userNotificationCenter); + NSParameterAssert(operationQueue); if (self = [super init]) { _application = application; _mobileEngage = mobileEngage; + _userNotificationCenter = userNotificationCenter; + _operationQueue = operationQueue; } return self; } @@ -45,7 +53,9 @@ - (instancetype)initWithApplication:(UIApplication *)application [validate valueExistsForKey:@"value" withType:[NSNumber class]]; }]; result = [badgeErrors count] == 0 ? [[EMSBadgeCountAction alloc] initWithActionDictionary:action - application:self.application] : nil; + application:self.application + userNotificationCenter:self.userNotificationCenter + operationQueue:self.operationQueue] : nil; } else if ([actionType isEqualToString:@"MEAppEvent"]) { NSArray *appEventErrors = [action validate:^(EMSDictionaryValidator *validate) { [validate valueExistsForKey:@"name" withType:[NSString class]]; diff --git a/Sources/MobileEngage/Push/Actions/EMSBadgeCountAction.m b/Sources/MobileEngage/Push/Actions/EMSBadgeCountAction.m index dc9dd738..a1793f8f 100644 --- a/Sources/MobileEngage/Push/Actions/EMSBadgeCountAction.m +++ b/Sources/MobileEngage/Push/Actions/EMSBadgeCountAction.m @@ -4,23 +4,33 @@ #import "EMSBadgeCountAction.h" #import "EMSDispatchWaiter.h" +#import +#import "EMSBlocks.h" @interface EMSBadgeCountAction () @property(nonatomic, strong) UIApplication *application; +@property(nonatomic, strong) UNUserNotificationCenter *notificationCenter; @property(nonatomic, strong) NSDictionary *action; +@property(nonatomic, strong) NSOperationQueue *operationQueue; @end @implementation EMSBadgeCountAction - (instancetype)initWithActionDictionary:(NSDictionary *)action - application:(UIApplication *)application { + application:(UIApplication *)application + userNotificationCenter:(UNUserNotificationCenter *)userNotificationCenter + operationQueue:(NSOperationQueue *)operationQueue { NSParameterAssert(action); NSParameterAssert(application); + NSParameterAssert(userNotificationCenter); + NSParameterAssert(operationQueue); if (self = [super init]) { _action = action; _application = application; + _notificationCenter = userNotificationCenter; + _operationQueue = operationQueue; } return self; } @@ -28,19 +38,40 @@ - (instancetype)initWithActionDictionary:(NSDictionary *)action - (void)execute { EMSDispatchWaiter *waiter = [[EMSDispatchWaiter alloc] init]; NSInteger value = [self.action[@"value"] integerValue]; - + if ([[self.action[@"method"] lowercaseString] isEqualToString:@"add"]) { + NSInteger currentBadgeCount = [self.application applicationIconBadgeNumber]; + value += currentBadgeCount; + } [waiter enter]; - dispatch_async(dispatch_get_main_queue(), ^{ - if ([[self.action[@"method"] lowercaseString] isEqualToString:@"add"]) { - NSInteger currentBadgeCount = [self.application applicationIconBadgeNumber]; - [self.application setApplicationIconBadgeNumber:currentBadgeCount + value]; - } else { - [self.application setApplicationIconBadgeNumber:value]; - } + [self setBadgeCount:value + completionBlock:^(NSError * _Nullable error) { [waiter exit]; - }); + }]; + [waiter waitWithInterval:2.0]; +} - [waiter waitWithInterval:2]; +- (void)setBadgeCount:(NSInteger)badgeCount + completionBlock:(EMSCompletionBlock)completionBlock { + __weak typeof(self) weakSelf = self; + if (@available(iOS 16.0, *)) { + [self.notificationCenter setBadgeCount:badgeCount + withCompletionHandler:^(NSError * _Nullable error) { + [weakSelf.operationQueue addOperationWithBlock:^{ + completionBlock(error); + }]; + }]; + } else { + NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; + NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ + [weakSelf.application setApplicationIconBadgeNumber:badgeCount]; + }]; + operation.completionBlock = ^{ + [weakSelf.operationQueue addOperationWithBlock:^{ + completionBlock(nil); + }]; + }; + [mainQueue addOperation:operation]; + } } -@end \ No newline at end of file +@end diff --git a/Sources/MobileEngage/Push/EMSPushV3Internal.m b/Sources/MobileEngage/Push/EMSPushV3Internal.m index 2d8ca68e..912bdf2d 100644 --- a/Sources/MobileEngage/Push/EMSPushV3Internal.m +++ b/Sources/MobileEngage/Push/EMSPushV3Internal.m @@ -83,16 +83,19 @@ - (void)setPushToken:(NSData *)pushToken EMSRequestModel *requestModel; if (deviceToken && [deviceToken length] > 0) { requestModel = [self.requestFactory createPushTokenRequestModelWithPushToken:deviceToken]; + __weak typeof(self) weakSelf = self; [self.requestManager submitRequestModel:requestModel withCompletionBlock:^(NSError *error) { - if (!error) { - [self.storage setData:pushToken - forKey:kEMSPushTokenKey]; - } - if (completionBlock) { - completionBlock(error); - } - }]; + [weakSelf.operationQueue addOperationWithBlock:^{ + if (!error) { + [weakSelf.storage setData:pushToken + forKey:kEMSPushTokenKey]; + } + }]; + if (completionBlock) { + completionBlock(error); + } + }]; } } } @@ -122,18 +125,18 @@ - (void)trackMessageOpenWithUserInfo:(NSDictionary *)userInfo { - (void)trackMessageOpenWithUserInfo:(NSDictionary *)userInfo completionBlock:(EMSCompletionBlock)completionBlock { NSParameterAssert(userInfo); - + NSString *sid = [userInfo messageId]; if (sid) { EMSRequestModel *requestModel = [self.requestFactory createEventRequestModelWithEventName:@"push:click" eventAttributes:@{ - @"origin": @"main", - @"sid": sid - } + @"origin": @"main", + @"sid": sid + } eventType:EventTypeInternal]; [self.requestManager submitRequestModel:requestModel withCompletionBlock:completionBlock]; - + } else if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock([NSError errorWithCode:1400 @@ -204,7 +207,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center if (userInfo[@"exception"]) { EMSLog([[EMSCrashLog alloc] initWithException:userInfo[@"exception"]], LogLevelError); } - + NSString *campaignId = userInfo[@"ems"][@"multichannelId"]; if (campaignId && weakSelf.notificationInformationBlock) { EMSNotificationInformation *notificationInformation = [[EMSNotificationInformation alloc] initWithCampaignId:campaignId]; @@ -212,20 +215,20 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center weakSelf.notificationInformationBlock(notificationInformation); }); } - + NSDictionary *inApp = userInfo[@"ems"][@"inapp"]; if (inApp) { [weakSelf.inAppInternal handleInApp:userInfo inApp:inApp]; } - + NSDictionary *action = [weakSelf actionFromResponse:response]; if (action && action[@"id"]) { EMSRequestModel *requestModel = [weakSelf.requestFactory createEventRequestModelWithEventName:@"push:click" eventAttributes:@{ - @"origin": @"button", - @"button_id": action[@"id"], - @"sid": [userInfo messageId]} + @"origin": @"button", + @"button_id": action[@"id"], + @"sid": [userInfo messageId]} eventType:EventTypeInternal]; [weakSelf.requestManager submitRequestModel:requestModel withCompletionBlock:nil]; diff --git a/Sources/MobileEngage/RequestTools/EMSMobileEngageRefreshTokenCompletionProxy.m b/Sources/MobileEngage/RequestTools/EMSMobileEngageRefreshTokenCompletionProxy.m index a1d6392b..3db9dfaa 100644 --- a/Sources/MobileEngage/RequestTools/EMSMobileEngageRefreshTokenCompletionProxy.m +++ b/Sources/MobileEngage/RequestTools/EMSMobileEngageRefreshTokenCompletionProxy.m @@ -62,7 +62,7 @@ - (EMSRESTClientCompletionBlock)completionBlock { weakSelf.originalResponseModel = responseModel; [weakSelf.restClient executeWithRequestModel:[weakSelf.requestFactory createRefreshTokenRequestModel] coreCompletionProxy:weakSelf]; - } else if (responseModel.isSuccess && [weakSelf.endpoint isRefreshContactTokenUrl:requestModel.url]) { + } else if (responseModel.isSuccess && [weakSelf.endpoint isRefreshContactTokenUrl:requestModel.url] && weakSelf.originalRequestModel) { [weakSelf.contactResponseHandler processResponse:responseModel]; [NSThread sleepForTimeInterval:0.5f]; weakSelf.retryCount += 1; diff --git a/Sources/MobileEngage/Session/EMSSession.m b/Sources/MobileEngage/Session/EMSSession.m index cc45edc6..73c7d9ce 100644 --- a/Sources/MobileEngage/Session/EMSSession.m +++ b/Sources/MobileEngage/Session/EMSSession.m @@ -12,6 +12,8 @@ @interface EMSSession () @property(nonatomic, strong) EMSRequestFactory *requestFactory; @property(nonatomic, strong) EMSRequestManager *requestManager; @property(nonatomic, strong) EMSTimestampProvider *timestampProvider; +@property(nonatomic, strong) NSOperationQueue *operationQueue; +@property(nonatomic, strong) NSMutableArray *observers; @end @@ -32,45 +34,54 @@ - (instancetype)initWithSessionIdHolder:(EMSSessionIdHolder *)sessionIdHolder _requestFactory = requestFactory; _timestampProvider = timestampProvider; _sessionIdHolder = sessionIdHolder; + _operationQueue = operationQueue; + _observers = [NSMutableArray array]; __weak typeof(self) weakSelf = self; - [NSNotificationCenter.defaultCenter addObserverForName:UIApplicationDidBecomeActiveNotification - object:nil - queue:operationQueue - usingBlock:^(NSNotification *notification) { + id becomeActiveObserver = [NSNotificationCenter.defaultCenter addObserverForName:UIApplicationDidBecomeActiveNotification + object:nil + queue:nil + usingBlock:^(NSNotification *notification) { [weakSelf startSessionWithCompletionBlock:nil]; }]; - [NSNotificationCenter.defaultCenter addObserverForName:UIApplicationDidEnterBackgroundNotification - object:nil - queue:operationQueue - usingBlock:^(NSNotification *notification) { + id enterBackgroundObserver = [NSNotificationCenter.defaultCenter addObserverForName:UIApplicationDidEnterBackgroundNotification + object:nil + queue:nil + usingBlock:^(NSNotification *notification) { [weakSelf stopSessionWithCompletionBlock:nil]; }]; + [_observers addObject:becomeActiveObserver]; + [_observers addObject:enterBackgroundObserver]; } return self; } - (void)startSessionWithCompletionBlock:(_Nullable EMSCompletionBlock)completionBlock { - - self.sessionIdHolder.sessionId = [NSUUID UUID].UUIDString; - self.sessionStartTime = [self.timestampProvider provideTimestamp]; - EMSRequestModel *requestModel = [self.requestFactory createEventRequestModelWithEventName:@"session:start" - eventAttributes:nil - eventType:EventTypeInternal]; - [self.requestManager submitRequestModel:requestModel - withCompletionBlock:completionBlock]; + __weak typeof(self) weakSelf = self; + [self.operationQueue addOperationWithBlock:^{ + weakSelf.sessionIdHolder.sessionId = [NSUUID UUID].UUIDString; + weakSelf.sessionStartTime = [weakSelf.timestampProvider provideTimestamp]; + EMSRequestModel *requestModel = [weakSelf.requestFactory createEventRequestModelWithEventName:@"session:start" + eventAttributes:nil + eventType:EventTypeInternal]; + [weakSelf.requestManager submitRequestModel:requestModel + withCompletionBlock:completionBlock]; + }]; } - (void)stopSessionWithCompletionBlock:(_Nullable EMSCompletionBlock)completionBlock { - NSDate *sessionStopTime = [self.timestampProvider provideTimestamp]; - NSString *elapsedTime = [[sessionStopTime numberValueInMillisFromDate:self.sessionStartTime] stringValue]; - NSMutableDictionary *eventAttributes = [NSMutableDictionary dictionary]; - eventAttributes[@"duration"] = elapsedTime; - EMSRequestModel *requestModel = [self.requestFactory createEventRequestModelWithEventName:@"session:end" - eventAttributes:[NSDictionary dictionaryWithDictionary:eventAttributes] - eventType:EventTypeInternal]; - [self.requestManager submitRequestModel:requestModel - withCompletionBlock:completionBlock]; - self.sessionIdHolder.sessionId = nil; + __weak typeof(self) weakSelf = self; + [self.operationQueue addOperationWithBlock:^{ + NSDate *sessionStopTime = [weakSelf.timestampProvider provideTimestamp]; + NSString *elapsedTime = [[sessionStopTime numberValueInMillisFromDate:self.sessionStartTime] stringValue]; + NSMutableDictionary *eventAttributes = [NSMutableDictionary dictionary]; + eventAttributes[@"duration"] = elapsedTime; + EMSRequestModel *requestModel = [weakSelf.requestFactory createEventRequestModelWithEventName:@"session:end" + eventAttributes:[NSDictionary dictionaryWithDictionary:eventAttributes] + eventType:EventTypeInternal]; + [weakSelf.requestManager submitRequestModel:requestModel + withCompletionBlock:completionBlock]; + weakSelf.sessionIdHolder.sessionId = nil; + }]; } @end diff --git a/Sources/MobileEngage/Storage/IAM/ButtonClick/MEButtonClickMapper.m b/Sources/MobileEngage/Storage/IAM/ButtonClick/MEButtonClickMapper.m index fe5b5196..d3c96262 100644 --- a/Sources/MobileEngage/Storage/IAM/ButtonClick/MEButtonClickMapper.m +++ b/Sources/MobileEngage/Storage/IAM/ButtonClick/MEButtonClickMapper.m @@ -5,13 +5,32 @@ #import "MEButtonClickMapper.h" #import "MEButtonClick.h" #import "MEButtonClickContract.h" +#import "EMSMacros.h" +#import "EMSStatusLog.h" + +@interface MEButtonClickMapper() + +- (nullable NSString *)mapToString:(const unsigned char *)utf8String; + +@end @implementation MEButtonClickMapper - (id)modelFromStatement:(sqlite3_stmt *)statement { - NSString *campaignId = [NSString stringWithUTF8String:(const char *) sqlite3_column_text(statement, 0)]; - NSString *buttonId = [NSString stringWithUTF8String:(const char *) sqlite3_column_text(statement, 1)]; + NSString *campaignId = [self mapToString:sqlite3_column_text(statement, 0)]; + NSString *buttonId = [self mapToString:sqlite3_column_text(statement, 1)]; NSDate *timestamp = [NSDate dateWithTimeIntervalSince1970:sqlite3_column_double(statement, 2)]; + if (!campaignId || !buttonId || !timestamp) { + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"campaignId"] = campaignId; + parameters[@"buttonId"] = buttonId; + parameters[@"timestamp"] = [timestamp description]; + EMSStatusLog *logEntry = [[EMSStatusLog alloc] initWithClass:[self class] + sel:_cmd + parameters:[NSDictionary dictionaryWithDictionary:parameters] + status:nil]; + EMSLog(logEntry, LogLevelError); + } return [[MEButtonClick alloc] initWithCampaignId:campaignId buttonId:buttonId timestamp:timestamp]; @@ -33,4 +52,12 @@ - (NSUInteger)fieldCount { return 3; } +- (nullable NSString *)mapToString:(const unsigned char *)utf8String { + NSString *result = nil; + if (utf8String) { + result = [NSString stringWithUTF8String:(char *)utf8String]; + } + return result; +} + @end diff --git a/Sources/MobileEngage/Storage/IAM/DisplayedIAM/MEDisplayedIAMMapper.m b/Sources/MobileEngage/Storage/IAM/DisplayedIAM/MEDisplayedIAMMapper.m index 0ff73ee2..9cc7d09e 100644 --- a/Sources/MobileEngage/Storage/IAM/DisplayedIAM/MEDisplayedIAMMapper.m +++ b/Sources/MobileEngage/Storage/IAM/DisplayedIAM/MEDisplayedIAMMapper.m @@ -5,12 +5,30 @@ #import "MEDisplayedIAMMapper.h" #import "MEDisplayedIAM.h" #import "MEDisplayedIAMContract.h" +#import "EMSMacros.h" +#import "EMSStatusLog.h" + +@interface MEDisplayedIAMMapper() + +- (nullable NSString *)mapToString:(const unsigned char *)utf8String; + +@end @implementation MEDisplayedIAMMapper - (id)modelFromStatement:(sqlite3_stmt *)statement { - NSString *campaignId = [NSString stringWithUTF8String:(const char *) sqlite3_column_text(statement, 0)]; + NSString *campaignId = [self mapToString:sqlite3_column_text(statement, 0)]; NSDate *timestamp = [NSDate dateWithTimeIntervalSince1970:sqlite3_column_double(statement, 1)]; + if (!campaignId || !timestamp) { + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + parameters[@"campaignId"] = campaignId; + parameters[@"timestamp"] = [timestamp description]; + EMSStatusLog *logEntry = [[EMSStatusLog alloc] initWithClass:[self class] + sel:_cmd + parameters:[NSDictionary dictionaryWithDictionary:parameters] + status:nil]; + EMSLog(logEntry, LogLevelError); + } return [[MEDisplayedIAM alloc] initWithCampaignId:campaignId timestamp:timestamp]; } @@ -29,5 +47,12 @@ - (NSUInteger)fieldCount { return 2; } +- (nullable NSString *)mapToString:(const unsigned char *)utf8String { + NSString *result = nil; + if (utf8String) { + result = [NSString stringWithUTF8String:(char *)utf8String]; + } + return result; +} -@end \ No newline at end of file +@end diff --git a/Sources/Predict/EMSCartItemUtils.m b/Sources/Predict/EMSCartItemUtils.m index d51a20b9..6963cc3e 100644 --- a/Sources/Predict/EMSCartItemUtils.m +++ b/Sources/Predict/EMSCartItemUtils.m @@ -3,7 +3,7 @@ // #import "EMSCartItemUtils.h" - +#import "NSString+EMSCore.h" @implementation EMSCartItemUtils { @@ -23,9 +23,9 @@ + (NSString *)queryParamFromCartItems:(NSArray> *)cartI + (NSString *)queryParamFromCartItem:(id )cartItem { return [NSString stringWithFormat:@"i:%@,p:%@,q:%@", - [cartItem itemId], + [[cartItem itemId] percentEncode], @([cartItem price]).stringValue, @([cartItem quantity]).stringValue]; } -@end \ No newline at end of file +@end diff --git a/Sources/Predict/EMSPredictInternal.m b/Sources/Predict/EMSPredictInternal.m index 2b0dde69..78edc69b 100644 --- a/Sources/Predict/EMSPredictInternal.m +++ b/Sources/Predict/EMSPredictInternal.m @@ -12,6 +12,7 @@ #import "EMSProductMapper.h" #import "EMSLogic.h" #import "EMSProduct.h" +#import "NSString+EMSCore.h" @interface EMSPredictInternal () @@ -81,7 +82,7 @@ - (void)trackItemViewWithItemId:(NSString *)itemId { [builder setType:@"predict_item_view"]; [builder addPayloadEntryWithKey:@"v" value:[NSString stringWithFormat:@"i:%@", - itemId]]; + [itemId percentEncode]]]; } timestampProvider:[self.requestContext timestampProvider] uuidProvider:[self.requestContext uuidProvider]]; @@ -281,7 +282,7 @@ - (void)trackRecommendationClick:(id )product { [builder setType:@"predict_item_view"]; [builder addPayloadEntryWithKey:@"v" value:[NSString stringWithFormat:@"i:%@,t:%@,c:%@", - product.productId, + [product.productId percentEncode], product.feature, product.cohort]]; } diff --git a/Sources/Predict/Mapper/EMSProductMapper.m b/Sources/Predict/Mapper/EMSProductMapper.m index 14cfa9d3..e0e8a9d3 100644 --- a/Sources/Predict/Mapper/EMSProductMapper.m +++ b/Sources/Predict/Mapper/EMSProductMapper.m @@ -15,43 +15,50 @@ @implementation EMSProductMapper NSDictionary *responseData = [responseModel parsedBody]; NSMutableArray *products = [NSMutableArray new]; - for (NSString *featureName in ((NSDictionary *) responseData[@"features"]).allKeys) { - for (NSDictionary *item in responseData[@"features"][featureName][@"items"]) { - NSString *key = item[@"id"]; - NSMutableDictionary *productData = [responseData[@"products"][key] mutableCopy]; - EMSProduct *product = [EMSProduct makeWithBuilder:^(EMSProductBuilder *builder) { - NSCharacterSet *allowedCharacters = [NSCharacterSet URLQueryAllowedCharacterSet]; - NSString *link = [[productData takeValueForKey:@"link"] stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; - [builder setRequiredFieldsWithProductId:[productData takeValueForKey:@"item"] - title:[productData takeValueForKey:@"title"] - linkUrl:[[NSURL alloc] initWithString:link] - feature:featureName - cohort:responseData[@"cohort"]]; - - [builder setCategoryPath:[productData takeValueForKey:@"category"]]; - [builder setAvailable:[productData takeValueForKey:@"available"]]; - [builder setMsrp:[productData takeValueForKey:@"msrp"]]; - [builder setPrice:[productData takeValueForKey:@"price"]]; - - NSString *imageUrl = [[productData takeValueForKey:@"image"] stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; - [builder setImageUrl:imageUrl ? [[NSURL alloc] initWithString:imageUrl] : nil]; - - NSString *zoomImageUrl = [[productData takeValueForKey:@"zoom_image"] stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; - [builder setZoomImageUrl:zoomImageUrl ? [[NSURL alloc] initWithString:zoomImageUrl] : nil]; - - [builder setProductDescription:[productData takeValueForKey:@"description"]]; - [builder setAlbum:[productData takeValueForKey:@"album"]]; - [builder setActor:[productData takeValueForKey:@"actor"]]; - [builder setArtist:[productData takeValueForKey:@"artist"]]; - [builder setAuthor:[productData takeValueForKey:@"author"]]; - [builder setBrand:[productData takeValueForKey:@"brand"]]; - [builder setYear:[productData takeValueForKey:@"year"]]; - [builder setCustomFields:[NSDictionary dictionaryWithDictionary:productData]]; - }]; - [products addObject:product]; + if (responseData && [responseData.allKeys containsObject:@"features"]) { + for (NSString *featureName in ((NSDictionary *) responseData[@"features"]).allKeys) { + for (NSDictionary *item in responseData[@"features"][featureName][@"items"]) { + NSString *key = item[@"id"]; + if (key && [responseData.allKeys containsObject:@"products"]) { + NSMutableDictionary *productData = [responseData[@"products"][key] mutableCopy]; + if (productData) { + EMSProduct *product = [EMSProduct makeWithBuilder:^(EMSProductBuilder *builder) { + NSCharacterSet *allowedCharacters = [NSCharacterSet URLQueryAllowedCharacterSet]; + NSString *link = [[productData takeValueForKey:@"link"] stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; + [builder setRequiredFieldsWithProductId:[productData takeValueForKey:@"item"] + title:[productData takeValueForKey:@"title"] + linkUrl:[[NSURL alloc] initWithString:link] + feature:featureName + cohort:responseData[@"cohort"]]; + + [builder setCategoryPath:[productData takeValueForKey:@"category"]]; + [builder setAvailable:[productData takeValueForKey:@"available"]]; + [builder setMsrp:[productData takeValueForKey:@"msrp"]]; + [builder setPrice:[productData takeValueForKey:@"price"]]; + + NSString *imageUrl = [[productData takeValueForKey:@"image"] stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; + [builder setImageUrl:imageUrl ? [[NSURL alloc] initWithString:imageUrl] : nil]; + + NSString *zoomImageUrl = [[productData takeValueForKey:@"zoom_image"] stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; + [builder setZoomImageUrl:zoomImageUrl ? [[NSURL alloc] initWithString:zoomImageUrl] : nil]; + + [builder setProductDescription:[productData takeValueForKey:@"description"]]; + [builder setAlbum:[productData takeValueForKey:@"album"]]; + [builder setActor:[productData takeValueForKey:@"actor"]]; + [builder setArtist:[productData takeValueForKey:@"artist"]]; + [builder setAuthor:[productData takeValueForKey:@"author"]]; + [builder setBrand:[productData takeValueForKey:@"brand"]]; + [builder setYear:[productData takeValueForKey:@"year"]]; + [builder setCustomFields:[NSDictionary dictionaryWithDictionary:productData]]; + }]; + [products addObject:product]; + } + } + } } + } return products; } -@end \ No newline at end of file +@end diff --git a/Sources/Predict/Recommendations/EMSLogic.m b/Sources/Predict/Recommendations/EMSLogic.m index f3e1ca3d..bbec228f 100644 --- a/Sources/Predict/Recommendations/EMSLogic.m +++ b/Sources/Predict/Recommendations/EMSLogic.m @@ -5,6 +5,7 @@ #import "EMSLogic.h" #import "EMSCartItemProtocol.h" #import "EMSCartItemUtils.h" +#import "NSString+EMSCore.h" @interface EMSLogic () @@ -61,7 +62,7 @@ + (EMSLogic *)related { + (EMSLogic *)relatedWithViewItemId:(nullable NSString *)itemId { NSMutableDictionary *data = [NSMutableDictionary dictionary]; if (itemId) { - data[@"v"] = [NSString stringWithFormat:@"i:%@", itemId]; + data[@"v"] = [NSString stringWithFormat:@"i:%@", [itemId percentEncode]]; } return [[EMSLogic alloc] initWithLogic:@"RELATED" data:[NSDictionary dictionaryWithDictionary:data] @@ -87,7 +88,7 @@ + (EMSLogic *)alsoBought { + (EMSLogic *)alsoBoughtWithViewItemId:(nullable NSString *)itemId { NSMutableDictionary *data = [NSMutableDictionary dictionary]; if (itemId) { - data[@"v"] = [NSString stringWithFormat:@"i:%@", itemId]; + data[@"v"] = [NSString stringWithFormat:@"i:%@", [itemId percentEncode]]; } return [[EMSLogic alloc] initWithLogic:@"ALSO_BOUGHT" data:[NSDictionary dictionaryWithDictionary:data] @@ -157,4 +158,4 @@ - (NSUInteger)hash { } -@end \ No newline at end of file +@end diff --git a/Sources/Private/EMSActionFactory.h b/Sources/Private/EMSActionFactory.h index afd90eba..a167cbcc 100644 --- a/Sources/Private/EMSActionFactory.h +++ b/Sources/Private/EMSActionFactory.h @@ -8,6 +8,7 @@ @protocol EMSActionProtocol; @protocol EMSMobileEngageProtocol; +@class UNUserNotificationCenter; NS_ASSUME_NONNULL_BEGIN @@ -17,7 +18,9 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithApplication:(UIApplication *)application - mobileEngage:(id )mobileEngage; + mobileEngage:(id )mobileEngage + userNotificationCenter:(UNUserNotificationCenter *)userNotificationCenter + operationQueue:(NSOperationQueue *)operationQueue; - (nullable id )createActionWithActionDictionary:(NSDictionary *)action; diff --git a/Sources/Private/EMSAppStartBlockProvider.h b/Sources/Private/EMSAppStartBlockProvider.h index 324b8e80..60c09dc0 100644 --- a/Sources/Private/EMSAppStartBlockProvider.h +++ b/Sources/Private/EMSAppStartBlockProvider.h @@ -12,6 +12,8 @@ @class EMSGeofenceInternal; @class EMSSdkStateLogger; @class EMSLogger; +@class EMSSQLiteHelper; +@class EMSCompletionBlockProvider; @interface EMSAppStartBlockProvider : NSObject @@ -22,7 +24,9 @@ configInternal:(EMSConfigInternal *)configInternal geofenceInternal:(EMSGeofenceInternal *)geofenceInternal sdkStateLogger:(EMSSdkStateLogger *)sdkStateLogger - logger:(EMSLogger *)logger; + logger:(EMSLogger *)logger + dbHelper:(EMSSQLiteHelper *)dbHelper + completionBlockProvider:(EMSCompletionBlockProvider *)completionBlockProvider; - (MEHandlerBlock)createAppStartEventBlock; @@ -32,4 +36,6 @@ - (MEHandlerBlock)createFetchGeofenceEventBlock; -@end \ No newline at end of file +- (MEHandlerBlock)createDbCloseEventBlock; + +@end diff --git a/Sources/Private/EMSBadgeCountAction.h b/Sources/Private/EMSBadgeCountAction.h index dc9c0861..28a90873 100644 --- a/Sources/Private/EMSBadgeCountAction.h +++ b/Sources/Private/EMSBadgeCountAction.h @@ -6,11 +6,15 @@ #import "EMSActionProtocol.h" #import +@class UNUserNotificationCenter; + @interface EMSBadgeCountAction : NSObject - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithActionDictionary:(NSDictionary *)action - application:(UIApplication *)application; + application:(UIApplication *)application + userNotificationCenter:(UNUserNotificationCenter *)userNotificationCenter + operationQueue:(NSOperationQueue *)operationQueue; -@end \ No newline at end of file +@end diff --git a/Sources/Private/EMSCompletionBlockProvider.h b/Sources/Private/EMSCompletionBlockProvider.h index caf53da2..d25b0610 100644 --- a/Sources/Private/EMSCompletionBlockProvider.h +++ b/Sources/Private/EMSCompletionBlockProvider.h @@ -1,13 +1,19 @@ +//// // -// Copyright (c) 2019 Emarsys. All rights reserved. +// Copyright © 2024 Emarsys-Technologies Kft. All rights reserved. // + #import #import "EMSBlocks.h" -@interface EMSCompletionBlockProvider : NSObject +NS_ASSUME_NONNULL_BEGIN + +@interface EMSCompletionBlockProvider: NSObject - (instancetype)initWithOperationQueue:(NSOperationQueue *)operationQueue; -- (EMSCompletion)provideCompletion:(EMSCompletion)completionBlock; +- (EMSCompletionBlock)provideCompletionBlock:(EMSCompletionBlock)completionBlock; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Sources/Private/EMSCompletionProvider.h b/Sources/Private/EMSCompletionProvider.h new file mode 100644 index 00000000..4e562cce --- /dev/null +++ b/Sources/Private/EMSCompletionProvider.h @@ -0,0 +1,13 @@ +// +// Copyright (c) 2019 Emarsys. All rights reserved. +// +#import +#import "EMSBlocks.h" + +@interface EMSCompletionProvider : NSObject + +- (instancetype)initWithOperationQueue:(NSOperationQueue *)operationQueue; + +- (EMSCompletion)provideCompletion:(EMSCompletion)completionBlock; + +@end diff --git a/Sources/Private/EMSConfigInternal.h b/Sources/Private/EMSConfigInternal.h index e5cc880a..764bd074 100644 --- a/Sources/Private/EMSConfigInternal.h +++ b/Sources/Private/EMSConfigInternal.h @@ -37,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN endpoint:(EMSEndpoint *)endpoint logger:(EMSLogger *)logger crypto:(EMSCrypto *)crypto - queue:(NSOperationQueue *)queue + coreQueue:(NSOperationQueue *)coreQueue waiter:(EMSDispatchWaiter *)waiter deviceInfoClient:(id )deviceInfoClient; diff --git a/Sources/Private/EMSDependencyContainerProtocol.h b/Sources/Private/EMSDependencyContainerProtocol.h index ba435b2e..65ccaf76 100644 --- a/Sources/Private/EMSDependencyContainerProtocol.h +++ b/Sources/Private/EMSDependencyContainerProtocol.h @@ -33,6 +33,8 @@ @class EMSEndpoint; @class MEButtonClickRepository; @protocol EMSOnEventActionProtocol; +@class EMSWrapperChecker; +@class EMSSession; @protocol EMSDependencyContainerProtocol @@ -100,4 +102,10 @@ - (MEButtonClickRepository *)buttonClickRepository; +- (NSURLSession *)urlSession; + +- (EMSWrapperChecker *)wrapperChecker; + +- (EMSSession *)session; + @end diff --git a/Sources/Private/EMSMobileEngageV3Internal.h b/Sources/Private/EMSMobileEngageV3Internal.h index 0643754e..6adb85d7 100644 --- a/Sources/Private/EMSMobileEngageV3Internal.h +++ b/Sources/Private/EMSMobileEngageV3Internal.h @@ -9,6 +9,7 @@ @class EMSRequestManager; @class MERequestContext; @class EMSSession; +@class EMSCompletionBlockProvider; @interface EMSMobileEngageV3Internal : NSObject @@ -16,6 +17,7 @@ requestManager:(EMSRequestManager *)requestManager requestContext:(MERequestContext *)requestContext storage:(id)storage - session:(EMSSession *)session; + session:(EMSSession *)session + completionBlockProvider:(EMSCompletionBlockProvider *)completionBlockProvider; @end diff --git a/Sources/Private/EMSProductBuilder.h b/Sources/Private/EMSProductBuilder.h index 450a8788..0cc07cf9 100644 --- a/Sources/Private/EMSProductBuilder.h +++ b/Sources/Private/EMSProductBuilder.h @@ -35,9 +35,9 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)setCustomFields:(NSDictionary *)customFields; -- (instancetype)setImageUrl:(NSURL *)imageUrl; +- (instancetype)setImageUrl:(nullable NSURL *)imageUrl; -- (instancetype)setZoomImageUrl:(NSURL *)zoomImageUrl; +- (instancetype)setZoomImageUrl:(nullable NSURL *)zoomImageUrl; - (instancetype)setCategoryPath:(NSString *)categoryPath; @@ -63,4 +63,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Sources/Private/EMSSQLiteHelper.h b/Sources/Private/EMSSQLiteHelper.h index 9672d162..e45a7537 100644 --- a/Sources/Private/EMSSQLiteHelper.h +++ b/Sources/Private/EMSSQLiteHelper.h @@ -17,6 +17,7 @@ @property(nonatomic, readonly) NSDictionary *registeredTriggers; - (instancetype)initWithDatabasePath:(NSString *)path - schemaDelegate:(id )schemaDelegate; + schemaDelegate:(id )schemaDelegate + operationQueue:(NSOperationQueue *)operationQueue; -@end \ No newline at end of file +@end diff --git a/Sources/Private/EMSSQLiteHelperProtocol.h b/Sources/Private/EMSSQLiteHelperProtocol.h index a5ec0b45..7e27d1a8 100644 --- a/Sources/Private/EMSSQLiteHelperProtocol.h +++ b/Sources/Private/EMSSQLiteHelperProtocol.h @@ -45,10 +45,7 @@ typedef void(^BindBlock)(sqlite3_stmt *statement); - (BOOL)executeCommand:(NSString *)command; -- (BOOL)execute:(NSString *)command - withBindBlock:(BindBlock)bindBlock; - - (NSArray *)executeQuery:(NSString *)query mapper:(id )mapper; -@end \ No newline at end of file +@end diff --git a/Sources/Private/EMSStorage.h b/Sources/Private/EMSStorage.h index 379a05c6..d2c31125 100644 --- a/Sources/Private/EMSStorage.h +++ b/Sources/Private/EMSStorage.h @@ -15,7 +15,8 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)new NS_UNAVAILABLE; - (instancetype)initWithSuiteNames:(NSArray *)suiteNames - accessGroup:(nullable NSString *)accessGroup; + accessGroup:(nullable NSString *)accessGroup + operationQueue:(NSOperationQueue *)operationQueue; - (void)setSharedData:(nullable NSData *)data forKey:(NSString *)key; @@ -24,4 +25,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Sources/Private/EMSWrapperChecker.h b/Sources/Private/EMSWrapperChecker.h index 93b1fecf..3f6f973c 100644 --- a/Sources/Private/EMSWrapperChecker.h +++ b/Sources/Private/EMSWrapperChecker.h @@ -5,15 +5,19 @@ #import @class EMSDispatchWaiter; +@protocol EMSStorageProtocol; NS_ASSUME_NONNULL_BEGIN +#define kInnerWrapperKey @"kInnerWrapperKey" + @interface EMSWrapperChecker : NSObject @property(nonatomic, readonly) NSString *wrapper; - (instancetype)initWithOperationQueue:(NSOperationQueue *)queue - waiter:(EMSDispatchWaiter *)waiter; + waiter:(EMSDispatchWaiter *)waiter + storage:(id)storage; @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/Sources/Private/MEInApp.h b/Sources/Private/MEInApp.h index e9a0c8dc..7464f0eb 100644 --- a/Sources/Private/MEInApp.h +++ b/Sources/Private/MEInApp.h @@ -18,7 +18,7 @@ @class EMSMainWindowProvider; @class EMSViewControllerProvider; @class MEButtonClickRepository; -@class EMSCompletionBlockProvider; +@class EMSCompletionProvider; typedef void (^MECompletionHandler)(void); @@ -33,7 +33,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithWindowProvider:(EMSWindowProvider *)windowProvider mainWindowProvider:(EMSMainWindowProvider *)mainWindowProvider timestampProvider:(EMSTimestampProvider *)timestampProvider - completionBlockProvider:(EMSCompletionBlockProvider *)completionBlockProvider + completionBlockProvider:(EMSCompletionProvider *)completionBlockProvider displayedIamRepository:(MEDisplayedIAMRepository *)displayedIamRepository buttonClickRepository:(MEButtonClickRepository *)buttonClickRepository operationQueue:(NSOperationQueue *)operationQueue; diff --git a/Sources/Private/NSString+EMSCore.h b/Sources/Private/NSString+EMSCore.h new file mode 100644 index 00000000..93d0a7a0 --- /dev/null +++ b/Sources/Private/NSString+EMSCore.h @@ -0,0 +1,16 @@ +// +// +// Copyright © 2024 Emarsys-Technologies Kft. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSString (EMSCore) + +- (NSString *)percentEncode; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Tests/CoreTests/Database/EMSSQLiteHelper+Test.h b/Tests/CoreTests/Database/EMSSQLiteHelper+Test.h index efc74cbe..2b357d25 100644 --- a/Tests/CoreTests/Database/EMSSQLiteHelper+Test.h +++ b/Tests/CoreTests/Database/EMSSQLiteHelper+Test.h @@ -7,5 +7,6 @@ @interface EMSSQLiteHelper (Test) - (instancetype)initWithSqlite3Db:(sqlite3 *)db - schemaDelegate:(id )schemaDelegate; -@end \ No newline at end of file + schemaDelegate:(id )schemaDelegate + operationQueue:(NSOperationQueue *)operationQueue; +@end diff --git a/Tests/CoreTests/Database/EMSSQLiteHelperTests.m b/Tests/CoreTests/Database/EMSSQLiteHelperTests.m index 712a7985..05b4bbd5 100644 --- a/Tests/CoreTests/Database/EMSSQLiteHelperTests.m +++ b/Tests/CoreTests/Database/EMSSQLiteHelperTests.m @@ -3,6 +3,7 @@ // #import "Kiwi.h" +#import #import "EMSSQLiteHelper.h" #import "EMSSqliteSchemaHandler.h" #import "EMSRequestModel.h" @@ -14,669 +15,277 @@ #import "EMSShard.h" #import "EMSShardMapper.h" #import "EMSQueryOldestRowSpecification.h" +#import "EmarsysTestUtils.h" +#import "EMSTestColumnInfo.h" +#import "EMSTestColumnInfoMapper.h" +#import "XCTestCase+Helper.h" -@interface EMSTestColumnInfo : NSObject - -@property(nonatomic, strong) NSString *columnName; -@property(nonatomic, strong) NSString *columnType; -@property(nonatomic, strong) NSString *defaultValue; -@property(nonatomic, assign) BOOL primaryKey; -@property(nonatomic, assign) BOOL notNull; - -- (instancetype)initWithColumnName:(NSString *)columnName columnType:(NSString *)columnType; - -- (instancetype)initWithColumnName:(NSString *)columnName - columnType:(NSString *)columnType - defaultValue:(NSString *)defaultValue - primaryKey:(BOOL)primaryKey - notNull:(BOOL)notNull; - -- (BOOL)isEqual:(id)other; +#define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestDB.db"] -- (BOOL)isEqualToInfo:(EMSTestColumnInfo *)info; +@interface EMSSQLiteHelperTests: XCTestCase -- (NSUInteger)hash; +@property(nonatomic, strong) EMSSQLiteHelper *dbHelper; +@property(nonatomic, strong) EMSSqliteSchemaHandler *schemaHandler; +@property(nonatomic, strong) NSOperationQueue *operationQueue; -- (NSString *)description; @end -@implementation EMSTestColumnInfo - -- (instancetype)initWithColumnName:(NSString *)columnName columnType:(NSString *)columnType { - if (self = [super init]) { - _columnName = columnName; - _columnType = columnType; - } +@implementation EMSSQLiteHelperTests - return self; +- (void)setUp { + [super setUp]; + _schemaHandler = [[EMSSqliteSchemaHandler alloc] init]; + _operationQueue = [self createTestOperationQueue]; + _dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:self.schemaHandler + operationQueue:self.operationQueue]; + [self.dbHelper open]; } -- (instancetype)initWithColumnName:(NSString *)columnName - columnType:(NSString *)columnType - defaultValue:(NSString *)defaultValue - primaryKey:(BOOL)primaryKey - notNull:(BOOL)notNull { - if (self = [super init]) { - _columnName = columnName; - _columnType = columnType; - _defaultValue = defaultValue; - _primaryKey = primaryKey; - _notNull = notNull; - } - - return self; +- (void)tearDown { + [self.dbHelper executeCommand:@"PRAGMA user_version=1;"]; + [EmarsysTestUtils clearDb:self.dbHelper]; + [super tearDown]; } -- (BOOL)isEqual:(id)other { - if (other == self) - return YES; - if (!other || ![[other class] isEqual:[self class]]) - return NO; - - return [self isEqualToInfo:other]; +- (void)testGetVersion { + [self.dbHelper executeCommand:@"PRAGMA user_version=1;"]; + XCTAssertEqual([self.dbHelper version], 1); } -- (BOOL)isEqualToInfo:(EMSTestColumnInfo *)info { - if (self == info) - return YES; - if (info == nil) - return NO; - if (self.columnName != info.columnName && ![self.columnName isEqualToString:info.columnName]) - return NO; - if (self.columnType != info.columnType && ![self.columnType isEqualToString:info.columnType]) - return NO; - if (self.defaultValue != info.defaultValue && ![self.defaultValue isEqualToString:info.defaultValue]) - return NO; - if (self.primaryKey != info.primaryKey) - return NO; - if (self.notNull != info.notNull) - return NO; - return YES; +- (void)testOpenDatabase { + [self.dbHelper executeCommand:@"PRAGMA user_version=0;"]; + EMSSqliteSchemaHandler *schemaDelegate = OCMClassMock([EMSSqliteSchemaHandler class]); + self.dbHelper.schemaHandler = schemaDelegate; + OCMExpect([schemaDelegate onCreateWithDbHelper:OCMOCK_ANY]); + + [self.dbHelper open]; + + OCMVerifyAll(schemaDelegate); } -- (NSUInteger)hash { - NSUInteger hash = [self.columnName hash]; - hash = hash * 31u + [self.columnType hash]; - hash = hash * 31u + [self.defaultValue hash]; - hash = hash * 31u + self.primaryKey; - hash = hash * 31u + self.notNull; - return hash; +- (void)testOpenDatabaseWithUpgrade { + [self.dbHelper executeCommand:@"PRAGMA user_version=2;"]; + EMSSqliteSchemaHandler *schemaDelegate = OCMClassMock([EMSSqliteSchemaHandler class]); + self.dbHelper.schemaHandler = schemaDelegate; + OCMStub([schemaDelegate schemaVersion]).andReturn(100); + OCMExpect([schemaDelegate onUpgradeWithDbHelper:OCMOCK_ANY oldVersion:2 newVersion:100]); + + [self.dbHelper open]; + + OCMVerifyAll(schemaDelegate); } -- (NSString *)description { - NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: ", NSStringFromClass([self class])]; - [description appendFormat:@"self.columnName=%@", self.columnName]; - [description appendFormat:@", self.columnType=%@", self.columnType]; - [description appendFormat:@", self.defaultValue=%@", self.defaultValue]; - [description appendFormat:@", self.primaryKey=%d", self.primaryKey]; - [description appendFormat:@", self.notNull=%d", self.notNull]; - [description appendString:@">"]; - return description; +- (void)testExecuteCommandFailure { + BOOL returnedValue = [self.dbHelper executeCommand:@"invalid sql;"]; + XCTAssertFalse(returnedValue); } -@end - - -#define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestDB.db"] - -SPEC_BEGIN(EMSSQLiteHelperTests) - - __block EMSSQLiteHelper *dbHelper; - __block EMSSqliteSchemaHandler *schemaHandler; - - beforeEach(^{ - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; - schemaHandler = [[EMSSqliteSchemaHandler alloc] init]; - dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:schemaHandler]; - }); - - afterEach(^{ - [dbHelper close]; - }); - - id (^requestModel)(NSString *url, NSDictionary *payload) = ^id(NSString *url, NSDictionary *payload) { - return [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { - [builder setUrl:url]; - [builder setMethod:HTTPMethodPOST]; - [builder setPayload:payload]; - [builder setHeaders:@{@"headerKey": @"headerValue"}]; - } timestampProvider:[EMSTimestampProvider new] uuidProvider:[EMSUUIDProvider new]]; - }; - - void (^runCommandOnTestDB)(NSString *sql) = ^(NSString *sql) { - sqlite3 *db; - sqlite3_open([TEST_DB_PATH UTF8String], &db); - sqlite3_stmt *statement; - sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, nil); - sqlite3_step(statement); - sqlite3_close(db); - }; - - int (^columnIndexByName)(NSString *columnName, sqlite3_stmt *statement) = ^int(NSString *columnName, sqlite3_stmt *statement) { - int columnIndex = -1; - for (int i = 0; i < sqlite3_column_count(statement); i++) { - NSString *currentColumnName = [NSString stringWithUTF8String:sqlite3_column_name(statement, i)]; - if ([currentColumnName isEqualToString:columnName]) { - columnIndex = i; - break; - } - } - return columnIndex; - }; - - NSArray *(^tableSchemes)(NSString *tableName) = ^NSArray *(NSString *tableName) { - NSMutableArray *result = [NSMutableArray array]; - sqlite3 *db; - sqlite3_open([TEST_DB_PATH UTF8String], &db); - sqlite3_stmt *statement; - if (sqlite3_prepare_v2(db, [[NSString stringWithFormat:@"PRAGMA table_info(%@);", - tableName] UTF8String], -1, &statement, nil) == SQLITE_OK) { - while (sqlite3_step(statement) == SQLITE_ROW) { - NSString *columnName = [NSString stringWithUTF8String:(const char *) sqlite3_column_text(statement, columnIndexByName(@"name", statement))]; - NSString *columnType = [NSString stringWithUTF8String:(const char *) sqlite3_column_text(statement, columnIndexByName(@"type", statement))]; - int primaryKey = sqlite3_column_int(statement, columnIndexByName(@"pk", statement)); - int notNull = sqlite3_column_int(statement, columnIndexByName(@"notnull", statement)); - const unsigned char *defValue = sqlite3_column_text(statement, columnIndexByName(@"dflt_value", statement)); - NSString *defaultValue; - if (defValue != nil) { - defaultValue = [NSString stringWithUTF8String:(const char *) defValue]; - } - [result addObject:[[EMSTestColumnInfo alloc] initWithColumnName:columnName - columnType:columnType - defaultValue:defaultValue - primaryKey:[@(primaryKey) boolValue] - notNull:[@(notNull) boolValue]]]; - } - } else { - fail(@"sqlite3_prepare_v2 failed"); - }; - sqlite3_close(db); - return result; - }; - - - NSArray *(^indexedColumnsOfTable)(NSString *tableName) = ^NSArray *(NSString *tableName) { - NSMutableArray *result = [NSMutableArray array]; - sqlite3 *db; - sqlite3_open([TEST_DB_PATH UTF8String], &db); - sqlite3_stmt *indexListStatement; - if (sqlite3_prepare_v2(db, [[NSString stringWithFormat:@"PRAGMA index_list('%@');", - tableName] UTF8String], -1, &indexListStatement, nil) == SQLITE_OK) { - while (sqlite3_step(indexListStatement) == SQLITE_ROW) { - NSString *indexName = [NSString stringWithUTF8String:(const char *) sqlite3_column_text(indexListStatement, columnIndexByName(@"name", indexListStatement))]; - sqlite3_stmt *indexInfoStatement; - if (sqlite3_prepare_v2(db, [[NSString stringWithFormat:@"PRAGMA index_info('%@');", - indexName] UTF8String], -1, &indexInfoStatement, nil) == SQLITE_OK) { - while (sqlite3_step(indexInfoStatement) == SQLITE_ROW) { - NSString *indexedColumnName = [NSString stringWithUTF8String:(const char *) sqlite3_column_text(indexInfoStatement, columnIndexByName(@"name", indexInfoStatement))]; - [result addObject:indexedColumnName]; - } - } else { - fail(@"sqlite3_prepare_v2 failed"); - }; - } - } else { - fail(@"sqlite3_prepare_v2 failed"); - }; - sqlite3_close(db); - return result; - }; - - void (^initializeDbWithVersion)(int version) = ^(int version) { - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; - sqlite3 *db; - sqlite3_open([TEST_DB_PATH UTF8String], &db); - dbHelper = [[EMSSQLiteHelper alloc] initWithSqlite3Db:db - schemaDelegate:schemaHandler]; - [schemaHandler onUpgradeWithDbHelper:dbHelper - oldVersion:0 - newVersion:version]; - }; - - void (^isEqualArrays)(NSArray *expectedArray, NSArray *currentArray) = ^(NSArray *expectedArray, NSArray *currentArray) { - [[theValue(expectedArray.count == currentArray.count) should] beTrue]; - [[expectedArray should] containObjectsInArray:currentArray]; - [[currentArray should] containObjectsInArray:expectedArray]; - }; - - describe(@"getVersion", ^{ - - it(@"should return the latest version", ^{ - [dbHelper open]; - [[theValue([dbHelper version]) should] equal:@1]; - }); - - it(@"should assert when version called in case of the db is not opened", ^{ - @try { - [dbHelper version]; - fail(@"Expected exception when calling version in case the db is not opened"); - } @catch (NSException *exception) { - [[theValue(exception) shouldNot] beNil]; - } - - }); - - }); - - describe(@"open", ^{ - - it(@"should call onCreate when the database is opened the first time", ^{ - EMSSqliteSchemaHandler *schemaDelegate = [EMSSqliteSchemaHandler mock]; - dbHelper.schemaHandler = schemaDelegate; - [[schemaDelegate should] receive:@selector(onCreateWithDbHelper:) - withArguments:kw_any()]; - - [dbHelper open]; - }); - - it(@"should call onUpgrade when the oldVersion and newVersion are different", ^{ - runCommandOnTestDB(@"PRAGMA user_version=2;"); - - EMSSqliteSchemaHandler *schemaDelegate = [EMSSqliteSchemaHandler mock]; - dbHelper.schemaHandler = schemaDelegate; - [[schemaDelegate should] receive:@selector(schemaVersion) andReturn:theValue(100)]; - [[schemaDelegate should] receive:@selector(onUpgradeWithDbHelper:oldVersion:newVersion:) - withArguments:kw_any(), theValue(2), theValue(100)]; - - [dbHelper open]; - }); - - }); - - describe(@"executeCommand", ^{ - - it(@"should return YES when successfully executeCommand on DB", ^{ - [dbHelper open]; - BOOL returnedValue = [dbHelper executeCommand:@"PRAGMA user_version=42;"]; - [dbHelper close]; - - sqlite3 *db; - sqlite3_open([TEST_DB_PATH UTF8String], &db); - sqlite3_stmt *statement; - if (sqlite3_prepare_v2(db, [@"PRAGMA user_version;" UTF8String], -1, &statement, nil) == SQLITE_OK) { - if (sqlite3_step(statement) == SQLITE_ROW) { - [[theValue(returnedValue) should] beTrue]; - [[theValue(sqlite3_column_int(statement, 0)) should] equal:@42]; - } else { - fail(@"sqlite3_step failed"); - } - } else { - fail(@"sqlite3_prepare_v2 failed"); - }; - sqlite3_close(db); - - }); - - it(@"should return NO when executeCommand failed", ^{ - [dbHelper open]; - BOOL returnedValue = [dbHelper executeCommand:@"invalid sql;"]; - [[theValue(returnedValue) should] beFalse]; - }); - - }); - - describe(@"remove:fromTable:where:whereArgs:", ^{ - it(@"should remove every rows when called with no where", ^{ - EMSSqliteSchemaHandler *schemaDelegate = [EMSSqliteSchemaHandler new]; - [dbHelper setSchemaHandler:schemaDelegate]; - [dbHelper open]; - EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" - type:@"type" - data:@{} - timestamp:[NSDate date] - ttl:200.]; - EMSShardMapper *mapper = [EMSShardMapper new]; - - [dbHelper insertModel:model mapper:mapper]; - [dbHelper insertModel:model mapper:mapper]; - [dbHelper removeFromTable:[mapper tableName] - selection:nil - selectionArgs:nil]; - - NSArray *result = [dbHelper executeQuery:SQL_SHARD_SELECTALL mapper:mapper]; - [[result should] beEmpty]; - }); - - it(@"should remove every rows that matches to the where parameters", ^{ - EMSSqliteSchemaHandler *schemaDelegate = [EMSSqliteSchemaHandler new]; - [dbHelper setSchemaHandler:schemaDelegate]; - [dbHelper open]; - EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" - type:@"type" - data:@{} - timestamp:[NSDate date] - ttl:200.]; - EMSShard *model2 = [[EMSShard alloc] initWithShardId:@"id" - type:@"type2" - data:@{} - timestamp:[NSDate date] - ttl:200.]; - EMSShardMapper *mapper = [EMSShardMapper new]; - - [dbHelper insertModel:model mapper:mapper]; - [dbHelper insertModel:model2 mapper:mapper]; - [dbHelper removeFromTable:[mapper tableName] - selection:@"type=? AND ttl=?" - selectionArgs:@[@"type", @"200"]]; - - NSArray *result = [dbHelper executeQuery:SQL_SHARD_SELECTALL mapper:mapper]; - - [[result should] contain:model2]; - [[result shouldNot] contain:model]; - [[theValue([result count]) should] equal:theValue(1)]; - }); - - it(@"should remove every rows that matches to the where parameters", ^{ - EMSSqliteSchemaHandler *schemaDelegate = [EMSSqliteSchemaHandler new]; - [dbHelper setSchemaHandler:schemaDelegate]; - [dbHelper open]; - EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" - type:@"type" - data:@{} - timestamp:[NSDate date] - ttl:200.]; - EMSShard *model2 = [[EMSShard alloc] initWithShardId:@"id" - type:@"type2" - data:@{} - timestamp:[NSDate date] - ttl:200.]; - EMSShardMapper *mapper = [EMSShardMapper new]; - - [dbHelper insertModel:model mapper:mapper]; - [dbHelper insertModel:model2 mapper:mapper]; - [dbHelper removeFromTable:[mapper tableName] - selection:@"type=? AND ttl=?" - selectionArgs:@[@"type", @"300"]]; - - NSArray *result = [dbHelper executeQuery:SQL_SHARD_SELECTALL - mapper:mapper]; - - [[result should] contain:model2]; - [[result should] contain:model]; - [[theValue([result count]) should] equal:theValue(2)]; - }); - }); - - describe(@"queryWithTable:selection:selectionArgs:orderBy:limit:mapper:", ^{ - it(@"tableName should not be nil", ^{ - EMSShardMapper *mapper = [EMSShardMapper new]; - - @try { - [dbHelper queryWithTable:nil - selection:@"selection" - selectionArgs:@[@"args"] - orderBy:@"order by" - limit:@"1" - mapper:mapper]; - - fail(@"Expected exception when tableName is nil"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: tableName"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"mapper should not be nil", ^{ - - @try { - [dbHelper queryWithTable:@"tableName" - selection:@"selection" - selectionArgs:nil - orderBy:nil - limit:nil - mapper:nil]; - - fail(@"Expected exception when mapper is nil"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: mapper"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should query all item", ^{ - EMSSqliteSchemaHandler *schemaDelegate = [EMSSqliteSchemaHandler new]; - [dbHelper setSchemaHandler:schemaDelegate]; - [dbHelper open]; - EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" - type:@"type" - data:@{} - timestamp:[NSDate date] - ttl:200.]; - NSArray *expectedResult = @[model, model]; - - EMSShardMapper *mapper = [EMSShardMapper new]; - - [dbHelper insertModel:model - mapper:mapper]; - [dbHelper insertModel:model - mapper:mapper]; - - NSArray *result = [dbHelper queryWithTable:mapper.tableName - selection:nil - selectionArgs:nil - orderBy:nil - limit:nil - mapper:mapper]; - [[result should] equal:expectedResult]; - }); - - it(@"should query the expected type", ^{ - EMSSqliteSchemaHandler *schemaDelegate = [EMSSqliteSchemaHandler new]; - [dbHelper setSchemaHandler:schemaDelegate]; - [dbHelper open]; - EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" - type:@"type" - data:@{} - timestamp:[NSDate date] - ttl:200.]; - EMSShard *model2 = [[EMSShard alloc] initWithShardId:@"id2" - type:@"shard" - data:@{} - timestamp:[NSDate date] - ttl:200.]; - NSArray *expectedResult = @[model2]; - - EMSShardMapper *mapper = [EMSShardMapper new]; - - [dbHelper insertModel:model - mapper:mapper]; - [dbHelper insertModel:model2 - mapper:mapper]; - - NSArray *result = [dbHelper queryWithTable:mapper.tableName - selection:@"type LIKE ?" - selectionArgs:@[@"shard"] - orderBy:nil - limit:nil - mapper:mapper]; - [[result should] equal:expectedResult]; - }); - it(@"should query the expected count", ^{ - EMSSqliteSchemaHandler *schemaDelegate = [EMSSqliteSchemaHandler new]; - [dbHelper setSchemaHandler:schemaDelegate]; - [dbHelper open]; - - EMSRequestModel *model = requestModel(@"https://www.google.com", @{ - @"key": @"value" - }); - EMSRequestModel *model2 = requestModel(@"https://www.google.com", @{ - @"key": @"value" - }); - - NSArray *expectedResult = @[model]; - - EMSRequestModelMapper *mapper = [EMSRequestModelMapper new]; - - [dbHelper insertModel:model - mapper:mapper]; - [dbHelper insertModel:model2 - mapper:mapper]; - - EMSQueryOldestRowSpecification *queryByType = [[EMSQueryOldestRowSpecification alloc] init]; - - NSArray *result = [dbHelper queryWithTable:mapper.tableName - selection:nil - selectionArgs:nil - orderBy:@"ROWID ASC" - limit:@"1" - mapper:mapper]; - [[result should] equal:expectedResult]; - }); - - }); - - describe(@"insertModel:withQuery:mapper:", ^{ - it(@"should insert the correct model in the database", ^{ - EMSSqliteSchemaHandler *schemaDelegate = [EMSSqliteSchemaHandler new]; - [dbHelper setSchemaHandler:schemaDelegate]; - [dbHelper open]; - EMSRequestModel *model = requestModel(@"https://www.google.com", @{ - @"key": @"value" - }); - EMSRequestModelMapper *mapper = [EMSRequestModelMapper new]; - - BOOL returnedValue = [dbHelper insertModel:model - withQuery:SQL_REQUEST_INSERT - mapper:mapper]; - NSArray *requests = [dbHelper executeQuery:SQL_REQUEST_SELECTFIRST - mapper:mapper]; - EMSRequestModel *request = [requests firstObject]; - [[theValue(returnedValue) should] beTrue]; - [[model should] equal:request]; - }); - }); - - describe(@"insertModel:mapper:", ^{ - - it(@"should insert the correct model in the database", ^{ - EMSSqliteSchemaHandler *schemaDelegate = [EMSSqliteSchemaHandler new]; - [dbHelper setSchemaHandler:schemaDelegate]; - [dbHelper open]; - EMSRequestModel *model = requestModel(@"https://www.google.com", @{ - @"key": @"value" - }); - EMSRequestModelMapper *mapper = [EMSRequestModelMapper new]; - - BOOL returnedValue = [dbHelper insertModel:model - mapper:mapper]; - NSArray *requests = [dbHelper executeQuery:SQL_REQUEST_SELECTFIRST - mapper:mapper]; - EMSRequestModel *request = [requests firstObject]; - [[theValue(returnedValue) should] beTrue]; - [[model should] equal:request]; - }); - - it(@"should insert the correct model in the database", ^{ - EMSSqliteSchemaHandler *schemaDelegate = [EMSSqliteSchemaHandler new]; - [dbHelper setSchemaHandler:schemaDelegate]; - [dbHelper open]; - EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" - type:@"type" - data:@{} - timestamp:[NSDate date] - ttl:200.]; - EMSShardMapper *mapper = [EMSShardMapper new]; - - BOOL returnedValue = [dbHelper insertModel:model - mapper:mapper]; - NSArray *shards = [dbHelper executeQuery:SQL_SHARD_SELECTALL - mapper:mapper]; - EMSShard *expectedShard = [shards lastObject]; - [[theValue(returnedValue) should] beTrue]; - [[model should] equal:expectedShard]; - }); - }); - - describe(@"schemaHandler onCreate", ^{ - - it(@"should initialize the database with version three", ^{ - initializeDbWithVersion(1); - - NSArray *expectedRequestColumnInfos = tableSchemes(@"request"); - NSArray *expectedShardColumnInfos = tableSchemes(@"shard"); - [dbHelper close]; - - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; - sqlite3 *db; - sqlite3_open([TEST_DB_PATH UTF8String], &db); - dbHelper = [[EMSSQLiteHelper alloc] initWithSqlite3Db:db - schemaDelegate:schemaHandler]; - [schemaHandler onCreateWithDbHelper:dbHelper]; - - NSArray *currentRequestColumnInfos = tableSchemes(@"request"); - NSArray *currentShardColumnInfos = tableSchemes(@"shard"); +- (void)testRemoveFromTableWithoutWhereClause { + EMSSqliteSchemaHandler *schemaDelegate = [EMSSqliteSchemaHandler new]; + [self.dbHelper setSchemaHandler:schemaDelegate]; + EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" type:@"type" data:@{} timestamp:[NSDate date] ttl:200.0]; + EMSShardMapper *mapper = [EMSShardMapper new]; + + [self.dbHelper insertModel:model mapper:mapper]; + [self.dbHelper insertModel:model mapper:mapper]; + [self.dbHelper removeFromTable:[mapper tableName] selection:nil selectionArgs:nil]; + + NSArray *result = [self.dbHelper executeQuery:SQL_SHARD_SELECTALL mapper:mapper]; + XCTAssertTrue(result.count == 0); +} - isEqualArrays(expectedRequestColumnInfos, currentRequestColumnInfos); - isEqualArrays(expectedShardColumnInfos, currentShardColumnInfos); +- (void)testRemoveFromTableWithWhereClause { + EMSSqliteSchemaHandler *schemaDelegate = [EMSSqliteSchemaHandler new]; + [self.dbHelper setSchemaHandler:schemaDelegate]; + EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" type:@"type" data:@{} timestamp:[NSDate date] ttl:200.0]; + EMSShard *model2 = [[EMSShard alloc] initWithShardId:@"id" type:@"type2" data:@{} timestamp:[NSDate date] ttl:200.0]; + EMSShardMapper *mapper = [EMSShardMapper new]; + + [self.dbHelper insertModel:model mapper:mapper]; + [self.dbHelper insertModel:model2 mapper:mapper]; + [self.dbHelper removeFromTable:[mapper tableName] selection:@"type=? AND ttl=?" selectionArgs:@[@"type", @"200"]]; + + NSArray *result = [self.dbHelper executeQuery:SQL_SHARD_SELECTALL mapper:mapper]; + XCTAssertTrue([result containsObject:model2]); + XCTAssertFalse([result containsObject:model]); + XCTAssertEqual(result.count, 1); +} - [[theValue([dbHelper version]) should] equal:@1]; - }); +- (void)testRemoveFromTableWithWhereClauseNoMatch { + EMSSqliteSchemaHandler *schemaDelegate = [EMSSqliteSchemaHandler new]; + [self.dbHelper setSchemaHandler:schemaDelegate]; + EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" type:@"type" data:@{} timestamp:[NSDate date] ttl:200.0]; + EMSShard *model2 = [[EMSShard alloc] initWithShardId:@"id" type:@"type2" data:@{} timestamp:[NSDate date] ttl:200.0]; + EMSShardMapper *mapper = [EMSShardMapper new]; + + [self.dbHelper insertModel:model mapper:mapper]; + [self.dbHelper insertModel:model2 mapper:mapper]; + [self.dbHelper removeFromTable:[mapper tableName] selection:@"type=? AND ttl=?" selectionArgs:@[@"type", @"300"]]; + + NSArray *result = [self.dbHelper executeQuery:SQL_SHARD_SELECTALL mapper:mapper]; + XCTAssertTrue([result containsObject:model2]); + XCTAssertTrue([result containsObject:model]); + XCTAssertEqual(result.count, 2); +} - }); +- (void)testQueryWithTable { + EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" type:@"type" data:@{} timestamp:[NSDate date] ttl:200.0]; + NSArray *expectedResult = @[model, model]; + + EMSShardMapper *mapper = [EMSShardMapper new]; + + [self.dbHelper insertModel:model mapper:mapper]; + [self.dbHelper insertModel:model mapper:mapper]; + + NSArray *result = [self.dbHelper queryWithTable:mapper.tableName selection:nil selectionArgs:nil orderBy:nil limit:nil mapper:mapper]; + XCTAssertEqualObjects(result, expectedResult); +} - describe(@"schema migration", ^{ +- (void)testQueryWithType { + EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" type:@"type" data:@{} timestamp:[NSDate date] ttl:200.0]; + EMSShard *model2 = [[EMSShard alloc] initWithShardId:@"id2" type:@"shard" data:@{} timestamp:[NSDate date] ttl:200.0]; + NSArray *expectedResult = @[model2]; + + EMSShardMapper *mapper = [EMSShardMapper new]; + + [self.dbHelper insertModel:model mapper:mapper]; + [self.dbHelper insertModel:model2 mapper:mapper]; + + NSArray *result = [self.dbHelper queryWithTable:mapper.tableName selection:@"type LIKE ?" selectionArgs:@[@"shard"] orderBy:nil limit:nil mapper:mapper]; + XCTAssertEqualObjects(result, expectedResult); +} - it(@"should update from 0 to 1 by adding Request table to database", ^{ - initializeDbWithVersion(0); +- (void)testQueryWithLimit { + EMSRequestModel *model = [self requestModelWithUrl:@"https://www.google.com" payload:@{@"key": @"value"}]; + EMSRequestModel *model2 = [self requestModelWithUrl:@"https://www.google.com" payload:@{@"key": @"value"}]; + + NSArray *expectedResult = @[model]; + + EMSRequestModelMapper *mapper = [EMSRequestModelMapper new]; + + [self.dbHelper insertModel:model mapper:mapper]; + [self.dbHelper insertModel:model2 mapper:mapper]; + + NSArray *result = [self.dbHelper queryWithTable:mapper.tableName selection:nil selectionArgs:nil orderBy:@"ROWID ASC" limit:@"1" mapper:mapper]; + XCTAssertEqualObjects(result, expectedResult); +} - NSArray *expectedRequestColumnInfos = @[ - [[EMSTestColumnInfo alloc] initWithColumnName:@"request_id" - columnType:@"TEXT"], - [[EMSTestColumnInfo alloc] initWithColumnName:@"method" - columnType:@"TEXT"], - [[EMSTestColumnInfo alloc] initWithColumnName:@"url" - columnType:@"TEXT"], - [[EMSTestColumnInfo alloc] initWithColumnName:@"headers" - columnType:@"BLOB"], - [[EMSTestColumnInfo alloc] initWithColumnName:@"payload" - columnType:@"BLOB"], - [[EMSTestColumnInfo alloc] initWithColumnName:@"timestamp" - columnType:@"REAL"], - [[EMSTestColumnInfo alloc] initWithColumnName:@"expiry" - columnType:@"DOUBLE" - defaultValue:[NSString stringWithFormat:@"%f", DEFAULT_REQUESTMODEL_EXPIRY] - primaryKey:false - notNull:false] - ]; - NSArray *expectedShardColumnInfos = @[ - [[EMSTestColumnInfo alloc] initWithColumnName:@"shard_id" - columnType:@"TEXT"], - [[EMSTestColumnInfo alloc] initWithColumnName:@"type" - columnType:@"TEXT"], - [[EMSTestColumnInfo alloc] initWithColumnName:@"data" - columnType:@"BLOB"], - [[EMSTestColumnInfo alloc] initWithColumnName:@"timestamp" - columnType:@"REAL"], - [[EMSTestColumnInfo alloc] initWithColumnName:@"ttl" - columnType:@"REAL"] - ]; +- (void)testInsertModelWithQuery { + EMSRequestModel *model = [self requestModelWithUrl:@"https://www.google.com" payload:@{@"key": @"value"}]; + EMSRequestModelMapper *mapper = [EMSRequestModelMapper new]; + + BOOL returnedValue = [self.dbHelper insertModel:model withQuery:SQL_REQUEST_INSERT mapper:mapper]; + NSArray *requests = [self.dbHelper executeQuery:SQL_REQUEST_SELECTFIRST mapper:mapper]; + + EMSRequestModel *request = [requests firstObject]; + + XCTAssertTrue(returnedValue); + XCTAssertEqualObjects(model, request); +} - [schemaHandler onUpgradeWithDbHelper:dbHelper - oldVersion:0 - newVersion:1]; +- (void)testInsertModel { + EMSRequestModel *model = [self requestModelWithUrl:@"https://www.google.com" payload:@{@"key": @"value"}]; + EMSRequestModelMapper *mapper = [EMSRequestModelMapper new]; + + BOOL returnedValue = [self.dbHelper insertModel:model mapper:mapper]; + NSArray *requests = [self.dbHelper executeQuery:SQL_REQUEST_SELECTFIRST mapper:mapper]; + + EMSRequestModel *request = [requests firstObject]; + + XCTAssertTrue(returnedValue); + XCTAssertEqualObjects(model, request); +} - NSArray *currentRequestColumnInfos = tableSchemes(@"request"); - NSArray *currentShardColumnInfos = tableSchemes(@"shard"); +- (void)testInsertShard { + EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" type:@"type" data:@{} timestamp:[NSDate date] ttl:200.0]; + EMSShardMapper *mapper = [EMSShardMapper new]; + + BOOL returnedValue = [self.dbHelper insertModel:model mapper:mapper]; + NSArray *shards = [self.dbHelper executeQuery:SQL_SHARD_SELECTALL mapper:mapper]; + + EMSShard *expectedShard = [shards lastObject]; + + XCTAssertTrue(returnedValue); + XCTAssertEqualObjects(model, expectedShard); +} - isEqualArrays(expectedRequestColumnInfos, currentRequestColumnInfos); - isEqualArrays(expectedShardColumnInfos, currentShardColumnInfos); +- (void)testSchemaHandlerOnCreate { + NSArray *expectedRequestColumnInfos = [self tableSchemes:@"request"]; + NSArray *expectedShardColumnInfos = [self tableSchemes:@"shard"]; + + + NSArray *currentRequestColumnInfos = [self tableSchemes:@"request"]; + NSArray *currentShardColumnInfos = [self tableSchemes:@"shard"]; + + [self isEqualArrays:expectedRequestColumnInfos currentArray:currentRequestColumnInfos]; + [self isEqualArrays:expectedShardColumnInfos currentArray:currentShardColumnInfos]; + + XCTAssertEqual([self.dbHelper version], 1); +} - NSArray *indexedRequestColumns = indexedColumnsOfTable(@"request"); - [[indexedRequestColumns should] beEmpty]; +- (void)testSchemaMigration { + [self tearDown]; + [self.dbHelper executeCommand:@"DROP TABLE shard;"]; + [self.dbHelper executeCommand:@"DROP TABLE request;"]; + [self.dbHelper executeCommand:@"PRAGMA user_version=0;"]; + + NSArray *expectedRequestColumnInfos = @[ + [[EMSTestColumnInfo alloc] initWithColumnName:@"request_id" columnType:@"TEXT"], + [[EMSTestColumnInfo alloc] initWithColumnName:@"method" columnType:@"TEXT"], + [[EMSTestColumnInfo alloc] initWithColumnName:@"url" columnType:@"TEXT"], + [[EMSTestColumnInfo alloc] initWithColumnName:@"headers" columnType:@"BLOB"], + [[EMSTestColumnInfo alloc] initWithColumnName:@"payload" columnType:@"BLOB"], + [[EMSTestColumnInfo alloc] initWithColumnName:@"timestamp" columnType:@"REAL"], + [[EMSTestColumnInfo alloc] initWithColumnName:@"expiry" columnType:@"DOUBLE" defaultValue:[NSString stringWithFormat:@"%f", DEFAULT_REQUESTMODEL_EXPIRY] primaryKey:NO notNull:NO] + ]; + NSArray *expectedShardColumnInfos = @[ + [[EMSTestColumnInfo alloc] initWithColumnName:@"shard_id" columnType:@"TEXT"], + [[EMSTestColumnInfo alloc] initWithColumnName:@"type" + columnType:@"TEXT"], + [[EMSTestColumnInfo alloc] initWithColumnName:@"data" + columnType:@"BLOB"], + [[EMSTestColumnInfo alloc] initWithColumnName:@"timestamp" + columnType:@"REAL"], + [[EMSTestColumnInfo alloc] initWithColumnName:@"ttl" + columnType:@"REAL"] + ]; + + [self.schemaHandler onUpgradeWithDbHelper:self.dbHelper + oldVersion:0 + newVersion:1]; + + NSArray *currentRequestColumnInfos = [self tableSchemes:@"request"]; + NSArray *currentShardColumnInfos = [self tableSchemes:@"shard"]; + + XCTAssertEqualObjects(expectedRequestColumnInfos, currentRequestColumnInfos); + XCTAssertEqualObjects(expectedShardColumnInfos, currentShardColumnInfos); + + XCTAssertEqual([self.dbHelper version], 1); - NSArray *indexedShardColumns = indexedColumnsOfTable(@"shard"); +} - [[theValue(indexedShardColumns.count) should] equal:theValue(2)]; - [[indexedShardColumns should] contain:@"shard_id"]; - [[indexedShardColumns should] contain:@"type"]; +- (id)requestModelWithUrl:(NSString *)url payload:(NSDictionary *)payload { + return [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { + [builder setUrl:url]; + [builder setMethod:HTTPMethodPOST]; + [builder setPayload:payload]; + [builder setHeaders:@{@"headerKey": @"headerValue"}]; + } timestampProvider:[EMSTimestampProvider new] uuidProvider:[EMSUUIDProvider new]]; +} - [[theValue([dbHelper version]) should] equal:@1]; - }); +- (NSArray *)tableSchemes:(NSString *)tableName { + return [self.dbHelper executeQuery:[NSString stringWithFormat:@"PRAGMA table_info(%@);", tableName] mapper:[[EMSTestColumnInfoMapper alloc] initWithTableName:tableName]]; +} - }); +- (void)isEqualArrays:(NSArray *)expectedArray currentArray:(NSArray *)currentArray { + XCTAssertEqual(expectedArray.count, currentArray.count); + for (id obj in expectedArray) { + XCTAssertTrue([currentArray containsObject:obj]); + } + for (id obj in currentArray) { + XCTAssertTrue([expectedArray containsObject:obj]); + } +} -SPEC_END +@end diff --git a/Tests/CoreTests/Database/EMSTestColumnInfo.h b/Tests/CoreTests/Database/EMSTestColumnInfo.h new file mode 100644 index 00000000..5c88e342 --- /dev/null +++ b/Tests/CoreTests/Database/EMSTestColumnInfo.h @@ -0,0 +1,36 @@ +//// +// +// Copyright © 2024 Emarsys-Technologies Kft. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface EMSTestColumnInfo : NSObject + +@property(nonatomic, strong) NSString *columnName; +@property(nonatomic, strong) NSString *columnType; +@property(nonatomic, strong) NSString *defaultValue; +@property(nonatomic, assign) BOOL primaryKey; +@property(nonatomic, assign) BOOL notNull; + +- (instancetype)initWithColumnName:(NSString *)columnName columnType:(NSString *)columnType; + +- (instancetype)initWithColumnName:(NSString *)columnName + columnType:(NSString *)columnType + defaultValue:(NSString *)defaultValue + primaryKey:(BOOL)primaryKey + notNull:(BOOL)notNull; + +- (BOOL)isEqual:(id)other; + +- (BOOL)isEqualToInfo:(EMSTestColumnInfo *)info; + +- (NSUInteger)hash; + +- (NSString *)description; +@end + + +NS_ASSUME_NONNULL_END diff --git a/Tests/CoreTests/Database/EMSTestColumnInfo.m b/Tests/CoreTests/Database/EMSTestColumnInfo.m new file mode 100644 index 00000000..edb68df6 --- /dev/null +++ b/Tests/CoreTests/Database/EMSTestColumnInfo.m @@ -0,0 +1,82 @@ +//// +// +// Copyright © 2024 Emarsys-Technologies Kft. All rights reserved. +// + +#import "EMSTestColumnInfo.h" + +@implementation EMSTestColumnInfo + +- (instancetype)initWithColumnName:(NSString *)columnName columnType:(NSString *)columnType { + if (self = [super init]) { + _columnName = columnName; + _columnType = columnType; + } + + return self; +} + +- (instancetype)initWithColumnName:(NSString *)columnName + columnType:(NSString *)columnType + defaultValue:(NSString *)defaultValue + primaryKey:(BOOL)primaryKey + notNull:(BOOL)notNull { + if (self = [super init]) { + _columnName = columnName; + _columnType = columnType; + _defaultValue = defaultValue; + _primaryKey = primaryKey; + _notNull = notNull; + } + + return self; +} + +- (BOOL)isEqual:(id)other { + if (other == self) + return YES; + if (!other || ![[other class] isEqual:[self class]]) + return NO; + + return [self isEqualToInfo:other]; +} + +- (BOOL)isEqualToInfo:(EMSTestColumnInfo *)info { + if (self == info) + return YES; + if (info == nil) + return NO; + if (self.columnName != info.columnName && ![self.columnName isEqualToString:info.columnName]) + return NO; + if (self.columnType != info.columnType && ![self.columnType isEqualToString:info.columnType]) + return NO; + if (self.defaultValue != info.defaultValue && ![self.defaultValue isEqualToString:info.defaultValue]) + return NO; + if (self.primaryKey != info.primaryKey) + return NO; + if (self.notNull != info.notNull) + return NO; + return YES; +} + +- (NSUInteger)hash { + NSUInteger hash = [self.columnName hash]; + hash = hash * 31u + [self.columnType hash]; + hash = hash * 31u + [self.defaultValue hash]; + hash = hash * 31u + self.primaryKey; + hash = hash * 31u + self.notNull; + return hash; +} + +- (NSString *)description { + NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: ", NSStringFromClass([self class])]; + [description appendFormat:@"self.columnName=%@", self.columnName]; + [description appendFormat:@", self.columnType=%@", self.columnType]; + [description appendFormat:@", self.defaultValue=%@", self.defaultValue]; + [description appendFormat:@", self.primaryKey=%d", self.primaryKey]; + [description appendFormat:@", self.notNull=%d", self.notNull]; + [description appendString:@">"]; + return description; +} + +@end diff --git a/Tests/CoreTests/Database/EMSTestColumnInfoMapper.h b/Tests/CoreTests/Database/EMSTestColumnInfoMapper.h new file mode 100644 index 00000000..e4ca8885 --- /dev/null +++ b/Tests/CoreTests/Database/EMSTestColumnInfoMapper.h @@ -0,0 +1,17 @@ +//// +// +// Copyright © 2024 Emarsys-Technologies Kft. All rights reserved. +// + +#import +#import "EMSModelMapperProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface EMSTestColumnInfoMapper: NSObject + +- (instancetype)initWithTableName:(NSString *)tableName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Tests/CoreTests/Database/EMSTestColumnInfoMapper.m b/Tests/CoreTests/Database/EMSTestColumnInfoMapper.m new file mode 100644 index 00000000..d7ab1f1c --- /dev/null +++ b/Tests/CoreTests/Database/EMSTestColumnInfoMapper.m @@ -0,0 +1,71 @@ +// +// +// Copyright © 2024 Emarsys-Technologies Kft. All rights reserved. +// + +#import "EMSTestColumnInfoMapper.h" +#import "EMSTestColumnInfo.h" + +@interface EMSTestColumnInfoMapper() + +@property(nonatomic, strong) NSString *tableName; + +- (int)columnIndexByName:(NSString *)columnName + inStatement:(sqlite3_stmt *)statement; + +@end + +@implementation EMSTestColumnInfoMapper + +- (nonnull instancetype)initWithTableName:(nonnull NSString *)tableName { + if (self = [super init]) { + _tableName = tableName; + } + return self; +} + + +- (sqlite3_stmt *)bindStatement:(sqlite3_stmt *)statement + fromModel:(id)model { + return NULL; +} + +- (NSUInteger)fieldCount { + return 5; +} + +- (id)modelFromStatement:(sqlite3_stmt *)statement { + NSString *columnName = [NSString stringWithUTF8String:(const char *) sqlite3_column_text(statement, [self columnIndexByName:@"name" inStatement:statement])]; + NSString *columnType = [NSString stringWithUTF8String:(const char *) sqlite3_column_text(statement, [self columnIndexByName:@"type" inStatement:statement])]; + int primaryKey = sqlite3_column_int(statement, [self columnIndexByName:@"pk" inStatement:statement]); + int notNull = sqlite3_column_int(statement, [self columnIndexByName:@"notnull" inStatement:statement]); + const unsigned char *defValue = sqlite3_column_text(statement, [self columnIndexByName:@"dflt_value" inStatement:statement]); + NSString *defaultValue; + if (defValue != nil) { + defaultValue = [NSString stringWithUTF8String:(const char *) defValue]; + } + return [[EMSTestColumnInfo alloc] initWithColumnName:columnName + columnType:columnType + defaultValue:defaultValue + primaryKey:[@(primaryKey) boolValue] + notNull:[@(notNull) boolValue]]; +} + +- (NSString *)tableName { + return self.tableName; +} + +- (int)columnIndexByName:(NSString *)columnName + inStatement:(sqlite3_stmt *)statement { + int columnIndex = -1; + for (int i = 0; i < sqlite3_column_count(statement); i++) { + NSString *currentColumnName = [NSString stringWithUTF8String:sqlite3_column_name(statement, i)]; + if ([currentColumnName isEqualToString:columnName]) { + columnIndex = i; + break; + } + } + return columnIndex; +} + +@end diff --git a/Tests/CoreTests/Database/Triggers/DbTriggerTests.m b/Tests/CoreTests/Database/Triggers/DbTriggerTests.m index e3f5c908..cd45e08f 100644 --- a/Tests/CoreTests/Database/Triggers/DbTriggerTests.m +++ b/Tests/CoreTests/Database/Triggers/DbTriggerTests.m @@ -11,364 +11,201 @@ #import "EMSShardMapper.h" #import "EMSDBTriggerKey.h" #import "FakeDBTrigger.h" +#import "EmarsysTestUtils.h" +#import +#import "XCTestCase+Helper.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestDB.db"] #define TEST_SHARD_SELECT_ALL @"SELECT * FROM shard ORDER BY ROWID ASC;" -SPEC_BEGIN(DBTriggerTests) - - __block EMSSQLiteHelper *dbHelper; - __block EMSSqliteSchemaHandler *schemaHandler; - __block XCTestExpectation *defaultExpectation; - __block id dbTrigger; - - beforeEach(^{ - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; - schemaHandler = [[EMSSqliteSchemaHandler alloc] init]; - dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:schemaHandler]; - - defaultExpectation = [[XCTestExpectation alloc] initWithDescription:@"expectation"]; - dbTrigger = [[FakeDBTrigger alloc] initWithExpectation:defaultExpectation]; - }); - - afterEach(^{ - [dbHelper close]; - }); - - - void (^runCommandOnTestDB)(NSString *sql) = ^(NSString *sql) { - sqlite3 *db; - sqlite3_open([TEST_DB_PATH UTF8String], &db); - sqlite3_stmt *statement; - sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, nil); - sqlite3_step(statement); - sqlite3_close(db); - }; - - - describe(@"registerTriggerWithTableName:triggerType:triggerEvent:trigger:", ^{ - - it(@"should run afterInsert trigger when inserting something in the given table", ^{ - [dbHelper open]; - EMSShard *shard = [[EMSShard alloc] initWithShardId:@"id" - type:@"type" - data:@{} - timestamp:[NSDate date] - ttl:30]; - - [dbHelper registerTriggerWithTableName:@"shard" - triggerType:[EMSDBTriggerType afterType] - triggerEvent:[EMSDBTriggerEvent insertEvent] - trigger:dbTrigger]; - [dbHelper insertModel:shard - mapper:[EMSShardMapper new]]; - - XCTWaiterResult result = [XCTWaiter waitForExpectations:@[defaultExpectation] timeout:10]; - [[theValue(result) should] equal:theValue(XCTWaiterResultCompleted)]; - }); - - it(@"should run all afterInsert trigger when inserting something in the given table", ^{ - [dbHelper open]; - EMSShard *shard = [[EMSShard alloc] initWithShardId:@"id" - type:@"type" - data:@{} - timestamp:[NSDate date] - ttl:30]; - - XCTestExpectation *expectation1 = [[XCTestExpectation alloc] initWithDescription:@"expectation1"]; - XCTestExpectation *expectation2 = [[XCTestExpectation alloc] initWithDescription:@"expectation2"]; - XCTestExpectation *expectation3 = [[XCTestExpectation alloc] initWithDescription:@"expectation3"]; - - [dbHelper registerTriggerWithTableName:@"shard" - triggerType:[EMSDBTriggerType afterType] - triggerEvent:[EMSDBTriggerEvent insertEvent] - trigger:[[FakeDBTrigger alloc] initWithExpectation:expectation1]]; - [dbHelper registerTriggerWithTableName:@"shard" - triggerType:[EMSDBTriggerType afterType] - triggerEvent:[EMSDBTriggerEvent insertEvent] - trigger:[[FakeDBTrigger alloc] initWithExpectation:expectation2]]; - [dbHelper registerTriggerWithTableName:@"shard" - triggerType:[EMSDBTriggerType afterType] - triggerEvent:[EMSDBTriggerEvent insertEvent] - trigger:[[FakeDBTrigger alloc] initWithExpectation:expectation3]]; - - [dbHelper insertModel:shard - mapper:[EMSShardMapper new]]; - - XCTWaiterResult result = [XCTWaiter waitForExpectations:@[expectation1, expectation2, expectation3] - timeout:2 - enforceOrder:YES]; - [[theValue(result) should] equal:theValue(XCTWaiterResultCompleted)]; - }); - - it(@"should run afterInsert trigger after inserting something in the given table", ^{ - [dbHelper open]; - EMSShard *shard = [[EMSShard alloc] initWithShardId:@"id" - type:@"type" - data:@{} - timestamp:[NSDate date] - ttl:30]; - - [dbHelper registerTriggerWithTableName:@"shard" - triggerType:[EMSDBTriggerType afterType] - triggerEvent:[EMSDBTriggerEvent insertEvent] - trigger:dbTrigger]; - - [dbHelper insertModel:shard - mapper:[EMSShardMapper new]]; - - XCTWaiterResult result = [XCTWaiter waitForExpectations:@[defaultExpectation] timeout:10]; - [[theValue(result) should] equal:theValue(XCTWaiterResultCompleted)]; - - NSArray *shards = [dbHelper executeQuery:TEST_SHARD_SELECT_ALL - mapper:[EMSShardMapper new]]; - EMSShard *expectedShard = [shards lastObject]; - [[shard should] equal:expectedShard]; - [[theValue([shards count]) should] equal:theValue(1)]; - }); - - it(@"should run beforeInsert trigger when inserting something in the given table", ^{ - [dbHelper open]; - EMSShard *shard = [[EMSShard alloc] initWithShardId:@"id" - type:@"type" - data:@{} - timestamp:[NSDate date] - ttl:30]; - - [dbHelper registerTriggerWithTableName:@"shard" - triggerType:[EMSDBTriggerType beforeType] - triggerEvent:[EMSDBTriggerEvent insertEvent] - trigger:dbTrigger]; - [dbHelper insertModel:shard - mapper:[EMSShardMapper new]]; - - XCTWaiterResult result = [XCTWaiter waitForExpectations:@[defaultExpectation] timeout:10]; - [[theValue(result) should] equal:theValue(XCTWaiterResultCompleted)]; - }); - - }); - - describe(@"Database trigger tests", ^{ - const EMSShard *shard = [[EMSShard alloc] initWithShardId:@"id" - type:@"type" - data:@{} - timestamp:[NSDate date] - ttl:30]; - - __block EMSDBTriggerType *type; - __block EMSDBTriggerEvent *event; - __block NSNumber *expectedItemCount; - __block XCTestExpectation *expectation; - __block NSArray *testDataSet; - - __block id trigger; - __block void (^actionBlock)(); - - beforeEach(^{ - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; - schemaHandler = [[EMSSqliteSchemaHandler alloc] init]; - dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:schemaHandler]; - [dbHelper open]; - expectation = [[XCTestExpectation alloc] initWithDescription:@"expectation"]; - - testDataSet = @[ - @[ - [EMSDBTriggerType beforeType], - [EMSDBTriggerEvent insertEvent], - [[FakeDBTrigger alloc] initWithExpectation:expectation - triggerAction:^{ - NSArray *shards = [dbHelper executeQuery:SQL_SHARD_SELECTALL - mapper:[EMSShardMapper new]]; - [[theValue([shards count]) should] beZero]; - }], - ^{ - [dbHelper insertModel:shard - mapper:[EMSShardMapper new]]; - } - ], - @[ - [EMSDBTriggerType afterType], - [EMSDBTriggerEvent insertEvent], - [[FakeDBTrigger alloc] initWithExpectation:expectation - triggerAction:^{ - NSArray *shards = [dbHelper executeQuery:SQL_SHARD_SELECTALL - mapper:[EMSShardMapper new]]; - [[theValue([shards count]) should] equal:theValue(1)]; - }], - ^{ - [dbHelper insertModel:shard - mapper:[EMSShardMapper new]]; - } - ] - - ]; - }); - - afterEach(^{ - [dbHelper close]; - }); - - for (NSArray *parameters in testDataSet) { - type = parameters[0]; - event = parameters[1]; - trigger = parameters[2]; - actionBlock = parameters[3]; - - it([NSString stringWithFormat:@"should call the trigger action for %@ %@", type, event], ^{ - [dbHelper registerTriggerWithTableName:@"shard" - triggerType:type - triggerEvent:event - trigger:trigger]; - actionBlock(); - - - XCTWaiterResult result = [XCTWaiter waitForExpectations:@[expectation] timeout:2 enforceOrder:YES]; - [[theValue(result) should] equal:theValue(XCTWaiterResultCompleted)]; - }); - } - - }); - - describe(@"registerTriggerWithTableName:triggerType:triggerEvent:trigger:", ^{ - - it(@"should run beforeDelete trigger when deleting something from the given table", ^{ - [dbHelper open]; - - [dbHelper registerTriggerWithTableName:@"shard" - triggerType:[EMSDBTriggerType beforeType] - triggerEvent:[EMSDBTriggerEvent deleteEvent] - trigger:dbTrigger]; - - EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" - type:@"type" - data:@{} - timestamp:[NSDate date] - ttl:200.]; - EMSShard *model2 = [[EMSShard alloc] initWithShardId:@"id" - type:@"type2" - data:@{} - timestamp:[NSDate date] - ttl:200.]; - EMSShardMapper *mapper = [EMSShardMapper new]; - - [dbHelper insertModel:model mapper:mapper]; - [dbHelper insertModel:model2 mapper:mapper]; - - [dbHelper removeFromTable:[mapper tableName] - selection:@"type=? AND ttl=?" - selectionArgs:@[@"type", @"200"]]; - - XCTWaiterResult result = [XCTWaiter waitForExpectations:@[defaultExpectation] timeout:10]; - [[theValue(result) should] equal:theValue(XCTWaiterResultCompleted)]; - - }); - - it(@"should run beforeDelete and afterDelete trigger in the right sequence when deleting something from the given table", ^{ - [dbHelper open]; - - XCTestExpectation *beforeDeleteExpectation = [[XCTestExpectation alloc] initWithDescription:@"beforeDeleteExpectation"]; - XCTestExpectation *afterDeleteExpectation = [[XCTestExpectation alloc] initWithDescription:@"afterDeleteExpectation"]; - [dbHelper registerTriggerWithTableName:@"shard" - triggerType:[EMSDBTriggerType beforeType] - triggerEvent:[EMSDBTriggerEvent deleteEvent] - trigger:[[FakeDBTrigger alloc] initWithExpectation:beforeDeleteExpectation - triggerAction:^{ - NSArray *result = [dbHelper executeQuery:SQL_SHARD_SELECTALL - mapper:[EMSShardMapper new]]; - [[theValue([result count]) should] equal:theValue(2)]; - }] - ]; - - [dbHelper registerTriggerWithTableName:@"shard" - triggerType:[EMSDBTriggerType afterType] - triggerEvent:[EMSDBTriggerEvent deleteEvent] - trigger:[[FakeDBTrigger alloc] initWithExpectation:afterDeleteExpectation - triggerAction:^{ - NSArray *result = [dbHelper executeQuery:SQL_SHARD_SELECTALL - mapper:[EMSShardMapper new]]; - [[theValue([result count]) should] equal:theValue(1)]; - }]]; - - EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" - type:@"type" - data:@{} - timestamp:[NSDate date] - ttl:200.]; - EMSShard *model2 = [[EMSShard alloc] initWithShardId:@"id" - type:@"type2" - data:@{} - timestamp:[NSDate date] - ttl:200.]; - EMSShardMapper *mapper = [EMSShardMapper new]; - - [dbHelper insertModel:model mapper:mapper]; - [dbHelper insertModel:model2 mapper:mapper]; - - [dbHelper removeFromTable:[mapper tableName] - selection:@"type=? AND ttl=?" - selectionArgs:@[@"type", @"200"]]; - - XCTWaiterResult result = [XCTWaiter waitForExpectations:@[beforeDeleteExpectation, afterDeleteExpectation] - timeout:2 - enforceOrder:YES]; - [[theValue(result) should] equal:theValue(XCTWaiterResultCompleted)]; - }); - }); - - describe(@"registeredTriggers", ^{ - it(@"should run return the registered triggers", ^{ - - id afterInsertTrigger = [KWMock mockForProtocol:@protocol(EMSDBTriggerProtocol)]; - id beforeInsertTrigger = [KWMock mockForProtocol:@protocol(EMSDBTriggerProtocol)]; - id afterDeleteTrigger = [KWMock mockForProtocol:@protocol(EMSDBTriggerProtocol)]; - id beforeDeleteTrigger = [KWMock mockForProtocol:@protocol(EMSDBTriggerProtocol)]; - - [dbHelper registerTriggerWithTableName:@"shard" - triggerType:[EMSDBTriggerType afterType] - triggerEvent:[EMSDBTriggerEvent insertEvent] - trigger:afterInsertTrigger]; - [dbHelper registerTriggerWithTableName:@"shard" - triggerType:[EMSDBTriggerType beforeType] - triggerEvent:[EMSDBTriggerEvent insertEvent] - trigger:beforeInsertTrigger]; - [dbHelper registerTriggerWithTableName:@"shard" - triggerType:[EMSDBTriggerType afterType] - triggerEvent:[EMSDBTriggerEvent deleteEvent] - trigger:afterDeleteTrigger]; - [dbHelper registerTriggerWithTableName:@"shard" - triggerType:[EMSDBTriggerType beforeType] - triggerEvent:[EMSDBTriggerEvent deleteEvent] - trigger:beforeDeleteTrigger]; - - NSArray *afterInsertTriggers = dbHelper.registeredTriggers[ - [[EMSDBTriggerKey alloc] initWithTableName:@"shard" - withEvent:[EMSDBTriggerEvent insertEvent] - withType:[EMSDBTriggerType afterType]]]; - - NSArray *beforeInsertTriggers = dbHelper.registeredTriggers[ - [[EMSDBTriggerKey alloc] initWithTableName:@"shard" - withEvent:[EMSDBTriggerEvent insertEvent] - withType:[EMSDBTriggerType beforeType]]]; - - NSArray *beforeDeleteTriggers = dbHelper.registeredTriggers[ - [[EMSDBTriggerKey alloc] initWithTableName:@"shard" - withEvent:[EMSDBTriggerEvent deleteEvent] - withType:[EMSDBTriggerType beforeType]]]; - - NSArray *afterDeleteTriggers = dbHelper.registeredTriggers[ - [[EMSDBTriggerKey alloc] initWithTableName:@"shard" - withEvent:[EMSDBTriggerEvent deleteEvent] - withType:[EMSDBTriggerType afterType]]]; - - [[afterInsertTriggers should] equal:@[afterInsertTrigger]]; - [[beforeInsertTriggers should] equal:@[beforeInsertTrigger]]; - [[afterDeleteTriggers should] equal:@[afterDeleteTrigger]]; - [[beforeDeleteTriggers should] equal:@[beforeDeleteTrigger]]; - }); - }); - -SPEC_END +@interface DBTriggerTests : XCTestCase + +@property (nonatomic, strong) EMSSQLiteHelper *dbHelper; +@property (nonatomic, strong) EMSSqliteSchemaHandler *schemaHandler; +@property (nonatomic, strong) NSOperationQueue *queue; + +@end + +@implementation DBTriggerTests + +- (void)setUp { + [super setUp]; + _queue = [self createTestOperationQueue]; + self.schemaHandler = [[EMSSqliteSchemaHandler alloc] init]; + self.dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:self.schemaHandler + operationQueue:self.queue]; + [self.dbHelper open]; +} + +- (void)tearDown { + [EmarsysTestUtils clearDb:self.dbHelper]; + [super tearDown]; +} + +- (void)testRegisterTriggerWithTableNameTriggerTypeTriggerEventTrigger_afterInsert { + XCTestExpectation *defaultExpectation = [[XCTestExpectation alloc] initWithDescription:@"expectation"]; + id dbTrigger = [[FakeDBTrigger alloc] initWithExpectation:defaultExpectation]; + + EMSShard *shard = [[EMSShard alloc] initWithShardId:@"id" type:@"type" data:@{} timestamp:[NSDate date] ttl:30]; + + [self.dbHelper registerTriggerWithTableName:@"shard" triggerType:[EMSDBTriggerType afterType] triggerEvent:[EMSDBTriggerEvent insertEvent] trigger:dbTrigger]; + [self.dbHelper insertModel:shard mapper:[EMSShardMapper new]]; + + XCTWaiterResult result = [XCTWaiter waitForExpectations:@[defaultExpectation] timeout:10]; + XCTAssertEqual(result, XCTWaiterResultCompleted); +} + +- (void)testRegisterTriggerWithTableNameTriggerTypeTriggerEventTrigger_allAfterInsert { + EMSShard *shard = [[EMSShard alloc] initWithShardId:@"id" type:@"type" data:@{} timestamp:[NSDate date] ttl:30]; + + XCTestExpectation *expectation1 = [[XCTestExpectation alloc] initWithDescription:@"expectation1"]; + XCTestExpectation *expectation2 = [[XCTestExpectation alloc] initWithDescription:@"expectation2"]; + XCTestExpectation *expectation3 = [[XCTestExpectation alloc] initWithDescription:@"expectation3"]; + + [self.dbHelper registerTriggerWithTableName:@"shard" triggerType:[EMSDBTriggerType afterType] triggerEvent:[EMSDBTriggerEvent insertEvent] trigger:[[FakeDBTrigger alloc] initWithExpectation:expectation1]]; + [self.dbHelper registerTriggerWithTableName:@"shard" triggerType:[EMSDBTriggerType afterType] triggerEvent:[EMSDBTriggerEvent insertEvent] trigger:[[FakeDBTrigger alloc] initWithExpectation:expectation2]]; + [self.dbHelper registerTriggerWithTableName:@"shard" triggerType:[EMSDBTriggerType afterType] triggerEvent:[EMSDBTriggerEvent insertEvent] trigger:[[FakeDBTrigger alloc] initWithExpectation:expectation3]]; + + [self.dbHelper insertModel:shard mapper:[EMSShardMapper new]]; + + XCTWaiterResult result = [XCTWaiter waitForExpectations:@[expectation1, expectation2, expectation3] timeout:2 enforceOrder:YES]; + XCTAssertEqual(result, XCTWaiterResultCompleted); +} + +- (void)testRegisterTriggerWithTableNameTriggerTypeTriggerEventTrigger_afterInsert_verifyData { + XCTestExpectation *defaultExpectation = [[XCTestExpectation alloc] initWithDescription:@"expectation"]; + id dbTrigger = [[FakeDBTrigger alloc] initWithExpectation:defaultExpectation]; + + EMSShard *shard = [[EMSShard alloc] initWithShardId:@"id" type:@"type" data:@{} timestamp:[NSDate date] ttl:30]; + + [self.dbHelper registerTriggerWithTableName:@"shard" triggerType:[EMSDBTriggerType afterType] triggerEvent:[EMSDBTriggerEvent insertEvent] trigger:dbTrigger]; + + [self.dbHelper insertModel:shard mapper:[EMSShardMapper new]]; + + XCTWaiterResult result = [XCTWaiter waitForExpectations:@[defaultExpectation] timeout:10]; + XCTAssertEqual(result, XCTWaiterResultCompleted); + + NSArray *shards = [self.dbHelper executeQuery:TEST_SHARD_SELECT_ALL mapper:[EMSShardMapper new]]; + EMSShard *expectedShard = [shards lastObject]; + XCTAssertEqualObjects(shard, expectedShard); + XCTAssertEqual([shards count], 1); +} + +- (void)testRegisterTriggerWithTableNameTriggerTypeTriggerEventTrigger_beforeInsert { + XCTestExpectation *defaultExpectation = [[XCTestExpectation alloc] initWithDescription:@"expectation"]; + id dbTrigger = [[FakeDBTrigger alloc] initWithExpectation:defaultExpectation]; + + EMSShard *shard = [[EMSShard alloc] initWithShardId:@"id" type:@"type" data:@{} timestamp:[NSDate date] ttl:30]; + + [self.dbHelper registerTriggerWithTableName:@"shard" triggerType:[EMSDBTriggerType beforeType] triggerEvent:[EMSDBTriggerEvent insertEvent] trigger:dbTrigger]; + [self.dbHelper insertModel:shard mapper:[EMSShardMapper new]]; + + XCTWaiterResult result = [XCTWaiter waitForExpectations:@[defaultExpectation] timeout:10]; + XCTAssertEqual(result, XCTWaiterResultCompleted); +} + +- (void)testTriggerActions { + const EMSShard *shard = [[EMSShard alloc] initWithShardId:@"id" type:@"type" data:@{} timestamp:[NSDate date] ttl:30]; + + XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"expectation"]; + [expectation setExpectedFulfillmentCount:2]; + NSArray *testDataSet = @[ + @[ [EMSDBTriggerType beforeType], [EMSDBTriggerEvent insertEvent], [[FakeDBTrigger alloc] initWithExpectation:[[XCTestExpectation alloc] initWithDescription:@"expectation"] + triggerAction:^{ + [expectation fulfill]; + }], ^{ + [self.dbHelper insertModel:shard mapper:[EMSShardMapper new]]; + }], + @[ [EMSDBTriggerType afterType], [EMSDBTriggerEvent insertEvent], [[FakeDBTrigger alloc] initWithExpectation:[[XCTestExpectation alloc] initWithDescription:@"expectation"] + triggerAction:^{ + [expectation fulfill]; + }], ^{ + [self.dbHelper insertModel:shard mapper:[EMSShardMapper new]]; + }] + ]; + + for (NSArray *parameters in testDataSet) { + EMSDBTriggerType *type = parameters[0]; + EMSDBTriggerEvent *event = parameters[1]; + id trigger = parameters[2]; + void (^actionBlock)() = parameters[3]; + + [self.dbHelper registerTriggerWithTableName:@"shard" triggerType:type triggerEvent:event trigger:trigger]; + actionBlock(); + + } + XCTWaiterResult result = [XCTWaiter waitForExpectations:@[expectation] timeout:2 enforceOrder:YES]; + XCTAssertEqual(result, XCTWaiterResultCompleted); + +} + +- (void)testRegisterTriggerWithTableNameTriggerTypeTriggerEventTrigger_beforeDelete { + XCTestExpectation *defaultExpectation = [[XCTestExpectation alloc] initWithDescription:@"expectation"]; + id dbTrigger = [[FakeDBTrigger alloc] initWithExpectation:defaultExpectation]; + + [self.dbHelper registerTriggerWithTableName:@"shard" triggerType:[EMSDBTriggerType beforeType] triggerEvent:[EMSDBTriggerEvent deleteEvent] trigger:dbTrigger]; + + EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" type:@"type" data:@{} timestamp:[NSDate date] ttl:200.]; + EMSShard *model2 = [[EMSShard alloc] initWithShardId:@"id" type:@"type2" data:@{} timestamp:[NSDate date] ttl:200.]; + EMSShardMapper *mapper = [EMSShardMapper new]; + + [self.dbHelper insertModel:model mapper:mapper]; + [self.dbHelper insertModel:model2 mapper:mapper]; + + [self.dbHelper removeFromTable:[mapper tableName] selection:@"type=? AND ttl=?" selectionArgs:@[@"type", @"200"]]; + + XCTWaiterResult result = [XCTWaiter waitForExpectations:@[defaultExpectation] timeout:10]; + XCTAssertEqual(result, XCTWaiterResultCompleted); +} + +- (void)testRegisterTriggerWithTableNameTriggerTypeTriggerEventTrigger_beforeAfterDeleteSequence { + XCTestExpectation *beforeDeleteExpectation = [[XCTestExpectation alloc] initWithDescription:@"beforeDeleteExpectation"]; + XCTestExpectation *afterDeleteExpectation = [[XCTestExpectation alloc] initWithDescription:@"afterDeleteExpectation"]; + + [self.dbHelper registerTriggerWithTableName:@"shard" triggerType:[EMSDBTriggerType beforeType] triggerEvent:[EMSDBTriggerEvent deleteEvent] trigger:[[FakeDBTrigger alloc] initWithExpectation:beforeDeleteExpectation triggerAction:^{ + NSArray *result = [self.dbHelper executeQuery:SQL_SHARD_SELECTALL mapper:[EMSShardMapper new]]; + XCTAssertEqual([result count], 2); + }]]; + + [self.dbHelper registerTriggerWithTableName:@"shard" triggerType:[EMSDBTriggerType afterType] triggerEvent:[EMSDBTriggerEvent deleteEvent] trigger:[[FakeDBTrigger alloc] initWithExpectation:afterDeleteExpectation triggerAction:^{ + NSArray *result = [self.dbHelper executeQuery:SQL_SHARD_SELECTALL mapper:[EMSShardMapper new]]; + XCTAssertEqual([result count], 1); + }]]; + + EMSShard *model = [[EMSShard alloc] initWithShardId:@"id" type:@"type" data:@{} timestamp:[NSDate date] ttl:200.]; + EMSShard *model2 = [[EMSShard alloc] initWithShardId:@"id" type:@"type2" data:@{} timestamp:[NSDate date] ttl:200.]; + EMSShardMapper *mapper = [EMSShardMapper new]; + + [self.dbHelper insertModel:model mapper:mapper]; + [self.dbHelper insertModel:model2 mapper:mapper]; + + [self.dbHelper removeFromTable:[mapper tableName] selection:@"type=? AND ttl=?" selectionArgs:@[@"type", @"200"]]; + + XCTWaiterResult result = [XCTWaiter waitForExpectations:@[beforeDeleteExpectation, afterDeleteExpectation] timeout:2 enforceOrder:YES]; + XCTAssertEqual(result, XCTWaiterResultCompleted); +} + +- (void)testRegisteredTriggers { + id afterInsertTrigger = OCMProtocolMock(@protocol(EMSDBTriggerProtocol)); + id beforeInsertTrigger = OCMProtocolMock(@protocol(EMSDBTriggerProtocol)); + id afterDeleteTrigger = OCMProtocolMock(@protocol(EMSDBTriggerProtocol)); + id beforeDeleteTrigger = OCMProtocolMock(@protocol(EMSDBTriggerProtocol)); + + [self.dbHelper registerTriggerWithTableName:@"shard" triggerType:[EMSDBTriggerType afterType] triggerEvent:[EMSDBTriggerEvent insertEvent] trigger:afterInsertTrigger]; + [self.dbHelper registerTriggerWithTableName:@"shard" triggerType:[EMSDBTriggerType beforeType] triggerEvent:[EMSDBTriggerEvent insertEvent] trigger:beforeInsertTrigger]; + [self.dbHelper registerTriggerWithTableName:@"shard" triggerType:[EMSDBTriggerType afterType] triggerEvent:[EMSDBTriggerEvent deleteEvent] trigger:afterDeleteTrigger]; + [self.dbHelper registerTriggerWithTableName:@"shard" triggerType:[EMSDBTriggerType beforeType] triggerEvent:[EMSDBTriggerEvent deleteEvent] trigger:beforeDeleteTrigger]; + + NSArray *afterInsertTriggers = self.dbHelper.registeredTriggers[[[EMSDBTriggerKey alloc] initWithTableName:@"shard" withEvent:[EMSDBTriggerEvent insertEvent] withType:[EMSDBTriggerType afterType]]]; + NSArray *beforeInsertTriggers = self.dbHelper.registeredTriggers[[[EMSDBTriggerKey alloc] initWithTableName:@"shard" withEvent:[EMSDBTriggerEvent insertEvent] withType:[EMSDBTriggerType beforeType]]]; + NSArray *beforeDeleteTriggers = self.dbHelper.registeredTriggers[[[EMSDBTriggerKey alloc] initWithTableName:@"shard" withEvent:[EMSDBTriggerEvent deleteEvent] withType:[EMSDBTriggerType beforeType]]]; + NSArray *afterDeleteTriggers = self.dbHelper.registeredTriggers[[[EMSDBTriggerKey alloc] initWithTableName:@"shard" withEvent:[EMSDBTriggerEvent deleteEvent] withType:[EMSDBTriggerType afterType]]]; + + XCTAssertEqualObjects(afterInsertTriggers, @[afterInsertTrigger]); + XCTAssertEqualObjects(beforeInsertTriggers, @[beforeInsertTrigger]); + XCTAssertEqualObjects(afterDeleteTriggers, @[afterDeleteTrigger]); + XCTAssertEqualObjects(beforeDeleteTriggers, @[beforeDeleteTrigger]); +} + +@end diff --git a/Tests/CoreTests/DennaTests.m b/Tests/CoreTests/DennaTests.m index dd8add6a..881507bd 100644 --- a/Tests/CoreTests/DennaTests.m +++ b/Tests/CoreTests/DennaTests.m @@ -2,7 +2,8 @@ // Copyright (c) 2017 Emarsys. All rights reserved. // -#import "Kiwi.h" +#import +#import #import "EMSRequestModel.h" #import "EMSRequestManager.h" #import "EMSResponseModel.h" @@ -18,227 +19,247 @@ #import "EMSRESTClientCompletionProxyFactory.h" #import "EMSMobileEngageNullSafeBodyParser.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" #define DennaUrl(ending) [NSString stringWithFormat:@"https://denna.gservice.emarsys.net%@", ending]; #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestDB.db"] -#define DB_PATH [[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"EMSSQLiteQueueDB.db"] -SPEC_BEGIN(DennaTest) - - NSString *error500 = DennaUrl(@"/customResponseCode/500"); - NSString *echo = DennaUrl(@"/echo"); - NSDictionary *inputHeaders = @{@"header1": @"value1", @"header2": @"value2"}; - NSDictionary *payload = @{@"key1": @"val1", @"key2": @"val2", @"key3": @"val3"}; - -__block NSOperationQueue *queue; - -beforeEach(^{ - queue = [self createTestOperationQueue]; -}); - -afterEach(^{ - [self tearDownOperationQueue:queue]; -}); - - void (^shouldEventuallySucceed)(EMSRequestModel *model, NSString *method, NSDictionary *headers, NSDictionary *body) = ^(EMSRequestModel *model, NSString *method, NSDictionary *headers, NSDictionary *body) { - __block NSString *checkableRequestId; - __block NSString *resultMethod; - __block BOOL expectedSubsetOfResultHeaders; - __block NSDictionary *resultPayload; - - EMSCompletionMiddleware *middleware = [[EMSCompletionMiddleware alloc] initWithSuccessBlock:^(NSString *requestId, EMSResponseModel *response) { - checkableRequestId = requestId; - NSDictionary *returnedPayload = [NSJSONSerialization JSONObjectWithData:response.body - options:NSJSONReadingFragmentsAllowed - error:nil]; - NSLog(@"RequestId: %@, responsePayload: %@", requestId, returnedPayload); - resultMethod = returnedPayload[@"method"]; - expectedSubsetOfResultHeaders = [returnedPayload[@"headers"] subsetOfDictionary:headers]; - resultPayload = returnedPayload[@"body"]; - - } - errorBlock:^(NSString *requestId, NSError *error) { - NSLog(@"ERROR!"); - fail(@"errorblock invoked"); - }]; - - EMSRequestModelRepository *requestRepository = [[EMSRequestModelRepository alloc] initWithDbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]] - operationQueue:queue]; - EMSShardRepository *shardRepository = [EMSShardRepository new]; - - - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - [sessionConfiguration setTimeoutIntervalForRequest:30.0]; - [sessionConfiguration setHTTPCookieStorage:nil]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration - delegate:nil - delegateQueue:queue]; - - EMSMobileEngageNullSafeBodyParser *mobileEngageNullSafeBodyParser = [[EMSMobileEngageNullSafeBodyParser alloc] initWithEndpoint:[EMSEndpoint nullMock]]; - - EMSRESTClient *restClient = [[EMSRESTClient alloc] initWithSession:session - queue:queue - timestampProvider:[EMSTimestampProvider new] - additionalHeaders:nil - requestModelMappers:nil - responseHandlers:nil - mobileEngageBodyParser:mobileEngageNullSafeBodyParser]; - EMSRESTClientCompletionProxyFactory *proxyFactory = [[EMSRESTClientCompletionProxyFactory alloc] initWithRequestRepository:requestRepository - operationQueue:queue - defaultSuccessBlock:middleware.successBlock - defaultErrorBlock:middleware.errorBlock]; - - EMSConnectionWatchdog *connectionWatchdog = [[EMSConnectionWatchdog alloc] initWithOperationQueue:queue]; - - EMSDefaultWorker *worker = [[EMSDefaultWorker alloc] initWithOperationQueue:queue - requestRepository:requestRepository - connectionWatchdog:connectionWatchdog - restClient:restClient - errorBlock:middleware.errorBlock - proxyFactory:proxyFactory]; - EMSRequestManager *core = [[EMSRequestManager alloc] initWithCoreQueue:queue - completionMiddleware:middleware - restClient:restClient - worker:worker - requestRepository:requestRepository - shardRepository:shardRepository - proxyFactory:proxyFactory]; - [core submitRequestModel:model - withCompletionBlock:nil]; - - [[expectFutureValue(resultMethod) shouldEventuallyBeforeTimingOutAfter(10.0)] equal:method]; - [[theValue(expectedSubsetOfResultHeaders) shouldEventuallyBeforeTimingOutAfter(10.0)] equal:theValue(YES)]; - if (body) { - [[expectFutureValue(resultPayload) shouldEventuallyBeforeTimingOutAfter(10.0)] equal:body]; - } - [[expectFutureValue(model.requestId) shouldEventuallyBeforeTimingOutAfter(10.0)] equal:checkableRequestId]; - }; - - - describe(@"EMSRequestManager", ^{ - - beforeEach(^{ - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; - [[NSFileManager defaultManager] removeItemAtPath:DB_PATH - error:nil]; - }); - - it(@"should invoke errorBlock when calling error500 on Denna", ^{ - EMSRequestModel *model = [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { - [builder setUrl:error500]; - [builder setMethod:HTTPMethodGET]; - } - timestampProvider:[EMSTimestampProvider new] - uuidProvider:[EMSUUIDProvider new]]; - - EMSCompletionMiddleware *middleware = [[EMSCompletionMiddleware alloc] initWithSuccessBlock:^(NSString *requestId, EMSResponseModel *response) { - NSLog(@"ERROR!"); - fail(@"successBlock invoked :'("); - } - errorBlock:^(NSString *requestId, NSError *error) { - NSLog(@"ERROR!"); - fail(@"errorblock invoked"); - }]; - EMSRequestModelRepository *requestRepository = [[EMSRequestModelRepository alloc] initWithDbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]] - operationQueue:queue]; - EMSShardRepository *shardRepository = [EMSShardRepository new]; - - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; - [sessionConfiguration setTimeoutIntervalForRequest:30.0]; - [sessionConfiguration setHTTPCookieStorage:nil]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration - delegate:nil - delegateQueue:queue]; - - EMSMobileEngageNullSafeBodyParser *mobileEngageNullSafeBodyParser = [[EMSMobileEngageNullSafeBodyParser alloc] initWithEndpoint:[EMSEndpoint nullMock]]; - - EMSRESTClient *restClient = [[EMSRESTClient alloc] initWithSession:session - queue:queue - timestampProvider:[EMSTimestampProvider new] - additionalHeaders:nil - requestModelMappers:nil - responseHandlers:nil - mobileEngageBodyParser:mobileEngageNullSafeBodyParser]; - EMSRESTClientCompletionProxyFactory *proxyFactory = [[EMSRESTClientCompletionProxyFactory alloc] initWithRequestRepository:requestRepository - operationQueue:queue - defaultSuccessBlock:middleware.successBlock - defaultErrorBlock:middleware.errorBlock]; - - EMSConnectionWatchdog *connectionWatchdog = [[EMSConnectionWatchdog alloc] initWithOperationQueue:queue]; - - EMSDefaultWorker *worker = [[EMSDefaultWorker alloc] initWithOperationQueue:queue - requestRepository:requestRepository - connectionWatchdog:connectionWatchdog - restClient:restClient - errorBlock:middleware.errorBlock - proxyFactory:proxyFactory]; - EMSRequestManager *core = [[EMSRequestManager alloc] initWithCoreQueue:queue - completionMiddleware:middleware - restClient:restClient - worker:worker - requestRepository:requestRepository - shardRepository:shardRepository - proxyFactory:proxyFactory]; - XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"waitForResult"]; - [core submitRequestModel:model - withCompletionBlock:^(NSError *error) { - [expectation fulfill]; - }]; - XCTWaiterResult result = [XCTWaiter waitForExpectations:@[expectation] - timeout:10]; - [[theValue(result) should] equal:theValue(XCTWaiterResultTimedOut)]; - }); - - it(@"should respond with the GET request's headers/body", ^{ - EMSRequestModel *model = [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { - [builder setUrl:echo]; - [builder setMethod:HTTPMethodGET]; - [builder setHeaders:inputHeaders]; - } - timestampProvider:[EMSTimestampProvider new] - uuidProvider:[EMSUUIDProvider new]]; - shouldEventuallySucceed(model, @"GET", inputHeaders, nil); - }); - - it(@"should respond with the POST request's headers/body", ^{ - EMSRequestModel *model = [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { - [builder setUrl:echo]; - [builder setMethod:HTTPMethodPOST]; - [builder setHeaders:inputHeaders]; - [builder setPayload:payload]; - } - timestampProvider:[EMSTimestampProvider new] - uuidProvider:[EMSUUIDProvider new]]; - shouldEventuallySucceed(model, @"POST", inputHeaders, payload); - }); - - it(@"should respond with the PUT request's headers/body", ^{ - EMSRequestModel *model = [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { - [builder setUrl:echo]; - [builder setMethod:HTTPMethodPUT]; - [builder setHeaders:inputHeaders]; - [builder setPayload:payload]; - } - timestampProvider:[EMSTimestampProvider new] - uuidProvider:[EMSUUIDProvider new]]; - shouldEventuallySucceed(model, @"PUT", inputHeaders, payload); - }); - - it(@"should respond with the DELETE request's headers/body", ^{ - EMSRequestModel *model = [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { - [builder setUrl:echo]; - [builder setMethod:HTTPMethodDELETE]; - [builder setHeaders:inputHeaders]; - } - timestampProvider:[EMSTimestampProvider new] - uuidProvider:[EMSUUIDProvider new]]; - shouldEventuallySucceed(model, @"DELETE", inputHeaders, nil); - }); - - - }); - -SPEC_END +@interface DennaTest : XCTestCase + +@property (nonatomic, strong) NSOperationQueue *queue; +@property (nonatomic, strong) NSOperationQueue *dbQueue; +@property (nonatomic, strong) EMSSQLiteHelper *dbHelper; + +@end + +@implementation DennaTest + +- (void)setUp { + [super setUp]; + self.queue = [self createTestOperationQueue]; + self.dbQueue = [self createTestOperationQueue]; + self.dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:self.dbQueue]; + [self.dbHelper open]; +} + +- (void)tearDown { + [EmarsysTestUtils tearDownOperationQueue:self.queue]; + [EmarsysTestUtils clearDb:self.dbHelper]; + [super tearDown]; +} + +- (void)testError500 { + NSString *error500 = DennaUrl(@"/customResponseCode/500"); + + EMSRequestModel *model = [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { + [builder setUrl:error500]; + [builder setMethod:HTTPMethodGET]; + } + timestampProvider:[EMSTimestampProvider new] + uuidProvider:[EMSUUIDProvider new]]; + + EMSCompletionMiddleware *middleware = [[EMSCompletionMiddleware alloc] initWithSuccessBlock:^(NSString *requestId, EMSResponseModel *response) { + XCTFail(@"successBlock invoked :'("); + } + errorBlock:^(NSString *requestId, NSError *error) { + NSLog(@"ERROR!"); + XCTFail(@"errorblock invoked"); + }]; + + EMSRequestModelRepository *requestRepository = [[EMSRequestModelRepository alloc] initWithDbHelper:self.dbHelper + operationQueue:self.queue]; + EMSShardRepository *shardRepository = [EMSShardRepository new]; + + NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; + [sessionConfiguration setTimeoutIntervalForRequest:30.0]; + [sessionConfiguration setHTTPCookieStorage:nil]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration + delegate:nil + delegateQueue:self.queue]; + + EMSMobileEngageNullSafeBodyParser *mobileEngageNullSafeBodyParser = [[EMSMobileEngageNullSafeBodyParser alloc] initWithEndpoint:OCMClassMock([EMSEndpoint class])]; + + EMSRESTClient *restClient = [[EMSRESTClient alloc] initWithSession:session + queue:self.queue + timestampProvider:[EMSTimestampProvider new] + additionalHeaders:nil + requestModelMappers:nil + responseHandlers:nil + mobileEngageBodyParser:mobileEngageNullSafeBodyParser]; + EMSRESTClientCompletionProxyFactory *proxyFactory = [[EMSRESTClientCompletionProxyFactory alloc] initWithRequestRepository:requestRepository + operationQueue:self.queue + defaultSuccessBlock:middleware.successBlock + defaultErrorBlock:middleware.errorBlock]; + + EMSConnectionWatchdog *connectionWatchdog = [[EMSConnectionWatchdog alloc] initWithOperationQueue:self.queue]; + + EMSDefaultWorker *worker = [[EMSDefaultWorker alloc] initWithOperationQueue:self.queue + requestRepository:requestRepository + connectionWatchdog:connectionWatchdog + restClient:restClient + errorBlock:middleware.errorBlock + proxyFactory:proxyFactory]; + EMSRequestManager *core = [[EMSRequestManager alloc] initWithCoreQueue:self.queue + completionMiddleware:middleware + restClient:restClient + worker:worker + requestRepository:requestRepository + shardRepository:shardRepository + proxyFactory:proxyFactory]; + + XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"waitForResult"]; + [core submitRequestModel:model + withCompletionBlock:^(NSError *error) { + [expectation fulfill]; + }]; + + XCTWaiterResult result = [XCTWaiter waitForExpectations:@[expectation] timeout:10]; + XCTAssertEqual(result, XCTWaiterResultTimedOut); +} + +- (void)testShouldRespondWithGetRequestHeadersBody { + NSString *echo = DennaUrl(@"/echo"); + NSDictionary *inputHeaders = @{@"header1": @"value1", @"header2": @"value2"}; + + EMSRequestModel *model = [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { + [builder setUrl:echo]; + [builder setMethod:HTTPMethodGET]; + [builder setHeaders:inputHeaders]; + } + timestampProvider:[EMSTimestampProvider new] + uuidProvider:[EMSUUIDProvider new]]; + + [self shouldEventuallySucceed:model method:@"GET" headers:inputHeaders body:nil]; +} + +- (void)testShouldRespondWithPostRequestHeadersBody { + NSString *echo = DennaUrl(@"/echo"); + NSDictionary *inputHeaders = @{@"header1": @"value1", @"header2": @"value2"}; + NSDictionary *payload = @{@"key1": @"val1", @"key2": @"val2", @"key3": @"val3"}; + + EMSRequestModel *model = [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { + [builder setUrl:echo]; + [builder setMethod:HTTPMethodPOST]; + [builder setHeaders:inputHeaders]; + [builder setPayload:payload]; + } + timestampProvider:[EMSTimestampProvider new] + uuidProvider:[EMSUUIDProvider new]]; + + [self shouldEventuallySucceed:model method:@"POST" headers:inputHeaders body:payload]; +} + +- (void)testShouldRespondWithPutRequestHeadersBody { + NSString *echo = DennaUrl(@"/echo"); + NSDictionary *inputHeaders = @{@"header1": @"value1", @"header2": @"value2"}; + NSDictionary *payload = @{@"key1": @"val1", @"key2": @"val2", @"key3": @"val3"}; + + EMSRequestModel *model = [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { + [builder setUrl:echo]; + [builder setMethod:HTTPMethodPUT]; + [builder setHeaders:inputHeaders]; + [builder setPayload:payload]; + } + timestampProvider:[EMSTimestampProvider new] + uuidProvider:[EMSUUIDProvider new]]; + + [self shouldEventuallySucceed:model method:@"PUT" headers:inputHeaders body:payload]; +} + +- (void)testShouldRespondWithDeleteRequestHeadersBody { + NSString *echo = DennaUrl(@"/echo"); + NSDictionary *inputHeaders = @{@"header1": @"value1", @"header2": @"value2"}; + + EMSRequestModel *model = [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { + [builder setUrl:echo]; + [builder setMethod:HTTPMethodDELETE]; + [builder setHeaders:inputHeaders]; + } + timestampProvider:[EMSTimestampProvider new] + uuidProvider:[EMSUUIDProvider new]]; + + [self shouldEventuallySucceed:model method:@"DELETE" headers:inputHeaders body:nil]; +} + +- (void)shouldEventuallySucceed:(EMSRequestModel *)model + method:(NSString *)method + headers:(NSDictionary *)headers + body:(NSDictionary *)body { + __block NSString *checkableRequestId; + __block NSString *resultMethod; + __block BOOL expectedSubsetOfResultHeaders; + __block NSDictionary *resultPayload; + XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"waitForMiddleware"]; + EMSCompletionMiddleware *middleware = [[EMSCompletionMiddleware alloc] initWithSuccessBlock:^(NSString *requestId, EMSResponseModel *response) { + checkableRequestId = requestId; + NSDictionary *returnedPayload = [NSJSONSerialization JSONObjectWithData:response.body + options:NSJSONReadingFragmentsAllowed + error:nil]; + NSLog(@"RequestId: %@, responsePayload: %@", requestId, returnedPayload); + resultMethod = returnedPayload[@"method"]; + expectedSubsetOfResultHeaders = [returnedPayload[@"headers"] subsetOfDictionary:headers]; + resultPayload = returnedPayload[@"body"]; + [expectation fulfill]; + } + errorBlock:^(NSString *requestId, NSError *error) { + NSLog(@"ERROR!"); + XCTFail(@"errorblock invoked"); + }]; + + EMSRequestModelRepository *requestRepository = [[EMSRequestModelRepository alloc] initWithDbHelper:self.dbHelper + operationQueue:self.queue]; + EMSShardRepository *shardRepository = [EMSShardRepository new]; + + NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; + [sessionConfiguration setTimeoutIntervalForRequest:30.0]; + [sessionConfiguration setHTTPCookieStorage:nil]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration + delegate:nil + delegateQueue:self.queue]; + + EMSMobileEngageNullSafeBodyParser *mobileEngageNullSafeBodyParser = [[EMSMobileEngageNullSafeBodyParser alloc] initWithEndpoint:OCMClassMock([EMSEndpoint class])]; + + EMSRESTClient *restClient = [[EMSRESTClient alloc] initWithSession:session + queue:self.queue + timestampProvider:[EMSTimestampProvider new] + additionalHeaders:nil + requestModelMappers:nil + responseHandlers:nil + mobileEngageBodyParser:mobileEngageNullSafeBodyParser]; + EMSRESTClientCompletionProxyFactory *proxyFactory = [[EMSRESTClientCompletionProxyFactory alloc] initWithRequestRepository:requestRepository + operationQueue:self.queue + defaultSuccessBlock:middleware.successBlock + defaultErrorBlock:middleware.errorBlock]; + + EMSConnectionWatchdog *connectionWatchdog = [[EMSConnectionWatchdog alloc] initWithOperationQueue:self.queue]; + + EMSDefaultWorker *worker = [[EMSDefaultWorker alloc] initWithOperationQueue:self.queue + requestRepository:requestRepository + connectionWatchdog:connectionWatchdog + restClient:restClient + errorBlock:middleware.errorBlock + proxyFactory:proxyFactory]; + EMSRequestManager *core = [[EMSRequestManager alloc] initWithCoreQueue:self.queue + completionMiddleware:middleware + restClient:restClient + worker:worker + requestRepository:requestRepository + shardRepository:shardRepository + proxyFactory:proxyFactory]; + [core submitRequestModel:model + withCompletionBlock:nil]; + + XCTWaiterResult waiterResult = [XCTWaiter waitForExpectations:@[expectation] + timeout:10.0]; + XCTAssertEqual(waiterResult, XCTWaiterResultCompleted); + XCTAssertEqualObjects(resultMethod, method); + XCTAssertEqual(expectedSubsetOfResultHeaders, YES); + if (body) { + XCTAssertEqualObjects(resultPayload, body); + } + XCTAssertEqualObjects(model.requestId, checkableRequestId); +} + +@end diff --git a/Tests/CoreTests/EMSDefaultWorkerTests.m b/Tests/CoreTests/EMSDefaultWorkerTests.m index 8dcc1d10..6f594bc9 100644 --- a/Tests/CoreTests/EMSDefaultWorkerTests.m +++ b/Tests/CoreTests/EMSDefaultWorkerTests.m @@ -16,6 +16,7 @@ #import "EMSRESTClientCompletionProxyFactory.h" #import "EMSCoreCompletionHandlerMiddleware.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestDB.db"] @@ -29,29 +30,30 @@ __block EMSSQLiteHelper *helper; __block EMSRequestModelRepository *repository; __block NSOperationQueue *queue; + __block NSOperationQueue *dbQueue; beforeEach(^{ queue = [self createTestOperationQueue]; + dbQueue = [self createTestOperationQueue]; }); afterEach(^{ - [self tearDownOperationQueue:queue]; + [EmarsysTestUtils tearDownOperationQueue:queue]; }); describe(@"init", ^{ - beforeEach(^{ + beforeAll(^{ helper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:dbQueue]; [helper open]; repository = [[EMSRequestModelRepository alloc] initWithDbHelper:helper operationQueue:queue]; }); afterEach(^{ - [helper close]; - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; + [EmarsysTestUtils clearDb:helper]; }); @@ -137,18 +139,17 @@ describe(@"run", ^{ - beforeEach(^{ + beforeAll(^{ helper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:dbQueue]; [helper open]; repository = [[EMSRequestModelRepository alloc] initWithDbHelper:helper operationQueue:queue]; }); afterEach(^{ - [helper close]; - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; + [EmarsysTestUtils clearDb:helper]; }); id (^requestModel)(NSString *url, NSDictionary *payload, BOOL expired) = ^id(NSString *url, NSDictionary *payload, BOOL expired) { diff --git a/Tests/CoreTests/Log/EMSLoggerTests.m b/Tests/CoreTests/Log/EMSLoggerTests.m index 971ca993..cd3afb47 100644 --- a/Tests/CoreTests/Log/EMSLoggerTests.m +++ b/Tests/CoreTests/Log/EMSLoggerTests.m @@ -14,6 +14,7 @@ #import "EMSWrapperChecker.h" #import "XCTestCase+Helper.h" #import "EMSStorageProtocol.h" +#import "EmarsysTestUtils.h" @interface EMSLoggerTests : XCTestCase @@ -82,8 +83,8 @@ - (void)tearDown { _mockLogEntry = nil; _mockStorage = nil; _operationQueue = nil; - [self tearDownOperationQueue:self.operationQueue]; - [self tearDownOperationQueue:self.runnerQueue]; + [EmarsysTestUtils tearDownOperationQueue:self.operationQueue]; + [EmarsysTestUtils tearDownOperationQueue:self.runnerQueue]; } - (void)testInitShouldThrowExceptionWhenShardRepositoryIsNil { diff --git a/Tests/CoreTests/Networking/EMSCoreCompletionHandlerMiddlewareTests.m b/Tests/CoreTests/Networking/EMSCoreCompletionHandlerMiddlewareTests.m index 871791f2..c2b80775 100644 --- a/Tests/CoreTests/Networking/EMSCoreCompletionHandlerMiddlewareTests.m +++ b/Tests/CoreTests/Networking/EMSCoreCompletionHandlerMiddlewareTests.m @@ -14,6 +14,7 @@ #import "EMSRequestModel+RequestIds.h" #import "NSError+EMSCore.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" typedef void (^AssertionBlock)(XCTWaiterResult, EMSRequestModel *returnedRequestModel, EMSResponseModel *returnedResponseModel, NSError *returnedError, NSOperationQueue *returnedOperationQueue); @@ -57,7 +58,7 @@ - (void)setUp { } - (void)tearDown { - [self tearDownOperationQueue:self.operationQueue]; + [EmarsysTestUtils tearDownOperationQueue:self.operationQueue]; } - (void)testInit_completionHandler_mustNotBeNull { diff --git a/Tests/CoreTests/Networking/EMSRESTClientTests.m b/Tests/CoreTests/Networking/EMSRESTClientTests.m index 8a115d16..accac653 100644 --- a/Tests/CoreTests/Networking/EMSRESTClientTests.m +++ b/Tests/CoreTests/Networking/EMSRESTClientTests.m @@ -16,6 +16,7 @@ #import "EMSAbstractResponseHandler.h" #import "EMSMobileEngageNullSafeBodyParser.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" typedef void (^AssertionBlock)(XCTWaiterResult, EMSRequestModel *, EMSResponseModel *, NSError *, NSOperationQueue *operationQueue); @@ -73,7 +74,7 @@ - (void)setUp { } - (void)tearDown { - [self tearDownOperationQueue:self.expectedOperationQueue]; + [EmarsysTestUtils tearDownOperationQueue:self.expectedOperationQueue]; } - (void)testInit_session_mustNotBeNil { diff --git a/Tests/CoreTests/OfflineTests.m b/Tests/CoreTests/OfflineTests.m index f4fbc4a1..46c6bac9 100644 --- a/Tests/CoreTests/OfflineTests.m +++ b/Tests/CoreTests/OfflineTests.m @@ -22,6 +22,7 @@ #import "EMSRESTClientCompletionProxyFactory.h" #import "EMSMobileEngageNullSafeBodyParser.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestDB.db"] @@ -37,14 +38,15 @@ @interface OfflineTests : XCTestCase @implementation OfflineTests - (void)setUp { - _queue = [self createTestOperationQueue]; - - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; + NSOperationQueue *dbQueue = [self createTestOperationQueue]; _helper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:dbQueue]; [self.helper open]; - [self.helper executeCommand:SQL_REQUEST_PURGE]; + + _queue = [self createTestOperationQueue]; + + [EmarsysTestUtils clearDb:self.helper]; _requestModelRepository = [[EMSRequestModelRepository alloc] initWithDbHelper:self.helper operationQueue:self.queue]; @@ -52,10 +54,12 @@ - (void)setUp { } - (void)tearDown { - [self tearDownOperationQueue:self.queue]; - [self.helper close]; - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; + __weak typeof(self) weakSelf = self; + [self.queue addOperationWithBlock:^{ + [EmarsysTestUtils clearDb:weakSelf.helper]; + }]; + [self waitATickOnOperationQueue:self.queue]; + [EmarsysTestUtils tearDownOperationQueue:self.queue]; } - (void)testShouldReceive3Response_when3RequestHasBeenSent{ diff --git a/Tests/CoreTests/OperationQueue/EMSOperationQueueTests.m b/Tests/CoreTests/OperationQueue/EMSOperationQueueTests.m index 5c268e31..cb2b735f 100644 --- a/Tests/CoreTests/OperationQueue/EMSOperationQueueTests.m +++ b/Tests/CoreTests/OperationQueue/EMSOperationQueueTests.m @@ -5,6 +5,7 @@ #import #import "EMSOperationQueue.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" @interface EMSOperationQueueTests : XCTestCase @@ -33,7 +34,7 @@ - (void)testAddOperationWithBlock_shouldBeResilient_toExceptions { XCTWaiterResult waiterResult = [XCTWaiter waitForExpectations:@[expectation] timeout:10]; XCTAssertEqual(waiterResult, XCTWaiterResultCompleted); - [self tearDownOperationQueue:operationQueue]; + [EmarsysTestUtils tearDownOperationQueue:operationQueue]; } @end diff --git a/Tests/CoreTests/Providers/EMSCompletionBlockProviderTests.m b/Tests/CoreTests/Providers/EMSCompletionBlockProviderTests.m index 54f7a8f2..9baffade 100644 --- a/Tests/CoreTests/Providers/EMSCompletionBlockProviderTests.m +++ b/Tests/CoreTests/Providers/EMSCompletionBlockProviderTests.m @@ -3,9 +3,10 @@ // #import -#import "EMSCompletionBlockProvider.h" +#import "EMSCompletionProvider.h" #import "EMSOperationQueue.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" @interface EMSCompletionBlockProviderTests : XCTestCase @@ -20,12 +21,12 @@ - (void)setUp { } - (void)tearDown { - [self tearDownOperationQueue:self.queue]; + [EmarsysTestUtils tearDownOperationQueue:self.queue]; } - (void)testInit_operationQueue_mustNotBeNil { @try { - [[EMSCompletionBlockProvider alloc] initWithOperationQueue:nil]; + [[EMSCompletionProvider alloc] initWithOperationQueue:nil]; XCTFail(@"Expected Exception when operationQueue is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: operationQueue"); @@ -33,7 +34,7 @@ - (void)testInit_operationQueue_mustNotBeNil { } - (void)testProvideCompletion { - EMSCompletionBlockProvider *provider = [[EMSCompletionBlockProvider alloc] initWithOperationQueue:self.queue]; + EMSCompletionProvider *provider = [[EMSCompletionProvider alloc] initWithOperationQueue:self.queue]; __block NSOperationQueue *usedOperationQueue; diff --git a/Tests/CoreTests/Repository/EMSFilterByNothingSpecificationTests.m b/Tests/CoreTests/Repository/EMSFilterByNothingSpecificationTests.m index d61f26c6..bf2d25b5 100644 --- a/Tests/CoreTests/Repository/EMSFilterByNothingSpecificationTests.m +++ b/Tests/CoreTests/Repository/EMSFilterByNothingSpecificationTests.m @@ -10,6 +10,7 @@ #import "EMSSQLiteHelper.h" #import "EMSSqliteSchemaHandler.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestDB.db"] @@ -17,6 +18,8 @@ @interface EMSFilterByNothingSpecificationTests : XCTestCase @property(nonatomic, strong) EMSRequestModelRepository *repository; @property(nonatomic, strong) NSOperationQueue *queue; +@property(nonatomic, strong) NSOperationQueue *dbQueue; +@property(nonatomic, strong) EMSSQLiteHelper *dbHelper; @end @@ -24,17 +27,19 @@ @implementation EMSFilterByNothingSpecificationTests - (void)setUp { _queue = [self createTestOperationQueue]; - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; - EMSSQLiteHelper *helper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; - [helper open]; - _repository = [[EMSRequestModelRepository alloc] initWithDbHelper:helper + _dbQueue = [self createTestOperationQueue]; + + _dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:self.dbQueue]; + [self.dbHelper open]; + _repository = [[EMSRequestModelRepository alloc] initWithDbHelper:self.dbHelper operationQueue:self.queue]; } - (void)tearDown { - [self tearDownOperationQueue:self.queue]; + [EmarsysTestUtils tearDownOperationQueue:self.queue]; + [EmarsysTestUtils clearDb:self.dbHelper]; } - (void)testQueryShouldReturnWithAllOfTheRequestModels { diff --git a/Tests/CoreTests/Repository/EMSFilterByTypeSpecificationTests.m b/Tests/CoreTests/Repository/EMSFilterByTypeSpecificationTests.m index dfe7cfc1..e334bbaf 100644 --- a/Tests/CoreTests/Repository/EMSFilterByTypeSpecificationTests.m +++ b/Tests/CoreTests/Repository/EMSFilterByTypeSpecificationTests.m @@ -12,6 +12,7 @@ #import "EMSSqliteSchemaHandler.h" #import "EMSSchemaContract.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestDB.db"] @@ -19,6 +20,8 @@ @interface EMSFilterByTypeSpecificationTests : XCTestCase @property(nonatomic, strong) EMSRequestModelRepository *repository; @property(nonatomic, strong) NSOperationQueue *queue; +@property(nonatomic, strong) NSOperationQueue *dbQueue; +@property(nonatomic, strong) EMSSQLiteHelper *dbHelper; @end @@ -26,17 +29,18 @@ @implementation EMSFilterByTypeSpecificationTests - (void)setUp { _queue = [self createTestOperationQueue]; - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; - EMSSQLiteHelper *helper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; - [helper open]; - _repository = [[EMSRequestModelRepository alloc] initWithDbHelper:helper + _dbQueue = [self createTestOperationQueue]; + _dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:self.dbQueue]; + [self.dbHelper open]; + _repository = [[EMSRequestModelRepository alloc] initWithDbHelper:self.dbHelper operationQueue:self.queue]; } - (void)tearDown { - [self tearDownOperationQueue:self.queue]; + [EmarsysTestUtils tearDownOperationQueue:self.queue]; + [EmarsysTestUtils clearDb:self.dbHelper]; } - (void)testQueryShouldReturnWithTheExactRequstModels { diff --git a/Tests/CoreTests/Repository/EMSFilterByValuesSpecificationTests.m b/Tests/CoreTests/Repository/EMSFilterByValuesSpecificationTests.m index f3bda95e..02d2854c 100644 --- a/Tests/CoreTests/Repository/EMSFilterByValuesSpecificationTests.m +++ b/Tests/CoreTests/Repository/EMSFilterByValuesSpecificationTests.m @@ -12,6 +12,7 @@ #import "EMSSqliteSchemaHandler.h" #import "EMSSchemaContract.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestDB.db"] @@ -19,6 +20,8 @@ @interface EMSFilterByValuesSpecificationTests : XCTestCase @property(nonatomic, strong) EMSRequestModelRepository *repository; @property(nonatomic, strong) NSOperationQueue *queue; +@property(nonatomic, strong) NSOperationQueue *dbQueue; +@property(nonatomic, strong) EMSSQLiteHelper *dbHelper; @end @@ -26,17 +29,18 @@ @implementation EMSFilterByValuesSpecificationTests - (void)setUp { _queue = [self createTestOperationQueue]; - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; - EMSSQLiteHelper *helper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; - [helper open]; - _repository = [[EMSRequestModelRepository alloc] initWithDbHelper:helper + _dbQueue = [self createTestOperationQueue]; + _dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:self.dbQueue]; + [self.dbHelper open]; + _repository = [[EMSRequestModelRepository alloc] initWithDbHelper:self.dbHelper operationQueue:self.queue]; } - (void)tearDown { - [self tearDownOperationQueue:self.queue]; + [EmarsysTestUtils tearDownOperationQueue:self.queue]; + [EmarsysTestUtils clearDb:self.dbHelper]; } - (void)testQueryShouldReturnWithTheExactRequstModels { diff --git a/Tests/CoreTests/Repository/EMSQueryOldestRowSpecificationTests.m b/Tests/CoreTests/Repository/EMSQueryOldestRowSpecificationTests.m index dce3531d..3d8f8622 100644 --- a/Tests/CoreTests/Repository/EMSQueryOldestRowSpecificationTests.m +++ b/Tests/CoreTests/Repository/EMSQueryOldestRowSpecificationTests.m @@ -10,6 +10,7 @@ #import "EMSSQLiteHelper.h" #import "EMSSqliteSchemaHandler.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestDB.db"] @@ -17,6 +18,8 @@ @interface EMSQueryOldestRowSpecificationTests : XCTestCase @property(nonatomic, strong) EMSRequestModelRepository *repository; @property(nonatomic, strong) NSOperationQueue *queue; +@property(nonatomic, strong) NSOperationQueue *dbQueue; +@property(nonatomic, strong) EMSSQLiteHelper *dbHelper; @end @@ -24,18 +27,19 @@ @implementation EMSQueryOldestRowSpecificationTests - (void)setUp { _queue = [self createTestOperationQueue]; + _dbQueue = [self createTestOperationQueue]; - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; - EMSSQLiteHelper *helper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; - [helper open]; - _repository = [[EMSRequestModelRepository alloc] initWithDbHelper:helper + _dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:self.dbQueue]; + [self.dbHelper open]; + _repository = [[EMSRequestModelRepository alloc] initWithDbHelper:self.dbHelper operationQueue:self.queue]; } - (void)tearDown { - [self tearDownOperationQueue:self.queue]; + [EmarsysTestUtils tearDownOperationQueue:self.queue]; + [EmarsysTestUtils clearDb:self.dbHelper]; } - (void)testQueryShouldReturnWithTheOldestRequestModel { diff --git a/Tests/CoreTests/Repository/EMSRequestModelRepositoryTests.m b/Tests/CoreTests/Repository/EMSRequestModelRepositoryTests.m index e84959e7..77f00770 100644 --- a/Tests/CoreTests/Repository/EMSRequestModelRepositoryTests.m +++ b/Tests/CoreTests/Repository/EMSRequestModelRepositoryTests.m @@ -17,6 +17,8 @@ #import "EMSSchemaContract.h" #import "EMSRequestModel+RequestIds.h" #import "FakeDbHelper.h" +#import "EmarsysTestUtils.h" +#import "XCTestCase+Helper.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestDB.db"] @@ -25,15 +27,14 @@ __block EMSSQLiteHelper *helper; __block id repository; __block NSOperationQueue *testQueue; + __block NSOperationQueue *queue; - beforeEach(^{ - testQueue = [[NSOperationQueue alloc] init]; - [testQueue setMaxConcurrentOperationCount:1]; - testQueue.name = @"testOperationQueue"; - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; + beforeAll(^{ + testQueue = [self createTestOperationQueue]; + queue = [self createTestOperationQueue]; helper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:queue]; [helper open]; repository = [[EMSRequestModelRepository alloc] initWithDbHelper:helper operationQueue:testQueue]; @@ -42,7 +43,7 @@ afterEach(^{ [testQueue cancelAllOperations]; [testQueue waitUntilAllOperationsAreFinished]; - [helper close]; + [EmarsysTestUtils clearDb:helper]; }); @@ -89,28 +90,6 @@ [[theValue(exception) shouldNot] beNil]; } }); - - it(@"should call notification block on the given operationQueue", ^{ - __block NSOperationQueue *returnedOperationQueue = nil; - XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"waitForBlock"]; - FakeDbHelper *dbHelper = [FakeDbHelper new]; - EMSRequestModelRepository *requestModelRepositoryToTriggerDBClose = [[EMSRequestModelRepository alloc] initWithDbHelper:dbHelper - operationQueue:testQueue]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - dbHelper.closeOperationQueueBlock = ^(NSOperationQueue *operationQueue) { - returnedOperationQueue = operationQueue; - [expectation fulfill]; - }; - - [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillTerminateNotification - object:nil]; - }); - XCTWaiterResult waiterResult = [XCTWaiter waitForExpectations:@[expectation] - timeout:10]; - XCTAssertEqual(waiterResult, XCTWaiterResultCompleted); - XCTAssertEqualObjects(returnedOperationQueue, testQueue); - XCTAssertNotNil(requestModelRepositoryToTriggerDBClose); - }); }); describe(@"query", ^{ diff --git a/Tests/CoreTests/Repository/EMSShardRepositoryTests.m b/Tests/CoreTests/Repository/EMSShardRepositoryTests.m index b2bab3d8..f90d993c 100644 --- a/Tests/CoreTests/Repository/EMSShardRepositoryTests.m +++ b/Tests/CoreTests/Repository/EMSShardRepositoryTests.m @@ -14,6 +14,8 @@ #import "EMSFilterByValuesSpecification.h" #import "EMSFilterByTypeSpecification.h" #import "EMSSchemaContract.h" +#import "EmarsysTestUtils.h" +#import "XCTestCase+Helper.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestDB.db"] @@ -21,18 +23,19 @@ __block EMSSQLiteHelper *helper; __block id repository; + __block NSOperationQueue *queue; - beforeEach(^{ - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; + beforeAll(^{ + queue = [self createTestOperationQueue]; helper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:queue]; [helper open]; repository = [[EMSShardRepository alloc] initWithDbHelper:helper]; }); afterEach(^{ - [helper close]; + [EmarsysTestUtils clearDb:helper]; }); EMSShard *(^createShardWithType)(NSString *type) = ^EMSShard *(NSString *type) { diff --git a/Tests/CoreTests/Request/EMSRequestManagerTests.m b/Tests/CoreTests/Request/EMSRequestManagerTests.m index 1fec67d4..51d5d43f 100644 --- a/Tests/CoreTests/Request/EMSRequestManagerTests.m +++ b/Tests/CoreTests/Request/EMSRequestManagerTests.m @@ -21,17 +21,19 @@ #import "EMSOperationQueue.h" #import "EMSMobileEngageNullSafeBodyParser.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestDB.db"] @interface EMSRequestManagerTests : XCTestCase -@property(nonatomic, strong) EMSSQLiteHelper *helper; @property(nonatomic, strong) EMSRequestModelRepository *requestModelRepository; @property(nonatomic, strong) EMSShardRepository *shardRepository; @property(nonatomic, strong) EMSRequestManager *requestManager; @property(nonatomic, strong) NSOperationQueue *queue; +@property(nonatomic, strong) NSOperationQueue *dbQueue; +@property(nonatomic, strong) EMSSQLiteHelper *dbHelper; @end @@ -40,12 +42,14 @@ @implementation EMSRequestManagerTests - (void)setUp { _queue = [self createTestOperationQueue]; - - EMSSQLiteHelper *helper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; - [helper open]; - [helper executeCommand:SQL_REQUEST_PURGE]; - self.requestModelRepository = [[EMSRequestModelRepository alloc] initWithDbHelper:helper + _dbQueue = [self createTestOperationQueue]; + + _dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:self.dbQueue]; + [self.dbHelper open]; + [self.dbHelper executeCommand:SQL_REQUEST_PURGE]; + self.requestModelRepository = [[EMSRequestModelRepository alloc] initWithDbHelper:self.dbHelper operationQueue:self.queue]; self.shardRepository = OCMClassMock([EMSShardRepository class]); @@ -55,14 +59,12 @@ - (void)setUp { CoreErrorBlock errorBlock = ^(NSString *requestId, NSError *error) { }; - self.requestManager = [self createRequestManagerWithSuccessBlock:successBlock errorBlock:errorBlock requestRepository:[[EMSRequestModelRepository alloc] initWithDbHelper:helper + self.requestManager = [self createRequestManagerWithSuccessBlock:successBlock errorBlock:errorBlock requestRepository:[[EMSRequestModelRepository alloc] initWithDbHelper:self.dbHelper operationQueue:self.queue] shardRepository:self.shardRepository]; } - (void)tearDown { - [self tearDownOperationQueue:self.queue]; - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; + [EmarsysTestUtils tearDownOperationQueue:self.queue]; } - (void)testShouldThrowAnExceptionWhenCoreQueueIsNil { diff --git a/Tests/CoreTests/Schedulers/EMSSchedulerTests.m b/Tests/CoreTests/Schedulers/EMSSchedulerTests.m index 0c669ba3..a4c37e4c 100644 --- a/Tests/CoreTests/Schedulers/EMSSchedulerTests.m +++ b/Tests/CoreTests/Schedulers/EMSSchedulerTests.m @@ -7,6 +7,7 @@ #import "EMSWaiter.h" #import "EMSAgenda.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" SPEC_BEGIN(EMSSchedulerTests) @@ -26,7 +27,7 @@ }); afterEach(^{ - [self tearDownOperationQueue:queue]; + [EmarsysTestUtils tearDownOperationQueue:queue]; }); describe(@"initWithOperationQueue:leeway:", ^{ diff --git a/Tests/CoreTests/Store/EMSStorageTests.m b/Tests/CoreTests/Store/EMSStorageTests.m index d0c8c946..ea543e99 100644 --- a/Tests/CoreTests/Store/EMSStorageTests.m +++ b/Tests/CoreTests/Store/EMSStorageTests.m @@ -7,12 +7,15 @@ #import "EMSStorage.h" #import "XCTestCase+Helper.h" #import "EMSStorageProtocol.h" +#import "EmarsysTestUtils.h" @interface EMSStorage (Tests) @property(nonatomic, strong) NSUserDefaults *fallbackUserDefaults; -- (OSStatus)storeInSecureStorageWithQuery:(NSDictionary *)query; +- (OSStatus)setData:(nullable NSData *)data + forKey:(NSString *)key + accessGroup:(nullable NSString *)accessGroup; @end @@ -55,11 +58,12 @@ - (void)setUp { }; _storage = [[EMSStorage alloc] initWithSuiteNames:self.suiteNames - accessGroup:@"7ZFXXDJH82.com.emarsys.SdkHostTestGroup"]; + accessGroup:@"7ZFXXDJH82.com.emarsys.SdkHostTestGroup" + operationQueue:self.queue]; } - (void)tearDown { - [self tearDownOperationQueue:self.queue]; + [EmarsysTestUtils tearDownOperationQueue:self.queue]; NSDictionary *deleteQuery = @{ (id) kSecClass: (id) kSecClassGenericPassword, (id) kSecAttrAccessible: (id) kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, @@ -81,17 +85,30 @@ - (void)tearDown { - (void)testInit_suiteNames_mustNotBeNil { @try { [[EMSStorage alloc] initWithSuiteNames:nil - accessGroup:@"testAccessGroup"]; + accessGroup:@"testAccessGroup" + operationQueue:self.queue]; XCTFail(@"Expected Exception when suiteNames is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: suiteNames"); } } +- (void)testInit_operationQueue_mustNotBeNil { + @try { + [[EMSStorage alloc] initWithSuiteNames:@[@""] + accessGroup:@"testAccessGroup" + operationQueue:nil]; + XCTFail(@"Expected Exception when operationQueue is nil!"); + } @catch (NSException *exception) { + XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: operationQueue"); + } +} + - (void)testInit_withAccessGroup { NSString *accessGroup = @"testAccessGroup"; EMSStorage *storage = [[EMSStorage alloc] initWithSuiteNames:self.suiteNames - accessGroup:accessGroup]; + accessGroup:accessGroup + operationQueue:self.queue]; XCTAssertEqualObjects(storage.accessGroup, accessGroup); } @@ -136,7 +153,7 @@ - (void)testSetDataForKey { - (void)testSetDataForKey_whenKeyChainIsUnavailable { EMSStorage *partialMockStorage = OCMPartialMock(self.storage); - OCMStub([partialMockStorage storeInSecureStorageWithQuery:[OCMArg any]]).andReturn(errSecItemNotFound); + OCMStub([partialMockStorage setData:[OCMArg any] forKey:[OCMArg any] accessGroup:[OCMArg any]]).andReturn(errSecItemNotFound); [partialMockStorage setData:self.testValue1 forKey:kTestKey]; @@ -201,7 +218,9 @@ - (void)testDataForKey_should_returnAndDeleteUserDefaultsValue_when_missingFromK - (void)testDataForKey_should_returnAndDeleteUserDefaultsValue_when_missingFromKeychain_withNumber { NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:self.suiteNames[1]]; EMSStorage *mockStorage = OCMPartialMock(self.storage); - NSData *numberData = [NSKeyedArchiver archivedDataWithRootObject:self.testNumber]; + NSData *numberData = [NSKeyedArchiver archivedDataWithRootObject:self.testNumber + requiringSecureCoding:NO + error:nil]; [userDefaults setObject:self.testNumber forKey:kTestKey]; @@ -272,7 +291,9 @@ - (void)testSetNumberForKey { [self.storage setNumber:self.testNumber forKey:kTestKey]; - NSNumber *result = [NSKeyedUnarchiver unarchiveObjectWithData:self.storage[kTestKey]]; + NSNumber *result = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithArray:@[[NSDictionary class], [NSString class], [NSNumber class], [NSArray class], [NSNull class]]] + fromData:self.storage[kTestKey] + error:nil]; XCTAssertEqualObjects(result, self.testNumber); } @@ -296,7 +317,9 @@ - (void)testSetDictionaryForKey { [self.storage setDictionary:self.testDictionary forKey:kTestKey]; - NSDictionary *result = [NSKeyedUnarchiver unarchiveObjectWithData:self.storage[kTestKey]]; + NSDictionary *result = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithArray:@[[NSDictionary class], [NSString class], [NSNumber class], [NSArray class], [NSNull class]]] + fromData:self.storage[kTestKey] + error:nil]; XCTAssertEqualObjects(result, self.testDictionary); } diff --git a/Tests/CoreTests/Utils/EMSSession+Tests.h b/Tests/CoreTests/Utils/EMSSession+Tests.h new file mode 100644 index 00000000..673db45f --- /dev/null +++ b/Tests/CoreTests/Utils/EMSSession+Tests.h @@ -0,0 +1,17 @@ +//// +// +// Copyright © 2024 Emarsys-Technologies Kft. All rights reserved. +// + +#import "EMSSession.h" + +#ifndef EMSSession_Tests_h +#define EMSSession_Tests_h + +@interface EMSSession (Tests) + +@property(nonatomic, strong) NSMutableArray *observers; + +@end + +#endif /* EMSSession_Tests_h */ diff --git a/Tests/CoreTests/Utils/EmarsysTestUtils.h b/Tests/CoreTests/Utils/EmarsysTestUtils.h index eec92b3c..e37f2bfc 100644 --- a/Tests/CoreTests/Utils/EmarsysTestUtils.h +++ b/Tests/CoreTests/Utils/EmarsysTestUtils.h @@ -25,4 +25,8 @@ + (void)waitForSetPushToken; + (void)waitForSetCustomer; -@end \ No newline at end of file ++ (void)tearDownOperationQueue:(NSOperationQueue *)operationQueue; + ++ (void)clearDb:(EMSSQLiteHelper *)dbHelper; + +@end diff --git a/Tests/CoreTests/Utils/EmarsysTestUtils.m b/Tests/CoreTests/Utils/EmarsysTestUtils.m index 21b98d97..11b89124 100644 --- a/Tests/CoreTests/Utils/EmarsysTestUtils.m +++ b/Tests/CoreTests/Utils/EmarsysTestUtils.m @@ -11,6 +11,8 @@ #import "NSError+EMSCore.h" #import "EMSSQLiteHelper.h" #import "EMSNotificationCenterManager.h" +#import "EMSWrapperChecker.h" +#import "EMSSession+Tests.h" #define DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"MEDB.db"] #define REPOSITORY_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"EMSSQLiteQueueDB.db"] @@ -41,18 +43,20 @@ + (void)setupEmarsysWithConfig:(EMSConfig *)config + (void)setupEmarsysWithFeatures:(NSArray *)features withDependencyContainer:(id )dependencyContainer config:(EMSConfig *)config { - [EMSDependencyInjection.dependencyContainer.publicApiOperationQueue waitUntilAllOperationsAreFinished]; - [EMSDependencyInjection.dependencyContainer.publicApiOperationQueue cancelAllOperations]; - [EMSDependencyInjection.dependencyContainer.coreOperationQueue waitUntilAllOperationsAreFinished]; - [EMSDependencyInjection.dependencyContainer.coreOperationQueue cancelAllOperations]; - [EMSDependencyInjection.dependencyContainer.dbHelper close]; [self purge]; + [EmarsysTestUtils tearDownOperationQueue:EMSDependencyInjection.dependencyContainer.publicApiOperationQueue]; + [EmarsysTestUtils tearDownOperationQueue:EMSDependencyInjection.dependencyContainer.coreOperationQueue]; [EMSDependencyInjection tearDown]; + + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; + [userDefaults setObject:@"none" + forKey:kInnerWrapperKey]; + [userDefaults synchronize]; + if (dependencyContainer) { [EMSDependencyInjection setupWithDependencyContainer:dependencyContainer]; } - if (features) { EMSConfig *configWithFeatures = [EMSConfig makeWithBuilder:^(EMSConfigBuilder *builder) { [builder setMobileEngageApplicationCode:@"14C19-A121F"]; @@ -66,36 +70,46 @@ + (void)setupEmarsysWithFeatures:(NSArray *)features } + (void)purge { - [[NSFileManager defaultManager] removeItemAtPath:DB_PATH - error:nil]; - [[NSFileManager defaultManager] removeItemAtPath:REPOSITORY_DB_PATH - error:nil]; - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; - [userDefaults removeObjectForKey:kMEID]; - [userDefaults removeObjectForKey:kMEID_SIGNATURE]; - [userDefaults removeObjectForKey:kEMSLastAppLoginPayload]; - [userDefaults removeObjectForKey:kCLIENT_STATE]; - [userDefaults removeObjectForKey:kCONTACT_TOKEN]; - [userDefaults removeObjectForKey:@"kSDKAlreadyInstalled"]; - [userDefaults synchronize]; - - userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.emarsys.core"]; - [userDefaults setObject:@"IntegrationTests" - forKey:@"kHardwareIdKey"]; - [userDefaults synchronize]; + [EMSDependencyInjection.dependencyContainer.coreOperationQueue addOperationWithBlock:^{ + [EmarsysTestUtils clearDb:EMSDependencyInjection.dependencyContainer.dbHelper]; + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; + [userDefaults removeObjectForKey:kMEID]; + [userDefaults removeObjectForKey:kMEID_SIGNATURE]; + [userDefaults removeObjectForKey:kEMSLastAppLoginPayload]; + [userDefaults removeObjectForKey:kCLIENT_STATE]; + [userDefaults removeObjectForKey:kCONTACT_TOKEN]; + [userDefaults removeObjectForKey:@"kSDKAlreadyInstalled"]; + [userDefaults removeObjectForKey:@"CLIENT_SERVICE_URL"]; + [userDefaults removeObjectForKey:@"EVENT_SERVICE_URL"]; + [userDefaults removeObjectForKey:@"PREDICT_URL"]; + [userDefaults removeObjectForKey:@"DEEPLINK_URL"]; + [userDefaults removeObjectForKey:@"V3_MESSAGE_INBOX_URL"]; + [userDefaults synchronize]; + + userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.emarsys.core"]; + [userDefaults setObject:@"IntegrationTests" + forKey:@"kHardwareIdKey"]; + [userDefaults synchronize]; + + userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.emarsys.predict"]; + [userDefaults removeObjectForKey:@"contactFieldValue"]; + [userDefaults removeObjectForKey:@"visitorId"]; + [userDefaults synchronize]; + }]; } + (void)tearDownEmarsys { - [EMSDependencyInjection.dependencyContainer.publicApiOperationQueue waitUntilAllOperationsAreFinished]; - [EMSDependencyInjection.dependencyContainer.publicApiOperationQueue cancelAllOperations]; - [EMSDependencyInjection.dependencyContainer.coreOperationQueue waitUntilAllOperationsAreFinished]; - [EMSDependencyInjection.dependencyContainer.coreOperationQueue cancelAllOperations]; - [EMSDependencyInjection.dependencyContainer.dbHelper close]; - [self purge]; + for (id observer in EMSDependencyInjection.dependencyContainer.session.observers) { + [NSNotificationCenter.defaultCenter removeObserver:observer]; + } + [EMSDependencyInjection.dependencyContainer.urlSession invalidateAndCancel]; [EMSDependencyInjection.dependencyContainer.endpoint reset]; [MEExperimental reset]; [EMSDependencyInjection.dependencyContainer.requestContext reset]; [EMSDependencyInjection.dependencyContainer.notificationCenterManager removeHandlers]; + [self purge]; + [EmarsysTestUtils tearDownOperationQueue:EMSDependencyInjection.dependencyContainer.publicApiOperationQueue]; + [EmarsysTestUtils tearDownOperationQueue:EMSDependencyInjection.dependencyContainer.coreOperationQueue]; [EMSDependencyInjection tearDown]; } @@ -135,4 +149,17 @@ + (void)waitForSetPushToken { XCTAssertNil(returnedError); } ++ (void)tearDownOperationQueue:(NSOperationQueue *)operationQueue { + [operationQueue waitUntilAllOperationsAreFinished]; + [operationQueue setSuspended:YES]; + [operationQueue cancelAllOperations]; +} + ++ (void)clearDb:(EMSSQLiteHelper *)dbHelper { + [dbHelper executeCommand:@"DELETE FROM request;"]; + [dbHelper executeCommand:@"DELETE FROM shard;"]; + [dbHelper executeCommand:@"DELETE FROM displayed_iam;"]; + [dbHelper executeCommand:@"DELETE FROM button_click;"]; +} + @end diff --git a/Tests/E2ETests/EmarsysE2ETests.swift b/Tests/E2ETests/EmarsysE2ETests.swift index 0fe9fda5..3882a997 100644 --- a/Tests/E2ETests/EmarsysE2ETests.swift +++ b/Tests/E2ETests/EmarsysE2ETests.swift @@ -13,23 +13,28 @@ class EmarsysE2ETests: XCTestCase { case assertionError(assertionMessage: String) } - override class func tearDown() { + var timestamp: String! + + override func setUp() { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + + timestamp = dateFormatter.string(from: Date()) + } + + override func tearDown() { EmarsysTestUtils.tearDownEmarsys() } func testChangeApplicationCodeFromNil() throws { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - - let timestamp = dateFormatter.string(from: Date()) let config = EMSConfig.make { builder in } Emarsys.setup(config: config) - changeAppCode("EMS11-C3FD3", cId: 2575) + changeAppCode("EMS11-C3FD3") - setContact(cId: 3, "test@test.com") + setContact(cId: 2575, "test2@test.com") sendEvent("iosE2EChangeAppCodeFromNil", timestamp: timestamp) @@ -37,26 +42,46 @@ class EmarsysE2ETests: XCTestCase { } func testChangeApplicationCode() throws { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - - let timestamp = dateFormatter.string(from: Date()) let config = EMSConfig.make { builder in builder.setMobileEngageApplicationCode("14C19-A121F") } Emarsys.setup(config: config) - changeAppCode("EMS11-C3FD3", cId: 2575) + changeAppCode("EMS11-C3FD3") - setContact(cId: 3, "test@test.com") + setContact(cId: 2575, "test2@test.com") sendEvent("iosE2EChangeAppCodeFromNil", timestamp: timestamp) _ = filterForInboxMessage("iosE2EChangeAppCodeFromNil", body: timestamp) } + + func testChangeAppCodeShouldNotViolateDB() throws { + let config = EMSConfig.make { builder in + } - func changeAppCode(_ code: String, cId: NSNumber) { + Emarsys.setup(config: config) + changeAppCode("EMS11-C3FD3") + + setContact(cId: 2575, "test2@test.com") + + sendEvent("iosE2EChangeAppCodeFromNil", timestamp: timestamp) + + clearContact() + + changeAppCode(nil) + + sendEvent("iosE2EChangeAppCodeFromNil", timestamp: timestamp) + + sendEvent("iosE2EChangeAppCodeFromNil", timestamp: timestamp) + + changeAppCode("EMS11-C3FD3") + + setContact(cId: 2575, "test2@test.com") + } + + func changeAppCode(_ code: String?) { retry { [unowned self] () in var returnedError: Error? let changeAppCodeExpectation = XCTestExpectation(description: "waitForResult") @@ -87,6 +112,21 @@ class EmarsysE2ETests: XCTestCase { } } + func clearContact() { + retry { [unowned self] () in + var returnedError: Error? + let contactExpectation = XCTestExpectation(description: "waitForResult") + Emarsys.clearContact(completionBlock: { error in + returnedError = error + contactExpectation.fulfill() + }) + _ = XCTWaiter.wait(for: [contactExpectation], timeout: timeout) + if let _ = returnedError { + throw E2EError.assertionError(assertionMessage: "error is not nil") + } + } + } + func sendEvent(_ name: String, timestamp: String) { retry { [unowned self] () in var returnedError: Error? diff --git a/Tests/E2ETests/EmarsysInboxE2ETests.swift b/Tests/E2ETests/EmarsysInboxE2ETests.swift index 99dfa6c3..3f96d9e4 100644 --- a/Tests/E2ETests/EmarsysInboxE2ETests.swift +++ b/Tests/E2ETests/EmarsysInboxE2ETests.swift @@ -35,18 +35,16 @@ class EmarsysInboxE2ETests: XCTestCase { func testInboxTags_step2() { retry { [unowned self] () in - let message = filterForInboxMessage("iosE2EChangeAppCodeFromNil", body: timestamp) - Emarsys.messageInbox.addTag(tag: "testtag", messageId: message.id) + addTag("testtag", message.id) } } func testInboxTags_step3() { var messageWithTag: EMSMessage? - retry { [unowned self] () in - + retry(delay: 5.0) { [unowned self] () in messageWithTag = filterForInboxMessage("iosE2EChangeAppCodeFromNil", body: timestamp) if let tags = messageWithTag?.tags, !tags.contains("testtag") { @@ -56,14 +54,13 @@ class EmarsysInboxE2ETests: XCTestCase { XCTAssertTrue(messageWithTag?.tags?.contains("testtag") ?? false) - Emarsys.messageInbox.removeTag(tag: "testtag", messageId: messageWithTag?.id ?? "") + removeTag("testtag", messageWithTag!.id) } func testInboxTags_step4() { var messageWithoutTag: EMSMessage? retry { [unowned self] () in - messageWithoutTag = filterForInboxMessage("iosE2EChangeAppCodeFromNil", body: timestamp) if let tags = messageWithoutTag?.tags, tags.contains("testtag") { @@ -110,6 +107,36 @@ class EmarsysInboxE2ETests: XCTestCase { } } } + + func addTag(_ tag: String, _ messageId: String) { + retry { [unowned self] () in + var returnedError: Error? + let expectation = XCTestExpectation(description: "waitForAddTag") + Emarsys.messageInbox.addTag(tag: "testtag", messageId: messageId) { error in + returnedError = error + expectation.fulfill() + } + if let returnedError { + throw InboxError.assertionError(assertionMessage: "error is not nil: \(returnedError)") + } + _ = XCTWaiter.wait(for: [expectation], timeout: timeout) + } + } + + func removeTag(_ tag: String, _ messageId: String) { + retry { [unowned self] () in + var returnedError: Error? + let expectation = XCTestExpectation(description: "waitForRemoveTag") + Emarsys.messageInbox.removeTag(tag: "testtag", messageId: messageId) { error in + returnedError = error + expectation.fulfill() + } + if let returnedError { + throw InboxError.assertionError(assertionMessage: "error is not nil: \(returnedError)") + } + _ = XCTWaiter.wait(for: [expectation], timeout: timeout) + } + } func filterForInboxMessage(_ title: String, body: String) -> EMSMessage { var inboxMessage: EMSMessage? diff --git a/Tests/EmarsysSDKTests/AppStart/EMSAppStartBlockProviderTests.m b/Tests/EmarsysSDKTests/AppStart/EMSAppStartBlockProviderTests.m index 329d25dc..797e6976 100644 --- a/Tests/EmarsysSDKTests/AppStart/EMSAppStartBlockProviderTests.m +++ b/Tests/EmarsysSDKTests/AppStart/EMSAppStartBlockProviderTests.m @@ -16,6 +16,9 @@ #import "EMSStorage.h" #import "EMSSdkStateLogger.h" #import "EMSLogger.h" +#import "EMSSQLiteHelper.h" +#import "EMSCompletionBlockProvider.h" +#import "XCTestCase+Helper.h" @interface EMSAppStartBlockProviderTests : XCTestCase @@ -33,12 +36,14 @@ @interface EMSAppStartBlockProviderTests : XCTestCase @property(nonatomic, strong) EMSStorage *mockStorage; @property(nonatomic, strong) EMSSdkStateLogger *mockSdkStateLogger; @property(nonatomic, strong) EMSLogger *mockLogger; +@property(nonatomic, strong) EMSSQLiteHelper *mockDbHelper; +@property(nonatomic, strong) EMSCompletionBlockProvider *completionBlockProvider; +@property(nonatomic, strong) NSOperationQueue *operationQueue; @end @implementation EMSAppStartBlockProviderTests - - (void)setUp { _applicationCode = @"testApplicationCode"; _contactFieldId = @3; @@ -48,6 +53,7 @@ - (void)setUp { _mockRequestContext = OCMClassMock([MERequestContext class]); _mockConfigInternal = OCMClassMock([EMSConfigInternal class]); _mockGeofenceInternal = OCMClassMock([EMSGeofenceInternal class]); + _mockDbHelper = OCMClassMock([EMSSQLiteHelper class]); _mockStorage = OCMClassMock([EMSStorage class]); _requestContext = [[MERequestContext alloc] initWithApplicationCode:self.applicationCode uuidProvider:[EMSUUIDProvider new] @@ -59,6 +65,9 @@ - (void)setUp { _mockSdkStateLogger = OCMClassMock([EMSSdkStateLogger class]); _mockLogger = OCMClassMock([EMSLogger class]); + + _operationQueue = self.createTestOperationQueue; + _completionBlockProvider = [[EMSCompletionBlockProvider alloc] initWithOperationQueue:self.operationQueue]; _appStartBlockProvider = [[EMSAppStartBlockProvider alloc] initWithRequestManager:self.mockRequestManager requestFactory:self.mockRequestFactory @@ -67,7 +76,9 @@ - (void)setUp { configInternal:self.mockConfigInternal geofenceInternal:self.mockGeofenceInternal sdkStateLogger:self.mockSdkStateLogger - logger:self.mockLogger]; + logger:self.mockLogger + dbHelper:self.mockDbHelper + completionBlockProvider:self.completionBlockProvider]; _appStartEventBlock = [self.appStartBlockProvider createAppStartEventBlock]; } @@ -84,7 +95,9 @@ - (void)testInit_requestManager_mustNotBeNil { configInternal:self.mockConfigInternal geofenceInternal:self.mockGeofenceInternal sdkStateLogger:self.mockSdkStateLogger - logger:self.mockLogger]; + logger:self.mockLogger + dbHelper:self.mockDbHelper + completionBlockProvider:self.completionBlockProvider]; XCTFail(@"Expected Exception when requestManager is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: requestManager"); @@ -100,7 +113,9 @@ - (void)testInit_requestFactory_mustNotBeNil { configInternal:self.mockConfigInternal geofenceInternal:self.mockGeofenceInternal sdkStateLogger:self.mockSdkStateLogger - logger:self.mockLogger]; + logger:self.mockLogger + dbHelper:self.mockDbHelper + completionBlockProvider:self.completionBlockProvider]; XCTFail(@"Expected Exception when requestFactory is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: requestFactory"); @@ -116,7 +131,9 @@ - (void)testInit_requestContext_mustNotBeNil { configInternal:self.mockConfigInternal geofenceInternal:self.mockGeofenceInternal sdkStateLogger:self.mockSdkStateLogger - logger:self.mockLogger]; + logger:self.mockLogger + dbHelper:self.mockDbHelper + completionBlockProvider:self.completionBlockProvider]; XCTFail(@"Expected Exception when requestContext is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: requestContext"); @@ -132,7 +149,9 @@ - (void)testInit_deviceInfoClient_mustNotBeNil { configInternal:self.mockConfigInternal geofenceInternal:self.mockGeofenceInternal sdkStateLogger:self.mockSdkStateLogger - logger:self.mockLogger]; + logger:self.mockLogger + dbHelper:self.mockDbHelper + completionBlockProvider:self.completionBlockProvider]; XCTFail(@"Expected Exception when deviceInfoClient is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: deviceInfoClient"); @@ -148,7 +167,9 @@ - (void)testInit_configInternal_mustNotBeNil { configInternal:nil geofenceInternal:self.mockGeofenceInternal sdkStateLogger:self.mockSdkStateLogger - logger:self.mockLogger]; + logger:self.mockLogger + dbHelper:self.mockDbHelper + completionBlockProvider:self.completionBlockProvider]; XCTFail(@"Expected Exception when configInternal is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: configInternal"); @@ -164,7 +185,9 @@ - (void)testInit_geofenceInternal_mustNotBeNil { configInternal:self.mockConfigInternal geofenceInternal:nil sdkStateLogger:self.mockSdkStateLogger - logger:self.mockLogger]; + logger:self.mockLogger + dbHelper:self.mockDbHelper + completionBlockProvider:self.completionBlockProvider]; XCTFail(@"Expected Exception when geofenceInternal is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: geofenceInternal"); @@ -180,7 +203,9 @@ - (void)testInit_sdkStateLogger_mustNotBeNil { configInternal:self.mockConfigInternal geofenceInternal:self.mockGeofenceInternal sdkStateLogger:nil - logger:self.mockLogger]; + logger:self.mockLogger + dbHelper:self.mockDbHelper + completionBlockProvider:self.completionBlockProvider]; XCTFail(@"Expected Exception when sdkStateLogger is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: sdkStateLogger"); @@ -196,13 +221,51 @@ - (void)testInit_logger_mustNotBeNil { configInternal:self.mockConfigInternal geofenceInternal:self.mockGeofenceInternal sdkStateLogger:self.mockSdkStateLogger - logger:nil]; + logger:nil + dbHelper:self.mockDbHelper + completionBlockProvider:self.completionBlockProvider]; XCTFail(@"Expected Exception when logger is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: logger"); } } +- (void)testInit_dbHelper_mustNotBeNil { + @try { + [[EMSAppStartBlockProvider alloc] initWithRequestManager:self.mockRequestManager + requestFactory:self.mockRequestFactory + requestContext:self.mockRequestContext + deviceInfoClient:self.mockDeviceInfoClient + configInternal:self.mockConfigInternal + geofenceInternal:self.mockGeofenceInternal + sdkStateLogger:self.mockSdkStateLogger + logger:self.mockLogger + dbHelper:nil + completionBlockProvider:self.completionBlockProvider]; + XCTFail(@"Expected Exception when dbHelper is nil!"); + } @catch (NSException *exception) { + XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: dbHelper"); + } +} + +- (void)testInit_completionBlockProvider_mustNotBeNil { + @try { + [[EMSAppStartBlockProvider alloc] initWithRequestManager:self.mockRequestManager + requestFactory:self.mockRequestFactory + requestContext:self.mockRequestContext + deviceInfoClient:self.mockDeviceInfoClient + configInternal:self.mockConfigInternal + geofenceInternal:self.mockGeofenceInternal + sdkStateLogger:self.mockSdkStateLogger + logger:self.mockLogger + dbHelper:self.mockDbHelper + completionBlockProvider:nil]; + XCTFail(@"Expected Exception when completionBlockProvider is nil!"); + } @catch (NSException *exception) { + XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: completionBlockProvider"); + } +} + - (void)testCreateAppStartBlockWithRequestManagerRequestContext_shouldSubmitAppStartEvent_whenInvokingHandlerBlock { [self.requestContext setContactToken:@"testContactToken"]; @@ -258,6 +321,8 @@ - (void)testCreateRemoteConfigEventBlock_refreshConfigBlockInvocation_triggersSd _appStartEventBlock = [self.appStartBlockProvider createRemoteConfigEventBlock]; self.appStartEventBlock(); + + [self waitATickOnOperationQueue:self.operationQueue]; OCMVerify([self.mockSdkStateLogger log]); } @@ -280,4 +345,12 @@ - (void)testCreateFetchGeofenceEventBlock { OCMVerify([self.mockGeofenceInternal fetchGeofences]); } -@end \ No newline at end of file +- (void)testCreateDbCloseEventBlock_shouldCloseDb { + _appStartEventBlock = [self.appStartBlockProvider createDbCloseEventBlock]; + + self.appStartEventBlock(); + + OCMVerify([self.mockDbHelper close]); +} + +@end diff --git a/Tests/EmarsysSDKTests/AppStart/EMSWrapperCheckerTests.m b/Tests/EmarsysSDKTests/AppStart/EMSWrapperCheckerTests.m index edf39a9c..16ffc440 100644 --- a/Tests/EmarsysSDKTests/AppStart/EMSWrapperCheckerTests.m +++ b/Tests/EmarsysSDKTests/AppStart/EMSWrapperCheckerTests.m @@ -3,9 +3,12 @@ // #import +#import #import "EMSWrapperChecker.h" #import "EMSDispatchWaiter.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" +#import "EMSStorage.h" @interface EMSWrapperCheckerTests : XCTestCase @@ -13,6 +16,7 @@ @interface EMSWrapperCheckerTests : XCTestCase @property(nonatomic, strong) NSOperationQueue *queue; @property(nonatomic, strong) EMSDispatchWaiter *waiter; @property(nonatomic, strong) NSObject *observer; +@property(nonatomic, strong) id storage; @end @@ -20,14 +24,15 @@ @implementation EMSWrapperCheckerTests - (void)setUp { _queue = [self createTestOperationQueue]; - _waiter = [EMSDispatchWaiter new]; + _storage = OCMClassMock([EMSStorage class]); _wrapperChecker = [[EMSWrapperChecker alloc] initWithOperationQueue:self.queue - waiter:self.waiter]; + waiter:self.waiter + storage:self.storage]; } - (void)tearDown { - [self tearDownOperationQueue:self.queue]; + [EmarsysTestUtils tearDownOperationQueue:self.queue]; if (self.observer) { [[NSNotificationCenter defaultCenter] removeObserver:self.observer]; _observer = nil; @@ -37,7 +42,8 @@ - (void)tearDown { - (void)testInit_queue_mustNotBeNil { @try { [[EMSWrapperChecker alloc] initWithOperationQueue:nil - waiter:self.waiter]; + waiter:self.waiter + storage:self.storage]; XCTFail(@"Expected Exception when queue is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: queue"); @@ -47,23 +53,42 @@ - (void)testInit_queue_mustNotBeNil { - (void)testInit_waiter_mustNotBeNil { @try { [[EMSWrapperChecker alloc] initWithOperationQueue:self.queue - waiter:nil]; + waiter:nil + storage:self.storage]; XCTFail(@"Expected Exception when waiter is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: waiter"); } } +- (void)testInit_storage_mustNotBeNil { + @try { + [[EMSWrapperChecker alloc] initWithOperationQueue:self.queue + waiter:self.waiter + storage:nil]; + XCTFail(@"Expected Exception when storage is nil!"); + } @catch (NSException *exception) { + XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: storage"); + } +} + - (void)testWrapper_wrapperExists { - _observer = [NSNotificationCenter.defaultCenter addObserverForName:@"EmarsysSDKWrapperCheckerNotification" - object:nil - queue:nil - usingBlock:^(NSNotification *note) { - [NSNotificationCenter.defaultCenter postNotificationName:@"EmarsysSDKWrapperExist" - object:@"testWrapper"]; - }]; - NSString *wrapper = self.wrapperChecker.wrapper; + XCTestExpectation *expectation = [self expectationForNotification:@"EmarsysSDKWrapperCheckerNotification" object:nil handler:^BOOL(NSNotification * _Nonnull notification) { + [NSNotificationCenter.defaultCenter postNotificationName:@"EmarsysSDKWrapperExist" + object:@"testWrapper"]; + return YES; + }]; + self.wrapperChecker.wrapper; + + XCTWaiterResult waiterResult = [XCTWaiter waitForExpectations:@[expectation] + timeout:2.0]; + XCTAssertEqual(waiterResult, XCTWaiterResultCompleted); + + [self waitATickOnOperationQueue:self.queue]; + + NSString *wrapper = self.wrapperChecker.wrapper; + XCTAssertEqualObjects(wrapper, @"testWrapper"); } diff --git a/Tests/EmarsysSDKTests/DI/EMSDependencyInjectionTests.m b/Tests/EmarsysSDKTests/DI/EMSDependencyInjectionTests.m index 6f6f691a..ffc2c1d3 100644 --- a/Tests/EmarsysSDKTests/DI/EMSDependencyInjectionTests.m +++ b/Tests/EmarsysSDKTests/DI/EMSDependencyInjectionTests.m @@ -75,10 +75,6 @@ + (void)setDependencyContainer:(EMSDependencyContainer *)dependencyContainer; describe(@"mobileEngage", ^{ - beforeEach(^{ - [EmarsysTestUtils tearDownEmarsys]; - }); - afterEach(^{ [EmarsysTestUtils tearDownEmarsys]; }); diff --git a/Tests/EmarsysSDKTests/EmarsysTests.m b/Tests/EmarsysSDKTests/EmarsysTests.m index 7afc6653..4f6130c7 100644 --- a/Tests/EmarsysSDKTests/EmarsysTests.m +++ b/Tests/EmarsysSDKTests/EmarsysTests.m @@ -35,6 +35,7 @@ #import "EMSUUIDProvider.h" #import "EMSInnerFeature.h" #import "MEExperimental.h" +#import "XCTestCase+Helper.h" @interface EmarsysTests : XCTestCase @@ -47,17 +48,17 @@ - (void)tearDown { } - (void)testShouldInitializeCategoryForPush { - XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"testExpectation"]; - [EmarsysTestUtils setupEmarsysWithFeatures:@[] withDependencyContainer:nil]; + [self waitForSetup]; + + XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"testExpectation"]; __block NSSet *categorySet = nil; [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:^(NSSet *categories) { categorySet = categories; [expectation fulfill]; }]; - XCTWaiterResult result = [XCTWaiter waitForExpectations:@[expectation] timeout:10]; @@ -102,7 +103,6 @@ - (void)testShouldSetConfig { } - (void)testRegisterTriggers_when_PredictTurnedOn { - [EmarsysTestUtils tearDownEmarsys]; [EmarsysTestUtils setupEmarsysWithConfig:[EMSConfig makeWithBuilder:^(EMSConfigBuilder *builder) { [builder setMobileEngageApplicationCode:@"14C19-A121F"]; [builder setMerchantId:@"1428C8EE286EC34B"]; @@ -123,7 +123,6 @@ - (void)testRegisterTriggers_when_PredictTurnedOn { } - (void)testRegisterTriggers { - [EmarsysTestUtils tearDownEmarsys]; [EmarsysTestUtils setupEmarsysWithConfig:[EMSConfig makeWithBuilder:^(EMSConfigBuilder *builder) { [builder setMobileEngageApplicationCode:@"14C19-A121F"]; }] @@ -136,7 +135,7 @@ - (void)testRegisterTriggers { NSArray *afterInsertTriggers = triggers[[[EMSDBTriggerKey alloc] initWithTableName:@"shard" withEvent:[EMSDBTriggerEvent insertEvent] withType:[EMSDBTriggerType afterType]]]; - XCTAssertEqual([afterInsertTriggers count], 1); + XCTAssertEqual([afterInsertTriggers count], 2); XCTAssertTrue([afterInsertTriggers containsObject:EMSDependencyInjection.dependencyContainer.loggerTrigger]); } @@ -248,6 +247,7 @@ - (void)testShouldRegisterUIApplicationDidBecomeActiveNotification { } mobileEngageEnabled:YES predictEnabled:NO]; + [self waitForSetup]; OCMVerify([mockProvider createAppStartEventBlock]); OCMVerify([mockProvider createDeviceInfoEventBlock]); @@ -277,6 +277,7 @@ - (void)testSetupWithConfigShouldSendDeviceInfoAndLogin { } mobileEngageEnabled:YES predictEnabled:NO]; + [self waitForSetup]; OCMVerify([mockDeviceInfoClient trackDeviceInfoWithCompletionBlock:nil]); OCMVerify([mockMobileEngage setContactWithContactFieldId:nil @@ -303,6 +304,7 @@ - (void)testSetupWithConfigShouldNotSendDeviceInfoAndLogin_when_contactFieldValu } mobileEngageEnabled:YES predictEnabled:NO]; + [self waitForSetup]; } - (void)testSetupWithConfigShouldNotSendDeviceInfoAndLogin_when_contactTokenIsAvailable { @@ -325,6 +327,7 @@ - (void)testSetupWithConfigShouldNotSendDeviceInfoAndLogin_when_contactTokenIsAv } mobileEngageEnabled:YES predictEnabled:NO]; + [self waitForSetup]; } - (void)testShouldDelegateCallToDeeplink { @@ -339,6 +342,7 @@ - (void)testShouldDelegateCallToDeeplink { } mobileEngageEnabled:YES predictEnabled:NO]; + [self waitForSetup]; [Emarsys trackDeepLinkWithUserActivity:mockUserActivity sourceHandler:sourceHandler]; @@ -358,7 +362,8 @@ - (void)testShouldDelegateCallToMobileEngageWithNilCompletionBlock { } mobileEngageEnabled:YES predictEnabled:NO]; - + [self waitForSetup]; + [Emarsys trackCustomEventWithName:eventName eventAttributes:eventAttributes]; @@ -380,7 +385,8 @@ - (void)testShouldDelegateCallToMobileEngage { } mobileEngageEnabled:YES predictEnabled:NO]; - + [self waitForSetup]; + [Emarsys trackCustomEventWithName:eventName eventAttributes:eventAttributes completionBlock:completionBlock]; @@ -402,6 +408,7 @@ - (void)testSetAuthorizedContact_shouldDelegateCallToMobileEngage { } mobileEngageEnabled:YES predictEnabled:NO]; + [self waitForSetup]; [Emarsys setAuthenticatedContactWithContactFieldId:@3 openIdToken:idToken @@ -441,6 +448,7 @@ - (void)testSetAuthorizedContact_setAuthorizedContactIsNotCalledOnMobileEngage_w } mobileEngageEnabled:NO predictEnabled:YES]; + [self waitForSetup]; [Emarsys setAuthenticatedContactWithContactFieldId:@3 openIdToken:idToken @@ -458,6 +466,7 @@ - (void)testSetAuthorizedContactWithContactFieldValueIsOnlyCalledOnce_when_mobil } mobileEngageEnabled:NO predictEnabled:NO]; + [self waitForSetup]; [Emarsys setAuthenticatedContactWithContactFieldId:@3 openIdToken:idToken @@ -500,6 +509,7 @@ - (void)testSetAuthenticatedContactWithContactFieldValueSsCalledByMobileEngage_w } mobileEngageEnabled:YES predictEnabled:YES]; + [self waitForSetup]; [Emarsys setAuthenticatedContactWithContactFieldId:@3 openIdToken:idToken]; @@ -541,6 +551,7 @@ - (void)testSetContactWithContactFieldValueIsNotCalledByPredict_when_predictIsDi } mobileEngageEnabled:YES predictEnabled:NO]; + [self waitForSetup]; [Emarsys setContactWithContactFieldId:@3 contactFieldValue:@"contact"]; @@ -554,6 +565,7 @@ - (void)testSetContactWithContactFieldValueIsCalledByPredict_when_predictIsEnabl } mobileEngageEnabled:YES predictEnabled:YES]; + [self waitForSetup]; [Emarsys setContactWithContactFieldId:@3 contactFieldValue:@"contact"]; @@ -574,6 +586,7 @@ - (void)testSetContactWithContactFieldValueIsNotCalledByMobileEngage_when_mobile } mobileEngageEnabled:NO predictEnabled:YES]; + [self waitForSetup]; [Emarsys setContactWithContactFieldId:@3 contactFieldValue:@"contact"]; @@ -587,6 +600,7 @@ - (void)testSetContactWithContactFieldValueIsCalledByMobileEngage_when_mobileEng } mobileEngageEnabled:YES predictEnabled:YES]; + [self waitForSetup]; [Emarsys setContactWithContactFieldId:@3 contactFieldValue:@"contact"]; @@ -609,6 +623,7 @@ - (void)testSetContactWithContactFieldValueIsOnlyCalledOnce_when_mobileEngageAnd } mobileEngageEnabled:NO predictEnabled:NO]; + [self waitForSetup]; [Emarsys setContactWithContactFieldId:@3 contactFieldValue:@"contact"]; @@ -628,6 +643,7 @@ - (void)testClearContactIsNotCalledByPredict_when_predictIsDisabled { } mobileEngageEnabled:NO predictEnabled:NO]; + [self waitForSetup]; [Emarsys clearContact]; } @@ -640,6 +656,7 @@ - (void)testClearContactIsCalledByPredict_when_predictIsEnabled { } mobileEngageEnabled:NO predictEnabled:YES]; + [self waitForSetup]; [Emarsys clearContact]; @@ -656,7 +673,8 @@ - (void)testClearContactIsNotCalledByMobileEngage_when_mobileEngageIsDisabled { } mobileEngageEnabled:NO predictEnabled:YES]; - + [self waitForSetup]; + [Emarsys clearContact]; } @@ -668,6 +686,7 @@ - (void)testClearContactIsCalledByMobileEngage_when_mobileEngageIsEnabled { } mobileEngageEnabled:YES predictEnabled:NO]; + [self waitForSetup]; [Emarsys clearContact]; @@ -686,6 +705,7 @@ - (void)testClearContactIsOnlyCalledOnce_when_mobileEngageAndPredictAreDisabled } mobileEngageEnabled:NO predictEnabled:NO]; + [self waitForSetup]; [Emarsys clearContact]; @@ -697,6 +717,7 @@ - (void)testV4ShouldBeEnabled { } mobileEngageEnabled:YES predictEnabled:NO]; + [self waitForSetup]; XCTAssertTrue([MEExperimental isFeatureEnabled:EMSInnerFeature.eventServiceV4]); } @@ -706,6 +727,7 @@ - (void)testV4ShouldBeDisabledWhenMobileEngageIsDisabled { } mobileEngageEnabled:NO predictEnabled:NO]; + [self waitForSetup]; XCTAssertFalse([MEExperimental isFeatureEnabled:EMSInnerFeature.eventServiceV4]); } @@ -715,6 +737,7 @@ - (void)testShouldBeEMSPushV3Internal { } mobileEngageEnabled:YES predictEnabled:YES]; + [self waitForSetup]; XCTAssertEqual([((EMSQueueDelegator *) Emarsys.push).instanceRouter.instance class], [EMSPushV3Internal class]); } @@ -724,6 +747,7 @@ - (void)testShouldBeMEInApp { } mobileEngageEnabled:YES predictEnabled:YES]; + [self waitForSetup]; XCTAssertEqual([((EMSQueueDelegator *) Emarsys.inApp).instanceRouter.instance class], [MEInApp class]); } @@ -733,6 +757,7 @@ - (void)testShouldBeEMSPredictInternal { } mobileEngageEnabled:YES predictEnabled:YES]; + [self waitForSetup]; XCTAssertEqual([((EMSQueueDelegator *) Emarsys.predict).instanceRouter.instance class], [EMSPredictInternal class]); } @@ -742,6 +767,7 @@ - (void)testShouldBeEMSMobileEngageV3Internal { } mobileEngageEnabled:YES predictEnabled:YES]; + [self waitForSetup]; XCTAssertEqual([((EMSQueueDelegator *) EMSDependencyInjection.dependencyContainer.mobileEngage).instanceRouter.instance class], [EMSMobileEngageV3Internal class]); } @@ -751,6 +777,7 @@ - (void)testShouldBeEMSDeepLinkInternal { } mobileEngageEnabled:YES predictEnabled:YES]; + [self waitForSetup]; XCTAssertEqual([((EMSQueueDelegator *) EMSDependencyInjection.dependencyContainer.deepLink).instanceRouter.instance class], [EMSDeepLinkInternal class]); } @@ -760,6 +787,7 @@ - (void)testShouldBeEMSGeofenceInternal { } mobileEngageEnabled:YES predictEnabled:YES]; + [self waitForSetup]; XCTAssertEqual([((EMSQueueDelegator *) Emarsys.geofence).instanceRouter.instance class], [EMSGeofenceInternal class]); } @@ -769,6 +797,7 @@ - (void)testShouldBeEMSInboxV3 { } mobileEngageEnabled:YES predictEnabled:YES]; + [self waitForSetup]; XCTAssertEqual([((EMSQueueDelegator *) Emarsys.messageInbox).instanceRouter.instance class], [EMSInboxV3 class]); } @@ -778,7 +807,8 @@ - (void)testShouldBeEMSLoggingPushInternal { } mobileEngageEnabled:NO predictEnabled:NO]; - + [self waitForSetup]; + XCTAssertEqual([((EMSQueueDelegator *) Emarsys.push).instanceRouter.instance class], [EMSLoggingPushInternal class]); } @@ -787,6 +817,7 @@ - (void)testShouldBeEMSLoggingInApp { } mobileEngageEnabled:NO predictEnabled:NO]; + [self waitForSetup]; XCTAssertEqual([((EMSQueueDelegator *) Emarsys.inApp).instanceRouter.instance class], [EMSLoggingInApp class]); } @@ -796,6 +827,7 @@ - (void)testShouldBeEMSLoggingPredictInternal { } mobileEngageEnabled:NO predictEnabled:NO]; + [self waitForSetup]; XCTAssertEqual([((EMSQueueDelegator *) Emarsys.predict).instanceRouter.instance class], [EMSLoggingPredictInternal class]); } @@ -805,7 +837,8 @@ - (void)testShouldBeEMSLoggingMobileEngageInternal { } mobileEngageEnabled:NO predictEnabled:NO]; - + [self waitForSetup]; + XCTAssertEqual([((EMSQueueDelegator *) EMSDependencyInjection.mobileEngage).instanceRouter.instance class], [EMSLoggingMobileEngageInternal class]); } @@ -814,6 +847,7 @@ - (void)testShouldBeEMSLoggingGeofenceInternal { } mobileEngageEnabled:NO predictEnabled:NO]; + [self waitForSetup]; XCTAssertEqual([((EMSQueueDelegator *) Emarsys.geofence).instanceRouter.instance class], [EMSLoggingGeofenceInternal class]); } @@ -823,6 +857,7 @@ - (void)testShouldBeEMSLoggingInboxV3 { } mobileEngageEnabled:NO predictEnabled:NO]; + [self waitForSetup]; XCTAssertEqual([((EMSQueueDelegator *) Emarsys.messageInbox).instanceRouter.instance class], [EMSLoggingInboxV3 class]); } @@ -834,12 +869,14 @@ - (void)testShouldResetContextOnReinstall { } mobileEngageEnabled:YES predictEnabled:NO]; + [self waitForSetup]; + OCMVerify([mockContext reset]); - } - (void)testShouldNotResetContextOnReinstallWhenContactFieldValueIsPresent { MERequestContext *mockContext = OCMClassMock([MERequestContext class]); + MERequestContext *mockContex2 = OCMClassMock([MERequestContext class]); OCMStub([mockContext contactFieldValue]).andReturn(@"teszt@teszt.kom"); OCMReject([mockContext reset]); [self setupContainerWithMocks:^(EMSDependencyContainer *partialMockContainer) { @@ -847,11 +884,8 @@ - (void)testShouldNotResetContextOnReinstallWhenContactFieldValueIsPresent { } mobileEngageEnabled:YES predictEnabled:NO]; - - [EMSDependencyInjection.dependencyContainer.publicApiOperationQueue waitUntilAllOperationsAreFinished]; - [EMSDependencyInjection.dependencyContainer.publicApiOperationQueue cancelAllOperations]; - [EMSDependencyInjection.dependencyContainer.coreOperationQueue waitUntilAllOperationsAreFinished]; - [EMSDependencyInjection.dependencyContainer.coreOperationQueue cancelAllOperations]; + [EmarsysTestUtils tearDownOperationQueue:EMSDependencyInjection.dependencyContainer.publicApiOperationQueue]; + [EmarsysTestUtils tearDownOperationQueue:EMSDependencyInjection.dependencyContainer.coreOperationQueue]; [EMSDependencyInjection tearDown]; } @@ -866,24 +900,11 @@ - (void)testShouldNotResetContextOnSetupWhenItIsNotReinstall { } mobileEngageEnabled:YES predictEnabled:NO]; - - [EMSDependencyInjection.dependencyContainer.publicApiOperationQueue waitUntilAllOperationsAreFinished]; - [EMSDependencyInjection.dependencyContainer.publicApiOperationQueue cancelAllOperations]; - [EMSDependencyInjection.dependencyContainer.coreOperationQueue waitUntilAllOperationsAreFinished]; - [EMSDependencyInjection.dependencyContainer.coreOperationQueue cancelAllOperations]; + [EmarsysTestUtils tearDownOperationQueue:EMSDependencyInjection.dependencyContainer.publicApiOperationQueue]; + [EmarsysTestUtils tearDownOperationQueue:EMSDependencyInjection.dependencyContainer.coreOperationQueue]; [EMSDependencyInjection tearDown]; } -- (void)waitForSetup { - XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"waitForDescription"]; - [EMSDependencyInjection.dependencyContainer.publicApiOperationQueue addOperationWithBlock:^{ - [expectation fulfill]; - }]; - XCTWaiterResult waiterResult = [XCTWaiter waitForExpectations:@[expectation] - timeout:10]; - XCTAssertEqual(waiterResult, XCTWaiterResultCompleted); -} - - (void)setupContainerWithMocks:(void (^)(EMSDependencyContainer *partialMockContainer))partialMockContainerBlock mobileEngageEnabled:(BOOL)isMobileEngageEnabled predictEnabled:(BOOL)isPredictEnabled { @@ -907,4 +928,9 @@ - (void)setupContainerWithMocks:(void (^)(EMSDependencyContainer *partialMockCon [self waitForSetup]; } +- (void)waitForSetup { + [self waitATickOnOperationQueue:EMSDependencyInjection.dependencyContainer.publicApiOperationQueue]; + [self waitATickOnOperationQueue:EMSDependencyInjection.dependencyContainer.coreOperationQueue]; +} + @end diff --git a/Tests/EmarsysSDKTests/Setup/EMSConfigInternalTests.m b/Tests/EmarsysSDKTests/Setup/EMSConfigInternalTests.m index 54cfe721..04a0528f 100644 --- a/Tests/EmarsysSDKTests/Setup/EMSConfigInternalTests.m +++ b/Tests/EmarsysSDKTests/Setup/EMSConfigInternalTests.m @@ -23,6 +23,7 @@ #import "EmarsysTestUtils.h" #import "EmarsysSDKVersion.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" @interface EMSConfigInternal (Tests) @@ -101,14 +102,13 @@ - (void)setUp { endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; } - (void)tearDown { - [self tearDownOperationQueue:self.queue]; - [EmarsysTestUtils tearDownEmarsys]; + [EmarsysTestUtils tearDownOperationQueue:self.queue]; } - (void)testInit_requestManager_mustNotBeNil { @@ -124,7 +124,7 @@ - (void)testInit_requestManager_mustNotBeNil { endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; XCTFail(@"Expected Exception when requestManager is nil!"); @@ -146,7 +146,7 @@ - (void)testInit_meRequestContext_mustNotBeNil { endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; XCTFail(@"Expected Exception when meRequestContext is nil!"); @@ -168,7 +168,7 @@ - (void)testInit_mobileEngage_mustNotBeNil { endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; XCTFail(@"Expected Exception when mobileEngage is nil!"); @@ -190,7 +190,7 @@ - (void)testInit_pushInternal_mustNotBeNil { endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; XCTFail(@"Expected Exception when pushInternal is nil!"); @@ -212,7 +212,7 @@ - (void)testInit_preRequestContext_mustNotBeNil { endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; XCTFail(@"Expected Exception when preRequestContext is nil!"); @@ -234,7 +234,7 @@ - (void)testInit_deviceInfo_mustNotBeNil { endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; XCTFail(@"Expected Exception when deviceInfo is nil!"); @@ -256,7 +256,7 @@ - (void)testInit_emarsysRequestFactory_mustNotBeNil { endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; XCTFail(@"Expected Exception when emarsysRequestFactory is nil!"); @@ -278,7 +278,7 @@ - (void)testInit_remoteConfigResponseMapper_mustNotBeNil { endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; XCTFail(@"Expected Exception when remoteConfigResponseMapper is nil!"); @@ -300,7 +300,7 @@ - (void)testInit_endpoint_mustNotBeNil { endpoint:nil logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; XCTFail(@"Expected Exception when endpoint is nil!"); @@ -322,7 +322,7 @@ - (void)testInit_logger_mustNotBeNil { endpoint:self.mockEndpoint logger:nil crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; XCTFail(@"Expected Exception when logger is nil!"); @@ -344,7 +344,7 @@ - (void)testInit_crypto_mustNotBeNil { endpoint:self.mockEndpoint logger:self.mockLogger crypto:nil - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; XCTFail(@"Expected Exception when crypto is nil!"); @@ -366,12 +366,12 @@ - (void)testInit_queue_mustNotBeNil { endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:nil + coreQueue:nil waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; - XCTFail(@"Expected Exception when queue is nil!"); + XCTFail(@"Expected Exception when coreQueue is nil!"); } @catch (NSException *exception) { - XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: queue"); + XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: coreQueue"); } } @@ -388,7 +388,7 @@ - (void)testInit_waiter_mustNotBeNil { endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:nil deviceInfoClient:self.mockDeviceInfoClient]; XCTFail(@"Expected Exception when waiter is nil!"); @@ -411,7 +411,7 @@ - (void)testInit_deviceInfoClient_mustNotBeNil { endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:nil]; XCTFail(@"Expected Exception when deviceInfoClient is nil!"); @@ -438,7 +438,7 @@ - (void)testChangeApplicationCode_completionHandler_isNil { endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; @@ -493,7 +493,7 @@ - (void)testChangeApplicationCode { endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:strictMockDeviceInfoClient]; EMSConfigInternal *partialMockConfigInternal = OCMPartialMock(self.configInternal); @@ -558,7 +558,7 @@ - (void)testChangeApplicationCode_clearContact_shouldCallCompletionBlockWithErro endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; @@ -612,7 +612,7 @@ - (void)testChangeApplicationCode_shouldNotCallClearContact_whenSendDeviceInfoRe endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; @@ -649,7 +649,7 @@ - (void)testChangeApplicationCode_shouldNotCallClearContact_whenSendPushTokenRet endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; @@ -691,7 +691,7 @@ - (void)testChangeApplicationCode_setPushToken_shouldCallCompletionBlockWithErro endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; @@ -759,7 +759,7 @@ - (void)testChangeApplicationCode_setPushToken_shouldNotBeCalled_afterContactFie endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; @@ -797,7 +797,7 @@ - (void)testChangeApplicationCode_setPushToken_shouldNotBeCalled_whenPushTokenIs endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; @@ -847,7 +847,7 @@ - (void)testChangeApplicationCode_shouldCallSetPushTokenImmediately_whenApplicat endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; @@ -894,7 +894,7 @@ - (void)testChangeApplicationCode_shouldCallSetContactImmediately_whenPushTokenI endpoint:self.mockEndpoint logger:self.mockLogger crypto:self.mockCrypto - queue:self.queue + coreQueue:self.queue waiter:self.waiter deviceInfoClient:self.mockDeviceInfoClient]; diff --git a/Tests/MobileEngageTests/Category/EMSDeviceInfo+MEClientPayloadTests.m b/Tests/MobileEngageTests/Category/EMSDeviceInfo+MEClientPayloadTests.m index 8129974b..aeb056b5 100644 --- a/Tests/MobileEngageTests/Category/EMSDeviceInfo+MEClientPayloadTests.m +++ b/Tests/MobileEngageTests/Category/EMSDeviceInfo+MEClientPayloadTests.m @@ -9,29 +9,34 @@ #import "EMSDeviceInfo+MEClientPayload.h" #import "EMSStorage.h" #import "EMSUUIDProvider.h" +#import "XCTestCase+Helper.h" @interface EMSDeviceInfo_MEClientPayloadTests : XCTestCase +@property(nonatomic, strong) NSOperationQueue *queue; + @end @implementation EMSDeviceInfo_MEClientPayloadTests - (void)testClientPayload { + NSOperationQueue *queue = [self createTestOperationQueue]; EMSDeviceInfo *deviceInfo = [[EMSDeviceInfo alloc] initWithSDKVersion:@"testSDKVersion" notificationCenter:[UNUserNotificationCenter currentNotificationCenter] storage:[[EMSStorage alloc] initWithSuiteNames:@[] - accessGroup:nil] + accessGroup:nil + operationQueue:queue] uuidProvider:[EMSUUIDProvider new]]; - + EMSDeviceInfo *partialMockDeviceInfo = OCMPartialMock(deviceInfo); - + NSDictionary *pushSettings = @{ @"pushSettingKey1": @"pushSettingValue1", @"pushSettingKey2": @"pushSettingValue2" }; - + OCMStub([partialMockDeviceInfo pushSettings]).andReturn(pushSettings); - + NSDictionary *expectedDictionary = @{ @"platform": deviceInfo.platform, @"applicationVersion": deviceInfo.applicationVersion, @@ -45,9 +50,9 @@ - (void)testClientPayload { @"pushSettingKey2": @"pushSettingValue2" } }; - + NSDictionary *clientPayload = partialMockDeviceInfo.clientPayload; - + XCTAssertEqualObjects(clientPayload, expectedDictionary); } diff --git a/Tests/MobileEngageTests/DeviceInfoClient/EMSDeviceInfoV3ClientInternalTests.m b/Tests/MobileEngageTests/DeviceInfoClient/EMSDeviceInfoV3ClientInternalTests.m index 930615d1..897bde1a 100644 --- a/Tests/MobileEngageTests/DeviceInfoClient/EMSDeviceInfoV3ClientInternalTests.m +++ b/Tests/MobileEngageTests/DeviceInfoClient/EMSDeviceInfoV3ClientInternalTests.m @@ -14,6 +14,7 @@ #import "EMSStorage.h" #import "NSError+EMSCore.h" #import "EMSUUIDProvider.h" +#import "XCTestCase+Helper.h" @interface EMSDeviceInfoV3ClientInternalTests : XCTestCase @@ -24,6 +25,7 @@ @interface EMSDeviceInfoV3ClientInternalTests : XCTestCase @property(nonatomic, strong) MERequestContext *mockRequestContext; @property(nonatomic, strong) NSArray *suiteNames; @property(nonatomic, strong) EMSUUIDProvider *uuidProvider; +@property(nonatomic, strong) NSOperationQueue *queue; @end @@ -35,12 +37,13 @@ - (void)setUp { _mockDeviceInfo = OCMClassMock([EMSDeviceInfo class]); _mockRequestContext = OCMClassMock([MERequestContext class]); _uuidProvider = [EMSUUIDProvider new]; - + _queue = [self createTestOperationQueue]; + _deviceInfoInternal = [[EMSDeviceInfoV3ClientInternal alloc] initWithRequestManager:self.mockRequestManager requestFactory:self.mockRequestFactory deviceInfo:self.mockDeviceInfo requestContext:self.mockRequestContext]; - + _suiteNames = @[@"com.emarsys.core", @"com.emarsys.predict", @"com.emarsys.mobileengage"]; } @@ -99,20 +102,20 @@ - (void)testSendDeviceInfo_submitRequest { [userDefaults setObject:@{@"testStoredPayloadKey": @"testStoredPayloadValue"} forKey:kDEVICE_INFO]; [userDefaults synchronize]; - + NSDictionary *expectedDeviceInfoDict = @{ - @"testPayloadKey": @"testPayloadValue", - @"testPayloadKey2": @"testPayloadValue2" + @"testPayloadKey": @"testPayloadValue", + @"testPayloadKey2": @"testPayloadValue2" }; EMSRequestModel *requestModel = OCMClassMock([EMSRequestModel class]); - + OCMStub([self.mockDeviceInfo clientPayload]).andReturn(expectedDeviceInfoDict); OCMStub([self.mockRequestFactory createDeviceInfoRequestModel]).andReturn(requestModel); - + [self.deviceInfoInternal trackDeviceInfoWithCompletionBlock:completionBlock]; - + NSDictionary *storedDeviceInfoDict = [userDefaults dictionaryForKey:kDEVICE_INFO]; - + OCMVerify([self.mockRequestManager submitRequestModel:requestModel withCompletionBlock:completionBlock]); XCTAssertEqualObjects(storedDeviceInfoDict, expectedDeviceInfoDict); @@ -120,29 +123,30 @@ - (void)testSendDeviceInfo_submitRequest { - (void)testSendDeviceInfo_shouldNotSubmit_whenDeviceInfoHasNotChanged { OCMStub(self.mockRequestContext.clientState).andReturn(@"testClientState"); - + EMSDeviceInfo *deviceInfo = [[EMSDeviceInfo alloc] initWithSDKVersion:@"0.0.1" notificationCenter:[UNUserNotificationCenter currentNotificationCenter] storage:[[EMSStorage alloc] initWithSuiteNames:self.suiteNames - accessGroup:nil] + accessGroup:nil + operationQueue:self.queue] uuidProvider:self.uuidProvider]; - + _deviceInfoInternal = [[EMSDeviceInfoV3ClientInternal alloc] initWithRequestManager:self.mockRequestManager requestFactory:self.mockRequestFactory deviceInfo:deviceInfo requestContext:self.mockRequestContext]; OCMReject([self.mockRequestManager submitRequestModel:[OCMArg any] withCompletionBlock:[OCMArg any]]); - + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; - + NSDictionary *deviceInfoDictionary = [deviceInfo clientPayload]; [userDefaults setObject:deviceInfoDictionary forKey:kDEVICE_INFO]; [userDefaults synchronize]; - + __block NSError *returnedError = [NSError errorWithCode:-1400 - localizedDescription:@"testError"]; + localizedDescription:@"testError"]; XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"waitForCompletion"]; [self.deviceInfoInternal trackDeviceInfoWithCompletionBlock:^(NSError *error) { returnedError = error; @@ -159,35 +163,36 @@ - (void)testSendDeviceInfo_shouldSubmit_whenClientStateIsMissing { EMSCompletionBlock completionBlock = ^(NSError *error) { }; OCMStub(self.mockRequestContext.clientState).andReturn(nil); - + EMSDeviceInfo *deviceInfo = [[EMSDeviceInfo alloc] initWithSDKVersion:@"0.0.1" notificationCenter:[UNUserNotificationCenter currentNotificationCenter] storage:[[EMSStorage alloc] initWithSuiteNames:self.suiteNames - accessGroup:nil] + accessGroup:nil + operationQueue:self.queue] uuidProvider:self.uuidProvider]; _deviceInfoInternal = [[EMSDeviceInfoV3ClientInternal alloc] initWithRequestManager:self.mockRequestManager requestFactory:self.mockRequestFactory deviceInfo:deviceInfo requestContext:self.mockRequestContext]; EMSRequestModel *requestModel = OCMClassMock([EMSRequestModel class]); - + OCMStub([self.mockRequestFactory createDeviceInfoRequestModel]).andReturn(requestModel); - + [self.deviceInfoInternal trackDeviceInfoWithCompletionBlock:completionBlock]; - + OCMVerify([self.mockRequestManager submitRequestModel:requestModel withCompletionBlock:completionBlock]); } - (void)testTrackDeviceInfo_shouldCallSendDeviceInfo { EMSDeviceInfoV3ClientInternal *partialDeviceInfoClient = OCMPartialMock(self.deviceInfoInternal); - + EMSCompletionBlock completionBlock = ^(NSError *error) { - + }; - + [partialDeviceInfoClient trackDeviceInfoWithCompletionBlock:completionBlock]; - + OCMVerify([partialDeviceInfoClient sendDeviceInfoWithCompletionBlock:completionBlock]); } diff --git a/Tests/MobileEngageTests/EMSMobileEngageV3InternalTests.m b/Tests/MobileEngageTests/EMSMobileEngageV3InternalTests.m index 94e9153b..0dcefbe3 100644 --- a/Tests/MobileEngageTests/EMSMobileEngageV3InternalTests.m +++ b/Tests/MobileEngageTests/EMSMobileEngageV3InternalTests.m @@ -13,6 +13,8 @@ #import "EMSStorage.h" #import "EMSSession.h" #import "EMSStorageProtocol.h" +#import "EMSCompletionBlockProvider.h" +#import "XCTestCase+Helper.h" @interface EMSMobileEngageV3InternalTests : XCTestCase @@ -21,6 +23,7 @@ @interface EMSMobileEngageV3InternalTests : XCTestCase @property(nonatomic, strong) EMSRequestManager *mockRequestManager; @property(nonatomic, strong) MERequestContext *mockRequestContext; @property(nonatomic, strong) EMSSession *mockSession; +@property(nonatomic, strong) EMSCompletionBlockProvider *completionBlockProvider; @property(nonatomic, strong) EMSStorage *mockStorage; @property(nonatomic, strong) NSString *contactFieldValue; @property(nonatomic, strong) NSNumber *contactFieldId; @@ -29,6 +32,7 @@ @interface EMSMobileEngageV3InternalTests : XCTestCase @property(nonatomic, strong) NSString *eventName; @property(nonatomic, strong) NSDictionary *eventAttributes; @property(nonatomic, copy) void (^completionBlock)(NSError *); +@property(nonatomic, strong) NSOperationQueue *operationQueue; @end @@ -53,11 +57,15 @@ - (void)setUp { _mockStorage = OCMClassMock([EMSStorage class]); _mockSession = OCMClassMock([EMSSession class]); + _operationQueue = self.createTestOperationQueue; + _completionBlockProvider = [[EMSCompletionBlockProvider alloc] initWithOperationQueue:self.operationQueue]; + _internal = [[EMSMobileEngageV3Internal alloc] initWithRequestFactory:self.mockRequestFactory requestManager:self.mockRequestManager requestContext:self.mockRequestContext storage:self.mockStorage - session:self.mockSession]; + session:self.mockSession + completionBlockProvider:self.completionBlockProvider]; } - (void)testInit_requestFactory_mustNotBeNil { @@ -66,7 +74,8 @@ - (void)testInit_requestFactory_mustNotBeNil { requestManager:self.mockRequestManager requestContext:self.mockRequestContext storage:self.mockStorage - session:self.mockSession]; + session:self.mockSession + completionBlockProvider:self.completionBlockProvider]; XCTFail(@"Expected Exception when requestFactory is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: requestFactory"); @@ -79,7 +88,8 @@ - (void)testInit_requestManager_mustNotBeNil { requestManager:nil requestContext:self.mockRequestContext storage:self.mockStorage - session:self.mockSession]; + session:self.mockSession + completionBlockProvider:self.completionBlockProvider]; XCTFail(@"Expected Exception when requestManager is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: requestManager"); @@ -92,7 +102,8 @@ - (void)testInit_requestContext_mustNotBeNil { requestManager:self.mockRequestManager requestContext:nil storage:self.mockStorage - session:self.mockSession]; + session:self.mockSession + completionBlockProvider:self.completionBlockProvider]; XCTFail(@"Expected Exception when requestContext is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: requestContext"); @@ -105,7 +116,8 @@ - (void)testInit_storage_mustNotBeNil { requestManager:self.mockRequestManager requestContext:self.mockRequestContext storage:nil - session:self.mockSession]; + session:self.mockSession + completionBlockProvider:self.completionBlockProvider]; XCTFail(@"Expected Exception when storage is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: storage"); @@ -118,13 +130,28 @@ - (void)testInit_session_mustNotBeNil { requestManager:self.mockRequestManager requestContext:self.mockRequestContext storage:self.mockStorage - session:nil]; + session:nil + completionBlockProvider:self.completionBlockProvider]; XCTFail(@"Expected Exception when session is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: session"); } } +- (void)testInit_completionBlockProvider_mustNotBeNil { + @try { + [[EMSMobileEngageV3Internal alloc] initWithRequestFactory:self.mockRequestFactory + requestManager:self.mockRequestManager + requestContext:self.mockRequestContext + storage:self.mockStorage + session:self.mockSession + completionBlockProvider:nil]; + XCTFail(@"Expected Exception when completionBlockProvider is nil!"); + } @catch (NSException *exception) { + XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: completionBlockProvider"); + } +} + - (void)testSetContactWithContactFieldValue { EMSMobileEngageV3Internal *partialMockInternal = OCMPartialMock(self.internal); @@ -196,6 +223,7 @@ - (void)testSetAuthenticatedContactWithIdTokenCompletionBlock_resetSession { [self.internal setAuthenticatedContactWithContactFieldId:@3 openIdToken:idToken completionBlock:self.completionBlock]; + [self waitATickOnOperationQueue:self.operationQueue]; OCMVerify([self.mockRequestFactory createContactRequestModel]); OCMVerify([self.mockRequestManager submitRequestModel:requestModel @@ -217,6 +245,8 @@ - (void)testSetContactWithContactFieldValueCompletionBlock_resetSession { [self.internal setContactWithContactFieldId:self.contactFieldId contactFieldValue:self.contactFieldValue completionBlock:self.completionBlock]; + + [self waitATickOnOperationQueue:self.operationQueue]; OCMVerify([self.mockRequestContext setContactFieldValue:self.contactFieldValue]); @@ -243,6 +273,8 @@ - (void)testClearContactWithCompletionBlock { completionBlock:[OCMArg invokeBlock]]); [partialMockInternal clearContactWithCompletionBlock:self.completionBlock]; + + [self waitATickOnOperationQueue:self.operationQueue]; OCMVerify([self.mockStorage setData:nil forKey:@"EMSPushTokenKey"]); diff --git a/Tests/MobileEngageTests/HandlerTests/EMSClientStateResponseHandlerTests.m b/Tests/MobileEngageTests/HandlerTests/EMSClientStateResponseHandlerTests.m index 840b88af..5282af93 100644 --- a/Tests/MobileEngageTests/HandlerTests/EMSClientStateResponseHandlerTests.m +++ b/Tests/MobileEngageTests/HandlerTests/EMSClientStateResponseHandlerTests.m @@ -12,6 +12,7 @@ #import "EMSEndpoint.h" #import "EMSValueProvider.h" #import "EMSStorage.h" +#import "EmarsysTestUtils.h" @interface EMSClientStateResponseHandlerTests : XCTestCase @@ -47,6 +48,10 @@ - (void)setUp { endpoint:endpoint]; } +- (void)tearDown { + [EmarsysTestUtils tearDownEmarsys]; +} + - (void)testInit_requestContext_mustNotBeNull { @try { [[EMSClientStateResponseHandler alloc] initWithRequestContext:nil diff --git a/Tests/MobileEngageTests/HandlerTests/MEIAMCleanupResponseHandlerV3Tests.m b/Tests/MobileEngageTests/HandlerTests/MEIAMCleanupResponseHandlerV3Tests.m index 4d900ab7..a3550dee 100644 --- a/Tests/MobileEngageTests/HandlerTests/MEIAMCleanupResponseHandlerV3Tests.m +++ b/Tests/MobileEngageTests/HandlerTests/MEIAMCleanupResponseHandlerV3Tests.m @@ -13,6 +13,8 @@ #import "EMSUUIDProvider.h" #import "MEExperimental+Test.h" #import "EMSInnerFeature.h" +#import "EmarsysTestUtils.h" +#import "XCTestCase+Helper.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestMIAMCleanup.db"] @@ -25,6 +27,7 @@ @interface MEIAMCleanupResponseHandlerV3Tests : XCTestCase @property(nonatomic, strong) EMSRequestModel *requestModel; @property(nonatomic, strong) EMSSQLiteHelper *dbHelper; @property(nonatomic, strong) MEIAMCleanupResponseHandlerV3 *responseHandler; +@property(nonatomic, strong) NSOperationQueue *operationQueue; @end @@ -45,17 +48,16 @@ - (void)setUp { _responseHandler = [[MEIAMCleanupResponseHandlerV3 alloc] initWithButtonClickRepository:self.mockButtonClickRepository displayIamRepository:self.mockDisplayIamRepository endpoint:self.mockEndpoint]; - - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; + _operationQueue = [self createTestOperationQueue]; _dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:self.operationQueue]; [self.dbHelper open]; } - (void)tearDown { [MEExperimental reset]; - [self.dbHelper close]; + [EmarsysTestUtils clearDb:self.dbHelper]; } - (void)testInit_buttonClickRepository_mustNotBeNil { diff --git a/Tests/MobileEngageTests/HandlerTests/MEIAMCleanupResponseHandlerV4Tests.m b/Tests/MobileEngageTests/HandlerTests/MEIAMCleanupResponseHandlerV4Tests.m index 5f9b4905..e89003d2 100644 --- a/Tests/MobileEngageTests/HandlerTests/MEIAMCleanupResponseHandlerV4Tests.m +++ b/Tests/MobileEngageTests/HandlerTests/MEIAMCleanupResponseHandlerV4Tests.m @@ -12,6 +12,8 @@ #import "MEExperimental+Test.h" #import "EMSInnerFeature.h" #import "EMSFilterByNothingSpecification.h" +#import "EmarsysTestUtils.h" +#import "XCTestCase+Helper.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestMIAMCleanup.db"] @@ -24,6 +26,7 @@ @interface MEIAMCleanupResponseHandlerV4Tests : XCTestCase @property(nonatomic, strong) EMSTimestampProvider *timestampProvider; @property(nonatomic, strong) EMSSQLiteHelper *dbHelper; @property(nonatomic, strong) MEIAMCleanupResponseHandlerV4 *responseHandler; +@property(nonatomic, strong) NSOperationQueue *operationQueue; @end @@ -35,20 +38,20 @@ - (void)setUp { _mockEndpoint = OCMClassMock([EMSEndpoint class]); _timestampProvider = [EMSTimestampProvider new]; + _operationQueue = [self createTestOperationQueue]; _responseHandler = [[MEIAMCleanupResponseHandlerV4 alloc] initWithButtonClickRepository:self.mockButtonClickRepository displayIamRepository:self.mockDisplayIamRepository endpoint:self.mockEndpoint]; - - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; _dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:self.operationQueue]; [self.dbHelper open]; } - (void)tearDown { [MEExperimental reset]; + [EmarsysTestUtils clearDb:self.dbHelper]; } - (void)testInit_buttonClickRepository_mustNotBeNil { @@ -298,4 +301,4 @@ - (EMSRequestModel *)createRequestModelWithPayload:(NSDictionary *)payload { uuidProvider:[EMSUUIDProvider new]]; } -@end \ No newline at end of file +@end diff --git a/Tests/MobileEngageTests/IAM/EMSInAppTests.m b/Tests/MobileEngageTests/IAM/EMSInAppTests.m index ad77b26e..4572d398 100644 --- a/Tests/MobileEngageTests/IAM/EMSInAppTests.m +++ b/Tests/MobileEngageTests/IAM/EMSInAppTests.m @@ -8,7 +8,7 @@ #import "EMSWindowProvider.h" #import "EMSMainWindowProvider.h" #import "EMSTimestampProvider.h" -#import "EMSCompletionBlockProvider.h" +#import "EMSCompletionProvider.h" #import "MEDisplayedIAMRepository.h" #import "MEButtonClickRepository.h" #import "EMSOperationQueue.h" @@ -26,7 +26,7 @@ @interface EMSInAppTests : XCTestCase @property(nonatomic, strong) EMSWindowProvider *mockWindowProvider; @property(nonatomic, strong) EMSMainWindowProvider *mockMainWindowProvider; @property(nonatomic, strong) EMSTimestampProvider *mockTimestampProvider; -@property(nonatomic, strong) EMSCompletionBlockProvider *mockCompletionBlockProvider; +@property(nonatomic, strong) EMSCompletionProvider *mockCompletionBlockProvider; @property(nonatomic, strong) MEDisplayedIAMRepository *mockDisplayedIAMRepository; @property(nonatomic, strong) MEButtonClickRepository *mockButtonClickRepository; @@ -38,7 +38,7 @@ - (void)setUp { _mockWindowProvider = OCMClassMock([EMSWindowProvider class]); _mockMainWindowProvider = OCMClassMock([EMSMainWindowProvider class]); _mockTimestampProvider = OCMClassMock([EMSTimestampProvider class]); - _mockCompletionBlockProvider = OCMClassMock([EMSCompletionBlockProvider class]); + _mockCompletionBlockProvider = OCMClassMock([EMSCompletionProvider class]); _mockDisplayedIAMRepository = OCMClassMock([MEDisplayedIAMRepository class]); _mockButtonClickRepository = OCMClassMock([MEButtonClickRepository class]); _inApp = [[MEInApp alloc] initWithWindowProvider:self.mockWindowProvider diff --git a/Tests/MobileEngageTests/IAM/EMSInlineInAppViewTests.m b/Tests/MobileEngageTests/IAM/EMSInlineInAppViewTests.m index 746a2e7c..70ce64ef 100644 --- a/Tests/MobileEngageTests/IAM/EMSInlineInAppViewTests.m +++ b/Tests/MobileEngageTests/IAM/EMSInlineInAppViewTests.m @@ -15,6 +15,7 @@ #import "NSError+EMSCore.h" #import "FakeRequestManager.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" @interface EMSInlineInAppView (Tests) @@ -57,8 +58,8 @@ - (void)tearDown { [self.mockRequestManager stopMocking]; [self.mockInapp stopMocking]; [self.mockContainer stopMocking]; - [self tearDownOperationQueue:self.operationQueue]; - [self tearDownOperationQueue:self.publicApiOperationQueue]; + [EmarsysTestUtils tearDownOperationQueue:self.operationQueue]; + [EmarsysTestUtils tearDownOperationQueue:self.publicApiOperationQueue]; [EMSDependencyInjection tearDown]; } diff --git a/Tests/MobileEngageTests/IAM/MEInAppTests.m b/Tests/MobileEngageTests/IAM/MEInAppTests.m index 69b53999..b03f3a88 100644 --- a/Tests/MobileEngageTests/IAM/MEInAppTests.m +++ b/Tests/MobileEngageTests/IAM/MEInAppTests.m @@ -10,9 +10,10 @@ #import "MEDisplayedIAMRepository.h" #import "FakeInAppTracker.h" #import "EMSViewControllerProvider.h" -#import "EMSCompletionBlockProvider.h" +#import "EMSCompletionProvider.h" #import "EMSSceneProvider.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" SPEC_BEGIN(MEInAppTests) @@ -57,7 +58,7 @@ inApp = [[MEInApp alloc] initWithWindowProvider:windowProvider mainWindowProvider:[EMSMainWindowProvider nullMock] timestampProvider:timestampProvider - completionBlockProvider:[[EMSCompletionBlockProvider alloc] initWithOperationQueue:operationQueue] + completionBlockProvider:[[EMSCompletionProvider alloc] initWithOperationQueue:operationQueue] displayedIamRepository:displayedIAMRepository buttonClickRepository:[MEDisplayedIAMRepository mock] operationQueue:[self createTestOperationQueue]]; @@ -65,7 +66,7 @@ }); afterEach(^{ - [self tearDownOperationQueue:operationQueue]; + [EmarsysTestUtils tearDownOperationQueue:operationQueue]; }); describe(@"initWithWindowProvider:mainWindowProvider:iamViewControllerProvider:iamViewControllerProvider:timestampProvider:logRepository:displayedIamRepository:inAppTracker:", ^{ @@ -74,7 +75,7 @@ [[MEInApp alloc] initWithWindowProvider:nil mainWindowProvider:[EMSMainWindowProvider mock] timestampProvider:[EMSTimestampProvider mock] - completionBlockProvider:[EMSCompletionBlockProvider mock] + completionBlockProvider:[EMSCompletionProvider mock] displayedIamRepository:[MEDisplayedIAMRepository mock] buttonClickRepository:[MEDisplayedIAMRepository mock] operationQueue:[NSOperationQueue mock]]; @@ -90,7 +91,7 @@ [[MEInApp alloc] initWithWindowProvider:[EMSWindowProvider mock] mainWindowProvider:[EMSMainWindowProvider mock] timestampProvider:[EMSTimestampProvider mock] - completionBlockProvider:[EMSCompletionBlockProvider mock] + completionBlockProvider:[EMSCompletionProvider mock] displayedIamRepository:[MEDisplayedIAMRepository mock] buttonClickRepository:[MEDisplayedIAMRepository mock] operationQueue:nil]; @@ -106,7 +107,7 @@ [[MEInApp alloc] initWithWindowProvider:[EMSWindowProvider mock] mainWindowProvider:nil timestampProvider:[EMSTimestampProvider mock] - completionBlockProvider:[EMSCompletionBlockProvider mock] + completionBlockProvider:[EMSCompletionProvider mock] displayedIamRepository:[MEDisplayedIAMRepository mock] buttonClickRepository:[MEDisplayedIAMRepository mock] operationQueue:[NSOperationQueue mock]]; @@ -122,7 +123,7 @@ [[MEInApp alloc] initWithWindowProvider:[EMSWindowProvider mock] mainWindowProvider:[EMSMainWindowProvider mock] timestampProvider:nil - completionBlockProvider:[EMSCompletionBlockProvider mock] + completionBlockProvider:[EMSCompletionProvider mock] displayedIamRepository:[MEDisplayedIAMRepository mock] buttonClickRepository:[MEDisplayedIAMRepository mock] operationQueue:[NSOperationQueue mock]]; @@ -137,7 +138,7 @@ @try { [[MEInApp alloc] initWithWindowProvider:[EMSWindowProvider mock] mainWindowProvider:[EMSMainWindowProvider mock] - timestampProvider:[EMSCompletionBlockProvider mock] + timestampProvider:[EMSCompletionProvider mock] completionBlockProvider:nil displayedIamRepository:[MEDisplayedIAMRepository mock] buttonClickRepository:[MEDisplayedIAMRepository mock] @@ -154,7 +155,7 @@ [[MEInApp alloc] initWithWindowProvider:[EMSWindowProvider mock] mainWindowProvider:[EMSMainWindowProvider mock] timestampProvider:[EMSTimestampProvider mock] - completionBlockProvider:[EMSCompletionBlockProvider mock] + completionBlockProvider:[EMSCompletionProvider mock] displayedIamRepository:nil buttonClickRepository:[MEDisplayedIAMRepository mock] operationQueue:[NSOperationQueue mock]]; @@ -170,7 +171,7 @@ [[MEInApp alloc] initWithWindowProvider:[EMSWindowProvider mock] mainWindowProvider:[EMSMainWindowProvider mock] timestampProvider:[EMSTimestampProvider mock] - completionBlockProvider:[EMSCompletionBlockProvider mock] + completionBlockProvider:[EMSCompletionProvider mock] displayedIamRepository:[MEDisplayedIAMRepository mock] buttonClickRepository:nil operationQueue:[NSOperationQueue mock]]; diff --git a/Tests/MobileEngageTests/IAM/MEJSBridgeTests.m b/Tests/MobileEngageTests/IAM/MEJSBridgeTests.m index bee07159..d9eb0064 100644 --- a/Tests/MobileEngageTests/IAM/MEJSBridgeTests.m +++ b/Tests/MobileEngageTests/IAM/MEJSBridgeTests.m @@ -7,6 +7,7 @@ #import "EMSWaiter.h" #import "FakeCommand.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" MEJSBridge *_meJsBridge; @@ -64,7 +65,7 @@ + (void)assertForJSCommand:(NSString *)commandName { }); afterEach(^{ - [self tearDownOperationQueue:queue]; + [EmarsysTestUtils tearDownOperationQueue:queue]; }); describe(@"jsCommandNames", ^{ diff --git a/Tests/MobileEngageTests/Parser/EMSMobileEngageNullSafeBodyParserTests.m b/Tests/MobileEngageTests/Parser/EMSMobileEngageNullSafeBodyParserTests.m index dee55668..28658b35 100644 --- a/Tests/MobileEngageTests/Parser/EMSMobileEngageNullSafeBodyParserTests.m +++ b/Tests/MobileEngageTests/Parser/EMSMobileEngageNullSafeBodyParserTests.m @@ -25,7 +25,7 @@ - (void)setUp { _mockRequestModel = OCMClassMock([EMSRequestModel class]); _mockUrlResponse = OCMClassMock([NSHTTPURLResponse class]); _parser = [[EMSMobileEngageNullSafeBodyParser alloc] initWithEndpoint:self.mockEndpoint]; - _responseBody = [@"testData" dataUsingEncoding:NSUTF8StringEncoding]; + _responseBody = [@"{\"key\": \"value\"}" dataUsingEncoding:NSUTF8StringEncoding]; } - (void)testInit_endpointShouldNotBeNil { @@ -104,6 +104,21 @@ - (void)testShouldParse_shouldReturnFalse_whenPush2InAppUrl { XCTAssertFalse(result); } +- (void)testShouldParse_shouldReturnFalse_whenDataIsNotAJson { + NSString *textData = @"testData"; + NSData *data = [textData dataUsingEncoding:NSUTF8StringEncoding]; + + OCMStub([self.mockEndpoint isMobileEngageUrl:[OCMArg any]]).andReturn(YES); + OCMStub([self.mockEndpoint isPushToInAppUrl:[OCMArg any]]).andReturn(YES); + OCMStub([self.mockUrlResponse isSuccess]).andReturn(YES); + + BOOL result = [self.parser shouldParse:self.mockRequestModel + responseBody:data + httpUrlResponse:self.mockUrlResponse]; + + XCTAssertFalse(result); +} + - (void)testParse_shouldReturnParsedBodyFromResponseModel_whenNoNullObjectInBody { NSDictionary *dict = @{ @"k1": @"v1", @@ -184,7 +199,9 @@ - (void)testParse_shouldReturnParsedBodyFromResponseModelWithoutNSNulls_whenBody " },\n" " {\n" " \"k5\": \"v5\",\n" - " \"k6\": null\n" + " \"k6\": {\n" + " \"k7\": null,\n" + " }\n" " }\n" " ]\n" " }"; @@ -199,6 +216,7 @@ - (void)testParse_shouldReturnParsedBodyFromResponseModelWithoutNSNulls_whenBody }, @{ @"k5": @"v5", + @"k6": @{} } ] }; diff --git a/Tests/MobileEngageTests/Push/Actions/EMSActionFactoryTests.m b/Tests/MobileEngageTests/Push/Actions/EMSActionFactoryTests.m index e769b812..824b97eb 100644 --- a/Tests/MobileEngageTests/Push/Actions/EMSActionFactoryTests.m +++ b/Tests/MobileEngageTests/Push/Actions/EMSActionFactoryTests.m @@ -9,6 +9,8 @@ #import "EMSAppEventAction.h" #import "EMSOpenExternalUrlAction.h" #import "EMSCustomEventAction.h" +#import "XCTestCase+Helper.h" +#import @interface EMSActionFactoryTests : XCTestCase @@ -16,6 +18,7 @@ @interface EMSActionFactoryTests : XCTestCase @property(nonatomic, strong) id mockMobileEngage; @property(nonatomic, strong) id mockApplication; @property(nonatomic, strong) EMSEventHandlerBlock eventHandler; +@property(nonatomic, strong) NSOperationQueue *operationQueue; @end @@ -26,8 +29,11 @@ - (void)setUp { _mockApplication = OCMClassMock([UIApplication class]); _eventHandler = ^(NSString *eventName, NSDictionary *payload) { }; + _operationQueue = [self createTestOperationQueue]; _factory = [[EMSActionFactory alloc] initWithApplication:self.mockApplication - mobileEngage:self.mockMobileEngage]; + mobileEngage:self.mockMobileEngage + userNotificationCenter:[UNUserNotificationCenter currentNotificationCenter] + operationQueue:self.operationQueue]; } - (void)tearDown { @@ -38,7 +44,9 @@ - (void)tearDown { - (void)testInit_application_mustNotBeNil { @try { [[EMSActionFactory alloc] initWithApplication:nil - mobileEngage:self.mockMobileEngage]; + mobileEngage:self.mockMobileEngage + userNotificationCenter:[UNUserNotificationCenter currentNotificationCenter] + operationQueue:self.operationQueue]; XCTFail(@"Expected Exception when application is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: application"); @@ -48,108 +56,134 @@ - (void)testInit_application_mustNotBeNil { - (void)testInit_mobileEngage_mustNotBeNil { @try { [[EMSActionFactory alloc] initWithApplication:self.mockApplication - mobileEngage:nil]; + mobileEngage:nil + userNotificationCenter:[UNUserNotificationCenter currentNotificationCenter] + operationQueue:self.operationQueue]; XCTFail(@"Expected Exception when mobileEngage is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: mobileEngage"); } } +- (void)testInit_userNotificationCenter_mustNotBeNil { + @try { + [[EMSActionFactory alloc] initWithApplication:self.mockApplication + mobileEngage:self.mockMobileEngage + userNotificationCenter:nil + operationQueue:self.operationQueue]; + XCTFail(@"Expected Exception when userNotificationCenter is nil!"); + } @catch (NSException *exception) { + XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: userNotificationCenter"); + } +} + +- (void)testInit_operationQueue_mustNotBeNil { + @try { + [[EMSActionFactory alloc] initWithApplication:self.mockApplication + mobileEngage:self.mockMobileEngage + userNotificationCenter:[UNUserNotificationCenter currentNotificationCenter] + operationQueue:nil]; + XCTFail(@"Expected Exception when operationQueue is nil!"); + } @catch (NSException *exception) { + XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: operationQueue"); + } +} + - (void)testCreateActionWithActionDictionary_shouldNotCreateBadgeCountAction_whenTypeIsBadgeCount_necessaryFieldsAreMissing { NSDictionary *actionDictionary = @{ - @"type": @"BadgeCount" + @"type": @"BadgeCount" }; - + id action = [self.factory createActionWithActionDictionary:actionDictionary]; - + XCTAssertNil(action); } - (void)testCreateActionWithActionDictionary_shouldCreateBadgeCountAction_whenMethodAndValueIsAvailable { NSDictionary *actionDictionary = @{ - @"type": @"BadgeCount", - @"method": @"SET", - @"value": @123 + @"type": @"BadgeCount", + @"method": @"SET", + @"value": @123 }; - + id action = [self.factory createActionWithActionDictionary:actionDictionary]; - + XCTAssertTrue([action isKindOfClass:[EMSBadgeCountAction class]]); } - (void)testCreateActionWithActionDictionary_shouldNotCreateAppEventAction_whenTypeIsMEAppEvent_necessaryFieldsAreMissing { NSDictionary *actionDictionary = @{ - @"type": @"MEAppEvent" + @"type": @"MEAppEvent" }; - + [self.factory setEventHandler:self.eventHandler]; id action = [self.factory createActionWithActionDictionary:actionDictionary]; - + XCTAssertNil(action); } - (void)testCreateActionWithActionDictionary_shouldNotCreateAppEventAction_whenEventHandlerIsMissing { NSDictionary *actionDictionary = @{ - @"type": @"MEAppEvent", - @"name": @"testName" + @"type": @"MEAppEvent", + @"name": @"testName" }; - + id action = [self.factory createActionWithActionDictionary:actionDictionary]; - + XCTAssertNil(action); } - (void)testCreateActionWithActionDictionary_shouldCreateAppEventAction_whenNameAndEventHandlerAreAvailable { NSDictionary *actionDictionary = @{ - @"type": @"MEAppEvent", - @"name": @"testName" + @"type": @"MEAppEvent", + @"name": @"testName" }; - + [self.factory setEventHandler:self.eventHandler]; id action = [self.factory createActionWithActionDictionary:actionDictionary]; - + XCTAssertTrue([action isKindOfClass:[EMSAppEventAction class]]); } - (void)testCreateActionWithActionDictionary_shouldNotCreateOpenExternalEventAction_whenTypeIsOpenExternalUrl_necessaryFieldsAreMissing { NSDictionary *actionDictionary = @{ - @"type": @"OpenExternalUrl" + @"type": @"OpenExternalUrl" }; - + id action = [self.factory createActionWithActionDictionary:actionDictionary]; - + XCTAssertNil(action); } - (void)testCreateActionWithActionDictionary_shouldCreateOpenExternalEventAction_whenUrlIsAvailable { NSDictionary *actionDictionary = @{ - @"type": @"OpenExternalUrl", - @"url": @"https://www.emarsys.com" + @"type": @"OpenExternalUrl", + @"url": @"https://www.emarsys.com" }; - + id action = [self.factory createActionWithActionDictionary:actionDictionary]; - + XCTAssertTrue([action isKindOfClass:[EMSOpenExternalUrlAction class]]); } - (void)testCreateActionWithActionDictionary_shouldNotCreateEMSCustomEventAction_whenTypeIsMECustomEvent_necessaryFieldsAreMissing { NSDictionary *actionDictionary = @{ - @"type": @"MECustomEvent" + @"type": @"MECustomEvent" }; - + id action = [self.factory createActionWithActionDictionary:actionDictionary]; - + XCTAssertNil(action); } - (void)testCreateActionWithActionDictionary_shouldCreateEMSCustomEventAction_whenTypeIsMECustomEvent { NSDictionary *actionDictionary = @{ - @"type": @"MECustomEvent", - @"name": @"testName" + @"type": @"MECustomEvent", + @"name": @"testName" }; - + id action = [self.factory createActionWithActionDictionary:actionDictionary]; - + XCTAssertTrue([action isKindOfClass:[EMSCustomEventAction class]]); } diff --git a/Tests/MobileEngageTests/Push/Actions/EMSBadgeCountActionTests.m b/Tests/MobileEngageTests/Push/Actions/EMSBadgeCountActionTests.m index 6aebfaea..b256f9c7 100644 --- a/Tests/MobileEngageTests/Push/Actions/EMSBadgeCountActionTests.m +++ b/Tests/MobileEngageTests/Push/Actions/EMSBadgeCountActionTests.m @@ -4,30 +4,37 @@ #import #import #import "EMSBadgeCountAction.h" +#import "XCTestCase+Helper.h" +#import @interface EMSBadgeCountActionTests : XCTestCase -@property(nonatomic, strong) id mockApplication; +@property(nonatomic, strong) UIApplication *application; +@property(nonatomic, strong) UNUserNotificationCenter *userNotificationCenter; +@property(nonatomic, strong) NSOperationQueue *operationQueue; @end @implementation EMSBadgeCountActionTests - (void)setUp { - UIApplication *application = [UIApplication sharedApplication]; - application.applicationIconBadgeNumber = 3; - _mockApplication = OCMPartialMock(application); + _operationQueue = [self createTestOperationQueue]; + _userNotificationCenter = [UNUserNotificationCenter currentNotificationCenter]; + _application = [UIApplication sharedApplication]; + self.application.applicationIconBadgeNumber = 3; + [self waitForOperationOnMainQueue]; } - (void)tearDown { - [self.mockApplication stopMocking]; [super tearDown]; } - (void)testInit_action_mustNotBeNil { @try { [[EMSBadgeCountAction alloc] initWithActionDictionary:nil - application:self.mockApplication]; + application:self.application + userNotificationCenter:self.userNotificationCenter + operationQueue:self.operationQueue]; XCTFail(@"Expected Exception when action is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: action"); @@ -37,13 +44,39 @@ - (void)testInit_action_mustNotBeNil { - (void)testInit_application_mustNotBeNil { @try { [[EMSBadgeCountAction alloc] initWithActionDictionary:@{} - application:nil]; + application:nil + userNotificationCenter:self.userNotificationCenter + operationQueue:self.operationQueue]; XCTFail(@"Expected Exception when application is nil!"); } @catch (NSException *exception) { XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: application"); } } +- (void)testInit_userNotificationCenter_mustNotBeNil { + @try { + [[EMSBadgeCountAction alloc] initWithActionDictionary:@{} + application:self.application + userNotificationCenter:nil + operationQueue:self.operationQueue]; + XCTFail(@"Expected Exception when userNotificationCenter is nil!"); + } @catch (NSException *exception) { + XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: userNotificationCenter"); + } +} + +- (void)testInit_operationQueue_mustNotBeNil { + @try { + [[EMSBadgeCountAction alloc] initWithActionDictionary:@{} + application:self.application + userNotificationCenter:self.userNotificationCenter + operationQueue:nil]; + XCTFail(@"Expected Exception when operationQueue is nil!"); + } @catch (NSException *exception) { + XCTAssertEqualObjects(exception.reason, @"Invalid parameter not satisfying: operationQueue"); + } +} + - (void)testHandleMessageWithUserInfo_shouldSetBadgeNumberWhenMultipleActionsAreAvailable { NSDictionary *actionDictionary = @{ @"type": @"testType", @@ -51,13 +84,14 @@ - (void)testHandleMessageWithUserInfo_shouldSetBadgeNumberWhenMultipleActionsAre }; EMSBadgeCountAction *badgeCountAction = [[EMSBadgeCountAction alloc] initWithActionDictionary:actionDictionary - application:self.mockApplication]; + application:self.application + userNotificationCenter:self.userNotificationCenter + operationQueue:self.operationQueue]; [badgeCountAction execute]; [self waitForOperationOnMainQueue]; - OCMVerify([self.mockApplication setApplicationIconBadgeNumber:123]); - XCTAssertEqual([self.mockApplication applicationIconBadgeNumber], 123); + XCTAssertEqual([self.application applicationIconBadgeNumber], 123); } - (void)testExecute_shouldSetBadgeNumberCorrectly_whenMethodIsAdd { @@ -68,13 +102,14 @@ - (void)testExecute_shouldSetBadgeNumberCorrectly_whenMethodIsAdd { }; EMSBadgeCountAction *badgeCountAction = [[EMSBadgeCountAction alloc] initWithActionDictionary:actionDictionary - application:self.mockApplication]; - + application:self.application + userNotificationCenter:self.userNotificationCenter + operationQueue:self.operationQueue]; + [badgeCountAction execute]; [self waitForOperationOnMainQueue]; - OCMVerify([self.mockApplication setApplicationIconBadgeNumber:5]); - XCTAssertEqual([self.mockApplication applicationIconBadgeNumber], 5); + XCTAssertEqual([self.application applicationIconBadgeNumber], 5); } @@ -86,22 +121,26 @@ - (void)testHandleMessageWithUserInfo_shouldSetBadgeNumberCorrectly_whenMethodIs }; EMSBadgeCountAction *badgeCountAction = [[EMSBadgeCountAction alloc] initWithActionDictionary:actionDictionary - application:self.mockApplication]; + application:self.application + userNotificationCenter:self.userNotificationCenter + operationQueue:self.operationQueue]; [badgeCountAction execute]; [self waitForOperationOnMainQueue]; - OCMVerify([self.mockApplication setApplicationIconBadgeNumber:1]); - XCTAssertEqual([self.mockApplication applicationIconBadgeNumber], 1); + XCTAssertEqual([self.application applicationIconBadgeNumber], 1); } - (void)waitForOperationOnMainQueue { + NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"waitForOperationOnMainQueue"]; - dispatch_async(dispatch_get_main_queue(), ^{ - [expectation fulfill]; - }); + [mainQueue addOperationWithBlock:^{ + [NSThread sleepForTimeInterval:0.2]; + [expectation fulfill]; + }]; XCTWaiterResult waiterResult = [XCTWaiter waitForExpectations:@[expectation] timeout:10]; + [mainQueue waitUntilAllOperationsAreFinished]; XCTAssertEqual(waiterResult, XCTWaiterResultCompleted); }; diff --git a/Tests/MobileEngageTests/Push/EMSPushV3InternalTests.m b/Tests/MobileEngageTests/Push/EMSPushV3InternalTests.m index 786a6565..16f78b69 100644 --- a/Tests/MobileEngageTests/Push/EMSPushV3InternalTests.m +++ b/Tests/MobileEngageTests/Push/EMSPushV3InternalTests.m @@ -16,6 +16,7 @@ #import "EMSInAppInternal.h" #import "XCTestCase+Helper.h" #import "EMSStorageProtocol.h" +#import "EmarsysTestUtils.h" @interface EMSPushV3Internal () @@ -80,7 +81,7 @@ - (void)setUp { - (void)tearDown { [self.mockPushTokenData stopMocking]; - [self tearDownOperationQueue:self.operationQueue]; + [EmarsysTestUtils tearDownOperationQueue:self.operationQueue]; [super tearDown]; } diff --git a/Tests/MobileEngageTests/Request/MERequestContextTests.m b/Tests/MobileEngageTests/Request/MERequestContextTests.m index 86bdefb2..b3fbc525 100644 --- a/Tests/MobileEngageTests/Request/MERequestContextTests.m +++ b/Tests/MobileEngageTests/Request/MERequestContextTests.m @@ -6,303 +6,311 @@ #import "EMSInnerFeature.h" #import "EMSStorage.h" #import "EMSStorageProtocol.h" +#import "XCTestCase+Helper.h" SPEC_BEGIN(MERequestContextTests) - __block EMSUUIDProvider *uuidProvider = [EMSUUIDProvider new]; - __block EMSTimestampProvider *timestampProvider = [EMSTimestampProvider new]; - __block EMSDeviceInfo *deviceInfo = [EMSDeviceInfo new]; - __block NSString *applicationCode = @"testApplicationCode"; - __block EMSStorage *storage = [[EMSStorage alloc] initWithSuiteNames:@[kEMSSuiteName] - accessGroup:@"7ZFXXDJH82.com.emarsys.SdkHostTestGroup"]; - - describe(@"initialization", ^{ - - it(@"should throw exception when uuidProvider is nil", ^{ - @try { - MERequestContext *requestContext = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:nil - timestampProvider:[EMSTimestampProvider mock] - deviceInfo:[EMSDeviceInfo mock] - storage:[EMSStorage mock]]; - fail(@"Expected Exception when uuidProvider is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: uuidProvider"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should throw exception when timestampProvider is nil", ^{ - @try { - MERequestContext *requestContext = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:[EMSUUIDProvider mock] - timestampProvider:nil - deviceInfo:[EMSDeviceInfo mock] - storage:[EMSStorage mock]]; - fail(@"Expected Exception when timestampProvider is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: timestampProvider"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should throw exception when deviceInfo is nil", ^{ - @try { - MERequestContext *requestContext = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:[EMSUUIDProvider mock] - timestampProvider:[EMSTimestampProvider mock] - deviceInfo:nil - storage:[EMSStorage mock]]; - fail(@"Expected Exception when deviceInfo is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: deviceInfo"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - it(@"should throw exception when storage is nil", ^{ - @try { - MERequestContext *requestContext = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:[EMSUUIDProvider mock] - timestampProvider:[EMSTimestampProvider mock] - deviceInfo:[EMSDeviceInfo mock] - storage:nil]; - fail(@"Expected Exception when storage is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: storage"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - }); - - describe(@"clientState", ^{ - - beforeEach(^{ - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; - [userDefaults removeObjectForKey:kCLIENT_STATE]; - [userDefaults synchronize]; - }); - - it(@"should load the stored value", ^{ - NSString *clientState = @"Stored client state"; - - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; - [userDefaults setObject:clientState - forKey:kCLIENT_STATE]; - [userDefaults synchronize]; - - MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:uuidProvider - timestampProvider:timestampProvider - deviceInfo:deviceInfo - storage:storage]; - [[context.clientState should] equal:clientState]; - }); - - it(@"should store client state", ^{ - NSString *expectedClientState = @"Stored client state"; - - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; - - MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:uuidProvider - timestampProvider:timestampProvider - deviceInfo:deviceInfo - storage:storage]; - [[context.clientState should] beNil]; - - [context setClientState:expectedClientState]; - - [[[userDefaults stringForKey:kCLIENT_STATE] should] equal:expectedClientState]; - [[context.clientState should] equal:expectedClientState]; - }); - }); - - describe(@"contactToken", ^{ - - beforeEach(^{ - [storage setData:nil - forKey:kCONTACT_TOKEN]; - }); - - it(@"should load the stored value", ^{ - NSString *contactToken = @"Stored contactToken"; - - [storage setString:contactToken forKey:kCONTACT_TOKEN]; - - MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:uuidProvider - timestampProvider:timestampProvider - deviceInfo:deviceInfo - storage:storage]; - [[context.contactToken should] equal:contactToken]; - }); - - it(@"should store contact token", ^{ - NSString *expectedContactToken = @"Stored contact token"; - - MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:uuidProvider - timestampProvider:timestampProvider - deviceInfo:deviceInfo - storage:storage]; - [[context.contactToken should] beNil]; - - [context setContactToken:expectedContactToken]; - - [[[storage stringForKey:kCONTACT_TOKEN] should] equal:expectedContactToken]; - [[context.contactToken should] equal:expectedContactToken]; - }); - }); - - describe(@"refreshToken", ^{ - - beforeEach(^{ - [storage setData:nil - forKey:kREFRESH_TOKEN]; - }); - - it(@"should load the stored value", ^{ - NSString *refreshToken = @"Stored refreshToken"; - - [storage setString:refreshToken forKey:kREFRESH_TOKEN]; - - MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:uuidProvider - timestampProvider:timestampProvider - deviceInfo:deviceInfo - storage:storage]; - [[context.refreshToken should] equal:refreshToken]; - }); - - it(@"should store refresh token", ^{ - NSString *expectedRefreshToken = @"Stored refresh token"; - - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; - - MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:uuidProvider - timestampProvider:timestampProvider - deviceInfo:deviceInfo - storage:storage]; - [[context.refreshToken should] beNil]; - - [context setRefreshToken:expectedRefreshToken]; - - [[[storage stringForKey:kREFRESH_TOKEN] should] equal:expectedRefreshToken]; - [[context.refreshToken should] equal:expectedRefreshToken]; - }); - }); - - describe(@"contactFieldValue", ^{ - - beforeEach(^{ - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; - [userDefaults removeObjectForKey:kCONTACT_FIELD_VALUE]; - [userDefaults synchronize]; - }); - - it(@"should load the stored value", ^{ - NSString *contactFieldValue = @"Stored contactFieldValue"; - - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; - [userDefaults setObject:contactFieldValue - forKey:kCONTACT_FIELD_VALUE]; - [userDefaults synchronize]; - - MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:uuidProvider - timestampProvider:timestampProvider - deviceInfo:deviceInfo - storage:storage]; - [[context.contactFieldValue should] equal:contactFieldValue]; - }); - - it(@"should store contactFieldValue", ^{ - NSString *expectedContactFieldValue = @"Stored contactFieldValue"; - - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; - - MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:uuidProvider - timestampProvider:timestampProvider - deviceInfo:deviceInfo - storage:storage]; - [[context.contactFieldValue should] beNil]; - - [context setContactFieldValue:expectedContactFieldValue]; - - [[[userDefaults stringForKey:kCONTACT_FIELD_VALUE] should] equal:expectedContactFieldValue]; - [[context.contactFieldValue should] equal:expectedContactFieldValue]; - }); - - }); - - describe(@"setApplicationCode", ^{ - - it(@"should disable mobileEngage feature when appCode is set to nil", ^{ - MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:uuidProvider - timestampProvider:timestampProvider - deviceInfo:deviceInfo - storage:storage]; - - [context setApplicationCode:nil]; - [[theValue([MEExperimental isFeatureEnabled:EMSInnerFeature.mobileEngage]) should] beNo]; - }); - - it(@"should enableWithCompletionBlock: mobileEngage, v4 feature when appCode is set", ^{ - MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:uuidProvider - timestampProvider:timestampProvider - deviceInfo:deviceInfo - storage:storage]; - [context setApplicationCode:@"EMS11-C3FD3"]; - [[theValue([MEExperimental isFeatureEnabled:EMSInnerFeature.mobileEngage]) should] beYes]; - [[theValue([MEExperimental isFeatureEnabled:EMSInnerFeature.eventServiceV4]) should] beYes]; - }); - }); - - describe(@"hasContactIdentification", ^{ - it(@"should return YES when contactFieldValue or openIdToken is not nil", ^{ - MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:uuidProvider - timestampProvider:timestampProvider - deviceInfo:deviceInfo - storage:storage]; - [context setOpenIdToken:@"testIdToken"]; - - [[theValue([context hasContactIdentification]) should] beYes]; - }); - }); - - describe(@"reset", ^{ - - it(@"should clear contactFieldValue, contactToken, refreshToken, openIdToken", ^{ - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; - MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:uuidProvider - timestampProvider:timestampProvider - deviceInfo:deviceInfo - storage:storage]; - [context setOpenIdToken:@"testIdToken"]; - - [userDefaults setObject:@"testContactFieldValue" - forKey:kCONTACT_FIELD_VALUE]; - [storage setString:@"testContactToken" - forKey:kCONTACT_TOKEN]; - [storage setString:@"testRefreshToken" - forKey:kREFRESH_TOKEN]; - [userDefaults synchronize]; - - [context reset]; - - [[userDefaults stringForKey:kCONTACT_FIELD_VALUE] shouldBeNil]; - [[storage stringForKey:kCONTACT_TOKEN] shouldBeNil]; - [[storage stringForKey:kREFRESH_TOKEN] shouldBeNil]; - [[context openIdToken] shouldBeNil]; - [[context contactFieldId] shouldBeNil]; - }); - }); +__block EMSUUIDProvider *uuidProvider = [EMSUUIDProvider new]; +__block EMSTimestampProvider *timestampProvider = [EMSTimestampProvider new]; +__block EMSDeviceInfo *deviceInfo = [EMSDeviceInfo new]; +__block NSString *applicationCode = @"testApplicationCode"; +__block NSOperationQueue *queue; +__block EMSStorage *storage; + +describe(@"initialization", ^{ + + beforeEach(^{ + queue = [self createTestOperationQueue]; + storage = [[EMSStorage alloc] initWithSuiteNames:@[kEMSSuiteName] + accessGroup:@"7ZFXXDJH82.com.emarsys.SdkHostTestGroup" + operationQueue:queue]; + }); + + it(@"should throw exception when uuidProvider is nil", ^{ + @try { + MERequestContext *requestContext = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:nil + timestampProvider:[EMSTimestampProvider mock] + deviceInfo:[EMSDeviceInfo mock] + storage:[EMSStorage mock]]; + fail(@"Expected Exception when uuidProvider is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: uuidProvider"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw exception when timestampProvider is nil", ^{ + @try { + MERequestContext *requestContext = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:[EMSUUIDProvider mock] + timestampProvider:nil + deviceInfo:[EMSDeviceInfo mock] + storage:[EMSStorage mock]]; + fail(@"Expected Exception when timestampProvider is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: timestampProvider"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw exception when deviceInfo is nil", ^{ + @try { + MERequestContext *requestContext = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:[EMSUUIDProvider mock] + timestampProvider:[EMSTimestampProvider mock] + deviceInfo:nil + storage:[EMSStorage mock]]; + fail(@"Expected Exception when deviceInfo is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: deviceInfo"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + it(@"should throw exception when storage is nil", ^{ + @try { + MERequestContext *requestContext = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:[EMSUUIDProvider mock] + timestampProvider:[EMSTimestampProvider mock] + deviceInfo:[EMSDeviceInfo mock] + storage:nil]; + fail(@"Expected Exception when storage is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: storage"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + +}); + +describe(@"clientState", ^{ + + beforeEach(^{ + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; + [userDefaults removeObjectForKey:kCLIENT_STATE]; + [userDefaults synchronize]; + }); + + it(@"should load the stored value", ^{ + NSString *clientState = @"Stored client state"; + + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; + [userDefaults setObject:clientState + forKey:kCLIENT_STATE]; + [userDefaults synchronize]; + + MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:uuidProvider + timestampProvider:timestampProvider + deviceInfo:deviceInfo + storage:storage]; + [[context.clientState should] equal:clientState]; + }); + + it(@"should store client state", ^{ + NSString *expectedClientState = @"Stored client state"; + + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; + + MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:uuidProvider + timestampProvider:timestampProvider + deviceInfo:deviceInfo + storage:storage]; + [[context.clientState should] beNil]; + + [context setClientState:expectedClientState]; + + [[[userDefaults stringForKey:kCLIENT_STATE] should] equal:expectedClientState]; + [[context.clientState should] equal:expectedClientState]; + }); +}); + +describe(@"contactToken", ^{ + + beforeEach(^{ + [storage setData:nil + forKey:kCONTACT_TOKEN]; + }); + + it(@"should load the stored value", ^{ + NSString *contactToken = @"Stored contactToken"; + + [storage setString:contactToken forKey:kCONTACT_TOKEN]; + + MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:uuidProvider + timestampProvider:timestampProvider + deviceInfo:deviceInfo + storage:storage]; + [[context.contactToken should] equal:contactToken]; + }); + + it(@"should store contact token", ^{ + NSString *expectedContactToken = @"Stored contact token"; + + MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:uuidProvider + timestampProvider:timestampProvider + deviceInfo:deviceInfo + storage:storage]; + [[context.contactToken should] beNil]; + + [context setContactToken:expectedContactToken]; + + [[[storage stringForKey:kCONTACT_TOKEN] should] equal:expectedContactToken]; + [[context.contactToken should] equal:expectedContactToken]; + }); +}); + +describe(@"refreshToken", ^{ + + beforeEach(^{ + [storage setData:nil + forKey:kREFRESH_TOKEN]; + }); + + it(@"should load the stored value", ^{ + NSString *refreshToken = @"Stored refreshToken"; + + [storage setString:refreshToken forKey:kREFRESH_TOKEN]; + + MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:uuidProvider + timestampProvider:timestampProvider + deviceInfo:deviceInfo + storage:storage]; + [[context.refreshToken should] equal:refreshToken]; + }); + + it(@"should store refresh token", ^{ + NSString *expectedRefreshToken = @"Stored refresh token"; + + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; + + MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:uuidProvider + timestampProvider:timestampProvider + deviceInfo:deviceInfo + storage:storage]; + [[context.refreshToken should] beNil]; + + [context setRefreshToken:expectedRefreshToken]; + + [[[storage stringForKey:kREFRESH_TOKEN] should] equal:expectedRefreshToken]; + [[context.refreshToken should] equal:expectedRefreshToken]; + }); +}); + +describe(@"contactFieldValue", ^{ + + beforeEach(^{ + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; + [userDefaults removeObjectForKey:kCONTACT_FIELD_VALUE]; + [userDefaults synchronize]; + }); + + it(@"should load the stored value", ^{ + NSString *contactFieldValue = @"Stored contactFieldValue"; + + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; + [userDefaults setObject:contactFieldValue + forKey:kCONTACT_FIELD_VALUE]; + [userDefaults synchronize]; + + MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:uuidProvider + timestampProvider:timestampProvider + deviceInfo:deviceInfo + storage:storage]; + [[context.contactFieldValue should] equal:contactFieldValue]; + }); + + it(@"should store contactFieldValue", ^{ + NSString *expectedContactFieldValue = @"Stored contactFieldValue"; + + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; + + MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:uuidProvider + timestampProvider:timestampProvider + deviceInfo:deviceInfo + storage:storage]; + [[context.contactFieldValue should] beNil]; + + [context setContactFieldValue:expectedContactFieldValue]; + + [[[userDefaults stringForKey:kCONTACT_FIELD_VALUE] should] equal:expectedContactFieldValue]; + [[context.contactFieldValue should] equal:expectedContactFieldValue]; + }); + +}); + +describe(@"setApplicationCode", ^{ + + it(@"should disable mobileEngage feature when appCode is set to nil", ^{ + MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:uuidProvider + timestampProvider:timestampProvider + deviceInfo:deviceInfo + storage:storage]; + + [context setApplicationCode:nil]; + [[theValue([MEExperimental isFeatureEnabled:EMSInnerFeature.mobileEngage]) should] beNo]; + }); + + it(@"should enableWithCompletionBlock: mobileEngage, v4 feature when appCode is set", ^{ + MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:uuidProvider + timestampProvider:timestampProvider + deviceInfo:deviceInfo + storage:storage]; + [context setApplicationCode:@"EMS11-C3FD3"]; + [[theValue([MEExperimental isFeatureEnabled:EMSInnerFeature.mobileEngage]) should] beYes]; + [[theValue([MEExperimental isFeatureEnabled:EMSInnerFeature.eventServiceV4]) should] beYes]; + }); +}); + +describe(@"hasContactIdentification", ^{ + it(@"should return YES when contactFieldValue or openIdToken is not nil", ^{ + MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:uuidProvider + timestampProvider:timestampProvider + deviceInfo:deviceInfo + storage:storage]; + [context setOpenIdToken:@"testIdToken"]; + + [[theValue([context hasContactIdentification]) should] beYes]; + }); +}); + +describe(@"reset", ^{ + + it(@"should clear contactFieldValue, contactToken, refreshToken, openIdToken", ^{ + NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kEMSSuiteName]; + MERequestContext *context = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:uuidProvider + timestampProvider:timestampProvider + deviceInfo:deviceInfo + storage:storage]; + [context setOpenIdToken:@"testIdToken"]; + + [userDefaults setObject:@"testContactFieldValue" + forKey:kCONTACT_FIELD_VALUE]; + [storage setString:@"testContactToken" + forKey:kCONTACT_TOKEN]; + [storage setString:@"testRefreshToken" + forKey:kREFRESH_TOKEN]; + [userDefaults synchronize]; + + [context reset]; + + [[userDefaults stringForKey:kCONTACT_FIELD_VALUE] shouldBeNil]; + [[storage stringForKey:kCONTACT_TOKEN] shouldBeNil]; + [[storage stringForKey:kREFRESH_TOKEN] shouldBeNil]; + [[context openIdToken] shouldBeNil]; + [[context contactFieldId] shouldBeNil]; + }); +}); SPEC_END diff --git a/Tests/MobileEngageTests/Session/EMSSessionTests.m b/Tests/MobileEngageTests/Session/EMSSessionTests.m index 9ce4c00a..c78e723f 100644 --- a/Tests/MobileEngageTests/Session/EMSSessionTests.m +++ b/Tests/MobileEngageTests/Session/EMSSessionTests.m @@ -10,6 +10,7 @@ #import "EMSTimestampProvider.h" #import "NSDate+EMSCore.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" @interface EMSSessionTests : XCTestCase @@ -38,7 +39,7 @@ - (void)setUp { } - (void)tearDown { - [self tearDownOperationQueue:self.operationQueue]; + [EmarsysTestUtils tearDownOperationQueue:self.operationQueue]; } - (void)testInit_sessionIdHolder_mustNotBeNil { @@ -109,6 +110,8 @@ - (void)testInit_timestampProvider_mustNotBeNil { - (void)testStartSession_shouldGenerateSessionId { [self.session startSessionWithCompletionBlock:nil]; + [self waitATickOnOperationQueue:self.operationQueue]; + XCTAssertNotNil(self.session.sessionIdHolder.sessionId); } @@ -118,6 +121,8 @@ - (void)testStartSession_shouldSetSessionStartTime { OCMStub([self.mockTimestampProvider provideTimestamp]).andReturn(sessionStartTime); [self.session startSessionWithCompletionBlock:nil]; + + [self waitATickOnOperationQueue:self.operationQueue]; XCTAssertEqualObjects(self.session.sessionStartTime, sessionStartTime); } @@ -129,25 +134,45 @@ - (void)testStartSession_shouldSendSessionStartEvent { eventAttributes:nil eventType:EventTypeInternal]).andReturn(mockRequestModel); [self.session startSessionWithCompletionBlock:nil]; + + [self waitATickOnOperationQueue:self.operationQueue]; OCMVerify([self.mockRequestManager submitRequestModel:mockRequestModel withCompletionBlock:nil]); } - (void)testDidBecomeActiveNotification_shouldInvokeStartSession { + XCTestExpectation *expectation = [self expectationForNotification:UIApplicationDidBecomeActiveNotification + object:nil + handler:^BOOL(NSNotification * _Nonnull notification) { + return YES; + }]; EMSSession *partialMockSession = OCMPartialMock(self.session); [NSNotificationCenter.defaultCenter postNotification:[NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification object:nil]]; OCMVerify([partialMockSession startSessionWithCompletionBlock:nil]); + + XCTWaiterResult waiterResult = [XCTWaiter waitForExpectations:@[expectation] + timeout:2.0]; + XCTAssertEqual(waiterResult, XCTWaiterResultCompleted); } - (void)testDidEnterBackgroundNotification_shouldInvokeStopSession { + XCTestExpectation *expectation = [self expectationForNotification:UIApplicationDidEnterBackgroundNotification + object:nil + handler:^BOOL(NSNotification * _Nonnull notification) { + return YES; + }]; EMSSession *partialMockSession = OCMPartialMock(self.session); - + [NSNotificationCenter.defaultCenter postNotification:[NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification object:nil]]; OCMVerify([partialMockSession stopSessionWithCompletionBlock:nil]); + + XCTWaiterResult waiterResult = [XCTWaiter waitForExpectations:@[expectation] + timeout:2.0]; + XCTAssertEqual(waiterResult, XCTWaiterResultCompleted); } - (void)testStopSession_shouldSendSessionStopEvent { @@ -161,6 +186,8 @@ - (void)testStopSession_shouldSendSessionStopEvent { eventType:EventTypeInternal]).andReturn(mockRequestModel); [self.session stopSessionWithCompletionBlock:nil]; + [self waitATickOnOperationQueue:self.operationQueue]; + OCMVerify([self.mockRequestManager submitRequestModel:mockRequestModel withCompletionBlock:nil]); XCTAssertNil(self.sessionIdHolder.sessionId); diff --git a/Tests/MobileEngageTests/Storage/MEButtonClickRepositoryTests.m b/Tests/MobileEngageTests/Storage/MEButtonClickRepositoryTests.m index 9d869b5b..aa1070bb 100644 --- a/Tests/MobileEngageTests/Storage/MEButtonClickRepositoryTests.m +++ b/Tests/MobileEngageTests/Storage/MEButtonClickRepositoryTests.m @@ -7,6 +7,8 @@ #import "EMSFilterByNothingSpecification.h" #import "EMSFilterByValuesSpecification.h" #import "EMSSchemaContract.h" +#import "EmarsysTestUtils.h" +#import "XCTestCase+Helper.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestMEDB.db"] @@ -14,20 +16,19 @@ __block EMSSQLiteHelper *helper; __block MEButtonClickRepository *repository; + __block NSOperationQueue *queue; - beforeEach(^{ - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; + beforeAll(^{ + queue = [self createTestOperationQueue]; helper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:queue]; [helper open]; repository = [[MEButtonClickRepository alloc] initWithDbHelper:helper]; }); afterEach(^{ - [helper close]; - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; + [EmarsysTestUtils clearDb:helper]; }); describe(@"requestModelRepository", ^{ diff --git a/Tests/MobileEngageTests/Storage/MEDisplayedIAMRepositoryTests.m b/Tests/MobileEngageTests/Storage/MEDisplayedIAMRepositoryTests.m index d8914851..2957aa73 100644 --- a/Tests/MobileEngageTests/Storage/MEDisplayedIAMRepositoryTests.m +++ b/Tests/MobileEngageTests/Storage/MEDisplayedIAMRepositoryTests.m @@ -7,6 +7,8 @@ #import "EMSFilterByValuesSpecification.h" #import "EMSSqliteSchemaHandler.h" #import "EMSSchemaContract.h" +#import "EmarsysTestUtils.h" +#import "XCTestCase+Helper.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestMEDB.db"] @@ -14,17 +16,19 @@ __block EMSSQLiteHelper *helper; __block MEDisplayedIAMRepository *repository; + __block NSOperationQueue *queue; - beforeEach(^{ - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH - error:nil]; - helper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH schemaDelegate:[EMSSqliteSchemaHandler new]]; + beforeAll(^{ + queue = [self createTestOperationQueue]; + helper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:queue]; [helper open]; repository = [[MEDisplayedIAMRepository alloc] initWithDbHelper:helper]; }); afterEach(^{ - [helper close]; + [EmarsysTestUtils clearDb:helper]; }); describe(@"requestModelRepository", ^{ diff --git a/Tests/MobileEngageTests/Storage/MERequestModelRepositoryFactoryTests.m b/Tests/MobileEngageTests/Storage/MERequestModelRepositoryFactoryTests.m index abb806c5..32289394 100644 --- a/Tests/MobileEngageTests/Storage/MERequestModelRepositoryFactoryTests.m +++ b/Tests/MobileEngageTests/Storage/MERequestModelRepositoryFactoryTests.m @@ -12,236 +12,251 @@ #import "EMSSqliteSchemaHandler.h" #import "EMSEndpoint.h" #import "EMSStorage.h" +#import "XCTestCase+Helper.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestDB.db"] SPEC_BEGIN(MERequestModelRepositoryFactoryTests) - describe(@"initWithInApp:requestContext:dbHelper:buttonClickRepository:displayedIAMRepository:endpoint:", ^{ - it(@"should set inApp after init", ^{ - MEInApp *inApp = [MEInApp mock]; - MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:inApp - requestContext:[MERequestContext mock] - dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - endpoint:[EMSEndpoint mock] - operationQueue:[NSOperationQueue mock] - storage:[EMSStorage mock]]; - [[(id) factory.inApp shouldNot] beNil]; - }); +__block NSOperationQueue *queue; - it(@"should set requestContext after init", ^{ - MEInApp *inApp = [MEInApp mock]; - MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] - requestContext:[MERequestContext mock] - dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - endpoint:[EMSEndpoint mock] - operationQueue:[NSOperationQueue mock] - storage:[EMSStorage mock]]; - [[(id) factory.inApp shouldNot] beNil]; - }); +beforeEach(^{ + queue = [self createTestOperationQueue]; +}); - it(@"should throw an exception when there is no inApp", ^{ - @try { - MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:nil - requestContext:[MERequestContext mock] - dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - endpoint:[EMSEndpoint mock] - operationQueue:[NSOperationQueue mock] - storage:[EMSStorage mock]]; - fail(@"Expected Exception when inApp is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: inApp"]; - [[theValue(exception) shouldNot] beNil]; - } - }); +describe(@"initWithInApp:requestContext:dbHelper:buttonClickRepository:displayedIAMRepository:endpoint:", ^{ + it(@"should set inApp after init", ^{ + MEInApp *inApp = [MEInApp mock]; + MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:inApp + requestContext:[MERequestContext mock] + dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:queue] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + endpoint:[EMSEndpoint mock] + operationQueue:[NSOperationQueue mock] + storage:[EMSStorage mock]]; + [[(id) factory.inApp shouldNot] beNil]; + }); + + it(@"should set requestContext after init", ^{ + MEInApp *inApp = [MEInApp mock]; + MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] + requestContext:[MERequestContext mock] + dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:queue] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + endpoint:[EMSEndpoint mock] + operationQueue:[NSOperationQueue mock] + storage:[EMSStorage mock]]; + [[(id) factory.inApp shouldNot] beNil]; + }); + + it(@"should throw an exception when there is no inApp", ^{ + @try { + MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:nil + requestContext:[MERequestContext mock] + dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:queue] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + endpoint:[EMSEndpoint mock] + operationQueue:[NSOperationQueue mock] + storage:[EMSStorage mock]]; + fail(@"Expected Exception when inApp is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: inApp"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw an exception when there is no requestContext", ^{ + @try { + MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] + requestContext:nil + dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:queue] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + endpoint:[EMSEndpoint mock] + operationQueue:[NSOperationQueue mock] + storage:[EMSStorage mock]]; + fail(@"Expected Exception when requestContext is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: requestContext"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw an exception when there is no dbHelper", ^{ + @try { + MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] + requestContext:[MERequestContext mock] + dbHelper:nil + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + endpoint:[EMSEndpoint mock] + operationQueue:[NSOperationQueue mock] + storage:[EMSStorage mock]]; + fail(@"Expected Exception when dbHelper is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: dbHelper"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw an exception when there is no buttonClickRepository", ^{ + @try { + MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] + requestContext:[MERequestContext mock] + dbHelper:[EMSSQLiteHelper mock] + buttonClickRepository:nil + displayedIAMRepository:[MEDisplayedIAMRepository mock] + endpoint:[EMSEndpoint mock] + operationQueue:[NSOperationQueue mock] + storage:[EMSStorage mock]]; + fail(@"Expected Exception when buttonClickRepository is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: buttonClickRepository"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw an exception when there is no displayedIAMRepository", ^{ + @try { + MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] + requestContext:[MERequestContext mock] + dbHelper:[EMSSQLiteHelper mock] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:nil + endpoint:[EMSEndpoint mock] + operationQueue:[NSOperationQueue mock] + storage:[EMSStorage mock]]; + fail(@"Expected Exception when displayedIAMRepository is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: displayedIAMRepository"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw an exception when there is no endpoint", ^{ + @try { + MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] + requestContext:[MERequestContext mock] + dbHelper:[EMSSQLiteHelper mock] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + endpoint:nil + operationQueue:[NSOperationQueue mock] + storage:[EMSStorage mock]]; + fail(@"Expected Exception when endpoint is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: endpoint"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw an exception when there is no operationQueue", ^{ + @try { + MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] + requestContext:[MERequestContext mock] + dbHelper:[EMSSQLiteHelper mock] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + endpoint:[EMSEndpoint mock] + operationQueue:nil + storage:[EMSStorage mock]]; + fail(@"Expected Exception when operationQueue is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: operationQueue"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw an exception when there is no storage", ^{ + @try { + MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] + requestContext:[MERequestContext mock] + dbHelper:[EMSSQLiteHelper mock] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + endpoint:[EMSEndpoint mock] + operationQueue:[NSOperationQueue mock] + storage:nil]; + fail(@"Expected Exception when storage is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: storage"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + +}); - it(@"should throw an exception when there is no requestContext", ^{ - @try { - MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] - requestContext:nil - dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - endpoint:[EMSEndpoint mock] - operationQueue:[NSOperationQueue mock] - storage:[EMSStorage mock]]; - fail(@"Expected Exception when requestContext is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: requestContext"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should throw an exception when there is no dbHelper", ^{ - @try { - MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] - requestContext:[MERequestContext mock] - dbHelper:nil - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - endpoint:[EMSEndpoint mock] - operationQueue:[NSOperationQueue mock] - storage:[EMSStorage mock]]; - fail(@"Expected Exception when dbHelper is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: dbHelper"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should throw an exception when there is no buttonClickRepository", ^{ - @try { - MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] - requestContext:[MERequestContext mock] - dbHelper:[EMSSQLiteHelper mock] - buttonClickRepository:nil - displayedIAMRepository:[MEDisplayedIAMRepository mock] - endpoint:[EMSEndpoint mock] - operationQueue:[NSOperationQueue mock] - storage:[EMSStorage mock]]; - fail(@"Expected Exception when buttonClickRepository is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: buttonClickRepository"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should throw an exception when there is no displayedIAMRepository", ^{ - @try { - MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] - requestContext:[MERequestContext mock] - dbHelper:[EMSSQLiteHelper mock] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:nil - endpoint:[EMSEndpoint mock] - operationQueue:[NSOperationQueue mock] - storage:[EMSStorage mock]]; - fail(@"Expected Exception when displayedIAMRepository is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: displayedIAMRepository"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should throw an exception when there is no endpoint", ^{ - @try { - MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] - requestContext:[MERequestContext mock] - dbHelper:[EMSSQLiteHelper mock] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - endpoint:nil - operationQueue:[NSOperationQueue mock] - storage:[EMSStorage mock]]; - fail(@"Expected Exception when endpoint is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: endpoint"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should throw an exception when there is no operationQueue", ^{ - @try { - MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] - requestContext:[MERequestContext mock] - dbHelper:[EMSSQLiteHelper mock] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - endpoint:[EMSEndpoint mock] - operationQueue:nil - storage:[EMSStorage mock]]; - fail(@"Expected Exception when operationQueue is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: operationQueue"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should throw an exception when there is no storage", ^{ - @try { - MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] - requestContext:[MERequestContext mock] - dbHelper:[EMSSQLiteHelper mock] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - endpoint:[EMSEndpoint mock] - operationQueue:[NSOperationQueue mock] - storage:nil]; - fail(@"Expected Exception when storage is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: storage"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - }); - - describe(@"create", ^{ - it(@"should not return nil for parameter NO", ^{ - MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] - requestContext:[MERequestContext mock] - dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - endpoint:[EMSEndpoint mock] - operationQueue:[NSOperationQueue mock] - storage:[EMSStorage mock]]; - [[((NSObject *) [factory createWithBatchCustomEventProcessing:NO]) shouldNot] beNil]; - }); - - it(@"should not return nil for parameter YES", ^{ - MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] - requestContext:[MERequestContext mock] - dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - endpoint:[EMSEndpoint mock] - operationQueue:[NSOperationQueue mock] - storage:[EMSStorage mock]]; - [[((NSObject *) [factory createWithBatchCustomEventProcessing:YES]) shouldNot] beNil]; - }); - - it(@"should return EMSRequestModelRepository for parameter NO", ^{ - MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] - requestContext:[MERequestContext mock] - dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - endpoint:[EMSEndpoint mock] - operationQueue:[NSOperationQueue mock] - storage:[EMSStorage mock]]; - - id repository = [factory createWithBatchCustomEventProcessing:NO]; - [[[[repository class] description] should] equal:@"EMSRequestModelRepository"]; - }); - - it(@"should return MERequestRepositoryProxy for parameter YES", ^{ - MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] - requestContext:[MERequestContext mock] - dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - endpoint:[EMSEndpoint mock] - operationQueue:[NSOperationQueue mock] - storage:[EMSStorage mock]]; - - id repository = [factory createWithBatchCustomEventProcessing:YES]; - [[[[repository class] description] should] equal:@"MERequestRepositoryProxy"]; - }); - }); +describe(@"create", ^{ + it(@"should not return nil for parameter NO", ^{ + MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] + requestContext:[MERequestContext mock] + dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:queue] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + endpoint:[EMSEndpoint mock] + operationQueue:[NSOperationQueue mock] + storage:[EMSStorage mock]]; + [[((NSObject *) [factory createWithBatchCustomEventProcessing:NO]) shouldNot] beNil]; + }); + + it(@"should not return nil for parameter YES", ^{ + MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] + requestContext:[MERequestContext mock] + dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:queue] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + endpoint:[EMSEndpoint mock] + operationQueue:[NSOperationQueue mock] + storage:[EMSStorage mock]]; + [[((NSObject *) [factory createWithBatchCustomEventProcessing:YES]) shouldNot] beNil]; + }); + + it(@"should return EMSRequestModelRepository for parameter NO", ^{ + MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] + requestContext:[MERequestContext mock] + dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:queue] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + endpoint:[EMSEndpoint mock] + operationQueue:[NSOperationQueue mock] + storage:[EMSStorage mock]]; + + id repository = [factory createWithBatchCustomEventProcessing:NO]; + [[[[repository class] description] should] equal:@"EMSRequestModelRepository"]; + }); + + it(@"should return MERequestRepositoryProxy for parameter YES", ^{ + MERequestModelRepositoryFactory *factory = [[MERequestModelRepositoryFactory alloc] initWithInApp:[MEInApp mock] + requestContext:[MERequestContext mock] + dbHelper:[[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:queue] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + endpoint:[EMSEndpoint mock] + operationQueue:[NSOperationQueue mock] + storage:[EMSStorage mock]]; + + id repository = [factory createWithBatchCustomEventProcessing:YES]; + [[[[repository class] description] should] equal:@"MERequestRepositoryProxy"]; + }); +}); SPEC_END diff --git a/Tests/MobileEngageTests/Storage/MERequestRepositoryProxyTests.m b/Tests/MobileEngageTests/Storage/MERequestRepositoryProxyTests.m index b089dc75..950b0161 100644 --- a/Tests/MobileEngageTests/Storage/MERequestRepositoryProxyTests.m +++ b/Tests/MobileEngageTests/Storage/MERequestRepositoryProxyTests.m @@ -27,492 +27,497 @@ #import "MEExperimental+Test.h" #import "EMSInnerFeature.h" #import "EMSStorageProtocol.h" +#import "XCTestCase+Helper.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestMEDB.db"] SPEC_BEGIN(MERequestRepositoryProxyTests) - __block MEDisplayedIAMRepository *displayedRepository; - __block MEButtonClickRepository *buttonClickRepository; - __block EMSRequestModelRepository *requestModelRepository; - __block MERequestRepositoryProxy *compositeRequestModelRepository; - __block EMSTimestampProvider *timestampProvider; - __block EMSUUIDProvider *uuidProvider; - __block EMSDeviceInfo *deviceInfo; - __block MERequestContext *requestContext; - __block NSString *applicationCode; - __block NSNumber *contactFieldId; - __block EMSEndpoint *mockEndpoint; - __block EMSStorage *mockStorage; - - registerMatchers(@"EMS"); - - id (^customEventRequestModel)(NSString *eventName, NSDictionary *eventAttributes, MERequestContext *requestContext) = ^id(NSString *eventName, NSDictionary *eventAttributes, MERequestContext *requestContext) { - return [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { - NSMutableDictionary *event = [NSMutableDictionary dictionaryWithDictionary:@{ - @"type": @"custom", - @"name": eventName, - @"timestamp": [[timestampProvider provideTimestamp] numberValueInMillis]}]; - - if (eventAttributes) { - event[@"attributes"] = eventAttributes; - } - - [builder setUrl:@"https://mobile-events.eservice.emarsys.net/v3/apps/testAppplicationCode/client/events"]; - [builder setMethod:HTTPMethodPOST]; - [builder setPayload:@{@"events": @[event]}]; - } - timestampProvider:timestampProvider - uuidProvider:uuidProvider]; - }; - - id (^normalRequestModel)(NSString *url, MERequestContext *requestContext) = ^id(NSString *url, MERequestContext *requestContext) { - return [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { - [builder setUrl:url]; - [builder setMethod:HTTPMethodGET]; - } - timestampProvider:timestampProvider - uuidProvider:uuidProvider]; - }; - - id (^createFakeRequestRepository)(NSArray *nextRequest, NSArray *allCustomEvents, NSArray *AllRequests, MEInApp *inApp, MERequestContext *requestContext) = ^id(NSArray *nextRequest, NSArray *allCustomEvents, NSArray *AllRequests, MEInApp *inApp, MERequestContext *requestContext) { - EMSQueryOldestRowSpecification *selectFirstSpecification = [EMSQueryOldestRowSpecification new]; - EMSFilterByTypeSpecification *filterCustomEventsSpecification = [[EMSFilterByTypeSpecification alloc] initWitType:[NSString stringWithFormat:@"%%%@%%/events", - @"testEventServiceUrl"] - column:REQUEST_COLUMN_NAME_URL]; - - EMSFilterByNothingSpecification *selectAllRequestsSpecification = [EMSFilterByNothingSpecification new]; - - FakeRequestRepository *fakeRequestRepository = [FakeRequestRepository new]; - fakeRequestRepository.queryResponseMapping = @{ - NSStringFromClass([selectFirstSpecification class]): nextRequest, - NSStringFromClass([filterCustomEventsSpecification class]): allCustomEvents, - NSStringFromClass([selectAllRequestsSpecification class]): AllRequests}; - - compositeRequestModelRepository = [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:fakeRequestRepository - buttonClickRepository:buttonClickRepository - displayedIAMRepository:displayedRepository - inApp:inApp - requestContext:requestContext - endpoint:mockEndpoint - storage:mockStorage]; - return compositeRequestModelRepository; - }; - - beforeEach(^{ - mockEndpoint = [EMSEndpoint mock]; - mockStorage = [EMSStorage nullMock]; - [mockEndpoint stub:@selector(eventServiceUrl) andReturn:@"testEventServiceUrl"]; - - timestampProvider = [EMSTimestampProvider new]; - uuidProvider = [EMSUUIDProvider new]; - deviceInfo = [[EMSDeviceInfo alloc] initWithSDKVersion:@"testSDKVersion" - notificationCenter:[UNUserNotificationCenter mock] - storage:[[EMSStorage alloc] initWithSuiteNames:@[] - accessGroup:nil] - uuidProvider:[EMSUUIDProvider new]]; - - displayedRepository = [MEDisplayedIAMRepository nullMock]; - buttonClickRepository = [MEButtonClickRepository nullMock]; - requestModelRepository = [EMSRequestModelRepository mock]; - applicationCode = @"testApplicationCode"; - contactFieldId = @3; - requestContext = [[MERequestContext alloc] initWithApplicationCode:applicationCode - uuidProvider:uuidProvider - timestampProvider:timestampProvider - deviceInfo:deviceInfo - storage:[[EMSStorage alloc] initWithSuiteNames:@[] - accessGroup:nil]]; - compositeRequestModelRepository = [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:requestModelRepository - buttonClickRepository:buttonClickRepository - displayedIAMRepository:displayedRepository - inApp:[MEInApp mock] - requestContext:requestContext - endpoint:mockEndpoint - storage:mockStorage]; - }); - - afterEach(^{ - [MEExperimental reset]; - }); - - describe(@"initWithRequestModelRepository:buttonClickRepository:displayedIAMRepository:inApp:requestContext:deviceInfo:", ^{ - - it(@"should set inApp after init", ^{ - MEInApp *inApp = [MEInApp mock]; - MERequestRepositoryProxy *factory = [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - inApp:inApp - requestContext:[MERequestContext nullMock] - endpoint:mockEndpoint - storage:[EMSStorage mock]]; - [[factory.inApp shouldNot] beNil]; - }); - - it(@"should set requestContext after init", ^{ - MERequestRepositoryProxy *factory = [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - inApp:[MEInApp mock] - requestContext:requestContext - endpoint:mockEndpoint - storage:[EMSStorage mock]]; - [[factory.requestContext should] equal:requestContext]; - }); - - it(@"should throw an exception when there is no inApp", ^{ - @try { - [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - inApp:nil - requestContext:[MERequestContext mock] - endpoint:[EMSEndpoint mock] - storage:[EMSStorage mock]]; - fail(@"Expected Exception when inApp is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: inApp"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should throw an exception when there is no requestModelRepository", ^{ - @try { - [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:nil - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - inApp:[MEInApp mock] - requestContext:[MERequestContext mock] - endpoint:[EMSEndpoint mock] - storage:[EMSStorage mock]]; - fail(@"Expected Exception when requestModelRepository is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: requestModelRepository"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should throw an exception when there is no clickRepository", ^{ - @try { - [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] - buttonClickRepository:nil - displayedIAMRepository:[MEDisplayedIAMRepository mock] - inApp:[MEInApp mock] - requestContext:[MERequestContext mock] - endpoint:[EMSEndpoint mock] - storage:[EMSStorage mock]]; - fail(@"Expected Exception when clickRepository is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: buttonClickRepository"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should throw an exception when there is no displayedIAMRepository", ^{ - @try { - (void) [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:nil - inApp:[MEInApp mock] - requestContext:[MERequestContext mock] - endpoint:[EMSEndpoint mock] - storage:[EMSStorage mock]]; - fail(@"Expected Exception when displayedIAMRepository is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: displayedIAMRepository"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should throw an exception when there is no requestContext", ^{ - @try { - (void) [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - inApp:[MEInApp mock] - requestContext:nil - endpoint:[EMSEndpoint mock] - storage:[EMSStorage mock]]; - fail(@"Expected Exception when requestContext is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: requestContext"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should throw an exception when there is no endpoint", ^{ - @try { - (void) [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - inApp:[MEInApp mock] - requestContext:[MERequestContext mock] - endpoint:nil - storage:[EMSStorage mock]]; - fail(@"Expected Exception when endpoint is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: endpoint"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - - it(@"should throw an exception when there is no storage", ^{ - @try { - (void) [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] - buttonClickRepository:[MEButtonClickRepository mock] - displayedIAMRepository:[MEDisplayedIAMRepository mock] - inApp:[MEInApp mock] - requestContext:[MERequestContext mock] - endpoint:[EMSEndpoint mock] - storage:nil]; - fail(@"Expected Exception when storage is nil!"); - } @catch (NSException *exception) { - [[exception.reason should] equal:@"Invalid parameter not satisfying: storage"]; - [[theValue(exception) shouldNot] beNil]; - } - }); - }); - - describe(@"MERequestRepositoryProxy", ^{ - afterEach(^{ - [MEExperimental reset]; - }); - - it(@"should add buttonClicks on the custom event requests", ^{ - NSArray *clicks = @[ - [[MEButtonClick alloc] initWithCampaignId:@"campaignID" - buttonId:@"buttonID" - timestamp:[NSDate date]], - [[MEButtonClick alloc] initWithCampaignId:@"campaignID2" - buttonId:@"buttonID2" - timestamp:[NSDate date]] - ]; - - [[buttonClickRepository should] receive:@selector(query:) andReturn:clicks]; - - EMSRequestModel *modelCustomEvent1 = customEventRequestModel(@"event1", nil, requestContext); - - createFakeRequestRepository(@[modelCustomEvent1], @[modelCustomEvent1], @[modelCustomEvent1], [MEInApp new], requestContext); - - NSArray *result = [compositeRequestModelRepository query:[EMSFilterByNothingSpecification new]]; - [[[result[0] payload][@"clicks"] should] equal:@[ - @{@"campaignId": [clicks[0] campaignId], @"buttonId": [clicks[0] buttonId], @"timestamp": [clicks[0] timestamp].stringValueInUTC}, - @{@"campaignId": [clicks[1] campaignId], @"buttonId": [clicks[1] buttonId], @"timestamp": [clicks[1] timestamp].stringValueInUTC} - ]]; - }); - - it(@"should add viewedMessages on the custom event requests", ^{ - NSArray *viewedMessages = @[ - [[MEDisplayedIAM alloc] initWithCampaignId:@"123" timestamp:[NSDate date]], - [[MEDisplayedIAM alloc] initWithCampaignId:@"42" timestamp:[NSDate date]] - ]; - - [[displayedRepository should] receive:@selector(query:) andReturn:viewedMessages]; - - EMSRequestModel *modelCustomEvent1 = customEventRequestModel(@"event1", nil, requestContext); - - createFakeRequestRepository(@[modelCustomEvent1], @[modelCustomEvent1], @[modelCustomEvent1], [MEInApp new], requestContext); - - NSArray *result = [compositeRequestModelRepository query:[EMSFilterByNothingSpecification new]]; - [[[result[0] payload][@"viewedMessages"] should] equal:@[ - @{@"campaignId": [viewedMessages[0] campaignId], @"timestamp": [viewedMessages[0] timestamp].stringValueInUTC}, - @{@"campaignId": [viewedMessages[1] campaignId], @"timestamp": [viewedMessages[1] timestamp].stringValueInUTC} - ]]; - }); - - it(@"should add the element to the requestModelRepository", ^{ - EMSRequestModel *model = [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { - [builder setUrl:@"https://www.url.com"]; - [builder setMethod:HTTPMethodGET]; - } - timestampProvider:[EMSTimestampProvider new] - uuidProvider:[EMSUUIDProvider new]]; - [[requestModelRepository should] receive:@selector(add:) withArguments:model]; - - [compositeRequestModelRepository add:model]; - }); - - it(@"should remove the element from the requestModelRepository", ^{ - id spec = [KWMock mockForProtocol:@protocol(EMSSQLSpecificationProtocol)]; - - [[requestModelRepository should] receive:@selector(remove:) withArguments:spec]; - [compositeRequestModelRepository remove:spec]; - }); - - it(@"should query normal RequestModels from RequestRepository", ^{ - EMSFilterByNothingSpecification *specification = [EMSFilterByNothingSpecification new]; - - NSArray *const requests = @[[EMSRequestModel nullMock], [EMSRequestModel nullMock], [EMSRequestModel nullMock]]; - [[requestModelRepository should] receive:@selector(query:) - andReturn:requests - withArguments:specification]; - - NSArray *result = [compositeRequestModelRepository query:specification]; - [[result should] equal:requests]; - }); - - it(@"should return empty array if no elements were found", ^{ - EMSFilterByNothingSpecification *specification = [EMSFilterByNothingSpecification new]; - - NSArray *const requests = @[]; - [[requestModelRepository should] receive:@selector(query:) - andReturn:requests - withArguments:specification]; - - NSArray *result = [compositeRequestModelRepository query:specification]; - [[result should] equal:requests]; - }); - - it(@"should query composite RequestModel from RequestRepository when select first", ^{ - EMSRequestModel *modelCustomEvent1 = customEventRequestModel(@"event1", nil, requestContext); - EMSRequestModel *model1 = normalRequestModel(@"https://www.google.com", requestContext); - EMSRequestModel *modelCustomEvent2 = customEventRequestModel(@"event2", @{@"key1": @"value1", @"key2": @"value2"}, requestContext); - EMSRequestModel *model2 = normalRequestModel(@"https://www.google.com", requestContext); - EMSRequestModel *modelCustomEvent3 = customEventRequestModel(@"event3", @{@"star": @"wars"}, requestContext); - - EMSCompositeRequestModel *compositeModel = [EMSCompositeRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { - [builder setUrl:@"https://mobile-events.eservice.emarsys.net/v3/apps/testAppplicationCode/client/events"]; - [builder setMethod:HTTPMethodPOST]; - [builder setPayload:@{ - @"hardware_id": deviceInfo.hardwareId, - @"viewedMessages": @[], - @"clicks": @[], - @"events": @[ - [modelCustomEvent1.payload[@"events"] firstObject], - [modelCustomEvent2.payload[@"events"] firstObject], - [modelCustomEvent3.payload[@"events"] firstObject] - ], - @"language": deviceInfo.languageCode, - @"ems_sdk": EMARSYS_SDK_VERSION, - @"application_version": deviceInfo.applicationVersion - }]; - } - timestampProvider:requestContext.timestampProvider - uuidProvider:requestContext.uuidProvider]; - compositeModel.originalRequests = @[modelCustomEvent1, modelCustomEvent2, modelCustomEvent3]; - - createFakeRequestRepository(@[modelCustomEvent1], @[modelCustomEvent1, modelCustomEvent2, modelCustomEvent3], @[modelCustomEvent1, model1, modelCustomEvent2, model2, modelCustomEvent3], [MEInApp new], requestContext); - - NSArray *result = [compositeRequestModelRepository query:[EMSQueryOldestRowSpecification new]]; - [[theValue([result count]) should] equal:theValue(1)]; - [[[result firstObject] should] beSimilarWithRequest:compositeModel]; - }); - - it(@"should query composite RequestModels from RequestRepository when select all", ^{ - EMSRequestModel *model1 = normalRequestModel(@"https://www.google.com", requestContext); - EMSRequestModel *modelCustomEvent1 = customEventRequestModel(@"event1", nil, requestContext); - EMSRequestModel *modelCustomEvent2 = customEventRequestModel(@"event2", @{@"key1": @"value1", @"key2": @"value2"}, requestContext); - EMSRequestModel *model2 = normalRequestModel(@"https://mobile-events.eservice.emarsys.net/v3/apps/testAppplicationCode/client/events456", requestContext); - EMSRequestModel *modelCustomEvent3 = customEventRequestModel(@"event3", @{@"star": @"wars"}, requestContext); - - EMSCompositeRequestModel *compositeModel = [EMSCompositeRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { - [builder setUrl:@"https://mobile-events.eservice.emarsys.net/v3/apps/testAppplicationCode/client/events"]; - [builder setMethod:HTTPMethodPOST]; - [builder setPayload:@{ - @"hardware_id": deviceInfo.hardwareId, - @"viewedMessages": @[], - @"clicks": @[], - @"events": @[ - [modelCustomEvent1.payload[@"events"] firstObject], - [modelCustomEvent2.payload[@"events"] firstObject], - [modelCustomEvent3.payload[@"events"] firstObject] - ], - @"language": deviceInfo.languageCode, - @"ems_sdk": EMARSYS_SDK_VERSION, - @"application_version": deviceInfo.applicationVersion - }]; - } - timestampProvider:requestContext.timestampProvider - uuidProvider:requestContext.uuidProvider]; - compositeModel.originalRequests = @[modelCustomEvent1, modelCustomEvent2, modelCustomEvent3]; - - - createFakeRequestRepository(@[modelCustomEvent1], @[modelCustomEvent1, modelCustomEvent2, modelCustomEvent3], @[model1, modelCustomEvent1, modelCustomEvent2, model2, modelCustomEvent3], [MEInApp new], requestContext); - - NSArray *result = [compositeRequestModelRepository query:[EMSFilterByNothingSpecification new]]; - [[theValue([result count]) should] equal:theValue(3)]; - [[result[0] should] beSimilarWithRequest:model1]; - [[result[1] should] beSimilarWithRequest:compositeModel]; - [[result[2] should] beSimilarWithRequest:model2]; - }); - - it(@"should query composite RequestModels from RequestRepository when select all and should not crash when events is empty", ^{ - EMSRequestModel *model1 = normalRequestModel(@"https://www.google.com", requestContext); - EMSRequestModel *modelCustomEvent1 = [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { - [builder setUrl:@"https://mobile-events.eservice.emarsys.net/v3/apps/testAppplicationCode/client/events"]; - [builder setMethod:HTTPMethodPOST]; - [builder setPayload:@{@"events": @[]}]; - } - timestampProvider:timestampProvider - uuidProvider:uuidProvider]; - EMSRequestModel *modelCustomEvent2 = customEventRequestModel(@"event2", @{@"key1": @"value1", @"key2": @"value2"}, requestContext); - EMSRequestModel *model2 = normalRequestModel(@"https://mobile-events.eservice.emarsys.net/v3/apps/testAppplicationCode/client/events456", requestContext); - EMSRequestModel *modelCustomEvent3 = customEventRequestModel(@"event3", @{@"star": @"wars"}, requestContext); - - EMSCompositeRequestModel *compositeModel = [EMSCompositeRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { - [builder setUrl:@"https://mobile-events.eservice.emarsys.net/v3/apps/testAppplicationCode/client/events"]; - [builder setMethod:HTTPMethodPOST]; - [builder setPayload:@{ - @"hardware_id": deviceInfo.hardwareId, - @"viewedMessages": @[], - @"clicks": @[], - @"events": @[ - [modelCustomEvent2.payload[@"events"] firstObject], - [modelCustomEvent3.payload[@"events"] firstObject] - ], - @"language": deviceInfo.languageCode, - @"ems_sdk": EMARSYS_SDK_VERSION, - @"application_version": deviceInfo.applicationVersion - }]; - } - timestampProvider:requestContext.timestampProvider - uuidProvider:requestContext.uuidProvider]; - compositeModel.originalRequests = @[modelCustomEvent1, modelCustomEvent2, modelCustomEvent3]; - - - createFakeRequestRepository(@[modelCustomEvent1], @[modelCustomEvent1, modelCustomEvent2, modelCustomEvent3], @[model1, modelCustomEvent1, modelCustomEvent2, model2, modelCustomEvent3], [MEInApp new], requestContext); - - NSArray *result = [compositeRequestModelRepository query:[EMSFilterByNothingSpecification new]]; - [[theValue([result count]) should] equal:theValue(3)]; - [[result[0] should] beSimilarWithRequest:model1]; - [[result[1] should] beSimilarWithRequest:compositeModel]; - [[result[2] should] beSimilarWithRequest:model2]; - }); - - it(@"should return NO if request requestModelRepository is NOT empty", ^{ - [[requestModelRepository should] receive:@selector(isEmpty) andReturn:theValue(NO)]; - [[theValue([compositeRequestModelRepository isEmpty]) should] beNo]; - }); - - it(@"should return YES if request requestModelRepository is empty", ^{ - [[requestModelRepository should] receive:@selector(isEmpty) andReturn:theValue(YES)]; - [[theValue([compositeRequestModelRepository isEmpty]) should] beYes]; - }); - - it(@"should add dnd on the custom event requests with 'YES' value when inApp is paused", ^{ - EMSRequestModel *modelCustomEvent1 = customEventRequestModel(@"event1", nil, requestContext); - - MEInApp *meInApp = [MEInApp new]; - [meInApp pause]; - - createFakeRequestRepository(@[modelCustomEvent1], @[modelCustomEvent1], @[modelCustomEvent1], meInApp, requestContext); - NSArray *result = [compositeRequestModelRepository query:[EMSFilterByNothingSpecification new]]; - [[[result[0] payload][@"dnd"] should] equal:@(YES)]; - }); - - it(@"should not add dnd on the custom event requests when inApp is resumed", ^{ - EMSRequestModel *modelCustomEvent1 = customEventRequestModel(@"event1", nil, requestContext); - - MEInApp *meInApp = [MEInApp new]; - - createFakeRequestRepository(@[modelCustomEvent1], @[modelCustomEvent1], @[modelCustomEvent1], meInApp, requestContext); - NSArray *result = [compositeRequestModelRepository query:[EMSFilterByNothingSpecification new]]; - [[theValue([[[result[0] payload] allKeys] containsObject:@"dnd"]) should] beNo]; - }); - - }); +__block MEDisplayedIAMRepository *displayedRepository; +__block MEButtonClickRepository *buttonClickRepository; +__block EMSRequestModelRepository *requestModelRepository; +__block MERequestRepositoryProxy *compositeRequestModelRepository; +__block EMSTimestampProvider *timestampProvider; +__block EMSUUIDProvider *uuidProvider; +__block EMSDeviceInfo *deviceInfo; +__block MERequestContext *requestContext; +__block NSString *applicationCode; +__block NSNumber *contactFieldId; +__block EMSEndpoint *mockEndpoint; +__block EMSStorage *mockStorage; +__block NSOperationQueue *queue; + +registerMatchers(@"EMS"); + +id (^customEventRequestModel)(NSString *eventName, NSDictionary *eventAttributes, MERequestContext *requestContext) = ^id(NSString *eventName, NSDictionary *eventAttributes, MERequestContext *requestContext) { + return [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { + NSMutableDictionary *event = [NSMutableDictionary dictionaryWithDictionary:@{ + @"type": @"custom", + @"name": eventName, + @"timestamp": [[timestampProvider provideTimestamp] numberValueInMillis]}]; + + if (eventAttributes) { + event[@"attributes"] = eventAttributes; + } + + [builder setUrl:@"https://mobile-events.eservice.emarsys.net/v3/apps/testAppplicationCode/client/events"]; + [builder setMethod:HTTPMethodPOST]; + [builder setPayload:@{@"events": @[event]}]; + } + timestampProvider:timestampProvider + uuidProvider:uuidProvider]; +}; + +id (^normalRequestModel)(NSString *url, MERequestContext *requestContext) = ^id(NSString *url, MERequestContext *requestContext) { + return [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { + [builder setUrl:url]; + [builder setMethod:HTTPMethodGET]; + } + timestampProvider:timestampProvider + uuidProvider:uuidProvider]; +}; + +id (^createFakeRequestRepository)(NSArray *nextRequest, NSArray *allCustomEvents, NSArray *AllRequests, MEInApp *inApp, MERequestContext *requestContext) = ^id(NSArray *nextRequest, NSArray *allCustomEvents, NSArray *AllRequests, MEInApp *inApp, MERequestContext *requestContext) { + EMSQueryOldestRowSpecification *selectFirstSpecification = [EMSQueryOldestRowSpecification new]; + EMSFilterByTypeSpecification *filterCustomEventsSpecification = [[EMSFilterByTypeSpecification alloc] initWitType:[NSString stringWithFormat:@"%%%@%%/events", + @"testEventServiceUrl"] + column:REQUEST_COLUMN_NAME_URL]; + + EMSFilterByNothingSpecification *selectAllRequestsSpecification = [EMSFilterByNothingSpecification new]; + + FakeRequestRepository *fakeRequestRepository = [FakeRequestRepository new]; + fakeRequestRepository.queryResponseMapping = @{ + NSStringFromClass([selectFirstSpecification class]): nextRequest, + NSStringFromClass([filterCustomEventsSpecification class]): allCustomEvents, + NSStringFromClass([selectAllRequestsSpecification class]): AllRequests}; + + compositeRequestModelRepository = [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:fakeRequestRepository + buttonClickRepository:buttonClickRepository + displayedIAMRepository:displayedRepository + inApp:inApp + requestContext:requestContext + endpoint:mockEndpoint + storage:mockStorage]; + return compositeRequestModelRepository; +}; + +beforeEach(^{ + mockEndpoint = [EMSEndpoint mock]; + mockStorage = [EMSStorage nullMock]; + [mockEndpoint stub:@selector(eventServiceUrl) andReturn:@"testEventServiceUrl"]; + + timestampProvider = [EMSTimestampProvider new]; + uuidProvider = [EMSUUIDProvider new]; + queue = [self createTestOperationQueue]; + deviceInfo = [[EMSDeviceInfo alloc] initWithSDKVersion:@"testSDKVersion" + notificationCenter:[UNUserNotificationCenter mock] + storage:[[EMSStorage alloc] initWithSuiteNames:@[] + accessGroup:nil + operationQueue:queue] + uuidProvider:[EMSUUIDProvider new]]; + + displayedRepository = [MEDisplayedIAMRepository nullMock]; + buttonClickRepository = [MEButtonClickRepository nullMock]; + requestModelRepository = [EMSRequestModelRepository mock]; + applicationCode = @"testApplicationCode"; + contactFieldId = @3; + requestContext = [[MERequestContext alloc] initWithApplicationCode:applicationCode + uuidProvider:uuidProvider + timestampProvider:timestampProvider + deviceInfo:deviceInfo + storage:[[EMSStorage alloc] initWithSuiteNames:@[] + accessGroup:nil + operationQueue:queue]]; + compositeRequestModelRepository = [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:requestModelRepository + buttonClickRepository:buttonClickRepository + displayedIAMRepository:displayedRepository + inApp:[MEInApp mock] + requestContext:requestContext + endpoint:mockEndpoint + storage:mockStorage]; +}); + +afterEach(^{ + [MEExperimental reset]; +}); + +describe(@"initWithRequestModelRepository:buttonClickRepository:displayedIAMRepository:inApp:requestContext:deviceInfo:", ^{ + + it(@"should set inApp after init", ^{ + MEInApp *inApp = [MEInApp mock]; + MERequestRepositoryProxy *factory = [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + inApp:inApp + requestContext:[MERequestContext nullMock] + endpoint:mockEndpoint + storage:[EMSStorage mock]]; + [[factory.inApp shouldNot] beNil]; + }); + + it(@"should set requestContext after init", ^{ + MERequestRepositoryProxy *factory = [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + inApp:[MEInApp mock] + requestContext:requestContext + endpoint:mockEndpoint + storage:[EMSStorage mock]]; + [[factory.requestContext should] equal:requestContext]; + }); + + it(@"should throw an exception when there is no inApp", ^{ + @try { + [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + inApp:nil + requestContext:[MERequestContext mock] + endpoint:[EMSEndpoint mock] + storage:[EMSStorage mock]]; + fail(@"Expected Exception when inApp is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: inApp"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw an exception when there is no requestModelRepository", ^{ + @try { + [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:nil + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + inApp:[MEInApp mock] + requestContext:[MERequestContext mock] + endpoint:[EMSEndpoint mock] + storage:[EMSStorage mock]]; + fail(@"Expected Exception when requestModelRepository is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: requestModelRepository"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw an exception when there is no clickRepository", ^{ + @try { + [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] + buttonClickRepository:nil + displayedIAMRepository:[MEDisplayedIAMRepository mock] + inApp:[MEInApp mock] + requestContext:[MERequestContext mock] + endpoint:[EMSEndpoint mock] + storage:[EMSStorage mock]]; + fail(@"Expected Exception when clickRepository is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: buttonClickRepository"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw an exception when there is no displayedIAMRepository", ^{ + @try { + (void) [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:nil + inApp:[MEInApp mock] + requestContext:[MERequestContext mock] + endpoint:[EMSEndpoint mock] + storage:[EMSStorage mock]]; + fail(@"Expected Exception when displayedIAMRepository is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: displayedIAMRepository"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw an exception when there is no requestContext", ^{ + @try { + (void) [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + inApp:[MEInApp mock] + requestContext:nil + endpoint:[EMSEndpoint mock] + storage:[EMSStorage mock]]; + fail(@"Expected Exception when requestContext is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: requestContext"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw an exception when there is no endpoint", ^{ + @try { + (void) [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + inApp:[MEInApp mock] + requestContext:[MERequestContext mock] + endpoint:nil + storage:[EMSStorage mock]]; + fail(@"Expected Exception when endpoint is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: endpoint"]; + [[theValue(exception) shouldNot] beNil]; + } + }); + + it(@"should throw an exception when there is no storage", ^{ + @try { + (void) [[MERequestRepositoryProxy alloc] initWithRequestModelRepository:[EMSRequestModelRepository mock] + buttonClickRepository:[MEButtonClickRepository mock] + displayedIAMRepository:[MEDisplayedIAMRepository mock] + inApp:[MEInApp mock] + requestContext:[MERequestContext mock] + endpoint:[EMSEndpoint mock] + storage:nil]; + fail(@"Expected Exception when storage is nil!"); + } @catch (NSException *exception) { + [[exception.reason should] equal:@"Invalid parameter not satisfying: storage"]; + [[theValue(exception) shouldNot] beNil]; + } + }); +}); + +describe(@"MERequestRepositoryProxy", ^{ + afterEach(^{ + [MEExperimental reset]; + }); + + it(@"should add buttonClicks on the custom event requests", ^{ + NSArray *clicks = @[ + [[MEButtonClick alloc] initWithCampaignId:@"campaignID" + buttonId:@"buttonID" + timestamp:[NSDate date]], + [[MEButtonClick alloc] initWithCampaignId:@"campaignID2" + buttonId:@"buttonID2" + timestamp:[NSDate date]] + ]; + + [[buttonClickRepository should] receive:@selector(query:) andReturn:clicks]; + + EMSRequestModel *modelCustomEvent1 = customEventRequestModel(@"event1", nil, requestContext); + + createFakeRequestRepository(@[modelCustomEvent1], @[modelCustomEvent1], @[modelCustomEvent1], [MEInApp new], requestContext); + + NSArray *result = [compositeRequestModelRepository query:[EMSFilterByNothingSpecification new]]; + [[[result[0] payload][@"clicks"] should] equal:@[ + @{@"campaignId": [clicks[0] campaignId], @"buttonId": [clicks[0] buttonId], @"timestamp": [clicks[0] timestamp].stringValueInUTC}, + @{@"campaignId": [clicks[1] campaignId], @"buttonId": [clicks[1] buttonId], @"timestamp": [clicks[1] timestamp].stringValueInUTC} + ]]; + }); + + it(@"should add viewedMessages on the custom event requests", ^{ + NSArray *viewedMessages = @[ + [[MEDisplayedIAM alloc] initWithCampaignId:@"123" timestamp:[NSDate date]], + [[MEDisplayedIAM alloc] initWithCampaignId:@"42" timestamp:[NSDate date]] + ]; + + [[displayedRepository should] receive:@selector(query:) andReturn:viewedMessages]; + + EMSRequestModel *modelCustomEvent1 = customEventRequestModel(@"event1", nil, requestContext); + + createFakeRequestRepository(@[modelCustomEvent1], @[modelCustomEvent1], @[modelCustomEvent1], [MEInApp new], requestContext); + + NSArray *result = [compositeRequestModelRepository query:[EMSFilterByNothingSpecification new]]; + [[[result[0] payload][@"viewedMessages"] should] equal:@[ + @{@"campaignId": [viewedMessages[0] campaignId], @"timestamp": [viewedMessages[0] timestamp].stringValueInUTC}, + @{@"campaignId": [viewedMessages[1] campaignId], @"timestamp": [viewedMessages[1] timestamp].stringValueInUTC} + ]]; + }); + + it(@"should add the element to the requestModelRepository", ^{ + EMSRequestModel *model = [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { + [builder setUrl:@"https://www.url.com"]; + [builder setMethod:HTTPMethodGET]; + } + timestampProvider:[EMSTimestampProvider new] + uuidProvider:[EMSUUIDProvider new]]; + [[requestModelRepository should] receive:@selector(add:) withArguments:model]; + + [compositeRequestModelRepository add:model]; + }); + + it(@"should remove the element from the requestModelRepository", ^{ + id spec = [KWMock mockForProtocol:@protocol(EMSSQLSpecificationProtocol)]; + + [[requestModelRepository should] receive:@selector(remove:) withArguments:spec]; + [compositeRequestModelRepository remove:spec]; + }); + + it(@"should query normal RequestModels from RequestRepository", ^{ + EMSFilterByNothingSpecification *specification = [EMSFilterByNothingSpecification new]; + + NSArray *const requests = @[[EMSRequestModel nullMock], [EMSRequestModel nullMock], [EMSRequestModel nullMock]]; + [[requestModelRepository should] receive:@selector(query:) + andReturn:requests + withArguments:specification]; + + NSArray *result = [compositeRequestModelRepository query:specification]; + [[result should] equal:requests]; + }); + + it(@"should return empty array if no elements were found", ^{ + EMSFilterByNothingSpecification *specification = [EMSFilterByNothingSpecification new]; + + NSArray *const requests = @[]; + [[requestModelRepository should] receive:@selector(query:) + andReturn:requests + withArguments:specification]; + + NSArray *result = [compositeRequestModelRepository query:specification]; + [[result should] equal:requests]; + }); + + it(@"should query composite RequestModel from RequestRepository when select first", ^{ + EMSRequestModel *modelCustomEvent1 = customEventRequestModel(@"event1", nil, requestContext); + EMSRequestModel *model1 = normalRequestModel(@"https://www.google.com", requestContext); + EMSRequestModel *modelCustomEvent2 = customEventRequestModel(@"event2", @{@"key1": @"value1", @"key2": @"value2"}, requestContext); + EMSRequestModel *model2 = normalRequestModel(@"https://www.google.com", requestContext); + EMSRequestModel *modelCustomEvent3 = customEventRequestModel(@"event3", @{@"star": @"wars"}, requestContext); + + EMSCompositeRequestModel *compositeModel = [EMSCompositeRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { + [builder setUrl:@"https://mobile-events.eservice.emarsys.net/v3/apps/testAppplicationCode/client/events"]; + [builder setMethod:HTTPMethodPOST]; + [builder setPayload:@{ + @"hardware_id": deviceInfo.hardwareId, + @"viewedMessages": @[], + @"clicks": @[], + @"events": @[ + [modelCustomEvent1.payload[@"events"] firstObject], + [modelCustomEvent2.payload[@"events"] firstObject], + [modelCustomEvent3.payload[@"events"] firstObject] + ], + @"language": deviceInfo.languageCode, + @"ems_sdk": EMARSYS_SDK_VERSION, + @"application_version": deviceInfo.applicationVersion + }]; + } + timestampProvider:requestContext.timestampProvider + uuidProvider:requestContext.uuidProvider]; + compositeModel.originalRequests = @[modelCustomEvent1, modelCustomEvent2, modelCustomEvent3]; + + createFakeRequestRepository(@[modelCustomEvent1], @[modelCustomEvent1, modelCustomEvent2, modelCustomEvent3], @[modelCustomEvent1, model1, modelCustomEvent2, model2, modelCustomEvent3], [MEInApp new], requestContext); + + NSArray *result = [compositeRequestModelRepository query:[EMSQueryOldestRowSpecification new]]; + [[theValue([result count]) should] equal:theValue(1)]; + [[[result firstObject] should] beSimilarWithRequest:compositeModel]; + }); + + it(@"should query composite RequestModels from RequestRepository when select all", ^{ + EMSRequestModel *model1 = normalRequestModel(@"https://www.google.com", requestContext); + EMSRequestModel *modelCustomEvent1 = customEventRequestModel(@"event1", nil, requestContext); + EMSRequestModel *modelCustomEvent2 = customEventRequestModel(@"event2", @{@"key1": @"value1", @"key2": @"value2"}, requestContext); + EMSRequestModel *model2 = normalRequestModel(@"https://mobile-events.eservice.emarsys.net/v3/apps/testAppplicationCode/client/events456", requestContext); + EMSRequestModel *modelCustomEvent3 = customEventRequestModel(@"event3", @{@"star": @"wars"}, requestContext); + + EMSCompositeRequestModel *compositeModel = [EMSCompositeRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { + [builder setUrl:@"https://mobile-events.eservice.emarsys.net/v3/apps/testAppplicationCode/client/events"]; + [builder setMethod:HTTPMethodPOST]; + [builder setPayload:@{ + @"hardware_id": deviceInfo.hardwareId, + @"viewedMessages": @[], + @"clicks": @[], + @"events": @[ + [modelCustomEvent1.payload[@"events"] firstObject], + [modelCustomEvent2.payload[@"events"] firstObject], + [modelCustomEvent3.payload[@"events"] firstObject] + ], + @"language": deviceInfo.languageCode, + @"ems_sdk": EMARSYS_SDK_VERSION, + @"application_version": deviceInfo.applicationVersion + }]; + } + timestampProvider:requestContext.timestampProvider + uuidProvider:requestContext.uuidProvider]; + compositeModel.originalRequests = @[modelCustomEvent1, modelCustomEvent2, modelCustomEvent3]; + + + createFakeRequestRepository(@[modelCustomEvent1], @[modelCustomEvent1, modelCustomEvent2, modelCustomEvent3], @[model1, modelCustomEvent1, modelCustomEvent2, model2, modelCustomEvent3], [MEInApp new], requestContext); + + NSArray *result = [compositeRequestModelRepository query:[EMSFilterByNothingSpecification new]]; + [[theValue([result count]) should] equal:theValue(3)]; + [[result[0] should] beSimilarWithRequest:model1]; + [[result[1] should] beSimilarWithRequest:compositeModel]; + [[result[2] should] beSimilarWithRequest:model2]; + }); + + it(@"should query composite RequestModels from RequestRepository when select all and should not crash when events is empty", ^{ + EMSRequestModel *model1 = normalRequestModel(@"https://www.google.com", requestContext); + EMSRequestModel *modelCustomEvent1 = [EMSRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { + [builder setUrl:@"https://mobile-events.eservice.emarsys.net/v3/apps/testAppplicationCode/client/events"]; + [builder setMethod:HTTPMethodPOST]; + [builder setPayload:@{@"events": @[]}]; + } + timestampProvider:timestampProvider + uuidProvider:uuidProvider]; + EMSRequestModel *modelCustomEvent2 = customEventRequestModel(@"event2", @{@"key1": @"value1", @"key2": @"value2"}, requestContext); + EMSRequestModel *model2 = normalRequestModel(@"https://mobile-events.eservice.emarsys.net/v3/apps/testAppplicationCode/client/events456", requestContext); + EMSRequestModel *modelCustomEvent3 = customEventRequestModel(@"event3", @{@"star": @"wars"}, requestContext); + + EMSCompositeRequestModel *compositeModel = [EMSCompositeRequestModel makeWithBuilder:^(EMSRequestModelBuilder *builder) { + [builder setUrl:@"https://mobile-events.eservice.emarsys.net/v3/apps/testAppplicationCode/client/events"]; + [builder setMethod:HTTPMethodPOST]; + [builder setPayload:@{ + @"hardware_id": deviceInfo.hardwareId, + @"viewedMessages": @[], + @"clicks": @[], + @"events": @[ + [modelCustomEvent2.payload[@"events"] firstObject], + [modelCustomEvent3.payload[@"events"] firstObject] + ], + @"language": deviceInfo.languageCode, + @"ems_sdk": EMARSYS_SDK_VERSION, + @"application_version": deviceInfo.applicationVersion + }]; + } + timestampProvider:requestContext.timestampProvider + uuidProvider:requestContext.uuidProvider]; + compositeModel.originalRequests = @[modelCustomEvent1, modelCustomEvent2, modelCustomEvent3]; + + + createFakeRequestRepository(@[modelCustomEvent1], @[modelCustomEvent1, modelCustomEvent2, modelCustomEvent3], @[model1, modelCustomEvent1, modelCustomEvent2, model2, modelCustomEvent3], [MEInApp new], requestContext); + + NSArray *result = [compositeRequestModelRepository query:[EMSFilterByNothingSpecification new]]; + [[theValue([result count]) should] equal:theValue(3)]; + [[result[0] should] beSimilarWithRequest:model1]; + [[result[1] should] beSimilarWithRequest:compositeModel]; + [[result[2] should] beSimilarWithRequest:model2]; + }); + + it(@"should return NO if request requestModelRepository is NOT empty", ^{ + [[requestModelRepository should] receive:@selector(isEmpty) andReturn:theValue(NO)]; + [[theValue([compositeRequestModelRepository isEmpty]) should] beNo]; + }); + + it(@"should return YES if request requestModelRepository is empty", ^{ + [[requestModelRepository should] receive:@selector(isEmpty) andReturn:theValue(YES)]; + [[theValue([compositeRequestModelRepository isEmpty]) should] beYes]; + }); + + it(@"should add dnd on the custom event requests with 'YES' value when inApp is paused", ^{ + EMSRequestModel *modelCustomEvent1 = customEventRequestModel(@"event1", nil, requestContext); + + MEInApp *meInApp = [MEInApp new]; + [meInApp pause]; + + createFakeRequestRepository(@[modelCustomEvent1], @[modelCustomEvent1], @[modelCustomEvent1], meInApp, requestContext); + NSArray *result = [compositeRequestModelRepository query:[EMSFilterByNothingSpecification new]]; + [[[result[0] payload][@"dnd"] should] equal:@(YES)]; + }); + + it(@"should not add dnd on the custom event requests when inApp is resumed", ^{ + EMSRequestModel *modelCustomEvent1 = customEventRequestModel(@"event1", nil, requestContext); + + MEInApp *meInApp = [MEInApp new]; + + createFakeRequestRepository(@[modelCustomEvent1], @[modelCustomEvent1], @[modelCustomEvent1], meInApp, requestContext); + NSArray *result = [compositeRequestModelRepository query:[EMSFilterByNothingSpecification new]]; + [[theValue([[[result[0] payload] allKeys] containsObject:@"dnd"]) should] beNo]; + }); + +}); SPEC_END diff --git a/Tests/MobileEngageTests/Storage/RequestModelSpecificationTests.m b/Tests/MobileEngageTests/Storage/RequestModelSpecificationTests.m index ad5e0a3c..6f978606 100644 --- a/Tests/MobileEngageTests/Storage/RequestModelSpecificationTests.m +++ b/Tests/MobileEngageTests/Storage/RequestModelSpecificationTests.m @@ -13,6 +13,7 @@ #import "EMSSchemaContract.h" #import "EMSSQLiteHelper.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" #define TEST_DB_PATH [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"TestMEDB.db"] @@ -27,7 +28,7 @@ }); afterEach(^{ - [self tearDownOperationQueue:queue]; + [EmarsysTestUtils tearDownOperationQueue:queue]; }); id (^customEventRequestModel)(NSString *eventName, NSDictionary *eventAttributes) = ^id(NSString *eventName, NSDictionary *eventAttributes) { @@ -61,17 +62,18 @@ describe(@"MERequestModelSelectEventsSpecification", ^{ - beforeEach(^{ - [[NSFileManager defaultManager] removeItemAtPath:TEST_DB_PATH error:nil]; + beforeAll(^{ + NSOperationQueue *dbQueue = [self createTestOperationQueue]; _dbHelper = [[EMSSQLiteHelper alloc] initWithDatabasePath:TEST_DB_PATH - schemaDelegate:[EMSSqliteSchemaHandler new]]; + schemaDelegate:[EMSSqliteSchemaHandler new] + operationQueue:dbQueue]; [_dbHelper open]; _repository = [[EMSRequestModelRepository alloc] initWithDbHelper:_dbHelper operationQueue:queue]; }); afterEach(^{ - [_dbHelper close]; + [EmarsysTestUtils clearDb:_dbHelper]; }); it(@"should return all custom events", ^{ diff --git a/Tests/PredictTests/EMSPredictInternalTests.m b/Tests/PredictTests/EMSPredictInternalTests.m index 8caee067..19dbd647 100644 --- a/Tests/PredictTests/EMSPredictInternalTests.m +++ b/Tests/PredictTests/EMSPredictInternalTests.m @@ -21,6 +21,7 @@ #import "EMSLogic.h" #import "EMSRecommendationFilter.h" #import "XCTestCase+Helper.h" +#import "EmarsysTestUtils.h" SPEC_BEGIN(EMSPredictInternalTests) @@ -31,7 +32,7 @@ }); afterEach(^{ - [self tearDownOperationQueue:queue]; + [EmarsysTestUtils tearDownOperationQueue:queue]; }); describe(@"init", ^{ diff --git a/Tests/PredictTests/PredictIntegrationTests.m b/Tests/PredictTests/PredictIntegrationTests.m index 7a458fe8..4ddbc377 100644 --- a/Tests/PredictTests/PredictIntegrationTests.m +++ b/Tests/PredictTests/PredictIntegrationTests.m @@ -40,10 +40,11 @@ - (instancetype)initWithConfig:(EMSConfig *)config } - (void (^)(NSString *, EMSResponseModel *))createSuccessBlock { + __weak typeof(self) weakSelf = self; return ^(NSString *requestId, EMSResponseModel *response) { [super createSuccessBlock](requestId, response); - _lastResponseModel = response; - XCTestExpectation *expectation = [self popExpectation]; + weakSelf.lastResponseModel = response; + XCTestExpectation *expectation = [weakSelf popExpectation]; [expectation fulfill]; }; } @@ -62,32 +63,19 @@ - (XCTestExpectation *)popExpectation { __block PredictIntegrationDependencyContainer *dependencyContainer; beforeEach(^{ - [EmarsysTestUtils tearDownEmarsys]; - - NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.emarsys.predict"]; - [userDefaults removeObjectForKey:@"contactFieldValue"]; - [userDefaults removeObjectForKey:@"visitorId"]; - [userDefaults synchronize]; - EMSConfig *config = [EMSConfig makeWithBuilder:^(EMSConfigBuilder *builder) { [builder setMerchantId:@"1428C8EE286EC34B"]; }]; - [MEExperimental enableFeature:EMSInnerFeature.predict]; expectations = @[ [[XCTestExpectation alloc] initWithDescription:@"waitForExpectation"]]; dependencyContainer = [[PredictIntegrationDependencyContainer alloc] initWithConfig:config expectations:expectations]; - [EMSDependencyInjection setupWithDependencyContainer:dependencyContainer]; - - XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"waitForSetup"]; - [EMSDependencyInjection.dependencyContainer.publicApiOperationQueue addOperationWithBlock:^{ - [expectation fulfill]; - }]; - XCTWaiterResult waiterResult = [XCTWaiter waitForExpectations:@[expectation] - timeout:10]; + [EmarsysTestUtils setupEmarsysWithConfig:config + dependencyContainer:dependencyContainer]; - [Emarsys setupWithConfig:config]; + [self waitATickOnOperationQueue:dependencyContainer.publicApiOperationQueue]; + [self waitATickOnOperationQueue:dependencyContainer.coreOperationQueue]; }); afterEach(^{ @@ -160,9 +148,9 @@ - (XCTestExpectation *)popExpectation { describe(@"trackItemViewWithItemId:", ^{ it(@"should send request with item id", ^{ - NSString *expectedQueryParams = @"v=i%3A2508"; + NSString *expectedQueryParams = @"v=i%3A2508%252B"; - [Emarsys.predict trackItemViewWithItemId:@"2508"]; + [Emarsys.predict trackItemViewWithItemId:@"2508+"]; [EMSWaiter waitForExpectations:expectations timeout:10]; diff --git a/Tests/XCTestCase+Helper.h b/Tests/XCTestCase+Helper.h index 1c1df08a..29ea5c20 100644 --- a/Tests/XCTestCase+Helper.h +++ b/Tests/XCTestCase+Helper.h @@ -12,6 +12,7 @@ retryCount:(NSInteger)retryCount; - (NSOperationQueue *)createTestOperationQueue; -- (void)tearDownOperationQueue:(NSOperationQueue *)operationQueue; + +- (void)waitATickOnOperationQueue:(NSOperationQueue *)operationQueue; @end diff --git a/Tests/XCTestCase+Helper.m b/Tests/XCTestCase+Helper.m index eab729a2..d65edd42 100644 --- a/Tests/XCTestCase+Helper.m +++ b/Tests/XCTestCase+Helper.m @@ -38,16 +38,21 @@ - (void)retryWithRunnerBlock:(void (^)(XCTestExpectation *expectation))runnerBlo } - (NSOperationQueue *)createTestOperationQueue { + NSString *queueName = [NSString stringWithFormat:@"EMSTestOperationQueue - %@", [NSUUID UUID].UUIDString]; NSOperationQueue *operationQueue = [NSOperationQueue new]; - [operationQueue setName:@"testOperationQueue"]; + [operationQueue setName:queueName]; [operationQueue setMaxConcurrentOperationCount:1]; return operationQueue; } -- (void)tearDownOperationQueue:(NSOperationQueue *)operationQueue { - [operationQueue cancelAllOperations]; - [operationQueue waitUntilAllOperationsAreFinished]; - operationQueue = nil; +- (void)waitATickOnOperationQueue:(NSOperationQueue *)operationQueue { + XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"waitForOperationBlock"]; + [operationQueue addOperationWithBlock:^{ + [expectation fulfill]; + }]; + XCTWaiterResult waiterResult = [XCTWaiter waitForExpectations:@[expectation] + timeout:2.0]; + XCTAssertEqual(waiterResult, XCTWaiterResultCompleted); } @end