diff --git a/Example/MindboxNotificationServiceExtension/NotificationService.swift b/Example/MindboxNotificationServiceExtension/NotificationService.swift index a621b584..78f25e0d 100644 --- a/Example/MindboxNotificationServiceExtension/NotificationService.swift +++ b/Example/MindboxNotificationServiceExtension/NotificationService.swift @@ -15,6 +15,7 @@ class NotificationService: UNNotificationServiceExtension { lazy var mindboxService = MindboxNotificationService() override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + if let mindboxPushNotification = Mindbox.shared.getMindboxPushData(userInfo: request.content.userInfo) { Task { await saveSwiftDataItem(mindboxPushNotification) diff --git a/Example/Podfile b/Example/Podfile index f4ce6688..56f4389a 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -1,13 +1,13 @@ use_frameworks! target 'Example' do - pod 'Mindbox', '2.10.3-rc' + pod 'Mindbox', '2.11.0' end target 'MindboxNotificationServiceExtension' do - pod 'MindboxNotifications', '2.10.3-rc' + pod 'MindboxNotifications', '2.11.0' end target 'MindboxNotificationContentExtension' do - pod 'MindboxNotifications', '2.10.3-rc' + pod 'MindboxNotifications', '2.11.0' end diff --git a/Gemfile.lock b/Gemfile.lock index e5b0213d..8c944d1c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -23,20 +23,20 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.949.0) - aws-sdk-core (3.200.0) + aws-partitions (1.960.0) + aws-sdk-core (3.201.3) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.87.0) - aws-sdk-core (~> 3, >= 3.199.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.155.0) - aws-sdk-core (~> 3, >= 3.199.0) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.156.0) + aws-sdk-core (~> 3, >= 3.201.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.8) - aws-sigv4 (1.8.0) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.9.1) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) base64 (0.2.0) @@ -95,7 +95,7 @@ GEM escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - excon (0.110.0) + excon (0.111.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -117,7 +117,7 @@ GEM faraday-httpclient (1.0.1) faraday-multipart (1.0.4) multipart-post (~> 2) - faraday-net_http (1.0.1) + faraday-net_http (1.0.2) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) @@ -125,7 +125,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.3.1) - fastlane (2.221.1) + fastlane (2.222.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -218,7 +218,7 @@ GEM json (2.7.2) jwt (2.8.2) base64 - mini_magick (4.13.1) + mini_magick (4.13.2) mini_mime (1.1.5) minitest (5.24.1) molinillo (0.8.0) @@ -240,7 +240,7 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.9) + rexml (3.3.6) strscan rouge (2.0.7) ruby-macho (2.5.1) @@ -271,13 +271,13 @@ GEM uber (0.1.0) unicode-display_width (2.5.0) word_wrap (1.0.0) - xcodeproj (1.24.0) + xcodeproj (1.25.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (~> 3.2.4) + rexml (>= 3.3.2, < 4.0) xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) diff --git a/Mindbox.podspec b/Mindbox.podspec index e57d9c5a..ecbe5f25 100644 --- a/Mindbox.podspec +++ b/Mindbox.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Mindbox" - spec.version = "2.10.3-rc" + spec.version = "2.11.0" spec.summary = "SDK for integration with Mindbox" spec.description = "This library allows you to integrate data transfer to Mindbox Marketing Cloud" spec.homepage = "https://github.com/mindbox-cloud/ios-sdk" spec.license = { :type => "CC BY-NC-ND 4.0", :file => "LICENSE.md" } spec.author = { "Mindbox" => "ios-sdk@mindbox.ru" } - spec.platform = :ios, "10.0" + spec.platform = :ios, "12.0" spec.source = { :git => "https://github.com/mindbox-cloud/ios-sdk.git", :tag => spec.version } spec.source_files = "Mindbox/**/*.{swift}", "SDKVersionProvider/**/*.{swift}" spec.exclude_files = "Classes/Exclude" @@ -14,6 +14,6 @@ Pod::Spec.new do |spec| 'Mindbox' => ['Mindbox/**/*.xcassets', 'Mindbox/**/*.xcdatamodeld', 'Mindbox/**/*.xcprivacy'] } spec.swift_version = "5" - spec.dependency 'MindboxLogger', '2.10.3-rc' + spec.dependency 'MindboxLogger', '2.11.0' end diff --git a/Mindbox.xcodeproj/project.pbxproj b/Mindbox.xcodeproj/project.pbxproj index 9f6f6aa8..00a80393 100644 --- a/Mindbox.xcodeproj/project.pbxproj +++ b/Mindbox.xcodeproj/project.pbxproj @@ -8,13 +8,11 @@ /* Begin PBXBuildFile section */ 0A3D045A2BC6803E00E1FC52 /* ImageFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3D04592BC6803E00E1FC52 /* ImageFormat.swift */; }; - 3132DFF625C2A811007FE358 /* TestDependencyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3132DFF525C2A811007FE358 /* TestDependencyProvider.swift */; }; 313B233A25ADEA0F00A1CB72 /* Mindbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313B233025ADEA0F00A1CB72 /* Mindbox.framework */; }; 313B233F25ADEA0F00A1CB72 /* MindboxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313B233E25ADEA0F00A1CB72 /* MindboxTests.swift */; }; 313B234125ADEA0F00A1CB72 /* Mindbox.h in Headers */ = {isa = PBXBuildFile; fileRef = 313B233325ADEA0F00A1CB72 /* Mindbox.h */; settings = {ATTRIBUTES = (Public, ); }; }; 314B38FD25AEE8B200E947B9 /* MBConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B38FC25AEE8B200E947B9 /* MBConfiguration.swift */; }; 314B390025AEE96F00E947B9 /* CoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B38FF25AEE96F00E947B9 /* CoreController.swift */; }; - 317054C425AEF88E00AE624C /* DependencyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317054C325AEF88E00AE624C /* DependencyProvider.swift */; }; 317054CB25AF189800AE624C /* PersistenceStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317054CA25AF189800AE624C /* PersistenceStorage.swift */; }; 317AF8FC25B844DB006348FA /* UtilitiesFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317AF8FB25B844DB006348FA /* UtilitiesFetcher.swift */; }; 317F1FD525B879B200B54346 /* MBUtilitiesFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F1FD425B879B200B54346 /* MBUtilitiesFetcher.swift */; }; @@ -47,30 +45,12 @@ 330D8CCD26579581005106D5 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 330D8CCC26579581005106D5 /* Channel.swift */; }; 3328FE4226303F2F000A30D0 /* String+Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3328FE4126303F2F000A30D0 /* String+Regex.swift */; }; 3333C1A12681D3CF00B60D84 /* MindboxNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3333C1982681D3CF00B60D84 /* MindboxNotifications.framework */; }; - 3333C1A62681D3CF00B60D84 /* MindboxNotificationsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1A52681D3CF00B60D84 /* MindboxNotificationsTests.swift */; }; 3333C1A82681D3CF00B60D84 /* MindboxNotifications.h in Headers */ = {isa = PBXBuildFile; fileRef = 3333C19A2681D3CF00B60D84 /* MindboxNotifications.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3333C1AF2681D3DB00B60D84 /* MindboxNotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 337EDF332636DA3F0089559F /* MindboxNotificationService.swift */; }; 3333C1B22681D42000B60D84 /* Payload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1B12681D42000B60D84 /* Payload.swift */; }; 3333C1B42681D43C00B60D84 /* ImageFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1B32681D43C00B60D84 /* ImageFormat.swift */; }; - 3333C1B62681D69700B60D84 /* MBConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1B52681D69700B60D84 /* MBConfiguration.swift */; }; - 3333C1B82681DC0500B60D84 /* DeliveryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1B72681DC0500B60D84 /* DeliveryService.swift */; }; - 3333C1BA2681DC4B00B60D84 /* MBUtilitiesFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1B92681DC4B00B60D84 /* MBUtilitiesFetcher.swift */; }; - 3333C1BC2681DE0900B60D84 /* DeviceModelHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1BB2681DE0900B60D84 /* DeviceModelHelper.swift */; }; - 3333C1BD2681DE6600B60D84 /* PushDelivered.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842DE32B25E01B9A002BE5C6 /* PushDelivered.swift */; }; - 3333C1BF2681E46400B60D84 /* NotificationDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8400428B2614C4D100CA17C5 /* NotificationDecoder.swift */; }; - 3333C1C02681E4E400B60D84 /* NotificationsPayloads.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840042902614C65900CA17C5 /* NotificationsPayloads.swift */; }; - 3333C1C22681E59900B60D84 /* DeliveryOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1C12681E59900B60D84 /* DeliveryOperation.swift */; }; - 3333C1C42681E64F00B60D84 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1C32681E64F00B60D84 /* NetworkService.swift */; }; - 3333C1CA2681E74A00B60D84 /* Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1C92681E74A00B60D84 /* Route.swift */; }; - 3333C1CC2681E76000B60D84 /* EventWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1CB2681E76000B60D84 /* EventWrapper.swift */; }; - 3333C1D02681E83200B60D84 /* BodyDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1CE2681E83200B60D84 /* BodyDecoder.swift */; }; - 3333C1D12681E83200B60D84 /* BodyEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1CF2681E83200B60D84 /* BodyEncoder.swift */; }; - 3333C1D62681E85600B60D84 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1D32681E85600B60D84 /* HTTPMethod.swift */; }; - 3333C1D82681E85600B60D84 /* HTTPTypealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1D52681E85600B60D84 /* HTTPTypealiases.swift */; }; 3333C1DE2681E9F300B60D84 /* URLRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1DD2681E9F300B60D84 /* URLRequestBuilder.swift */; }; 3333C1E12681EA4D00B60D84 /* NotificationsPayloads.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1E02681EA4C00B60D84 /* NotificationsPayloads.swift */; }; - 3333C1E32681EEA800B60D84 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1E22681EEA800B60D84 /* Event.swift */; }; - 3333C1E52681EEEF00B60D84 /* URLRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333C1E42681EEEF00B60D84 /* URLRequestBuilder.swift */; }; 3333D7BE265E56F2004279B0 /* OperationResponseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3333D7BD265E56F2004279B0 /* OperationResponseType.swift */; }; 3337E6A3265FAB39006949EB /* BaseResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3337E6A2265FAB39006949EB /* BaseResponse.swift */; }; 3337E6A52660D878006949EB /* OperationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3337E6A42660D878006949EB /* OperationResponse.swift */; }; @@ -111,11 +91,30 @@ 33B1C976265B910F00E293F8 /* SwiftyJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33B1C975265B910F00E293F8 /* SwiftyJSON.swift */; }; 33BEE80D2681EB7700993720 /* NotificationDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33BEE80C2681EB7700993720 /* NotificationDecoder.swift */; }; 33BEE8172681EC5F00993720 /* EventRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33BEE8162681EC5F00993720 /* EventRoute.swift */; }; - 33BEE81D2681ED2900993720 /* PushDeliveredEventRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33BEE81C2681ED2900993720 /* PushDeliveredEventRoute.swift */; }; 33C81EA4264145CD00863380 /* TimerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33C81EA3264145CD00863380 /* TimerManager.swift */; }; 33E42E5C268323E60046CBCB /* CashdeskRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33E42E5B268323E60046CBCB /* CashdeskRequest.swift */; }; 33EBF0B0264E6283002A35D5 /* MBSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33EBF0AF264E6283002A35D5 /* MBSessionManager.swift */; }; + 472F549E2C6E272A0008C465 /* MBPushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472F549D2C6E272A0008C465 /* MBPushNotification.swift */; }; + 472F54A32C6E27C80008C465 /* NotificationFormatStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472F54A22C6E27C80008C465 /* NotificationFormatStrategy.swift */; }; + 472F54A52C6E27DD0008C465 /* NotificationStrategyFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472F54A42C6E27DD0008C465 /* NotificationStrategyFactory.swift */; }; + 472F54A72C6E28030008C465 /* NotificationFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472F54A62C6E28030008C465 /* NotificationFormatter.swift */; }; + 472F54AA2C6E294F0008C465 /* PushValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472F54A92C6E294F0008C465 /* PushValidator.swift */; }; + 472F54AC2C6E29E50008C465 /* MindboxPushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472F54AB2C6E29E50008C465 /* MindboxPushNotification.swift */; }; + 4747708B2C6B838B00C36FC8 /* SharedInternalMethodsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4747708A2C6B838B00C36FC8 /* SharedInternalMethodsTests.swift */; }; + 4747708F2C6B93AC00C36FC8 /* MindboxNotificationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4747708E2C6B93AC00C36FC8 /* MindboxNotificationServiceTests.swift */; }; + 474770912C6B9A7200C36FC8 /* MindboxNotificationContentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474770902C6B9A7200C36FC8 /* MindboxNotificationContentTests.swift */; }; + 474851C12C6A622E0026C38E /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474851C02C6A622E0026C38E /* NotificationService.swift */; }; + 474851C32C6A627F0026C38E /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474851C22C6A627F0026C38E /* Constants.swift */; }; + 474851C52C6A62AE0026C38E /* NotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474851C42C6A62AE0026C38E /* NotificationContent.swift */; }; + 474851C72C6A63C30026C38E /* SharedInternalMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474851C62C6A63C30026C38E /* SharedInternalMethods.swift */; }; + 475558C32C59300400CDA026 /* MigrationManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 475558C22C59300400CDA026 /* MigrationManagerTests.swift */; }; + 47B90E2F2C625F9A00BD93E7 /* TestBaseMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B90E2E2C625F9A00BD93E7 /* TestBaseMigrations.swift */; }; + 47B90E312C626B9300BD93E7 /* TestProtocolMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B90E302C626B9300BD93E7 /* TestProtocolMigrations.swift */; }; + 47BD5BFB2C578BC600F965C0 /* MigrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47BD5BFA2C578BC600F965C0 /* MigrationManager.swift */; }; + 47BD5BFE2C578FB400F965C0 /* BaseMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47BD5BFD2C578FB400F965C0 /* BaseMigration.swift */; }; 47D63E2B2C2EABDF0055E7D8 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = F3EC93BA2AF105DA0030D107 /* PrivacyInfo.xcprivacy */; }; + 47FDF0BA2C5BDAB80051F08C /* MigrationManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FDF0B92C5BDAB80051F08C /* MigrationManagerProtocol.swift */; }; + 47FDF0BC2C5BE8BB0051F08C /* MigrationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FDF0BB2C5BE8BB0051F08C /* MigrationProtocol.swift */; }; 6F1EAA16266A670E007A335B /* ProductListItemsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F1EAA15266A670E007A335B /* ProductListItemsResponse.swift */; }; 6FDD143B266F7BD900A50C35 /* ProcessingStatusResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDD143A266F7BD900A50C35 /* ProcessingStatusResponse.swift */; }; 6FDD143D266F7BEB00A50C35 /* ItemResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDD143C266F7BEB00A50C35 /* ItemResponse.swift */; }; @@ -149,7 +148,6 @@ 840C38B325D133B000D50183 /* GuaranteedDeliveryTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840C38B225D133B000D50183 /* GuaranteedDeliveryTestCase.swift */; }; 840C38B825D13A7D00D50183 /* EventGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840C38B725D13A7D00D50183 /* EventGenerator.swift */; }; 8410681325ECDC73004701C2 /* DatabaseLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8410681225ECDC73004701C2 /* DatabaseLoader.swift */; }; - 843DAA5C26087F3D00CAC489 /* DependencyContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843DAA5B26087F3D00CAC489 /* DependencyContainer.swift */; }; 847F57FE25C88BB700147A9A /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847F57FD25C88BB700147A9A /* HTTPMethod.swift */; }; 847F580325C88BBF00147A9A /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847F57FD25C88BB700147A9A /* HTTPMethod.swift */; }; 847F580725C88C7A00147A9A /* NetworkFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847F580625C88C7A00147A9A /* NetworkFetcher.swift */; }; @@ -335,6 +333,11 @@ F31A94802BC7E61800E6C978 /* InappFrequencyValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31A947F2BC7E61800E6C978 /* InappFrequencyValidator.swift */; }; F32CFFA42BB403C700A41E04 /* PushEnabledTargeting.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32CFFA32BB403C700A41E04 /* PushEnabledTargeting.swift */; }; F32CFFA62BB4044E00A41E04 /* PushEnabledTargetingChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32CFFA52BB4044E00A41E04 /* PushEnabledTargetingChecker.swift */; }; + F32E536F2C3F2B05002C7CA0 /* DITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32E536E2C3F2B05002C7CA0 /* DITests.swift */; }; + F32E53792C3FD64A002C7CA0 /* DIMainModuleRegistrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32E53782C3FD64A002C7CA0 /* DIMainModuleRegistrationTests.swift */; }; + F32E537B2C3FDA30002C7CA0 /* DIMainModuleReplaceableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32E537A2C3FDA30002C7CA0 /* DIMainModuleReplaceableTests.swift */; }; + F32E537D2C3FDB6D002C7CA0 /* DITestModuleReplaceableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32E537C2C3FDB6D002C7CA0 /* DITestModuleReplaceableTests.swift */; }; + F33087662C37590600F8DF10 /* InjectInappTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = F33087652C37590600F8DF10 /* InjectInappTools.swift */; }; F331DC842A80983000222120 /* FailableDecodableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331DC832A80983000222120 /* FailableDecodableArray.swift */; }; F331DCBF2A80993600222120 /* ContentElementSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331DC982A80993600222120 /* ContentElementSize.swift */; }; F331DCC02A80993600222120 /* ContentElementSizeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F331DC9A2A80993600222120 /* ContentElementSizeType.swift */; }; @@ -490,6 +493,15 @@ F3FAD8742AB8678900D98C03 /* negativeCloseButtonSizeValues.json in Resources */ = {isa = PBXBuildFile; fileRef = F3FAD8732AB8678900D98C03 /* negativeCloseButtonSizeValues.json */; }; F3FAD8762AB867E700D98C03 /* closeButtonMarginAboveOne.json in Resources */ = {isa = PBXBuildFile; fileRef = F3FAD8752AB867E700D98C03 /* closeButtonMarginAboveOne.json */; }; F3FAD8782AB8685D00D98C03 /* closeButtonMarginBelowZero.json in Resources */ = {isa = PBXBuildFile; fileRef = F3FAD8772AB8685D00D98C03 /* closeButtonMarginBelowZero.json */; }; + F3FEEA9B2C25AC68000E9D0F /* MBContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3FEEA9A2C25AC68000E9D0F /* MBContainer.swift */; }; + F3FEEA9D2C25ACFC000E9D0F /* MBInject.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3FEEA9C2C25ACFC000E9D0F /* MBInject.swift */; }; + F3FEEA9F2C25AF39000E9D0F /* StubContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3FEEA9E2C25AF39000E9D0F /* StubContainer.swift */; }; + F3FEEAA22C25B55C000E9D0F /* InjectCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3FEEAA12C25B55C000E9D0F /* InjectCore.swift */; }; + F3FEEAA42C25B765000E9D0F /* InjectUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3FEEAA32C25B765000E9D0F /* InjectUtilities.swift */; }; + F3FEEAA62C25CB2E000E9D0F /* XCTestCase+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3FEEAA52C25CB2E000E9D0F /* XCTestCase+Extensions.swift */; }; + F3FEEAA92C25CC9F000E9D0F /* InjectionMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3FEEAA82C25CC9F000E9D0F /* InjectionMocks.swift */; }; + F3FEEAAB2C25D874000E9D0F /* InjectReplaceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3FEEAAA2C25D874000E9D0F /* InjectReplaceable.swift */; }; + F3FEEAAD2C25FD1F000E9D0F /* InjectABTestUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3FEEAAC2C25FD1F000E9D0F /* InjectABTestUtilities.swift */; }; F78E92EF282E63320003B4A3 /* DispatchSemaphore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E92EE282E63320003B4A3 /* DispatchSemaphore.swift */; }; /* End PBXBuildFile section */ @@ -533,7 +545,6 @@ /* Begin PBXFileReference section */ 0A3D04592BC6803E00E1FC52 /* ImageFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFormat.swift; sourceTree = ""; }; - 3132DFF525C2A811007FE358 /* TestDependencyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDependencyProvider.swift; sourceTree = ""; }; 313B233025ADEA0F00A1CB72 /* Mindbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Mindbox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 313B233325ADEA0F00A1CB72 /* Mindbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Mindbox.h; sourceTree = ""; }; 313B233425ADEA0F00A1CB72 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -542,7 +553,6 @@ 313B234025ADEA0F00A1CB72 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 314B38FC25AEE8B200E947B9 /* MBConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MBConfiguration.swift; sourceTree = ""; }; 314B38FF25AEE96F00E947B9 /* CoreController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreController.swift; sourceTree = ""; }; - 317054C325AEF88E00AE624C /* DependencyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyProvider.swift; sourceTree = ""; }; 317054CA25AF189800AE624C /* PersistenceStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceStorage.swift; sourceTree = ""; }; 317AF8FB25B844DB006348FA /* UtilitiesFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilitiesFetcher.swift; sourceTree = ""; }; 317F1FD425B879B200B54346 /* MBUtilitiesFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MBUtilitiesFetcher.swift; sourceTree = ""; }; @@ -578,26 +588,11 @@ 3333C19A2681D3CF00B60D84 /* MindboxNotifications.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MindboxNotifications.h; sourceTree = ""; }; 3333C19B2681D3CF00B60D84 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3333C1A02681D3CF00B60D84 /* MindboxNotificationsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MindboxNotificationsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 3333C1A52681D3CF00B60D84 /* MindboxNotificationsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MindboxNotificationsTests.swift; sourceTree = ""; }; 3333C1A72681D3CF00B60D84 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3333C1B12681D42000B60D84 /* Payload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Payload.swift; sourceTree = ""; }; 3333C1B32681D43C00B60D84 /* ImageFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFormat.swift; sourceTree = ""; }; - 3333C1B52681D69700B60D84 /* MBConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MBConfiguration.swift; sourceTree = ""; }; - 3333C1B72681DC0500B60D84 /* DeliveryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeliveryService.swift; sourceTree = ""; }; - 3333C1B92681DC4B00B60D84 /* MBUtilitiesFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MBUtilitiesFetcher.swift; sourceTree = ""; }; - 3333C1BB2681DE0900B60D84 /* DeviceModelHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceModelHelper.swift; sourceTree = ""; }; - 3333C1C12681E59900B60D84 /* DeliveryOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeliveryOperation.swift; sourceTree = ""; }; - 3333C1C32681E64F00B60D84 /* NetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = ""; }; - 3333C1C92681E74A00B60D84 /* Route.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Route.swift; sourceTree = ""; }; - 3333C1CB2681E76000B60D84 /* EventWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventWrapper.swift; sourceTree = ""; }; - 3333C1CE2681E83200B60D84 /* BodyDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyDecoder.swift; sourceTree = ""; }; - 3333C1CF2681E83200B60D84 /* BodyEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyEncoder.swift; sourceTree = ""; }; - 3333C1D32681E85600B60D84 /* HTTPMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; - 3333C1D52681E85600B60D84 /* HTTPTypealiases.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPTypealiases.swift; sourceTree = ""; }; 3333C1DD2681E9F300B60D84 /* URLRequestBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLRequestBuilder.swift; sourceTree = ""; }; 3333C1E02681EA4C00B60D84 /* NotificationsPayloads.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationsPayloads.swift; sourceTree = ""; }; - 3333C1E22681EEA800B60D84 /* Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = ""; }; - 3333C1E42681EEEF00B60D84 /* URLRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestBuilder.swift; sourceTree = ""; }; 3333D7BD265E56F2004279B0 /* OperationResponseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationResponseType.swift; sourceTree = ""; }; 3337E6A2265FAB39006949EB /* BaseResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseResponse.swift; sourceTree = ""; }; 3337E6A42660D878006949EB /* OperationResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationResponse.swift; sourceTree = ""; }; @@ -639,10 +634,29 @@ 33B1C975265B910F00E293F8 /* SwiftyJSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyJSON.swift; sourceTree = ""; }; 33BEE80C2681EB7700993720 /* NotificationDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationDecoder.swift; sourceTree = ""; }; 33BEE8162681EC5F00993720 /* EventRoute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventRoute.swift; sourceTree = ""; }; - 33BEE81C2681ED2900993720 /* PushDeliveredEventRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushDeliveredEventRoute.swift; sourceTree = ""; }; 33C81EA3264145CD00863380 /* TimerManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimerManager.swift; sourceTree = ""; }; 33E42E5B268323E60046CBCB /* CashdeskRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CashdeskRequest.swift; sourceTree = ""; }; 33EBF0AF264E6283002A35D5 /* MBSessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MBSessionManager.swift; sourceTree = ""; }; + 472F549D2C6E272A0008C465 /* MBPushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MBPushNotification.swift; sourceTree = ""; }; + 472F54A22C6E27C80008C465 /* NotificationFormatStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFormatStrategy.swift; sourceTree = ""; }; + 472F54A42C6E27DD0008C465 /* NotificationStrategyFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStrategyFactory.swift; sourceTree = ""; }; + 472F54A62C6E28030008C465 /* NotificationFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFormatter.swift; sourceTree = ""; }; + 472F54A92C6E294F0008C465 /* PushValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushValidator.swift; sourceTree = ""; }; + 472F54AB2C6E29E50008C465 /* MindboxPushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MindboxPushNotification.swift; sourceTree = ""; }; + 4747708A2C6B838B00C36FC8 /* SharedInternalMethodsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedInternalMethodsTests.swift; sourceTree = ""; }; + 4747708E2C6B93AC00C36FC8 /* MindboxNotificationServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MindboxNotificationServiceTests.swift; sourceTree = ""; }; + 474770902C6B9A7200C36FC8 /* MindboxNotificationContentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MindboxNotificationContentTests.swift; sourceTree = ""; }; + 474851C02C6A622E0026C38E /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 474851C22C6A627F0026C38E /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 474851C42C6A62AE0026C38E /* NotificationContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContent.swift; sourceTree = ""; }; + 474851C62C6A63C30026C38E /* SharedInternalMethods.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedInternalMethods.swift; sourceTree = ""; }; + 475558C22C59300400CDA026 /* MigrationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManagerTests.swift; sourceTree = ""; }; + 47B90E2E2C625F9A00BD93E7 /* TestBaseMigrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestBaseMigrations.swift; sourceTree = ""; }; + 47B90E302C626B9300BD93E7 /* TestProtocolMigrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestProtocolMigrations.swift; sourceTree = ""; }; + 47BD5BFA2C578BC600F965C0 /* MigrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManager.swift; sourceTree = ""; }; + 47BD5BFD2C578FB400F965C0 /* BaseMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseMigration.swift; sourceTree = ""; }; + 47FDF0B92C5BDAB80051F08C /* MigrationManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManagerProtocol.swift; sourceTree = ""; }; + 47FDF0BB2C5BE8BB0051F08C /* MigrationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationProtocol.swift; sourceTree = ""; }; 6F1EAA15266A670E007A335B /* ProductListItemsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductListItemsResponse.swift; sourceTree = ""; }; 6FDD143A266F7BD900A50C35 /* ProcessingStatusResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessingStatusResponse.swift; sourceTree = ""; }; 6FDD143C266F7BEB00A50C35 /* ItemResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemResponse.swift; sourceTree = ""; }; @@ -666,8 +680,6 @@ 6FDD1460266F7CE300A50C35 /* DiscountAmountTypeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscountAmountTypeResponse.swift; sourceTree = ""; }; 6FDD1462266F7CED00A50C35 /* ProductElementReponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductElementReponse.swift; sourceTree = ""; }; 6FDD1464266F7CFE00A50C35 /* ItemProductResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemProductResponse.swift; sourceTree = ""; }; - 8400428B2614C4D100CA17C5 /* NotificationDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationDecoder.swift; sourceTree = ""; }; - 840042902614C65900CA17C5 /* NotificationsPayloads.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsPayloads.swift; sourceTree = ""; }; 840042A02614CE0000CA17C5 /* ClickNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClickNotificationManager.swift; sourceTree = ""; }; 840C387025CC1AF200D50183 /* CDEvent+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CDEvent+CoreDataClass.swift"; sourceTree = ""; }; 840C387125CC1AF200D50183 /* CDEvent+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CDEvent+CoreDataProperties.swift"; sourceTree = ""; }; @@ -678,8 +690,6 @@ 840C38B225D133B000D50183 /* GuaranteedDeliveryTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuaranteedDeliveryTestCase.swift; sourceTree = ""; }; 840C38B725D13A7D00D50183 /* EventGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGenerator.swift; sourceTree = ""; }; 8410681225ECDC73004701C2 /* DatabaseLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseLoader.swift; sourceTree = ""; }; - 842DE32B25E01B9A002BE5C6 /* PushDelivered.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushDelivered.swift; sourceTree = ""; }; - 843DAA5B26087F3D00CAC489 /* DependencyContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyContainer.swift; sourceTree = ""; }; 847F57FD25C88BB700147A9A /* HTTPMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; 847F580625C88C7A00147A9A /* NetworkFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFetcher.swift; sourceTree = ""; }; 847F580A25C88CAF00147A9A /* Route.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Route.swift; sourceTree = ""; }; @@ -865,6 +875,11 @@ F31A947F2BC7E61800E6C978 /* InappFrequencyValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InappFrequencyValidator.swift; sourceTree = ""; }; F32CFFA32BB403C700A41E04 /* PushEnabledTargeting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushEnabledTargeting.swift; sourceTree = ""; }; F32CFFA52BB4044E00A41E04 /* PushEnabledTargetingChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushEnabledTargetingChecker.swift; sourceTree = ""; }; + F32E536E2C3F2B05002C7CA0 /* DITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DITests.swift; sourceTree = ""; }; + F32E53782C3FD64A002C7CA0 /* DIMainModuleRegistrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DIMainModuleRegistrationTests.swift; sourceTree = ""; }; + F32E537A2C3FDA30002C7CA0 /* DIMainModuleReplaceableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DIMainModuleReplaceableTests.swift; sourceTree = ""; }; + F32E537C2C3FDB6D002C7CA0 /* DITestModuleReplaceableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DITestModuleReplaceableTests.swift; sourceTree = ""; }; + F33087652C37590600F8DF10 /* InjectInappTools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectInappTools.swift; sourceTree = ""; }; F331DC832A80983000222120 /* FailableDecodableArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailableDecodableArray.swift; sourceTree = ""; }; F331DC982A80993600222120 /* ContentElementSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentElementSize.swift; sourceTree = ""; }; F331DC9A2A80993600222120 /* ContentElementSizeType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentElementSizeType.swift; sourceTree = ""; }; @@ -1021,6 +1036,15 @@ F3FAD8732AB8678900D98C03 /* negativeCloseButtonSizeValues.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = negativeCloseButtonSizeValues.json; sourceTree = ""; }; F3FAD8752AB867E700D98C03 /* closeButtonMarginAboveOne.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = closeButtonMarginAboveOne.json; sourceTree = ""; }; F3FAD8772AB8685D00D98C03 /* closeButtonMarginBelowZero.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = closeButtonMarginBelowZero.json; sourceTree = ""; }; + F3FEEA9A2C25AC68000E9D0F /* MBContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MBContainer.swift; sourceTree = ""; }; + F3FEEA9C2C25ACFC000E9D0F /* MBInject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MBInject.swift; sourceTree = ""; }; + F3FEEA9E2C25AF39000E9D0F /* StubContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubContainer.swift; sourceTree = ""; }; + F3FEEAA12C25B55C000E9D0F /* InjectCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectCore.swift; sourceTree = ""; }; + F3FEEAA32C25B765000E9D0F /* InjectUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectUtilities.swift; sourceTree = ""; }; + F3FEEAA52C25CB2E000E9D0F /* XCTestCase+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Extensions.swift"; sourceTree = ""; }; + F3FEEAA82C25CC9F000E9D0F /* InjectionMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectionMocks.swift; sourceTree = ""; }; + F3FEEAAA2C25D874000E9D0F /* InjectReplaceable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectReplaceable.swift; sourceTree = ""; }; + F3FEEAAC2C25FD1F000E9D0F /* InjectABTestUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectABTestUtilities.swift; sourceTree = ""; }; F78E92EE282E63320003B4A3 /* DispatchSemaphore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchSemaphore.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1143,6 +1167,7 @@ children = ( D216DE4F2C07168D0020F58A /* Extensions */, D2F7E2452BADB9EF00B24BB8 /* UserVisitManagerTests */, + 475558C12C592FDE00CDA026 /* MigrationsTests */, F3D818B12A3885E00002957C /* ABTests */, F36730192B7B6E5500DD0039 /* PushNotificationTests */, A154E302299C187D00F8F074 /* MindboxLogger */, @@ -1177,8 +1202,9 @@ 317054C025AEF81900AE624C /* DI */ = { isa = PBXGroup; children = ( - 317054C325AEF88E00AE624C /* DependencyProvider.swift */, - 843DAA5B26087F3D00CAC489 /* DependencyContainer.swift */, + F3FEEA9A2C25AC68000E9D0F /* MBContainer.swift */, + F3FEEA9C2C25ACFC000E9D0F /* MBInject.swift */, + F3FEEAA02C25B4F5000E9D0F /* Injections */, ); path = DI; sourceTree = ""; @@ -1195,6 +1221,7 @@ 31A20D4A25B6E09900AAA0A3 /* Utilities */ = { isa = PBXGroup; children = ( + 47BD5BF82C578AD700F965C0 /* Migrations */, F3D818AE2A3885A40002957C /* ABTestDeviceMixer */, 9B9C952E2921116F00BB29DA /* UUIDDebugService */, 33BEE80C2681EB7700993720 /* NotificationDecoder.swift */, @@ -1214,8 +1241,14 @@ 3333C1992681D3CF00B60D84 /* MindboxNotifications */ = { isa = PBXGroup; children = ( - 3333C1B02681D3FF00B60D84 /* Network */, + 472F549F2C6E27770008C465 /* PushNotifications */, + 474770892C6B813700C36FC8 /* Utilities */, + 3333C1BE2681DE6C00B60D84 /* Models */, 337EDF332636DA3F0089559F /* MindboxNotificationService.swift */, + 472F54AB2C6E29E50008C465 /* MindboxPushNotification.swift */, + 474851C02C6A622E0026C38E /* NotificationService.swift */, + 474851C42C6A62AE0026C38E /* NotificationContent.swift */, + 474851C62C6A63C30026C38E /* SharedInternalMethods.swift */, 3333C19A2681D3CF00B60D84 /* MindboxNotifications.h */, 3333C19B2681D3CF00B60D84 /* Info.plist */, F3EC93B82AF1041A0030D107 /* PrivacyInfo.xcprivacy */, @@ -1226,61 +1259,22 @@ 3333C1A42681D3CF00B60D84 /* MindboxNotificationsTests */ = { isa = PBXGroup; children = ( - 3333C1A52681D3CF00B60D84 /* MindboxNotificationsTests.swift */, + 4747708E2C6B93AC00C36FC8 /* MindboxNotificationServiceTests.swift */, + 474770902C6B9A7200C36FC8 /* MindboxNotificationContentTests.swift */, + 4747708A2C6B838B00C36FC8 /* SharedInternalMethodsTests.swift */, 3333C1A72681D3CF00B60D84 /* Info.plist */, ); path = MindboxNotificationsTests; sourceTree = ""; }; - 3333C1B02681D3FF00B60D84 /* Network */ = { + 3333C1BE2681DE6C00B60D84 /* Models */ = { isa = PBXGroup; children = ( - 3333C1B72681DC0500B60D84 /* DeliveryService.swift */, - 3333C1BB2681DE0900B60D84 /* DeviceModelHelper.swift */, - 3333C1B52681D69700B60D84 /* MBConfiguration.swift */, - 3333C1B92681DC4B00B60D84 /* MBUtilitiesFetcher.swift */, - 3333C1C32681E64F00B60D84 /* NetworkService.swift */, - 33BEE81C2681ED2900993720 /* PushDeliveredEventRoute.swift */, - 3333C1C92681E74A00B60D84 /* Route.swift */, - 3333C1BE2681DE6C00B60D84 /* Model */, - 3333C1D22681E85600B60D84 /* Types */, - ); - path = Network; - sourceTree = ""; - }; - 3333C1BE2681DE6C00B60D84 /* Model */ = { - isa = PBXGroup; - children = ( - 3333C1E42681EEEF00B60D84 /* URLRequestBuilder.swift */, - 3333C1E22681EEA800B60D84 /* Event.swift */, - 3333C1CD2681E83200B60D84 /* Body+ */, - 3333C1CB2681E76000B60D84 /* EventWrapper.swift */, - 3333C1C12681E59900B60D84 /* DeliveryOperation.swift */, - 840042902614C65900CA17C5 /* NotificationsPayloads.swift */, - 8400428B2614C4D100CA17C5 /* NotificationDecoder.swift */, - 842DE32B25E01B9A002BE5C6 /* PushDelivered.swift */, + 472F549D2C6E272A0008C465 /* MBPushNotification.swift */, 3333C1B12681D42000B60D84 /* Payload.swift */, 3333C1B32681D43C00B60D84 /* ImageFormat.swift */, ); - path = Model; - sourceTree = ""; - }; - 3333C1CD2681E83200B60D84 /* Body+ */ = { - isa = PBXGroup; - children = ( - 3333C1CF2681E83200B60D84 /* BodyEncoder.swift */, - 3333C1CE2681E83200B60D84 /* BodyDecoder.swift */, - ); - path = "Body+"; - sourceTree = ""; - }; - 3333C1D22681E85600B60D84 /* Types */ = { - isa = PBXGroup; - children = ( - 3333C1D52681E85600B60D84 /* HTTPTypealiases.swift */, - 3333C1D32681E85600B60D84 /* HTTPMethod.swift */, - ); - path = Types; + path = Models; sourceTree = ""; }; 3333C1DF2681EA4C00B60D84 /* NotificationPayload */ = { @@ -1412,6 +1406,78 @@ path = SessionManager; sourceTree = ""; }; + 472F549F2C6E27770008C465 /* PushNotifications */ = { + isa = PBXGroup; + children = ( + 472F54A12C6E27BC0008C465 /* PushNotificationFormatStrategies */, + 472F54A62C6E28030008C465 /* NotificationFormatter.swift */, + 472F54A92C6E294F0008C465 /* PushValidator.swift */, + ); + path = PushNotifications; + sourceTree = ""; + }; + 472F54A12C6E27BC0008C465 /* PushNotificationFormatStrategies */ = { + isa = PBXGroup; + children = ( + 472F54A22C6E27C80008C465 /* NotificationFormatStrategy.swift */, + 472F54A42C6E27DD0008C465 /* NotificationStrategyFactory.swift */, + ); + path = PushNotificationFormatStrategies; + sourceTree = ""; + }; + 474770892C6B813700C36FC8 /* Utilities */ = { + isa = PBXGroup; + children = ( + 474851C22C6A627F0026C38E /* Constants.swift */, + ); + path = Utilities; + sourceTree = ""; + }; + 475558C12C592FDE00CDA026 /* MigrationsTests */ = { + isa = PBXGroup; + children = ( + 47B90E2D2C625F8A00BD93E7 /* TestMigrations */, + 475558C22C59300400CDA026 /* MigrationManagerTests.swift */, + ); + path = MigrationsTests; + sourceTree = ""; + }; + 47B90E2D2C625F8A00BD93E7 /* TestMigrations */ = { + isa = PBXGroup; + children = ( + 47B90E2E2C625F9A00BD93E7 /* TestBaseMigrations.swift */, + 47B90E302C626B9300BD93E7 /* TestProtocolMigrations.swift */, + ); + path = TestMigrations; + sourceTree = ""; + }; + 47BD5BF82C578AD700F965C0 /* Migrations */ = { + isa = PBXGroup; + children = ( + 47BD5BFC2C578FAD00F965C0 /* MigrationAbstractions */, + 47BD5BF92C578ADE00F965C0 /* MigrationManager */, + ); + path = Migrations; + sourceTree = ""; + }; + 47BD5BF92C578ADE00F965C0 /* MigrationManager */ = { + isa = PBXGroup; + children = ( + 47FDF0B92C5BDAB80051F08C /* MigrationManagerProtocol.swift */, + 47BD5BFA2C578BC600F965C0 /* MigrationManager.swift */, + ); + path = MigrationManager; + sourceTree = ""; + }; + 47BD5BFC2C578FAD00F965C0 /* MigrationAbstractions */ = { + isa = PBXGroup; + children = ( + 47FDF0BB2C5BE8BB0051F08C /* MigrationProtocol.swift */, + 47BD5BFD2C578FB400F965C0 /* BaseMigration.swift */, + ); + path = MigrationAbstractions; + sourceTree = ""; + }; 8400429F2614CDED00CA17C5 /* ClickNotificationManager */ = { isa = PBXGroup; children = ( @@ -1571,7 +1637,12 @@ 84B625F525C98EE000AB6228 /* DI */ = { isa = PBXGroup; children = ( - 3132DFF525C2A811007FE358 /* TestDependencyProvider.swift */, + F3FEEAA72C25CC8F000E9D0F /* Injections */, + F3FEEA9E2C25AF39000E9D0F /* StubContainer.swift */, + F32E536E2C3F2B05002C7CA0 /* DITests.swift */, + F32E53782C3FD64A002C7CA0 /* DIMainModuleRegistrationTests.swift */, + F32E537A2C3FDA30002C7CA0 /* DIMainModuleReplaceableTests.swift */, + F32E537C2C3FDB6D002C7CA0 /* DITestModuleReplaceableTests.swift */, ); path = DI; sourceTree = ""; @@ -2112,6 +2183,7 @@ isa = PBXGroup; children = ( D216DE502C0716B70020F58A /* StringExtensionsTests.swift */, + F3FEEAA52C25CB2E000E9D0F /* XCTestCase+Extensions.swift */, ); path = Extensions; sourceTree = ""; @@ -2780,6 +2852,26 @@ path = PushNotificationFormatStrategies; sourceTree = ""; }; + F3FEEAA02C25B4F5000E9D0F /* Injections */ = { + isa = PBXGroup; + children = ( + F3FEEAA12C25B55C000E9D0F /* InjectCore.swift */, + F3FEEAA32C25B765000E9D0F /* InjectUtilities.swift */, + F3FEEAAC2C25FD1F000E9D0F /* InjectABTestUtilities.swift */, + F33087652C37590600F8DF10 /* InjectInappTools.swift */, + F3FEEAAA2C25D874000E9D0F /* InjectReplaceable.swift */, + ); + path = Injections; + sourceTree = ""; + }; + F3FEEAA72C25CC8F000E9D0F /* Injections */ = { + isa = PBXGroup; + children = ( + F3FEEAA82C25CC9F000E9D0F /* InjectionMocks.swift */, + ); + path = Injections; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -3152,6 +3244,7 @@ 84A312B725DA64C60096A017 /* BGTaskManager.swift in Sources */, F331DD1C2A84B5EF00222120 /* ImageContentBackgroundLayer.swift in Sources */, F3A8B9A52A3A6F2800E9C055 /* MonitoringModel.swift in Sources */, + F33087662C37590600F8DF10 /* InjectInappTools.swift in Sources */, 334F3AE8264C199900A6AC00 /* ItemRequest.swift in Sources */, 84CC799325CACF0C00C062BD /* EventRepository.swift in Sources */, 6FDD145B266F7CC300A50C35 /* ContentTypeResponse.swift in Sources */, @@ -3188,6 +3281,7 @@ 334F3A66264AA18500A6AC00 /* MindboxSceneDelegate.swift in Sources */, A192787029D441FC00CDB53D /* ProductSegmentTargeting.swift in Sources */, 84B625D925C9558E00AB6228 /* MBPersistenceStorage.swift in Sources */, + 47BD5BFB2C578BC600F965C0 /* MigrationManager.swift in Sources */, 84C65E5E25D4FBA3008996FA /* MobileApplicationInstalled.swift in Sources */, A17958772978AEC600609E91 /* TrueTargetingChecker.swift in Sources */, B3F4F98A268EEB360092EC3C /* PeriodTypeResponse.swift in Sources */, @@ -3241,6 +3335,7 @@ 334F3AF5264C199900A6AC00 /* CodableDictionary.swift in Sources */, F3F5BB8A2B79F2600022AC3F /* PushNotificationFormatter.swift in Sources */, 334F3AF3264C199900A6AC00 /* AreaRequest.swift in Sources */, + F3FEEAAB2C25D874000E9D0F /* InjectReplaceable.swift in Sources */, 33B1C976265B910F00E293F8 /* SwiftyJSON.swift in Sources */, F397EEAD2A4456C300D48CEC /* ProtocolError.swift in Sources */, 84A312B025DA5CF30096A017 /* BackgroundTaskManagerProxy.swift in Sources */, @@ -3265,6 +3360,8 @@ 0A3D045A2BC6803E00E1FC52 /* ImageFormat.swift in Sources */, 337C79562656533F0092B580 /* ViewProductRequest.swift in Sources */, 6FDD145F266F7CD900A50C35 /* DiscountResponse.swift in Sources */, + F3FEEAA22C25B55C000E9D0F /* InjectCore.swift in Sources */, + 47FDF0BA2C5BDAB80051F08C /* MigrationManagerProtocol.swift in Sources */, 33072F462664CC9A001F1AB2 /* UsedPointOfContactResponse.swift in Sources */, F397EEB12A44571300D48CEC /* UnknownDecodable.swift in Sources */, 33072F382664C577001F1AB2 /* RecommendationResponse.swift in Sources */, @@ -3278,6 +3375,7 @@ 6FDD144F266F7C7000A50C35 /* UsedResponse.swift in Sources */, 330D8CCD26579581005106D5 /* Channel.swift in Sources */, 84BAEF8225D54919002E8A26 /* BodyDecoder.swift in Sources */, + F3FEEA9D2C25ACFC000E9D0F /* MBInject.swift in Sources */, 847F580F25C88E8C00147A9A /* MBNetworkFetcher.swift in Sources */, F3A8B9AB2A3A719C00E9C055 /* ABTestModel.swift in Sources */, 33072F342664C3E1001F1AB2 /* ProductResponse.swift in Sources */, @@ -3310,6 +3408,7 @@ A15D701A29AF8142007131E7 /* SDKLogsManager.swift in Sources */, 334F3AF6264C199900A6AC00 /* ProductListRequest.swift in Sources */, F36128492BA3193E000382D9 /* PresentationClickTracker.swift in Sources */, + 47BD5BFE2C578FB400F965C0 /* BaseMigration.swift in Sources */, 84A0CD54260AF8C8004CD91B /* TrackClick.swift in Sources */, A1D017F02976CC8600CD9F99 /* OrTargeting.swift in Sources */, 334F3AEA264C199900A6AC00 /* CustomerRequest.swift in Sources */, @@ -3319,11 +3418,12 @@ 33072F362664C4D7001F1AB2 /* RecommendationRequest.swift in Sources */, 84DC49CC25D1832300D5D758 /* EventWrapper.swift in Sources */, F39116FA2AA9B32E00852298 /* LayersFilter.swift in Sources */, - 317054C425AEF88E00AE624C /* DependencyProvider.swift in Sources */, F31A947E2BC6A00D00E6C978 /* OnceFrequency.swift in Sources */, 84DC4A0225D27D0500D5D758 /* UNAuthorizationStatusProviding.swift in Sources */, + F3FEEAAD2C25FD1F000E9D0F /* InjectABTestUtilities.swift in Sources */, 6FDD1465266F7CFE00A50C35 /* ItemProductResponse.swift in Sources */, 3337E6A92664C16A006949EB /* CustomerResponse.swift in Sources */, + F3FEEAA42C25B765000E9D0F /* InjectUtilities.swift in Sources */, 84EAEDFC25C8B18B00726063 /* DeviceModelHelper.swift in Sources */, 84DC4A0725D27D6000D5D758 /* UNAuthorizationStatusProvider.swift in Sources */, F331DCC82A80993600222120 /* ContentBackgroundLayer.swift in Sources */, @@ -3367,15 +3467,16 @@ 314B390025AEE96F00E947B9 /* CoreController.swift in Sources */, F331DCCB2A80993600222120 /* ContentBackgroundLayerAction.swift in Sources */, 84B625E425C988FA00AB6228 /* URLValidator.swift in Sources */, - 843DAA5C26087F3D00CAC489 /* DependencyContainer.swift in Sources */, F39B67A52A3C6BE3005C0CCA /* SegmentationCheckRequest.swift in Sources */, F30F1A282B9F0C8A0099DFD9 /* PushPermissionLayerAction.swift in Sources */, A184654329C3102A00E64780 /* CategoryIDTargeting.swift in Sources */, F33608282A8CD17E00C7C9B7 /* SnackbarPresentationStrategy.swift in Sources */, 330D8CCB26579521005106D5 /* DiscountTypeRequest.swift in Sources */, F382F2112BAC6AD100BC97FF /* UNAuthorizationStatus+Extensions.swift in Sources */, + F3FEEA9B2C25AC68000E9D0F /* MBContainer.swift in Sources */, A184654729C32FEF00E64780 /* CategoryIDChecker.swift in Sources */, A192786729D389EE00CDB53D /* ProductIDTargeting.swift in Sources */, + 47FDF0BC2C5BE8BB0051F08C /* MigrationProtocol.swift in Sources */, 6FDD1445266F7C2200A50C35 /* CouponResponse.swift in Sources */, F78E92EF282E63320003B4A3 /* DispatchSemaphore.swift in Sources */, 314B38FD25AEE8B200E947B9 /* MBConfiguration.swift in Sources */, @@ -3428,11 +3529,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F3FEEA9F2C25AF39000E9D0F /* StubContainer.swift in Sources */, 840C38B325D133B000D50183 /* GuaranteedDeliveryTestCase.swift in Sources */, + F32E537B2C3FDA30002C7CA0 /* DIMainModuleReplaceableTests.swift in Sources */, 84FCD3B925CA109E00D1E574 /* MockNetworkFetcher.swift in Sources */, 9B9C9538292111A700BB29DA /* MockUUIDDebugService.swift in Sources */, 84B625F025C98B1200AB6228 /* ValidatorsTestCase.swift in Sources */, - 3132DFF625C2A811007FE358 /* TestDependencyProvider.swift in Sources */, 847F580325C88BBF00147A9A /* HTTPMethod.swift in Sources */, 9B9C953A292111A700BB29DA /* MockNotificationCenter.swift in Sources */, F3A8B95C2A389EAD00E9C055 /* GeoServiceTests.swift in Sources */, @@ -3447,15 +3549,18 @@ 84B09FB42611C74400B0A06E /* MockDatabaseRepository.swift in Sources */, F39B67B82A3FAA75005C0CCA /* ABTests.swift in Sources */, A1F3EE4A298BEE4B005FA828 /* OSLogWritterMock.swift in Sources */, + F3FEEAA62C25CB2E000E9D0F /* XCTestCase+Extensions.swift in Sources */, F31470822B9634E000E01E5C /* InAppTargetingRequestsTests.swift in Sources */, 84ECB42E25D27EF100DA8AC9 /* MockUNAuthorizationStatusProvider.swift in Sources */, D2F7E24C2BADC4CA00B24BB8 /* MockSessionManager.swift in Sources */, F31470842B96355600E01E5C /* MockInAppConfigurationDataFacade.swift in Sources */, + 47B90E312C626B9300BD93E7 /* TestProtocolMigrations.swift in Sources */, F3A8B9982A3A421C00E9C055 /* SDKVersionValidatorTests.swift in Sources */, D216DE512C0716B70020F58A /* StringExtensionsTests.swift in Sources */, A17958812978B3FA00609E91 /* InAppResponseModelTests.swift in Sources */, 9B4F9E0528D0945F002C9CF0 /* InAppCoreManagerMock.swift in Sources */, 840C387F25CD15C200D50183 /* MockDataBaseLoader.swift in Sources */, + F32E53792C3FD64A002C7CA0 /* DIMainModuleRegistrationTests.swift in Sources */, A1F3EE4E298BEF0B005FA828 /* OSLogWritterTests.swift in Sources */, 848A895E2620C54900EDFB6D /* VersioningTestCase.swift in Sources */, A1A916E529C9191900D59D9E /* InAppConfigStub.swift in Sources */, @@ -3468,19 +3573,24 @@ 9B9C953B292111A700BB29DA /* MockDateProvider.swift in Sources */, A17958972978D2B300609E91 /* InAppTargetingCheckerTests.swift in Sources */, F39B67AE2A3C737F005C0CCA /* ImageDownloadServiceTests.swift in Sources */, + 47B90E2F2C625F9A00BD93E7 /* TestBaseMigrations.swift in Sources */, 84A0CD5A260B021F004CD91B /* MockFailureNetworkFetcher.swift in Sources */, + 475558C32C59300400CDA026 /* MigrationManagerTests.swift in Sources */, 84E1D6A9261D8D54002BF03A /* DatabaseLoaderTest.swift in Sources */, 840C38B825D13A7D00D50183 /* EventGenerator.swift in Sources */, 84CC79A525CAE14200C062BD /* EventRepositoryTestCase.swift in Sources */, F367301B2B7B6E7600DD0039 /* MindboxPushValidatorTests.swift in Sources */, A154E304299C189300F8F074 /* MBLoggerCoreDataManagerTests.swift in Sources */, + F32E537D2C3FDB6D002C7CA0 /* DITestModuleReplaceableTests.swift in Sources */, 840C388525CD169400D50183 /* DatabaseRepositoryTestCase.swift in Sources */, 84FCD3B525CA0FD300D1E574 /* MockPersistenceStorage.swift in Sources */, F3A8B99C2A3A47F800E9C055 /* ABTestVariantsValidatorTests.swift in Sources */, 848A89592620C3AE00EDFB6D /* APNSTokenGenerator.swift in Sources */, + F32E536F2C3F2B05002C7CA0 /* DITests.swift in Sources */, F3D818B32A3885F50002957C /* ABTestDeviceMixerTests.swift in Sources */, 9BC24E7728F6BF8C00C2619C /* InAppConfigResponseTests.swift in Sources */, F391170B2AB2E74F00852298 /* InappFilterServiceTests.swift in Sources */, + F3FEEAA92C25CC9F000E9D0F /* InjectionMocks.swift in Sources */, F39B67AC2A3C7315005C0CCA /* MockImageDownloadService.swift in Sources */, F306291A2BD27D7500EF6609 /* InappFrequencyTests.swift in Sources */, F3AF0AD02BC40FCF0063BA58 /* InappTTLTests.swift in Sources */, @@ -3491,28 +3601,20 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3333C1D62681E85600B60D84 /* HTTPMethod.swift in Sources */, - 3333C1CC2681E76000B60D84 /* EventWrapper.swift in Sources */, - 33BEE81D2681ED2900993720 /* PushDeliveredEventRoute.swift in Sources */, - 3333C1BF2681E46400B60D84 /* NotificationDecoder.swift in Sources */, - 3333C1B82681DC0500B60D84 /* DeliveryService.swift in Sources */, - 3333C1D12681E83200B60D84 /* BodyEncoder.swift in Sources */, - 3333C1D82681E85600B60D84 /* HTTPTypealiases.swift in Sources */, + 474851C72C6A63C30026C38E /* SharedInternalMethods.swift in Sources */, + 472F54A52C6E27DD0008C465 /* NotificationStrategyFactory.swift in Sources */, + 472F549E2C6E272A0008C465 /* MBPushNotification.swift in Sources */, 3333C1B42681D43C00B60D84 /* ImageFormat.swift in Sources */, - 3333C1BD2681DE6600B60D84 /* PushDelivered.swift in Sources */, - 3333C1CA2681E74A00B60D84 /* Route.swift in Sources */, - 3333C1BA2681DC4B00B60D84 /* MBUtilitiesFetcher.swift in Sources */, - 3333C1E52681EEEF00B60D84 /* URLRequestBuilder.swift in Sources */, - 3333C1BC2681DE0900B60D84 /* DeviceModelHelper.swift in Sources */, + 472F54AA2C6E294F0008C465 /* PushValidator.swift in Sources */, 3333C1B22681D42000B60D84 /* Payload.swift in Sources */, + 472F54A32C6E27C80008C465 /* NotificationFormatStrategy.swift in Sources */, 3333C1AF2681D3DB00B60D84 /* MindboxNotificationService.swift in Sources */, - 3333C1B62681D69700B60D84 /* MBConfiguration.swift in Sources */, - 3333C1C02681E4E400B60D84 /* NotificationsPayloads.swift in Sources */, - 3333C1D02681E83200B60D84 /* BodyDecoder.swift in Sources */, - 3333C1C42681E64F00B60D84 /* NetworkService.swift in Sources */, - 3333C1E32681EEA800B60D84 /* Event.swift in Sources */, + 472F54AC2C6E29E50008C465 /* MindboxPushNotification.swift in Sources */, + 474851C12C6A622E0026C38E /* NotificationService.swift in Sources */, + 474851C52C6A62AE0026C38E /* NotificationContent.swift in Sources */, + 472F54A72C6E28030008C465 /* NotificationFormatter.swift in Sources */, 9BC24E7028F694EB00C2619C /* SDKVersionProvider.swift in Sources */, - 3333C1C22681E59900B60D84 /* DeliveryOperation.swift in Sources */, + 474851C32C6A627F0026C38E /* Constants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3520,7 +3622,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3333C1A62681D3CF00B60D84 /* MindboxNotificationsTests.swift in Sources */, + 4747708B2C6B838B00C36FC8 /* SharedInternalMethodsTests.swift in Sources */, + 4747708F2C6B93AC00C36FC8 /* MindboxNotificationServiceTests.swift in Sources */, + 474770912C6B9A7200C36FC8 /* MindboxNotificationContentTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3645,7 +3749,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 10.6; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -3708,7 +3812,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 10.6; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -3827,7 +3931,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = MindboxNotifications/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3855,7 +3959,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = MindboxNotifications/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3878,7 +3982,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = N39VVWZXXP; INFOPLIST_FILE = MindboxNotificationsTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3898,7 +4002,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = N39VVWZXXP; INFOPLIST_FILE = MindboxNotificationsTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3925,7 +4029,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Mindbox. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3955,7 +4059,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Mindbox. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3980,7 +4084,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = N39VVWZXXP; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = cloud.organization.MindboxLoggerTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3999,7 +4103,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = N39VVWZXXP; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = cloud.organization.MindboxLoggerTests; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/Mindbox.xcodeproj/xcshareddata/xcschemes/MindboxNotifications.xcscheme b/Mindbox.xcodeproj/xcshareddata/xcschemes/MindboxNotifications.xcscheme index 416165a2..02326893 100644 --- a/Mindbox.xcodeproj/xcshareddata/xcschemes/MindboxNotifications.xcscheme +++ b/Mindbox.xcodeproj/xcshareddata/xcschemes/MindboxNotifications.xcscheme @@ -26,7 +26,18 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES" + onlyGenerateCoverageForSpecifiedTargets = "YES"> + + + + diff --git a/Mindbox/CoreController/CoreController.swift b/Mindbox/CoreController/CoreController.swift index b3d2bed0..4fdc9afb 100644 --- a/Mindbox/CoreController/CoreController.swift +++ b/Mindbox/CoreController/CoreController.swift @@ -13,7 +13,6 @@ import MindboxLogger final class CoreController { private let persistenceStorage: PersistenceStorage private let utilitiesFetcher: UtilitiesFetcher - private let notificationStatusProvider: UNAuthorizationStatusProviding private let databaseRepository: MBDatabaseRepository private let guaranteedDeliveryManager: GuaranteedDeliveryManager private let trackVisitManager: TrackVisitManager @@ -31,6 +30,8 @@ final class CoreController { SessionTemporaryStorage.shared.isInstalledFromPersistenceStorageBeforeInitSDK = self.persistenceStorage.isInstalled SessionTemporaryStorage.shared.isInitializationCalled = true + DI.injectOrFail(MigrationManagerProtocol.self).migrate() + self.configValidation.compare(configuration, self.persistenceStorage.configuration) self.persistenceStorage.configuration = configuration if !self.persistenceStorage.isInstalled { @@ -38,6 +39,7 @@ final class CoreController { } else { self.repeatInitialization(with: configuration) } + self.guaranteedDeliveryManager.canScheduleOperations = true let appStateMessage = "[App State]: \(UIApplication.shared.appStateDescription)" @@ -92,6 +94,7 @@ final class CoreController { // MARK: - Private private func notificationStatus() -> Bool { + let notificationStatusProvider = DI.injectOrFail(UNAuthorizationStatusProviding.self) let lock = DispatchSemaphore(value: 0) var isNotificationsEnabled = false notificationStatusProvider.getStatus { @@ -147,7 +150,6 @@ final class CoreController { private func startUUIDDebugServiceIfNeeded(deviceUUID: String, configuration: MBConfiguration) { guard configuration.uuidDebugEnabled else { return } - uuidDebugService.start(with: deviceUUID) } @@ -241,7 +243,6 @@ final class CoreController { init( persistenceStorage: PersistenceStorage, utilitiesFetcher: UtilitiesFetcher, - notificationStatusProvider: UNAuthorizationStatusProviding, databaseRepository: MBDatabaseRepository, guaranteedDeliveryManager: GuaranteedDeliveryManager, trackVisitManager: TrackVisitManager, @@ -253,7 +254,6 @@ final class CoreController { ) { self.persistenceStorage = persistenceStorage self.utilitiesFetcher = utilitiesFetcher - self.notificationStatusProvider = notificationStatusProvider self.databaseRepository = databaseRepository self.guaranteedDeliveryManager = guaranteedDeliveryManager self.trackVisitManager = trackVisitManager @@ -272,11 +272,13 @@ final class CoreController { } } } - - TimerManager.shared.configurate(trackEvery: 20 * 60) { + + let timer = DI.injectOrFail(TimerManager.self) + timer.configurate(trackEvery: 20 * 60) { Logger.common(message: "Scheduled Time tracker started") sessionManager.trackForeground() } - TimerManager.shared.setupTimer() + + timer.setupTimer() } } diff --git a/Mindbox/CoreController/TimerManager.swift b/Mindbox/CoreController/TimerManager.swift index a0d52b2f..2b4d6747 100644 --- a/Mindbox/CoreController/TimerManager.swift +++ b/Mindbox/CoreController/TimerManager.swift @@ -11,8 +11,6 @@ import MindboxLogger public final class TimerManager { - public static let shared = TimerManager() - internal var didEnterBackgroundApplication: NSObjectProtocol? internal var didBecomeActiveApplication: NSObjectProtocol? @@ -23,6 +21,7 @@ public final class TimerManager { } } } + internal var deadline: TimeInterval? = nil internal var seconds: TimeInterval = 0 { @@ -93,11 +92,13 @@ public final class TimerManager { } self.seconds = 0 - TimerManager.shared.timer = Timer(timeInterval: 1, repeats: true) { [weak self] _ in + timer = Timer(timeInterval: 1, repeats: true) { [weak self] _ in self?.seconds += 1 } - guard let timer = TimerManager.shared.timer else { return } + guard let timer = timer else { + return + } RunLoop.main.add(timer, forMode: .common) Logger.common(message: "The timer is running") diff --git a/Mindbox/DI/DependencyContainer.swift b/Mindbox/DI/DependencyContainer.swift deleted file mode 100644 index 3f24776b..00000000 --- a/Mindbox/DI/DependencyContainer.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// DependencyContainer.swift -// Mindbox -// -// Created by Maksim Kazachkov on 22.03.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation - -protocol DependencyContainer { - var utilitiesFetcher: UtilitiesFetcher { get } - var persistenceStorage: PersistenceStorage { get } - var databaseLoader: DataBaseLoader { get } - var databaseRepository: MBDatabaseRepository { get } - var guaranteedDeliveryManager: GuaranteedDeliveryManager { get } - var authorizationStatusProvider: UNAuthorizationStatusProviding { get } - var instanceFactory: InstanceFactory { get } - var sessionManager: SessionManager { get } - var inAppTargetingChecker: InAppTargetingChecker { get } - var inAppMessagesManager: InAppCoreManagerProtocol { get } - var uuidDebugService: UUIDDebugService { get } - var inappMessageEventSender: InappMessageEventSender { get } - var sdkVersionValidator: SDKVersionValidator { get } - var geoService: GeoServiceProtocol { get } - var segmentationSevice: SegmentationServiceProtocol { get } - var imageDownloadService: ImageDownloadServiceProtocol { get } - var abTestDeviceMixer: ABTestDeviceMixer { get } - var urlExtractorService: VariantImageUrlExtractorService { get } - var inappFilterService: InappFilterProtocol { get } - var pushValidator: MindboxPushValidator { get } - var inAppConfigurationDataFacade: InAppConfigurationDataFacadeProtocol { get } - var userVisitManager: UserVisitManagerProtocol { get } - var ttlValidationService: TTLValidationProtocol { get } - var frequencyValidator: InappFrequencyValidator { get } -} - -protocol InstanceFactory { - func makeNetworkFetcher() -> NetworkFetcher - func makeEventRepository() -> EventRepository - func makeTrackVisitManager() -> TrackVisitManager -} diff --git a/Mindbox/DI/DependencyProvider.swift b/Mindbox/DI/DependencyProvider.swift deleted file mode 100644 index 449a2c65..00000000 --- a/Mindbox/DI/DependencyProvider.swift +++ /dev/null @@ -1,162 +0,0 @@ -// -// DIManager.swift -// Mindbox -// -// Created by Mikhail Barilov on 13.01.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import CoreData -import Foundation -import UIKit - -final class DependencyProvider: DependencyContainer { - let utilitiesFetcher: UtilitiesFetcher - let persistenceStorage: PersistenceStorage - let databaseLoader: DataBaseLoader - let databaseRepository: MBDatabaseRepository - let guaranteedDeliveryManager: GuaranteedDeliveryManager - let authorizationStatusProvider: UNAuthorizationStatusProviding - let sessionManager: SessionManager - let instanceFactory: InstanceFactory - let inAppTargetingChecker: InAppTargetingChecker - let inAppMessagesManager: InAppCoreManagerProtocol - let uuidDebugService: UUIDDebugService - var inappMessageEventSender: InappMessageEventSender - let sdkVersionValidator: SDKVersionValidator - let geoService: GeoServiceProtocol - let segmentationSevice: SegmentationServiceProtocol - var imageDownloadService: ImageDownloadServiceProtocol - var abTestDeviceMixer: ABTestDeviceMixer - var urlExtractorService: VariantImageUrlExtractorService - var inappFilterService: InappFilterProtocol - var pushValidator: MindboxPushValidator - var inAppConfigurationDataFacade: InAppConfigurationDataFacadeProtocol - var userVisitManager: UserVisitManagerProtocol - var ttlValidationService: TTLValidationProtocol - var frequencyValidator: InappFrequencyValidator - - init() throws { - utilitiesFetcher = MBUtilitiesFetcher() - persistenceStorage = MBPersistenceStorage(defaults: UserDefaults(suiteName: utilitiesFetcher.applicationGroupIdentifier)!) - persistenceStorage.migrateShownInAppsIds() - inAppTargetingChecker = InAppTargetingChecker(persistenceStorage: persistenceStorage) - databaseLoader = try DataBaseLoader(applicationGroupIdentifier: utilitiesFetcher.applicationGroupIdentifier) - let persistentContainer = try databaseLoader.loadPersistentContainer() - databaseRepository = try MBDatabaseRepository(persistentContainer: persistentContainer) - instanceFactory = MBInstanceFactory( - persistenceStorage: persistenceStorage, - utilitiesFetcher: utilitiesFetcher, - databaseRepository: databaseRepository - ) - guaranteedDeliveryManager = GuaranteedDeliveryManager( - persistenceStorage: persistenceStorage, - databaseRepository: databaseRepository, - eventRepository: instanceFactory.makeEventRepository() - ) - authorizationStatusProvider = UNAuthorizationStatusProvider() - sessionManager = MBSessionManager(trackVisitManager: instanceFactory.makeTrackVisitManager()) - let logsManager = SDKLogsManager(persistenceStorage: persistenceStorage, eventRepository: instanceFactory.makeEventRepository()) - - sdkVersionValidator = SDKVersionValidator(sdkVersionNumeric: Constants.Versions.sdkVersionNumeric) - geoService = GeoService(fetcher: instanceFactory.makeNetworkFetcher(), - targetingChecker: inAppTargetingChecker) - segmentationSevice = SegmentationService(customerSegmentsAPI: .live, - targetingChecker: inAppTargetingChecker) - let imageDownloader = URLSessionImageDownloader(persistenceStorage: persistenceStorage) - imageDownloadService = ImageDownloadService(imageDownloader: imageDownloader) - abTestDeviceMixer = ABTestDeviceMixer() - let tracker = InAppMessagesTracker(databaseRepository: databaseRepository) - let displayUseCase = PresentationDisplayUseCase(tracker: tracker) - let actionUseCaseFactory = ActionUseCaseFactory(tracker: tracker) - let actionHandler = InAppActionHandler(actionUseCaseFactory: actionUseCaseFactory) - let presentationManager = InAppPresentationManager(actionHandler: actionHandler, - displayUseCase: displayUseCase) - urlExtractorService = VariantImageUrlExtractorService() - let actionFilter = LayerActionFilterService() - let sourceFilter = LayersSourceFilterService() - let layersFilterService = LayersFilterService(actionFilter: actionFilter, sourceFilter: sourceFilter) - let sizeFilter = ElementSizeFilterService() - let colorFilter = ElementsColorFilterService() - let positionFilter = ElementsPositionFilterService() - let elementsFilterService = ElementsFilterService(sizeFilter: sizeFilter, positionFilter: positionFilter, colorFilter: colorFilter) - let contentPositionFilterService = ContentPositionFilterService() - - let variantsFilterService = VariantFilterService(layersFilter: layersFilterService, - elementsFilter: elementsFilterService, - contentPositionFilter: contentPositionFilterService) - - frequencyValidator = InappFrequencyValidator(persistenceStorage: persistenceStorage) - inappFilterService = InappsFilterService(persistenceStorage: persistenceStorage, - abTestDeviceMixer: abTestDeviceMixer, - variantsFilter: variantsFilterService, - sdkVersionValidator: sdkVersionValidator, - frequencyValidator: frequencyValidator) - - ttlValidationService = TTLValidationService(persistenceStorage: persistenceStorage) - inAppConfigurationDataFacade = InAppConfigurationDataFacade(geoService: geoService, - segmentationService: segmentationSevice, - targetingChecker: inAppTargetingChecker, - imageService: imageDownloadService, - tracker: tracker) - - inAppMessagesManager = InAppCoreManager( - configManager: InAppConfigurationManager( - inAppConfigAPI: InAppConfigurationAPI(persistenceStorage: persistenceStorage), - inAppConfigRepository: InAppConfigurationRepository(), - inAppConfigurationMapper: InAppConfigutationMapper(inappFilterService: inappFilterService, - targetingChecker: inAppTargetingChecker, - urlExtractorService: urlExtractorService, - dataFacade: inAppConfigurationDataFacade), - logsManager: logsManager, - persistenceStorage: persistenceStorage, - ttlValidationService: ttlValidationService), - presentationManager: presentationManager, - persistenceStorage: persistenceStorage - ) - inappMessageEventSender = InappMessageEventSender(inAppMessagesManager: inAppMessagesManager) - - uuidDebugService = PasteboardUUIDDebugService( - notificationCenter: NotificationCenter.default, - currentDateProvider: { return Date() }, - pasteboard: UIPasteboard.general - ) - - pushValidator = MindboxPushValidator() - userVisitManager = UserVisitManager(persistenceStorage: persistenceStorage) - } -} - -class MBInstanceFactory: InstanceFactory { - private let persistenceStorage: PersistenceStorage - private let utilitiesFetcher: UtilitiesFetcher - private let databaseRepository: MBDatabaseRepository - - init( - persistenceStorage: PersistenceStorage, - utilitiesFetcher: UtilitiesFetcher, - databaseRepository: MBDatabaseRepository - ) { - self.persistenceStorage = persistenceStorage - self.utilitiesFetcher = utilitiesFetcher - self.databaseRepository = databaseRepository - } - - func makeNetworkFetcher() -> NetworkFetcher { - return MBNetworkFetcher( - utilitiesFetcher: utilitiesFetcher, - persistenceStorage: persistenceStorage - ) - } - - func makeEventRepository() -> EventRepository { - return MBEventRepository( - fetcher: makeNetworkFetcher(), - persistenceStorage: persistenceStorage - ) - } - - func makeTrackVisitManager() -> TrackVisitManager { - return TrackVisitManager(databaseRepository: databaseRepository) - } -} diff --git a/Mindbox/DI/Injections/InjectABTestUtilities.swift b/Mindbox/DI/Injections/InjectABTestUtilities.swift new file mode 100644 index 00000000..c9414226 --- /dev/null +++ b/Mindbox/DI/Injections/InjectABTestUtilities.swift @@ -0,0 +1,29 @@ +// +// InjectABTestUtilities.swift +// Mindbox +// +// Created by vailence on 21.06.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation + +extension MBContainer { + func registerABTestUtilities() -> Self { + register(ABTestDeviceMixer.self, scope: .transient) { + ABTestDeviceMixer() + } + + register(ABTestVariantsValidator.self, scope: .transient) { + ABTestVariantsValidator() + } + + register(ABTestValidator.self, scope: .transient) { + let sdkVersionValidator = DI.injectOrFail(SDKVersionValidator.self) + let abTestVariantValidator = DI.injectOrFail(ABTestVariantsValidator.self) + return ABTestValidator(sdkVersionValidator: sdkVersionValidator, variantsValidator: abTestVariantValidator) + } + + return self + } +} diff --git a/Mindbox/DI/Injections/InjectCore.swift b/Mindbox/DI/Injections/InjectCore.swift new file mode 100644 index 00000000..a9deb56d --- /dev/null +++ b/Mindbox/DI/Injections/InjectCore.swift @@ -0,0 +1,55 @@ +// +// InjectCore.swift +// Mindbox +// +// Created by vailence on 21.06.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation + +extension MBContainer { + func registerCore() -> Self { + register(CoreController.self) { + CoreController(persistenceStorage: DI.injectOrFail(PersistenceStorage.self), + utilitiesFetcher: DI.injectOrFail(UtilitiesFetcher.self), + databaseRepository: DI.injectOrFail(MBDatabaseRepository.self), + guaranteedDeliveryManager: DI.injectOrFail(GuaranteedDeliveryManager.self), + trackVisitManager: DI.injectOrFail(TrackVisitManager.self), + sessionManager: DI.injectOrFail(SessionManager.self), + inAppMessagesManager: DI.injectOrFail(InAppCoreManagerProtocol.self), + uuidDebugService: DI.injectOrFail(UUIDDebugService.self), + userVisitManager: DI.injectOrFail(UserVisitManagerProtocol.self)) + } + + register(GuaranteedDeliveryManager.self) { + let persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + let databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) + let eventRepository = DI.injectOrFail(EventRepository.self) + return GuaranteedDeliveryManager( + persistenceStorage: persistenceStorage, + databaseRepository: databaseRepository, + eventRepository: eventRepository) + } + + register(InAppConfigurationMapperProtocol.self) { + let inappFilterService = DI.injectOrFail(InappFilterProtocol.self) + let targetingChecker = DI.injectOrFail(InAppTargetingCheckerProtocol.self) + let dataFacade = DI.injectOrFail(InAppConfigurationDataFacadeProtocol.self) + return InAppConfigutationMapper(inappFilterService: inappFilterService, + targetingChecker: targetingChecker, + dataFacade: dataFacade) + } + + register(InAppConfigurationManagerProtocol.self) { + let persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + return InAppConfigurationManager( + inAppConfigAPI: InAppConfigurationAPI(persistenceStorage: persistenceStorage), + inAppConfigRepository: InAppConfigurationRepository(), + inAppConfigurationMapper: DI.injectOrFail(InAppConfigurationMapperProtocol.self), + persistenceStorage: persistenceStorage) + } + + return self + } +} diff --git a/Mindbox/DI/Injections/InjectInappTools.swift b/Mindbox/DI/Injections/InjectInappTools.swift new file mode 100644 index 00000000..2df6b8c9 --- /dev/null +++ b/Mindbox/DI/Injections/InjectInappTools.swift @@ -0,0 +1,101 @@ +// +// InjectInappTools.swift +// Mindbox +// +// Created by vailence on 05.07.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation + +extension MBContainer { + func registerInappTools() -> Self { + // Layers + register(LayerActionFilterProtocol.self) { + LayerActionFilterService() + } + + register(LayersSourceFilterProtocol.self) { + LayersSourceFilterService() + } + + register(LayersFilterProtocol.self) { + let actionFilter = DI.injectOrFail(LayerActionFilterProtocol.self) + let sourceFilter = DI.injectOrFail(LayersSourceFilterProtocol.self) + return LayersFilterService(actionFilter: actionFilter, sourceFilter: sourceFilter) + } + + // Elements + register(ElementsSizeFilterProtocol.self) { + ElementSizeFilterService() + } + + register(ElementsColorFilterProtocol.self) { + ElementsColorFilterService() + } + + register(ElementsPositionFilterProtocol.self) { + ElementsPositionFilterService() + } + + register(ElementsFilterProtocol.self) { + let sizeFilter = DI.injectOrFail(ElementsSizeFilterProtocol.self) + let colorFilter = DI.injectOrFail(ElementsColorFilterProtocol.self) + let positionFilter = DI.injectOrFail(ElementsPositionFilterProtocol.self) + return ElementsFilterService(sizeFilter: sizeFilter, positionFilter: positionFilter, colorFilter: colorFilter) + } + + // Content + register(ContentPositionFilterProtocol.self) { + ContentPositionFilterService() + } + + register(VariantFilterProtocol.self) { + let layersFilter = DI.injectOrFail(LayersFilterProtocol.self) + let elementsFilter = DI.injectOrFail(ElementsFilterProtocol.self) + let contentPositionFilter = DI.injectOrFail(ContentPositionFilterProtocol.self) + return VariantFilterService(layersFilter: layersFilter, elementsFilter: elementsFilter, contentPositionFilter: contentPositionFilter) + } + + register(InappFilterProtocol.self) { + let persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + let variantsFilter = DI.injectOrFail(VariantFilterProtocol.self) + let sdkVersionValidator = DI.injectOrFail(SDKVersionValidator.self) + return InappsFilterService(persistenceStorage: persistenceStorage, + variantsFilter: variantsFilter, + sdkVersionValidator: sdkVersionValidator) + } + + return self + } + + func registerInappPresentation() -> Self { + register(InAppMessagesTracker.self) { + let databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) + return InAppMessagesTracker(databaseRepository: databaseRepository) + } + + register(PresentationDisplayUseCase.self) { + let tracker = DI.injectOrFail(InAppMessagesTracker.self) + return PresentationDisplayUseCase(tracker: tracker) + } + + register(UseCaseFactoryProtocol.self) { + let tracker = DI.injectOrFail(InAppMessagesTracker.self) + return ActionUseCaseFactory(tracker: tracker) + } + + register(InAppActionHandlerProtocol.self) { + let actionUseCaseFactory = DI.injectOrFail(UseCaseFactoryProtocol.self) + return InAppActionHandler(actionUseCaseFactory: actionUseCaseFactory) + } + + register(InAppPresentationManagerProtocol.self) { + let actionHandler = DI.injectOrFail(InAppActionHandlerProtocol.self) + let displayUseCase = DI.injectOrFail(PresentationDisplayUseCase.self) + return InAppPresentationManager(actionHandler: actionHandler, displayUseCase: displayUseCase) + } + + return self + } +} diff --git a/Mindbox/DI/Injections/InjectReplaceable.swift b/Mindbox/DI/Injections/InjectReplaceable.swift new file mode 100644 index 00000000..9f570e92 --- /dev/null +++ b/Mindbox/DI/Injections/InjectReplaceable.swift @@ -0,0 +1,90 @@ +// +// InjectReplaceable.swift +// Mindbox +// +// Created by vailence on 21.06.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation +import UIKit + +extension MBContainer { + func registerReplaceableUtilities() -> Self { + register(UUIDDebugService.self) { + PasteboardUUIDDebugService( + notificationCenter: NotificationCenter.default, + currentDateProvider: { return Date() }, + pasteboard: UIPasteboard.general + ) + } + + register(UNAuthorizationStatusProviding.self, scope: .transient) { + UNAuthorizationStatusProvider() + } + + register(SDKVersionValidator.self) { + SDKVersionValidator(sdkVersionNumeric: Constants.Versions.sdkVersionNumeric) + } + + register(PersistenceStorage.self) { + let utilitiesFetcher = DI.injectOrFail(UtilitiesFetcher.self) + let defaults = UserDefaults(suiteName: utilitiesFetcher.applicationGroupIdentifier)! + let storage = MBPersistenceStorage(defaults: defaults) + storage.migrateShownInAppsIds() + return storage + } + + register(MBDatabaseRepository.self) { + let databaseLoader = DI.injectOrFail(DataBaseLoader.self) + let persistentContainer = try! databaseLoader.loadPersistentContainer() + return try! MBDatabaseRepository(persistentContainer: persistentContainer) + } + + register(ImageDownloadServiceProtocol.self, scope: .container) { + let persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + let imageDownloader = URLSessionImageDownloader(persistenceStorage: persistenceStorage) + return ImageDownloadService(imageDownloader: imageDownloader) + } + + register(NetworkFetcher.self) { + let utilitiesFetcher = DI.injectOrFail(UtilitiesFetcher.self) + let persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + return MBNetworkFetcher(utilitiesFetcher: utilitiesFetcher, persistenceStorage: persistenceStorage) + } + + register(InAppConfigurationDataFacadeProtocol.self) { + let segmentationSevice = DI.injectOrFail(SegmentationServiceProtocol.self) + let targetingChecker = DI.injectOrFail(InAppTargetingCheckerProtocol.self) + let imageService = DI.injectOrFail(ImageDownloadServiceProtocol.self) + let tracker = DI.injectOrFail(InAppMessagesTracker.self) + + return InAppConfigurationDataFacade(segmentationService: segmentationSevice, + targetingChecker: targetingChecker, + imageService: imageService, + tracker: tracker) + } + + register(SessionManager.self) { + let trackVisitManager = DI.injectOrFail(TrackVisitManager.self) + return MBSessionManager(trackVisitManager: trackVisitManager) + } + + register(SDKLogsManagerProtocol.self, scope: .transient) { + let persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + let eventRepository = DI.injectOrFail(EventRepository.self) + return SDKLogsManager(persistenceStorage: persistenceStorage, eventRepository: eventRepository) + } + + register(InAppCoreManagerProtocol.self) { + let configManager = DI.injectOrFail(InAppConfigurationManagerProtocol.self) + let presentationManager = DI.injectOrFail(InAppPresentationManagerProtocol.self) + let persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + return InAppCoreManager(configManager: configManager, + presentationManager: presentationManager, + persistenceStorage: persistenceStorage) + } + + return self + } +} diff --git a/Mindbox/DI/Injections/InjectUtilities.swift b/Mindbox/DI/Injections/InjectUtilities.swift new file mode 100644 index 00000000..c74123c2 --- /dev/null +++ b/Mindbox/DI/Injections/InjectUtilities.swift @@ -0,0 +1,85 @@ +// +// InjectUtilities.swift +// Mindbox +// +// Created by vailence on 21.06.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation + +extension MBContainer { + func registerUtilitiesServices() -> Self { + register(UtilitiesFetcher.self) { + MBUtilitiesFetcher() + } + + register(TimerManager.self) { + TimerManager() + } + + register(MigrationManagerProtocol.self, scope: .transient) { + let persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + return MigrationManager(persistenceStorage: persistenceStorage) + } + + register(UserVisitManagerProtocol.self, scope: .transient) { + let persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + return UserVisitManager(persistenceStorage: persistenceStorage) + } + + register(MindboxPushValidator.self, scope: .transient) { + MindboxPushValidator() + } + + register(InAppTargetingCheckerProtocol.self) { + let persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + return InAppTargetingChecker(persistenceStorage: persistenceStorage) + } + + register(DataBaseLoader.self) { + let utilitiesFetcher = DI.injectOrFail(UtilitiesFetcher.self) + return try! DataBaseLoader(applicationGroupIdentifier: utilitiesFetcher.applicationGroupIdentifier) + } + + register(VariantImageUrlExtractorServiceProtocol.self, scope: .transient) { + VariantImageUrlExtractorService() + } + + register(GeoServiceProtocol.self, scope: .transient) { + let networkFetcher = DI.injectOrFail(NetworkFetcher.self) + let targetingChecker = DI.injectOrFail(InAppTargetingCheckerProtocol.self) + return GeoService(fetcher: networkFetcher, + targetingChecker: targetingChecker) + } + + register(SegmentationServiceProtocol.self) { + let inAppTargetingChecker = DI.injectOrFail(InAppTargetingCheckerProtocol.self) + return SegmentationService(customerSegmentsAPI: .live, + targetingChecker: inAppTargetingChecker) + } + + register(EventRepository.self) { + let networkFetcher = DI.injectOrFail(NetworkFetcher.self) + let persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + return MBEventRepository(fetcher: networkFetcher, persistenceStorage: persistenceStorage) + } + + register(TrackVisitManager.self) { + let databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) + return TrackVisitManager(databaseRepository: databaseRepository) + } + + register(InappMessageEventSender.self, scope: .transient) { + let inAppMessagesManager = DI.injectOrFail(InAppCoreManagerProtocol.self) + return InappMessageEventSender(inAppMessagesManager: inAppMessagesManager) + } + + register(ClickNotificationManager.self) { + let databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) + return ClickNotificationManager(databaseRepository: databaseRepository) + } + + return self + } +} diff --git a/Mindbox/DI/MBContainer.swift b/Mindbox/DI/MBContainer.swift new file mode 100644 index 00000000..c303ea4a --- /dev/null +++ b/Mindbox/DI/MBContainer.swift @@ -0,0 +1,50 @@ +// +// MBContainer.swift +// Mindbox +// +// Created by vailence on 21.06.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation + +enum ObjectScope { + case transient + case container +} + +class MBContainer { + private var factories: [String: (ObjectScope, () -> Any)] = [:] + private var singletons: [String: Any] = [:] + + func register(_ type: T.Type, scope: ObjectScope = .container, factory: @escaping () -> T) { + let key = String(describing: type) + factories[key] = (scope, factory) + } + + func resolve(_ type: T.Type) -> T? { + let key = String(describing: type) + + if let (scope, factory) = factories[key] { + switch scope { + case .container: + if let instance = singletons[key] as? T { + return instance + } + let instance = factory() + singletons[key] = instance + return instance as? T + case .transient: + return factory() as? T + } + } + return nil + } + + func resolveOrFail(_ serviceType: T.Type) -> T { + guard let service = self.resolve(serviceType) else { + fatalError("Service \(serviceType) not found") + } + return service + } +} diff --git a/Mindbox/DI/MBInject.swift b/Mindbox/DI/MBInject.swift new file mode 100644 index 00000000..66c3c3d3 --- /dev/null +++ b/Mindbox/DI/MBInject.swift @@ -0,0 +1,64 @@ +// +// MBInject.swift +// Mindbox +// +// Created by vailence on 21.06.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation + +protocol ModuleInjecting { + func inject(_ serviceType: Dependency.Type) -> Dependency? + func injectOrFail(_ serviceType: Dependency.Type) -> Dependency +} + +extension MBContainer: ModuleInjecting { + func inject(_ serviceType: Dependency.Type) -> Dependency? { + return self.resolve(serviceType) + } + + func injectOrFail(_ serviceType: Dependency.Type) -> Dependency { + return self.resolveOrFail(serviceType) + } +} + +enum MBInject { + enum InjectionMode { + case standard + case test + } + + static var container: MBContainer = MBInject.buildDefaulContainer() + + static var mode: InjectionMode = .standard { + didSet { + switch mode { + case .standard: + container = MBInject.buildDefaulContainer() + case .test: + container = MBInject.buildTestContainer() + } + } + } + + fileprivate static func buildDefaulContainer() -> MBContainer { + let container = MBContainer() + return container + .registerCore() + .registerUtilitiesServices() + .registerABTestUtilities() + .registerReplaceableUtilities() + .registerInappTools() + .registerInappPresentation() + } + + public static var buildTestContainer: () -> MBContainer = { + let container = MBContainer() + return container + } +} + +var DI: ModuleInjecting { + return MBInject.container +} diff --git a/Mindbox/InAppMessages/Configuration/InAppConfigurationManager.swift b/Mindbox/InAppMessages/Configuration/InAppConfigurationManager.swift index c898ae6d..b47f79a3 100644 --- a/Mindbox/InAppMessages/Configuration/InAppConfigurationManager.swift +++ b/Mindbox/InAppMessages/Configuration/InAppConfigurationManager.swift @@ -30,26 +30,20 @@ class InAppConfigurationManager: InAppConfigurationManagerProtocol { private var inapp: InAppFormData? private var rawConfigurationResponse: ConfigResponse! private let inAppConfigRepository: InAppConfigurationRepository - private let inAppConfigurationMapper: InAppConfigutationMapper + private let inAppConfigurationMapper: InAppConfigurationMapperProtocol private let inAppConfigAPI: InAppConfigurationAPI - private let logsManager: SDKLogsManagerProtocol private let persistenceStorage: PersistenceStorage - private let ttlValidationService: TTLValidationProtocol init( inAppConfigAPI: InAppConfigurationAPI, inAppConfigRepository: InAppConfigurationRepository, - inAppConfigurationMapper: InAppConfigutationMapper, - logsManager: SDKLogsManagerProtocol, - persistenceStorage: PersistenceStorage, - ttlValidationService: TTLValidationProtocol + inAppConfigurationMapper: InAppConfigurationMapperProtocol, + persistenceStorage: PersistenceStorage ) { self.inAppConfigRepository = inAppConfigRepository self.inAppConfigurationMapper = inAppConfigurationMapper self.inAppConfigAPI = inAppConfigAPI - self.logsManager = logsManager self.persistenceStorage = persistenceStorage - self.ttlValidationService = ttlValidationService } weak var delegate: InAppConfigurationDelegate? @@ -95,7 +89,7 @@ class InAppConfigurationManager: InAppConfigurationManagerProtocol { saveConfigToCache(data) setConfigPrepared(config) setupSettingsFromConfig(config.settings) - if let monitoring = config.monitoring { + if let monitoring = config.monitoring, let logsManager = DI.inject(SDKLogsManagerProtocol.self) { logsManager.sendLogs(logs: monitoring.logs) } } catch { @@ -120,6 +114,7 @@ class InAppConfigurationManager: InAppConfigurationManagerProtocol { return } + let ttlValidationService = createTTLValidationService() if ttlValidationService.needResetInapps(config: cachedConfig) { cachedConfig.inapps = nil Logger.common(message: "[TTL] Resetting in-app due to the expiration of the current configuration.") @@ -173,4 +168,8 @@ class InAppConfigurationManager: InAppConfigurationManagerProtocol { SessionTemporaryStorage.shared.operationsFromSettings.insert(viewProduct.systemName.lowercased()) } } + + private func createTTLValidationService() -> TTLValidationProtocol { + return TTLValidationService(persistenceStorage: self.persistenceStorage) + } } diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/InAppConfigutationMapper.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/InAppConfigutationMapper.swift index 9d212785..6f1d14f8 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/InAppConfigutationMapper.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/InAppConfigutationMapper.swift @@ -24,19 +24,15 @@ final class InAppConfigutationMapper: InAppConfigurationMapperProtocol { let dataFacade: InAppConfigurationDataFacadeProtocol private let inappFilterService: InappFilterProtocol - private let urlExtractorService: VariantImageUrlExtractorServiceProtocol - private var validInapps: [InApp] = [] private var savedEventForTargeting: ApplicationEvent? private var shownInnapId = "" init(inappFilterService: InappFilterProtocol, targetingChecker: InAppTargetingCheckerProtocol, - urlExtractorService: VariantImageUrlExtractorServiceProtocol, dataFacade: InAppConfigurationDataFacadeProtocol) { self.inappFilterService = inappFilterService self.targetingChecker = targetingChecker - self.urlExtractorService = urlExtractorService self.dataFacade = dataFacade } @@ -183,7 +179,8 @@ final class InAppConfigutationMapper: InAppConfigurationMapperProtocol { continue } - let imageValues = self.urlExtractorService.extractImageURL(from: inapp.content) + let urlExtractorService = DI.injectOrFail(VariantImageUrlExtractorServiceProtocol.self) + let imageValues = urlExtractorService.extractImageURL(from: inapp.content) Logger.common(message: "Starting in-app processing. [ID]: \(inapp.inAppId)", level: .debug, category: .inAppMessages) for imageValue in imageValues { diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InAppConfigurationDataFacade.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InAppConfigurationDataFacade.swift index 647219aa..fe176648 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InAppConfigurationDataFacade.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InAppConfigurationDataFacade.swift @@ -19,18 +19,16 @@ protocol InAppConfigurationDataFacadeProtocol { class InAppConfigurationDataFacade: InAppConfigurationDataFacadeProtocol { - let geoService: GeoServiceProtocol + var geoService: GeoServiceProtocol? let segmentationService: SegmentationServiceProtocol var targetingChecker: InAppTargetingCheckerProtocol let imageService: ImageDownloadServiceProtocol let tracker: InappTargetingTrackProtocol - init(geoService: GeoServiceProtocol, - segmentationService: SegmentationServiceProtocol, + init(segmentationService: SegmentationServiceProtocol, targetingChecker: InAppTargetingCheckerProtocol, imageService: ImageDownloadServiceProtocol, tracker: InappTargetingTrackProtocol) { - self.geoService = geoService self.segmentationService = segmentationService self.targetingChecker = targetingChecker self.imageService = imageService @@ -86,9 +84,11 @@ private extension InAppConfigurationDataFacade { if targetingChecker.context.isNeedGeoRequest && !SessionTemporaryStorage.shared.geoRequestCompleted { dispatchGroup.enter() - geoService.geoRequest { model in + geoService = DI.injectOrFail(GeoServiceProtocol.self) + geoService?.geoRequest { model in self.targetingChecker.geoModels = model self.dispatchGroup.leave() + self.geoService = nil } } } diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/InappFilter.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/InappFilter.swift index 0fb6dac1..56509115 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/InappFilter.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/InappFilter.swift @@ -21,17 +21,13 @@ final class InappsFilterService: InappFilterProtocol { var shownInAppDictionary: [String: Date] = [:] private let persistenceStorage: PersistenceStorage - private let abTestDeviceMixer: ABTestDeviceMixer private let variantsFilter: VariantFilterProtocol private let sdkVersionValidator: SDKVersionValidator - private let frequencyValidator: InappFrequencyValidator - init(persistenceStorage: PersistenceStorage, abTestDeviceMixer: ABTestDeviceMixer, variantsFilter: VariantFilterProtocol, sdkVersionValidator: SDKVersionValidator, frequencyValidator: InappFrequencyValidator) { + init(persistenceStorage: PersistenceStorage, variantsFilter: VariantFilterProtocol, sdkVersionValidator: SDKVersionValidator) { self.persistenceStorage = persistenceStorage - self.abTestDeviceMixer = abTestDeviceMixer self.variantsFilter = variantsFilter self.sdkVersionValidator = sdkVersionValidator - self.frequencyValidator = frequencyValidator } func filter(inapps: [InAppDTO]?, abTests: [ABTest]?) -> [InApp] { @@ -54,7 +50,6 @@ final class InappsFilterService: InappFilterProtocol { private extension InappsFilterService { func filterInappsBySDKVersion(_ inapps: [InAppDTO]) -> [InAppDTO] { let inapps = inapps - let filteredInapps = inapps.filter { sdkVersionValidator.isValid(item: $0.sdkVersion) } @@ -93,6 +88,7 @@ private extension InappsFilterService { } var result: [InApp] = responseInapps + let abTestDeviceMixer = DI.injectOrFail(ABTestDeviceMixer.self) for abTest in abTests { guard let uuid = UUID(uuidString: persistenceStorage.deviceUUID ?? "" ), @@ -159,11 +155,16 @@ private extension InappsFilterService { Logger.common(message: "Shown in-apps ids: [\(shownInAppDictionary.keys)]", level: .info, category: .inAppMessages) let filteredInapps = inapps.filter { Logger.common(message: "[Inapp frequency] Start checking frequency of inapp with id = \($0.id)", level: .debug, category: .inAppMessages) - let result = self.frequencyValidator.isValid(item: $0) + let frequencyValidator = self.createFrequencyValidator() + let result = frequencyValidator.isValid(item: $0) Logger.common(message: "[Inapp frequency] Finish checking frequency of inapp with id = \($0.id)", level: .debug, category: .inAppMessages) return result } return filteredInapps } + + private func createFrequencyValidator() -> InappFrequencyValidator { + InappFrequencyValidator(persistenceStorage: persistenceStorage) + } } diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/SegmentationService.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/SegmentationService.swift index 041474c5..4acd3826 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/SegmentationService.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/SegmentationService.swift @@ -15,7 +15,7 @@ protocol SegmentationServiceProtocol { } class SegmentationService: SegmentationServiceProtocol { - let customerSegmentsAPI: CustomerSegmentsAPI + var customerSegmentsAPI: CustomerSegmentsAPI var targetingChecker: InAppTargetingCheckerProtocol init(customerSegmentsAPI: CustomerSegmentsAPI, diff --git a/Mindbox/InAppMessages/Models/Config/ConfigResponse.swift b/Mindbox/InAppMessages/Models/Config/ConfigResponse.swift index 35cbcf3d..f7406dcc 100644 --- a/Mindbox/InAppMessages/Models/Config/ConfigResponse.swift +++ b/Mindbox/InAppMessages/Models/Config/ConfigResponse.swift @@ -25,9 +25,10 @@ struct ConfigResponse: Decodable { monitoring = ConfigResponse.decodeIfPresent(container, forKey: .monitoring, errorDesc: "Cannot decode Monitoring") settings = ConfigResponse.decodeIfPresent(container, forKey: .settings, errorDesc: "Cannot decode Settings") + let abTestValidator = DI.injectOrFail(ABTestValidator.self) if let decodedAbtests: [ABTest] = ConfigResponse.decodeIfPresent(container, forKey: .abtests, errorDesc: "Cannot decode ABTests"), decodedAbtests.allSatisfy({ - ABTestValidator(sdkVersionValidator: SDKVersionValidator(sdkVersionNumeric: Constants.Versions.sdkVersionNumeric)).isValid(item: $0) + abTestValidator.isValid(item: $0) }) { abtests = decodedAbtests } else { diff --git a/Mindbox/InAppMessages/Presentation/ActionHandler/InappActionHandler.swift b/Mindbox/InAppMessages/Presentation/ActionHandler/InappActionHandler.swift index f5d855d4..22c9485e 100644 --- a/Mindbox/InAppMessages/Presentation/ActionHandler/InappActionHandler.swift +++ b/Mindbox/InAppMessages/Presentation/ActionHandler/InappActionHandler.swift @@ -19,9 +19,9 @@ protocol InAppActionHandlerProtocol { final class InAppActionHandler: InAppActionHandlerProtocol { - private let actionUseCaseFactory: ActionUseCaseFactory + private let actionUseCaseFactory: UseCaseFactoryProtocol - init(actionUseCaseFactory: ActionUseCaseFactory) { + init(actionUseCaseFactory: UseCaseFactoryProtocol) { self.actionUseCaseFactory = actionUseCaseFactory } diff --git a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/PushPermissionActionUseCase.swift b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/PushPermissionActionUseCase.swift index f2a98d9c..cdf24aca 100644 --- a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/PushPermissionActionUseCase.swift +++ b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/PushPermissionActionUseCase.swift @@ -83,11 +83,7 @@ final class PushPermissionActionUseCase: PresentationActionUseCaseProtocol { } if UIApplication.shared.canOpenURL(settingsUrl) { - if #available(iOS 10.0, *) { - UIApplication.shared.open(settingsUrl) - } else { - UIApplication.shared.openURL(settingsUrl) - } + UIApplication.shared.open(settingsUrl) Logger.common(message: "Navigated to app settings for notification permission.", level: .debug, category: .inAppMessages) } diff --git a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/SnackbarPresentationStrategy.swift b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/SnackbarPresentationStrategy.swift index 97388213..afe0b83c 100644 --- a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/SnackbarPresentationStrategy.swift +++ b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/SnackbarPresentationStrategy.swift @@ -97,12 +97,11 @@ final class SnackbarPresentationStrategy: PresentationStrategyProtocol { private extension SnackbarPresentationStrategy { func getSafeAreaInset(gravity: GravityVerticalType) -> CGFloat { var safeAreaInset: CGFloat = 0 - if #available(iOS 11, *) { - if gravity == .bottom { - safeAreaInset = window?.safeAreaInsets.bottom ?? 0 - } else if gravity == .top { - safeAreaInset = window?.safeAreaInsets.top ?? 0 - } + + if gravity == .bottom { + safeAreaInset = window?.safeAreaInsets.bottom ?? 0 + } else if gravity == .top { + safeAreaInset = window?.safeAreaInsets.top ?? 0 } return safeAreaInset diff --git a/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarView.swift b/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarView.swift index 2258affe..09937358 100644 --- a/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarView.swift +++ b/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarView.swift @@ -20,14 +20,10 @@ class SnackbarView: UIView { private let animationTime: TimeInterval private var safeAreaInset: (top: CGFloat, bottom: CGFloat) { - if #available(iOS 11.0, *) { - return ( - window?.safeAreaInsets.top ?? Constants.defaultSafeAreaTopInset, - window?.safeAreaInsets.bottom ?? Constants.defaultSafeAreaBottomInset - ) - } else { - return (top: Constants.defaultSafeAreaTopInset, bottom: Constants.defaultSafeAreaBottomInset) - } + ( + top: window?.safeAreaInsets.top ?? Constants.defaultSafeAreaTopInset, + bottom: window?.safeAreaInsets.bottom ?? Constants.defaultSafeAreaBottomInset + ) } private enum Constants { diff --git a/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarViewController.swift b/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarViewController.swift index 77c4cc0a..c6341c87 100644 --- a/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarViewController.swift +++ b/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarViewController.swift @@ -48,7 +48,6 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { static let screenPart: CGFloat = 3.0 static let oneThirdScreenHeight: CGFloat = UIScreen.main.bounds.height / Constants.screenPart static let defaultEdgeConstraint: CGFloat = .zero - static let initialSafeAreaOffset: CGFloat = .zero static let noTopOffset: CGFloat = .zero static let noSize: CGSize = .zero } @@ -202,21 +201,11 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { } private func setupLayoutConstraints(with height: CGFloat) { - if #available(iOS 11.0, *) { - Logger.common(message: "SnackbarViewController setupLayoutConstraints iOS 11+.") - NSLayoutConstraint.activate([ - snackbarView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - snackbarView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - snackbarView.heightAnchor.constraint(equalToConstant: height), - ]) - } else { - Logger.common(message: "SnackbarViewController setupLayoutConstraints iOS 10.") - NSLayoutConstraint.activate([ - snackbarView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - snackbarView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - snackbarView.heightAnchor.constraint(equalToConstant: height), - ]) - } + NSLayoutConstraint.activate([ + snackbarView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + snackbarView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + snackbarView.heightAnchor.constraint(equalToConstant: height), + ]) } } @@ -253,10 +242,7 @@ class TopSnackbarViewController: SnackbarViewController { } override func setViewFrame(with height: CGFloat) { - var safeAreaTopOffset: CGFloat = Constants.initialSafeAreaOffset - if #available(iOS 11.0, *) { - safeAreaTopOffset = view.safeAreaInsets.top - } + let safeAreaTopOffset: CGFloat = view.safeAreaInsets.top let finalHeight = height + safeAreaTopOffset @@ -266,12 +252,7 @@ class TopSnackbarViewController: SnackbarViewController { } override func setupEdgeConstraint(with height: CGFloat) { - if #available(iOS 11.0, *) { - edgeConstraint = snackbarView.topAnchor.constraint(equalTo: view.topAnchor, constant: -height) - } else { - edgeConstraint = snackbarView.topAnchor.constraint(equalTo: view.topAnchor, constant: -height) - } - + edgeConstraint = snackbarView.topAnchor.constraint(equalTo: view.topAnchor, constant: -height) edgeConstraint?.isActive = true } } @@ -284,10 +265,7 @@ class BottomSnackbarViewController: SnackbarViewController { override func setViewFrame(with height: CGFloat) { let screenHeight = UIScreen.main.bounds.height - var safeAreaBottomOffset: CGFloat = Constants.initialSafeAreaOffset - if #available(iOS 11.0, *) { - safeAreaBottomOffset = view.safeAreaInsets.bottom - } + let safeAreaBottomOffset: CGFloat = view.safeAreaInsets.bottom let finalHeight = height + safeAreaBottomOffset @@ -298,14 +276,7 @@ class BottomSnackbarViewController: SnackbarViewController { } override func setupEdgeConstraint(with height: CGFloat) { - if #available(iOS 11.0, *) { - Logger.common(message: "SnackbarViewController setupEdgeConstraint iOS 11+.") - edgeConstraint = snackbarView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: height) - } else { - Logger.common(message: "SnackbarViewController setupEdgeConstraint iOS 10.") - edgeConstraint = snackbarView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: height) - } - + edgeConstraint = snackbarView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: height) edgeConstraint?.isActive = true } } diff --git a/Mindbox/Info.plist b/Mindbox/Info.plist index 87afad09..5660e5dd 100644 --- a/Mindbox/Info.plist +++ b/Mindbox/Info.plist @@ -17,6 +17,6 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 5615 + 5844 diff --git a/Mindbox/Mindbox.swift b/Mindbox/Mindbox.swift index 3a2b73b3..dedcb431 100644 --- a/Mindbox/Mindbox.swift +++ b/Mindbox/Mindbox.swift @@ -38,17 +38,13 @@ public class Mindbox: NSObject { private var persistenceStorage: PersistenceStorage? private var utilitiesFetcher: UtilitiesFetcher? private var guaranteedDeliveryManager: GuaranteedDeliveryManager? - private var notificationStatusProvider: UNAuthorizationStatusProviding? private var databaseRepository: MBDatabaseRepository? private var inAppMessagesManager: InAppCoreManagerProtocol? private var sessionTemporaryStorage: SessionTemporaryStorage? - private var inappMessageEventSender: InappMessageEventSender? - private var pushValidator: MindboxPushValidator? private let queue = DispatchQueue(label: "com.Mindbox.initialization", attributes: .concurrent) var coreController: CoreController? - var container: DependencyContainer? /** A set of methods that sdk uses to notify you of its behavior. @@ -237,8 +233,10 @@ public class Mindbox: NSObject { */ public func pushClicked(uniqueKey: String, buttonUniqueKey: String? = nil) { - guard let container = container else { return } - let tracker = ClickNotificationManager(databaseRepository: container.databaseRepository) + guard let tracker = DI.inject(ClickNotificationManager.self) else { + return + } + do { try tracker.track(uniqueKey: uniqueKey, buttonUniqueKey: buttonUniqueKey) Logger.common(message: "Track Click", level: .info, category: .notification) @@ -320,7 +318,8 @@ public class Mindbox: NSObject { let operationBodyJSON = BodyEncoder(encodable: operationBody).body let customEvent = CustomEvent(name: operationSystemName, payload: operationBodyJSON) let event = Event(type: .syncEvent, body: BodyEncoder(encodable: customEvent).body) - container?.instanceFactory.makeEventRepository().send(type: OperationResponse.self, event: event, completion: completion) + let eventRepository = DI.injectOrFail(EventRepository.self) + eventRepository.send(type: OperationResponse.self, event: event, completion: completion) sendEventToInAppMessagesIfNeeded(operationSystemName, jsonString: operationBodyJSON) Logger.common(message: "Track executeSyncOperation", level: .info, category: .notification) } @@ -349,7 +348,8 @@ public class Mindbox: NSObject { } let customEvent = CustomEvent(name: operationSystemName, payload: json) let event = Event(type: .syncEvent, body: BodyEncoder(encodable: customEvent).body) - container?.instanceFactory.makeEventRepository().send(type: OperationResponse.self, event: event, completion: completion) + let eventRepository = DI.injectOrFail(EventRepository.self) + eventRepository.send(type: OperationResponse.self, event: event, completion: completion) sendEventToInAppMessagesIfNeeded(operationSystemName, jsonString: json) Logger.common(message: "Track executeSyncOperation", level: .info, category: .notification) } @@ -378,7 +378,8 @@ public class Mindbox: NSObject { let operationBodyJSON = BodyEncoder(encodable: operationBody).body let customEvent = CustomEvent(name: operationSystemName, payload: operationBodyJSON) let event = Event(type: .syncEvent, body: BodyEncoder(encodable: customEvent).body) - container?.instanceFactory.makeEventRepository().send(type: P.self, event: event, completion: completion) + let eventRepository = DI.injectOrFail(EventRepository.self) + eventRepository.send(type: P.self, event: event, completion: completion) sendEventToInAppMessagesIfNeeded(operationSystemName, jsonString: operationBodyJSON) Logger.common(message: "Track executeSyncOperation", level: .info, category: .notification) } @@ -420,8 +421,9 @@ public class Mindbox: NSObject { */ public func pushClicked(response: UNNotificationResponse) { - guard let container = container else { return } - let tracker = ClickNotificationManager(databaseRepository: container.databaseRepository) + guard let tracker = DI.inject(ClickNotificationManager.self) else { + return + } do { try tracker.track(response: response) Logger.common(message: "Track Click", level: .info, category: .notification) @@ -438,8 +440,7 @@ public class Mindbox: NSObject { */ public func track(_ type: TrackVisitType) { - guard let container = container else { return } - let tracker = container.instanceFactory.makeTrackVisitManager() + let tracker = DI.injectOrFail(TrackVisitManager.self) do { try tracker.track(type) } catch { @@ -455,8 +456,7 @@ public class Mindbox: NSObject { */ public func track(data: TrackVisitData) { - guard let container = container else { return } - let tracker = container.instanceFactory.makeTrackVisitManager() + let tracker = DI.injectOrFail(TrackVisitManager.self) do { try tracker.track(data: data) } catch { @@ -519,7 +519,8 @@ public class Mindbox: NSObject { - Returns: A Boolean value indicating whether the notification is related to Mindbox. */ public func isMindboxPush(userInfo: [AnyHashable: Any]) -> Bool { - return pushValidator?.isValid(item: userInfo) ?? false + let pushValidator = DI.injectOrFail(MindboxPushValidator.self) + return pushValidator.isValid(item: userInfo) } /** @@ -541,47 +542,26 @@ public class Mindbox: NSObject { private override init() { super.init() - queue.sync(flags: .barrier) { - do { - let container = try DependencyProvider() - self.container = container - self.assembly(with: container) - Logger.common(message: "Did assembly dependencies with container", level: .info, category: .general) - } catch { - Logger.common(message: "Did fail to assembly dependencies with container with error: \(error.localizedDescription)", level: .fault, category: .general) - self.initError = error - } - self.persistenceStorage?.storeToFileBackgroundExecution() - } + self.assembly() + self.persistenceStorage?.storeToFileBackgroundExecution() } - func assembly(with container: DependencyContainer) { - persistenceStorage = container.persistenceStorage - utilitiesFetcher = container.utilitiesFetcher - guaranteedDeliveryManager = container.guaranteedDeliveryManager - notificationStatusProvider = container.authorizationStatusProvider - databaseRepository = container.databaseRepository - inAppMessagesManager = container.inAppMessagesManager + func assembly() { + persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + utilitiesFetcher = DI.injectOrFail(UtilitiesFetcher.self) + guaranteedDeliveryManager = DI.injectOrFail(GuaranteedDeliveryManager.self) + databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) + inAppMessagesManager = DI.injectOrFail(InAppCoreManagerProtocol.self) inAppMessagesDelegate = self - inappMessageEventSender = container.inappMessageEventSender - pushValidator = container.pushValidator - - coreController = CoreController( - persistenceStorage: container.persistenceStorage, - utilitiesFetcher: container.utilitiesFetcher, - notificationStatusProvider: container.authorizationStatusProvider, - databaseRepository: container.databaseRepository, - guaranteedDeliveryManager: container.guaranteedDeliveryManager, - trackVisitManager: container.instanceFactory.makeTrackVisitManager(), - sessionManager: container.sessionManager, - inAppMessagesManager: container.inAppMessagesManager, - uuidDebugService: container.uuidDebugService, - userVisitManager: container.userVisitManager - ) + coreController = DI.injectOrFail(CoreController.self) } private func sendEventToInAppMessagesIfNeeded(_ operationSystemName: String, jsonString: String?) { - inappMessageEventSender?.sendEventIfEnabled(operationSystemName, jsonString: jsonString) + guard let inappMessageEventSender = DI.inject(InappMessageEventSender.self) else { + return + } + + inappMessageEventSender.sendEventIfEnabled(operationSystemName, jsonString: jsonString) } @objc private func resetShownInApps() { diff --git a/Mindbox/PersistenceStorage/MBPersistenceStorage.swift b/Mindbox/PersistenceStorage/MBPersistenceStorage.swift index af3a213f..dcb11040 100644 --- a/Mindbox/PersistenceStorage/MBPersistenceStorage.swift +++ b/Mindbox/PersistenceStorage/MBPersistenceStorage.swift @@ -236,23 +236,21 @@ class MBPersistenceStorage: PersistenceStorage { } } + @UserDefaultsWrapper(key: .versionCodeForMigration, defaultValue: 0) + var versionCodeForMigration: Int? + @UserDefaultsWrapper(key: .configDownloadDate, defaultValue: nil) private var configDownloadDateString: String? { didSet { onDidChange?() } } - - func reset() { - installationDate = nil - deviceUUID = nil - installationId = nil - apnsToken = nil - apnsTokenSaveDate = nil - deprecatedEventsRemoveDate = nil - configuration = nil - isNotificationsEnabled = nil + + func softReset() { configDownloadDate = nil + shownInappsDictionary = nil + handledlogRequestIds = nil + userVisitCount = 0 resetBackgroundExecutions() } @@ -277,6 +275,24 @@ class MBPersistenceStorage: PersistenceStorage { } } +// MARK: - Functions for unit testing + +extension MBPersistenceStorage { + + func reset() { + installationDate = nil + deviceUUID = nil + installationId = nil + apnsToken = nil + apnsTokenSaveDate = nil + deprecatedEventsRemoveDate = nil + configuration = nil + isNotificationsEnabled = nil + configDownloadDate = nil + resetBackgroundExecutions() + } +} + struct BackgroudExecution: Codable { let taskID: String @@ -311,6 +327,7 @@ extension MBPersistenceStorage { case needUpdateInfoOnce = "MBPersistenceStorage-needUpdateInfoOnce" case userVisitCount = "MBPersistenceStorage-userVisitCount" case configDownloadDate = "MBPersistenceStorage-configDownloadDate" + case versionCodeForMigration = "MBPersistenceStorage-versionCodeForMigration" } private let key: Key diff --git a/Mindbox/PersistenceStorage/PersistenceStorage.swift b/Mindbox/PersistenceStorage/PersistenceStorage.swift index 8478e191..c54bfd85 100644 --- a/Mindbox/PersistenceStorage/PersistenceStorage.swift +++ b/Mindbox/PersistenceStorage/PersistenceStorage.swift @@ -40,8 +40,6 @@ protocol PersistenceStorage: AnyObject { func setBackgroundExecution(_ value: BackgroudExecution) - func reset() - func resetBackgroundExecutions() func storeToFileBackgroundExecution() @@ -54,5 +52,23 @@ protocol PersistenceStorage: AnyObject { var userVisitCount: Int? { get set } + /// The date when the InApps configuration was last downloaded. + /// It is optional and can be set to `nil` if the configuration has not yet been downloaded yet or reset. var configDownloadDate: Date? { get set } + + /// The version code used to track the current state of migrations. + /// This value is compared to `Constants.Migration.sdkVersionCode` to determine + /// if migrations need to be performed. If a migration fails, and the `versionCodeForMigration` + /// does not match the `Constants.Migration.sdkVersionCode`, a `softReset()` is performed to + /// ensure that the system remains in a consistent state. + var versionCodeForMigration: Int? { get set } + + /// Clears certain parts of the persistence storage to revert the system to a stable state. + func softReset() + + + // MARK: - Functions for testing + + /// Clears most parts of the persistence storage. It is used in unit tests. + func reset() } diff --git a/Mindbox/Utilities/Constants.swift b/Mindbox/Utilities/Constants.swift index 9a5c74ee..16cff023 100644 --- a/Mindbox/Utilities/Constants.swift +++ b/Mindbox/Utilities/Constants.swift @@ -29,7 +29,17 @@ enum Constants { } + /// Mobile configuration sdkVersion. enum Versions { + static let sdkVersionNumeric = 9 + + } + + /// Constants used for migration management. + enum Migration { + + /// The current SDK version code used for comparison in migrations. + static let sdkVersionCode = 0 } } diff --git a/Mindbox/Utilities/Migrations/MigrationAbstractions/BaseMigration.swift b/Mindbox/Utilities/Migrations/MigrationAbstractions/BaseMigration.swift new file mode 100644 index 00000000..e40cd590 --- /dev/null +++ b/Mindbox/Utilities/Migrations/MigrationAbstractions/BaseMigration.swift @@ -0,0 +1,68 @@ +// +// BaseMigration.swift +// Mindbox +// +// Created by Sergei Semko on 7/29/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation + +/// A base class that implements the `MigrationProtocol` and provides common functionality +/// for all migration objects. Subclasses must override specific properties and methods to +/// define the migration behavior. +/// +/// This class manages the persistence storage and provides a template method for performing +/// migrations, including automatic version incrementing. Subclasses must override and implement +/// the `performMigration` method and the `description`, `isNeeded`, and `version` properties. +/// +/// - Important: The `run` method is marked as `final` and cannot be overridden by subclasses. Implement `performMigration` instead. +/// - Note: When creating a migration based on `BaseMigration` (with auto-increment of `versionCodeForMigration`), +/// make sure that you also increment the `MigrationConstant.sdkVersionCode`. +class BaseMigration: MigrationProtocol { + + /// The persistence storage used to manage the migration state. + var persistenceStorage: PersistenceStorage = DI.injectOrFail(PersistenceStorage.self) + + /// Performs the specific migration logic. + /// Subclasses must implement this method to define the actual migration steps. + /// - Throws: An error if the migration fails. + func performMigration() throws { + fatalError("Subclasses must implement the `performMigration` method without calling super.") + } + + // MARK: MigrationProtocol + + /// A textual description of the migration. + /// Subclasses must override this property. + var description: String { + fatalError("Subclasses must implement the `description` property.") + } + + /// A condition that determines whether the migration is required. + /// Subclasses must override this property. + var isNeeded: Bool { + fatalError("Subclasses must implement the `isNeeded` property.") + } + + /// The version number of the migration, which can be used to sort and determine + /// whether to apply the migration. + /// Subclasses must override this property. + var version: Int { + fatalError("Subclasses must implement the `version` property.") + } + + /// Executes the migration by calling the `performMigration` method. If the migration is + /// successful, it increments the version in the `persistenceStorage`. + /// This method is `final` and cannot be overridden by subclasses. + /// - Throws: An error if the migration fails. + final func run() throws { + do { + try performMigration() + let versionCode = persistenceStorage.versionCodeForMigration ?? 0 + persistenceStorage.versionCodeForMigration = versionCode + 1 + } catch { + throw error + } + } +} diff --git a/Mindbox/Utilities/Migrations/MigrationAbstractions/MigrationProtocol.swift b/Mindbox/Utilities/Migrations/MigrationAbstractions/MigrationProtocol.swift new file mode 100644 index 00000000..db7bab3d --- /dev/null +++ b/Mindbox/Utilities/Migrations/MigrationAbstractions/MigrationProtocol.swift @@ -0,0 +1,34 @@ +// +// MigrationProtocol.swift +// Mindbox +// +// Created by Sergei Semko on 8/1/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation + +/// A protocol that defines the requirements for a migration object. +/// Migration objects are responsible for performing specific migrations, +/// which may include but are not limited to migrations in a persistent storage system. +/// Each migration object provides a description, a condition that determines whether the migration +/// is required, and a version number that is used for sorting and comparison. The migration itself +/// is performed by the `run` method, which may throw errors if the migration fails. +protocol MigrationProtocol { + + /// A textual description of the migration. + var description: String { get } + + /// A condition that determines whether the migration is required. + /// - Note: Make sure that `isNeeded` returns `true` in cases where the migration is based on `MigrationConstant`. + /// If this is not the case, and the migration fails, performing a `softReset` is considered acceptable. + var isNeeded: Bool { get } + + /// The version number of the migration, which can be used to sort and determine + /// whether to apply the migration. + var version: Int { get } + + /// Performs the migration. If the migration fails, an error is thrown. + /// - Throws: An error if the migration fails. + func run() throws +} diff --git a/Mindbox/Utilities/Migrations/MigrationManager/MigrationManager.swift b/Mindbox/Utilities/Migrations/MigrationManager/MigrationManager.swift new file mode 100644 index 00000000..ad79ef00 --- /dev/null +++ b/Mindbox/Utilities/Migrations/MigrationManager/MigrationManager.swift @@ -0,0 +1,128 @@ +// +// MigrationManager.swift +// Mindbox +// +// Created by Sergei Semko on 7/29/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation +import MindboxLogger + +/// A class responsible for managing and executing migrations. +/// It keeps a list of migrations, checks if they are needed, and runs them in the correct order. +final class MigrationManager { + + /// The list of migration objects that conform to `MigrationProtocol`. + private var migrations: [MigrationProtocol] + + /// The local sdk version code used to determine whether migrations need to be performed. + /// By default, it is set to `Constants.Migration.sdkVersionCode`. + /// Changing this value in `convenience init` is used when writing tests. + private var localSdkVersionCode: Int + + /// The persistence storage used for managing various application states, + /// including migration state and other critical data. It provides methods + /// for performing resets and managing configurations. + private var persistenceStorage: PersistenceStorage + + /// Initializes the migration manager with the provided persistence storage. + /// - Parameter persistenceStorage: The persistence storage used for managing various application states, + /// including migration state and other critical data. It provides methods + /// for performing resets and managing configurations. + /// + /// - Attention: When adding new migrations, make sure they are added to the `migrations` array + /// in the correct order. The order of the migrations in this array determines the order + /// in which they are performed. + /// Currently, migrations are sorted by their internal version (see `MigrationProtocol.version`). This may change in the future. + /// + /// Example: + /// ``` + /// self.migrations = [ + /// Migration_1(), + /// Migration_2(), + /// Migration_3(), + /// Migration_4(), + /// // Add new migrations here + /// ] + /// ``` + init(persistenceStorage: PersistenceStorage) { + self.persistenceStorage = persistenceStorage + self.localSdkVersionCode = Constants.Migration.sdkVersionCode + + self.migrations = [ + + ] + } +} + +// MARK: - MigrationManagerProtocol + +extension MigrationManager: MigrationManagerProtocol { + + /// Performs any necessary migrations. If this is the first installation, it sets the migration version code without performing migrations. + /// If any migration that involves `Constants.Migration.sdkVersionCode` and `persistenceStorage.versionCodeForMigration` fails, + /// a soft reset is performed on the persistence storage to ensure that the system remains in a consistent state. + func migrate() { + + guard persistenceStorage.isInstalled else { + let firstInstallationMessage = "[Migrations] The first installation. Migrations will not be performed." + Logger.common(message: firstInstallationMessage, level: .info, category: .migration) + persistenceStorage.versionCodeForMigration = localSdkVersionCode + return + } + + var migrationsStarted: Bool = false + + migrations + .lazy + .filter { $0.isNeeded } + .sorted { $0.version < $1.version } + .forEach { migration in + migrationsStarted = true + do { + try migration.run() + let message = "[Migration] Run migration: \(migration.description), version: \(migration.version)" + Logger.common(message: message, level: .info, category: .migration) + } catch { + let errorMessage = "[Migration] Migration \(migration.version) failed. Description: \(migrations.description). Error: \(error.localizedDescription)" + Logger.common(message: errorMessage, level: .error, category: .migration) + } + } + + if persistenceStorage.versionCodeForMigration != localSdkVersionCode { + Logger.common(message: "[Migrations] Migrations failed, soft reset memory", level: .info, category: .migration) + persistenceStorage.softReset() + persistenceStorage.versionCodeForMigration = localSdkVersionCode + return + } + + let message = migrationsStarted ? "[Migrations] Migrations have been successful" : "[Migrations] Migrations have been skipped" + + Logger.common(message: message, level: .info, category: .migration) + } +} + + +// MARK: - Convenience initializer for testing purposes + +extension MigrationManager { + + /// Convenience initializer for testing purposes. This initializer allows for the overwriting of + /// all existing migrations and the local migration version. + /// - Parameters: + /// - persistenceStorage: Persistence storage used for managing various application states, + /// including migration state and other critical data. It provides methods + /// for performing resets and managing configurations. + /// - migrations: Array of new migrations. + /// - sdkVersionCode: version for comparison with `persistenceStorage.versionCodeForMigration` after all migrations have been performed. + convenience init( + persistenceStorage: PersistenceStorage, + migrations: [MigrationProtocol], + sdkVersionCode: Int + ) { + self.init(persistenceStorage: persistenceStorage) + self.localSdkVersionCode = sdkVersionCode + self.migrations = migrations + } +} diff --git a/Mindbox/Utilities/Migrations/MigrationManager/MigrationManagerProtocol.swift b/Mindbox/Utilities/Migrations/MigrationManager/MigrationManagerProtocol.swift new file mode 100644 index 00000000..0c822463 --- /dev/null +++ b/Mindbox/Utilities/Migrations/MigrationManager/MigrationManagerProtocol.swift @@ -0,0 +1,24 @@ +// +// MigrationManagerProtocol.swift +// Mindbox +// +// Created by Sergei Semko on 8/1/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation + +/// A protocol that defines the requirements for a migration manager. +/// The migration manager is responsible for performing a series of migrations +/// to update the application data to a new version. +protocol MigrationManagerProtocol { + + /// Attempts to perform all necessary migrations. If a migration fails, + /// a `softReset()` is performed on the persistence storage to ensure + /// that the system remains in a consistent state. + /// + /// - Note: The `softReset()` method is responsible for clearing certain parts of + /// the persistence storage to revert the system to a stable state. + /// It is defined in the `PersistenceStorage` protocol. + func migrate() +} diff --git a/Mindbox/Utilities/UNAuthorizationStatusProviding/UNAuthorizationStatusProviding.swift b/Mindbox/Utilities/UNAuthorizationStatusProviding/UNAuthorizationStatusProviding.swift index 0d7c985d..9d4b68b1 100644 --- a/Mindbox/Utilities/UNAuthorizationStatusProviding/UNAuthorizationStatusProviding.swift +++ b/Mindbox/Utilities/UNAuthorizationStatusProviding/UNAuthorizationStatusProviding.swift @@ -9,6 +9,5 @@ import Foundation protocol UNAuthorizationStatusProviding { - func getStatus(result: @escaping (Bool) -> Void) } diff --git a/Mindbox/Validators/ABTestValidator.swift b/Mindbox/Validators/ABTestValidator.swift index 9d00e510..160b6270 100644 --- a/Mindbox/Validators/ABTestValidator.swift +++ b/Mindbox/Validators/ABTestValidator.swift @@ -14,10 +14,11 @@ class ABTestValidator: Validator { typealias T = ABTest? private let sdkVersionValidator: SDKVersionValidator - private lazy var variantsValidator = ABTestVariantsValidator() + private let variantsValidator: ABTestVariantsValidator - init(sdkVersionValidator: SDKVersionValidator) { + init(sdkVersionValidator: SDKVersionValidator, variantsValidator: ABTestVariantsValidator) { self.sdkVersionValidator = sdkVersionValidator + self.variantsValidator = variantsValidator } func isValid(item: ABTest?) -> Bool { diff --git a/Mindbox/Validators/MindboxPushValidator.swift b/Mindbox/Validators/MindboxPushValidator.swift index 2874d549..9a942192 100644 --- a/Mindbox/Validators/MindboxPushValidator.swift +++ b/Mindbox/Validators/MindboxPushValidator.swift @@ -15,7 +15,7 @@ class MindboxPushValidator: Validator { typealias T = [AnyHashable: Any] func isValid(item: [AnyHashable : Any]) -> Bool { - guard let pushModel = NotificationFormatter.formatNotification(item) else { + guard NotificationFormatter.formatNotification(item) != nil else { Logger.common(message: "MindboxPushValidator: Failed to convert item to Mindbox push model. Validation failed.", level: .error, category: .notification) return false } diff --git a/MindboxLogger.podspec b/MindboxLogger.podspec index cb1b0e82..55f06310 100644 --- a/MindboxLogger.podspec +++ b/MindboxLogger.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "MindboxLogger" - spec.version = "2.10.3-rc" + spec.version = "2.11.0" spec.summary = "SDK for utilities to work with Mindbox" spec.description = "-" spec.homepage = "https://github.com/mindbox-cloud/ios-sdk" spec.license = { :type => "CC BY-NC-ND 4.0", :file => "LICENSE.md" } spec.author = { "Mindbox" => "ios-sdk@mindbox.ru" } - spec.platform = :ios, "10.0" + spec.platform = :ios, "12.0" spec.source = { :git => "https://github.com/mindbox-cloud/ios-sdk.git", :tag => "#{spec.version}" } spec.source_files = "MindboxLogger/**/*.{swift}", "SDKVersionProvider/**/*.{swift}" spec.exclude_files = "Classes/Exclude" diff --git a/MindboxLogger/Shared/Extensions/FileManager+Extensions.swift b/MindboxLogger/Shared/Extensions/FileManager+Extensions.swift index 7e530d2b..408c51be 100644 --- a/MindboxLogger/Shared/Extensions/FileManager+Extensions.swift +++ b/MindboxLogger/Shared/Extensions/FileManager+Extensions.swift @@ -11,7 +11,7 @@ import Foundation extension FileManager { static func storeURL(for appGroup: String, databaseName: String) -> URL { guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { - fatalError("Container couldn't be created, please check your configurations.") + fatalError("Container couldn't be created, please set up your AppGroup correctly. It must be the same for all your targets. Read the documentation developers.mindbox.ru/docs/ios-sdk-initialization") } return fileContainer.appendingPathComponent("\(databaseName).sqlite") diff --git a/MindboxLogger/Shared/Group/LogCategory.swift b/MindboxLogger/Shared/Group/LogCategory.swift index 06d7a663..b22547e1 100644 --- a/MindboxLogger/Shared/Group/LogCategory.swift +++ b/MindboxLogger/Shared/Group/LogCategory.swift @@ -17,6 +17,7 @@ public enum LogCategory: String, CaseIterable { case background case notification case visit + case migration case inAppMessages var emoji: String { @@ -35,6 +36,8 @@ public enum LogCategory: String, CaseIterable { return "✉️" case .visit: return "👁" + case .migration: + return "✈️" case .inAppMessages: return "🖼️" } diff --git a/MindboxNotifications.podspec b/MindboxNotifications.podspec index 79366963..ebdbcf62 100644 --- a/MindboxNotifications.podspec +++ b/MindboxNotifications.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "MindboxNotifications" - spec.version = "2.10.3-rc" + spec.version = "2.11.0" spec.summary = "SDK for integration notifications with Mindbox" spec.description = "This library allows you to integrate notifications and transfer them to Mindbox Marketing Cloud" spec.homepage = "https://github.com/mindbox-cloud/ios-sdk" spec.license = { :type => "CC BY-NC-ND 4.0", :file => "LICENSE.md" } spec.author = { "Mindbox" => "ios-sdk@mindbox.ru" } - spec.platform = :ios, "10.0" + spec.platform = :ios, "12.0" spec.source = { :git => "https://github.com/mindbox-cloud/ios-sdk.git", :tag => spec.version } spec.source_files = "MindboxNotifications/**/*.{swift}", "SDKVersionProvider/**/*.{swift}" spec.exclude_files = "Classes/Exclude" @@ -14,6 +14,6 @@ Pod::Spec.new do |spec| 'MindboxNotifications' => ['MindboxNotifications/**/*.xcprivacy'] } spec.swift_version = "5" - spec.dependency 'MindboxLogger', '2.10.3-rc' + spec.dependency 'MindboxLogger', '2.11.0' end diff --git a/MindboxNotifications/MindboxNotificationService.swift b/MindboxNotifications/MindboxNotificationService.swift index 75c8458f..7e4b4293 100644 --- a/MindboxNotifications/MindboxNotificationService.swift +++ b/MindboxNotifications/MindboxNotificationService.swift @@ -7,236 +7,30 @@ // import UIKit +import MindboxLogger import UserNotifications import UserNotificationsUI -import os -import MindboxLogger @objcMembers public class MindboxNotificationService: NSObject { - // Public + + // MARK: MindboxNotificationServiceProtocol + public var contentHandler: ((UNNotificationContent) -> Void)? public var bestAttemptContent: UNMutableNotificationContent? - // Private - private var context: NSExtensionContext? - private var viewController: UIViewController? - private let log = OSLog(subsystem: "cloud.Mindbox", category: "Notifications") + // MARK: Internal properties + + var context: NSExtensionContext? + var viewController: UIViewController? + + var pushValidator: PushValidator? - /// Mindbox proxy for NotificationsService and NotificationViewController + // MARK: Public initializer + + /// Mindbox proxy for `NotificationsService` and `NotificationViewController` public override init() { super.init() - } - - /// Call this method in `didReceive(_ notification: UNNotification)` of `NotificationViewController` - public func didReceive(notification: UNNotification, viewController: UIViewController, extensionContext: NSExtensionContext?) { - context = extensionContext - self.viewController = viewController - - createContent(for: notification, extensionContext: extensionContext) - } - - /// Call this method in `didReceive(_ request, withContentHandler)` of `NotificationService` - public func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { - self.contentHandler = contentHandler - bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) - guard let bestAttemptContent = bestAttemptContent else { - Logger.common(message: "MindboxNotificationService: Failed to get bestAttemptContent. bestAttemptContent: \(String(describing: bestAttemptContent))", level: .error, category: .notification) - return - } - - pushDelivered(request) - - Logger.common(message: "Push notification UniqueKey: \(request.identifier)", level: .info, category: .notification) - - if let imageUrl = parse(request: request)?.withImageURL?.imageUrl, - let allowedUrl = imageUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), - let url = URL(string: allowedUrl) { - downloadImage(with: url) { [weak self] in - self?.proceedFinalStage(bestAttemptContent) - } - } else { - Logger.common(message: "MindboxNotificationService: Failed to parse imageUrl", level: .error, category: .notification) - proceedFinalStage(bestAttemptContent) - } - } - - /// Call this method in `didReceive(_ request, withContentHandler)` of your `NotificationService` if you have implemented a custom version of NotificationService. This is necessary as an indicator that the push notification has been delivered to Mindbox services. - public func pushDelivered(_ request: UNNotificationRequest) { - let utilities = MBUtilitiesFetcher() - guard let configuration = utilities.configuration else { - Logger.common(message: "MindboxNotificationService: Failed to get configuration", level: .error, category: .notification) - return - } - Logger.common(message: "MindboxNotificationService: Successfully received configuration. configuration: \(configuration)", level: .info, category: .notification) - - let networkService = NetworkService(utilitiesFetcher: utilities, configuration: configuration) - let deliveryService = DeliveryService(utilitiesFetcher: utilities, networkService: networkService) - - do { - try deliveryService.track(request: request) - Logger.common(message: "MindboxNotificationService: Successfully tracked. request: \(request)", level: .info, category: .notification) - } catch { - Logger.error(.init(errorType: .unknown, description: error.localizedDescription)) - } - } - - private func downloadImage(with url: URL, completion: @escaping () -> Void) { - Logger.common(message: "Image loading. [URL]: \(url)", level: .info, category: .notification) - URLSession.shared.dataTask(with: url) { [weak self] data, response, error in - defer { completion() } - guard let self = self, - let data = data else { - Logger.common(message: "MindboxNotificationService: Failed to get self or data. self: \(String(describing: self)), data: \(String(describing: data))", level: .error, category: .notification) - return - } - - Logger.response(data: data, response: response, error: error) - - if let attachment = self.saveImage(data) { - self.bestAttemptContent?.attachments = [attachment] - } - }.resume() - } - - private func proceedFinalStage(_ bestAttemptContent: UNMutableNotificationContent) { - bestAttemptContent.categoryIdentifier = "MindBoxCategoryIdentifier" - contentHandler?(bestAttemptContent) - } - - /// Call this method in `serviceExtensionTimeWillExpire()` of `NotificationService` - public func serviceExtensionTimeWillExpire() { - if let bestAttemptContent = bestAttemptContent { - Logger.common(message: "MindboxNotificationService: Failed to get bestAttemptContent. bestAttemptContent: \(bestAttemptContent)", level: .error, category: .notification) - proceedFinalStage(bestAttemptContent) - } - } - - private func createContent(for notification: UNNotification, extensionContext: NSExtensionContext?) { - let request = notification.request - guard let payload = parse(request: request) else { - Logger.common(message: "MindboxNotificationService: Failed to parse payload. request: \(request)", level: .error, category: .notification) - return - } - - if let attachment = notification.request.content.attachments.first, - attachment.url.startAccessingSecurityScopedResource() { - defer { - attachment.url.stopAccessingSecurityScopedResource() - } - createImageView(with: attachment.url.path, view: viewController?.view) - } - createActions(with: payload, context: context) - } - - private func createActions(with payload: Payload, context: NSExtensionContext?) { - guard let context = context, let buttons = payload.withButton?.buttons else { - Logger.common(message: "MindboxNotificationService: Failed to create actions. payload: \(payload), context: \(String(describing: context)), payload.withButton?.buttons: \(String(describing: payload.withButton?.buttons))", level: .error, category: .notification) - return - } - let actions = buttons.map { button in - UNNotificationAction( - identifier: button.uniqueKey, - title: button.text, - options: [.foreground] - ) - } - - if #available(iOS 12.0, *) { - context.notificationActions = [] - actions.forEach { - Logger.common(message: "Button title: \($0.title), id: \($0.identifier)", - level: .info, - category: .notification) - context.notificationActions.append($0) - } - } - } - - private func createImageView(with imagePath: String, view: UIView?) { - guard let view = view, - let data = FileManager.default.contents(atPath: imagePath) else { - Logger.common(message: "MindboxNotificationService: Failed to create view. imagePath: \(imagePath), view: \(String(describing: view))", level: .error, category: .notification) - return - } - - let image = UIImage(data: data) - let imageView = UIImageView(image: image) - imageView.contentMode = .scaleAspectFit - imageView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(imageView) - - let imageHeight = image?.size.height ?? 0 - let imageWidth = image?.size.width ?? 0 - - let imageRatio = (imageWidth > 0) ? imageHeight / imageWidth : 0 - let imageViewHeight = view.bounds.width * imageRatio - - NSLayoutConstraint.activate([ - imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - imageView.topAnchor.constraint(equalTo: view.topAnchor), - imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - imageView.heightAnchor.constraint(lessThanOrEqualToConstant: imageViewHeight), - ]) - } - - private func parse(request: UNNotificationRequest) -> Payload? { - guard let userInfo = getUserInfo(from: request) else { - Logger.common(message: "MindboxNotificationService: Failed to get userInfo", level: .error, category: .notification) - return nil - } - guard let data = try? JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted) else { - Logger.common(message: "MindboxNotificationService: Failed to get data. userInfo: \(userInfo)", level: .error, category: .notification) - return nil - } - - var payload = Payload() - - payload.withButton = try? JSONDecoder().decode(Payload.Button.self, from: data) - Logger.common(message: "MindboxNotificationService: payload.withButton: \(String(describing: payload.withButton))", level: .info, category: .notification) - - payload.withImageURL = try? JSONDecoder().decode(Payload.ImageURL.self, from: data) - Logger.common(message: "MindboxNotificationService: payload.withImageURL: \(String(describing: payload.withImageURL))", level: .info, category: .notification) - - return payload - } - - private func getUserInfo(from request: UNNotificationRequest) -> [AnyHashable: Any]? { - guard let userInfo = (request.content.mutableCopy() as? UNMutableNotificationContent)?.userInfo else { - Logger.common(message: "MindboxNotificationService: Failed to get userInfo", level: .error, category: .notification) - return nil - } - - if let innerUserInfo = userInfo["aps"] as? [AnyHashable: Any], innerUserInfo["uniqueKey"] != nil { - Logger.common(message: "MindboxNotificationService: userInfo: \(innerUserInfo), userInfo.keys.count: \(userInfo.keys.count), innerUserInfo: \(innerUserInfo)", level: .info, category: .notification) - return innerUserInfo - } else { - Logger.common(message: "MindboxNotificationService: userInfo: \(userInfo)", level: .info, category: .notification) - return userInfo - } - } - - private func saveImage(_ data: Data) -> UNNotificationAttachment? { - let name = UUID().uuidString - guard let format = ImageFormat(data) else { - Logger.common(message: "MindboxNotificationService: Image load failed, data: \(data)", level: .error, category: .notification) - return nil - } - let url = URL(fileURLWithPath: NSTemporaryDirectory()) - let directory = url.appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString, isDirectory: true) - do { - try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) - let fileURL = directory.appendingPathComponent(name, isDirectory: true).appendingPathExtension(format.extension) - try data.write(to: fileURL, options: .atomic) - return try UNNotificationAttachment(identifier: name, url: fileURL, options: nil) - } catch { - Logger.common(message: "MindboxNotificationService: Failed to save image. data: \(data), name: \(name), url: \(url), directory: \(directory)", level: .error, category: .notification) - return nil - } + pushValidator = MindboxPushValidator() } } - - - - diff --git a/MindboxNotifications/MindboxPushNotification.swift b/MindboxNotifications/MindboxPushNotification.swift new file mode 100644 index 00000000..b2251f0c --- /dev/null +++ b/MindboxNotifications/MindboxPushNotification.swift @@ -0,0 +1,32 @@ +// +// MindboxPushNotification.swift +// MindboxNotifications +// +// Created by Sergei Semko on 8/15/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation +import MindboxLogger + +public protocol MindboxPushNotificationProtocol { + func isMindboxPush(userInfo: [AnyHashable: Any]) -> Bool + func getMindboxPushData(userInfo: [AnyHashable: Any]) -> MBPushNotification? +} + +// MARK: - MindboxPushNotificationProtocol + +extension MindboxNotificationService: MindboxPushNotificationProtocol { + + public func isMindboxPush(userInfo: [AnyHashable : Any]) -> Bool { + let message = "[NotificationService]: \(#function)" + Logger.common(message: message, level: .info, category: .notification) + return pushValidator?.isValid(item: userInfo) ?? false + } + + public func getMindboxPushData(userInfo: [AnyHashable : Any]) -> MBPushNotification? { + let message = "[NotificationService]: \(#function)" + Logger.common(message: message, level: .info, category: .notification) + return NotificationFormatter.formatNotification(userInfo) + } +} diff --git a/MindboxNotifications/Network/Model/ImageFormat.swift b/MindboxNotifications/Models/ImageFormat.swift similarity index 100% rename from MindboxNotifications/Network/Model/ImageFormat.swift rename to MindboxNotifications/Models/ImageFormat.swift diff --git a/MindboxNotifications/Models/MBPushNotification.swift b/MindboxNotifications/Models/MBPushNotification.swift new file mode 100644 index 00000000..a86e80f9 --- /dev/null +++ b/MindboxNotifications/Models/MBPushNotification.swift @@ -0,0 +1,46 @@ +// +// MBPushNotification.swift +// MindboxNotifications +// +// Created by Sergei Semko on 8/15/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation + +public struct MBPushNotification: Codable { + public let aps: MBAps? + public let clickUrl: String? + public let imageUrl: String? + public let payload: String? + public let buttons: [MBPushNotificationButton]? + public let uniqueKey: String? + + enum CodingKeys: String, CodingKey { + case aps, clickUrl, imageUrl, payload, buttons, uniqueKey + } +} + +public struct MBAps: Codable { + public let alert: MBApsAlert? + public let sound: String? + public let mutableContent: Int? + public let contentAvailable: Int? + + enum CodingKeys: String, CodingKey { + case alert, sound + case mutableContent = "mutable-content" + case contentAvailable = "content-available" + } +} + +public struct MBApsAlert: Codable { + public let title: String? + public let body: String? +} + +public struct MBPushNotificationButton: Codable { + public let text: String? + public let url: String? + public let uniqueKey: String? +} diff --git a/MindboxNotifications/Network/Model/Payload.swift b/MindboxNotifications/Models/Payload.swift similarity index 100% rename from MindboxNotifications/Network/Model/Payload.swift rename to MindboxNotifications/Models/Payload.swift diff --git a/MindboxNotifications/Network/DeliveryService.swift b/MindboxNotifications/Network/DeliveryService.swift deleted file mode 100644 index 8bdfecc2..00000000 --- a/MindboxNotifications/Network/DeliveryService.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// DeliveryService.swift -// MindboxNotifications -// -// Created by Ihor Kandaurov on 22.06.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation -import UserNotifications - -import MindboxLogger - -public class DeliveryService { - private let networkService: NetworkService - private let utilitiesFetcher: MBUtilitiesFetcher - - init(utilitiesFetcher: MBUtilitiesFetcher, networkService: NetworkService) { - self.utilitiesFetcher = utilitiesFetcher - self.networkService = networkService - } - - private let queue: OperationQueue = { - let queue = OperationQueue() - queue.maxConcurrentOperationCount = 1 - queue.name = "MindboxNotifications-DeliveryServiceQueue" - return queue - }() - - private let semaphore = DispatchSemaphore(value: 0) - - private let timeout: TimeInterval = 5.0 - - @discardableResult - func track(uniqueKey: String) throws -> Bool { - let pushDelivered = PushDelivered(uniqKey: uniqueKey) - let event = Event(type: .pushDelivered, body: BodyEncoder(encodable: pushDelivered).body) - track(event: event) - return performSemaphoreWait() - } - - @discardableResult - func track(request: UNNotificationRequest) throws -> Bool { - guard let decoder = NotificationDecoder(request: request) else { - Logger.common(message: "DeliveryService: Failed to create decoder. request: \(request)", level: .error, category: .notification) - return false - } - guard decoder.isMindboxNotification else { - Logger.common(message: "DeliveryService: Not MindboxNotification. decoder.isMindboxNotification: \(decoder.isMindboxNotification)", level: .error, category: .notification) - return false - } - let payload = try decoder.decode() - return try track(uniqueKey: payload.uniqueKey) - } - - private func performSemaphoreWait() -> Bool { - let methodStart = Date() - switch semaphore.wait(wallTimeout: .now() + timeout) { - case .success: - let methodEnd = Date() - Logger.common(message: "Finished operation in \(methodEnd.timeIntervalSince(methodStart)) sec", level: .debug, category: .delivery) - return true - case .timedOut: - let methodEnd = Date() - Logger.common(message: "Finished operation in \(methodEnd.timeIntervalSince(methodStart)) sec", level: .debug, category: .delivery) - queue.cancelAllOperations() - return false - } - } - - private func track(event: Event) { - let isConfigurationSet = utilitiesFetcher.configuration != nil - guard isConfigurationSet else { - semaphore.signal() - Logger.common(message: "Can't find configuration", level: .info, category: .delivery) - return - } - let deliverOperation = PushDeliveryOperation( - event: event, - service: networkService - ) - deliverOperation.onCompleted = { [weak self] _, _ in - Logger.common(message: "Operation completed", level: .info, category: .delivery) - self?.semaphore.signal() - } - queue.addOperations([deliverOperation], waitUntilFinished: false) - } -} diff --git a/MindboxNotifications/Network/DeviceModelHelper.swift b/MindboxNotifications/Network/DeviceModelHelper.swift deleted file mode 100644 index ebb745e8..00000000 --- a/MindboxNotifications/Network/DeviceModelHelper.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// DeviceModelHelper.swift -// Mindbox -// -// Created by Maksim Kazachkov on 02.02.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation -import UIKit.UIDevice -import MindboxLogger - -struct DeviceModelHelper { - - static let os = UIDevice.current.systemName - static let iOSVersion = UIDevice.current.systemVersion - - static let model: String = { - var systemInfo = utsname() - uname(&systemInfo) - let machineMirror = Mirror(reflecting: systemInfo.machine) - let identifier = machineMirror.children.reduce("") { identifier, element in - guard let value = element.value as? Int8, value != 0 else { - return identifier - } - return identifier + String(UnicodeScalar(UInt8(value))) - } - return identifier - }() - -} diff --git a/MindboxNotifications/Network/MBConfiguration.swift b/MindboxNotifications/Network/MBConfiguration.swift deleted file mode 100644 index 4dbb8e10..00000000 --- a/MindboxNotifications/Network/MBConfiguration.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// MBConfiguration.swift -// MindboxNotifications -// -// Created by Ihor Kandaurov on 22.06.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - - -import Foundation -import MindboxLogger - -public struct MBConfiguration: Codable { - public let endpoint: String - public let domain: String - public var previousInstallationId: String? - public var previousDeviceUUID: String? - public var subscribeCustomerIfCreated: Bool - - public init?(plistName: String) { - let decoder = PropertyListDecoder() - var findeURL: URL? - - for bundle in Bundle.allBundles { - if let url = bundle.url(forResource: plistName, withExtension: "plist") { - findeURL = url - break - } - } - - guard let url = findeURL else { - return nil - } - - guard let data = try? Data(contentsOf: url) else { - return nil - } - - guard let configuration = try? decoder.decode(MBConfiguration.self, from: data) else { - return nil - } - - self = configuration - } - - enum CodingKeys: String, CodingKey { - case endpoint - case domain - case previousInstallationId - case previousDeviceUUID - case subscribeCustomerIfCreated - } - - public init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - let endpoint = try values.decode(String.self, forKey: .endpoint) - let domain = try values.decode(String.self, forKey: .domain) - var previousInstallationId: String? - - if let value = try? values.decode(String.self, forKey: .previousInstallationId) { - if !value.isEmpty { - previousInstallationId = value - } - } - - var previousDeviceUUID: String? - - if let value = try? values.decode(String.self, forKey: .previousDeviceUUID) { - if !value.isEmpty { - previousDeviceUUID = value - } - } - - let subscribeCustomerIfCreated = try values.decodeIfPresent(Bool.self, forKey: .subscribeCustomerIfCreated) ?? false - - self.endpoint = endpoint - self.domain = domain - self.previousDeviceUUID = previousDeviceUUID - self.previousInstallationId = previousInstallationId - self.subscribeCustomerIfCreated = subscribeCustomerIfCreated - } -} - diff --git a/MindboxNotifications/Network/MBUtilitiesFetcher.swift b/MindboxNotifications/Network/MBUtilitiesFetcher.swift deleted file mode 100644 index 1ecfd31f..00000000 --- a/MindboxNotifications/Network/MBUtilitiesFetcher.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// MBUtilitiesFetcher.swift -// MindboxNotifications -// -// Created by Ihor Kandaurov on 22.06.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation -import MindboxLogger -#if SWIFT_PACKAGE -import SDKVersionProvider -#endif - -class MBUtilitiesFetcher { - - let appBundle: Bundle = { - var bundle: Bundle = .main - prepareBundle(&bundle) - return bundle - }() - - let sdkBundle: Bundle = { - var bundle = Bundle(for: MindboxNotificationService.self) - return bundle - }() - - var applicationGroupIdentifier: String? { - guard let hostApplicationName = hostApplicationName else { - Logger.common(message: "MBUtilitiesFetcher: Failed to get applicationGroupIdentifier. hostApplicationName: \(String(describing: hostApplicationName))", level: .error, category: .notification) - return nil - } - let identifier = "group.cloud.Mindbox.\(hostApplicationName)" - let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: identifier) - guard url != nil else { - #if targetEnvironment(simulator) - return "" - #else - Logger.common(message: "MBUtilitiesFetcher: Failed to get AppGroup for \(hostApplicationName). identifier: \(identifier))", level: .error, category: .notification) - return nil - #endif - } - return identifier - } - - init() { - - } - - private static func prepareBundle(_ bundle: inout Bundle) { - if Bundle.main.bundleURL.pathExtension == "appex" { - // Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex - let url = bundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent() - if let otherBundle = Bundle(url: url) { - bundle = otherBundle - Logger.common(message: "MBUtilitiesFetcher: Successfully prepared bundle. bundle: \(bundle)", level: .debug, category: .notification) - } - } - } - - var appVerson: String? { - appBundle.object(forInfoDictionaryKey:"CFBundleShortVersionString") as? String - } - - var sdkVersion: String? { - SDKVersionProvider.sdkVersion - } - - var hostApplicationName: String? { - appBundle.bundleIdentifier - } - - var userDefaults: UserDefaults? { - return UserDefaults(suiteName: applicationGroupIdentifier) - } - - var configuration: MBConfiguration? { - guard let data = userDefaults?.data(forKey: "MBPersistenceStorage-configurationData") else { - Logger.common(message: "MBUtilitiesFetcher: Failed to get data from userDefaults for key 'MBPersistenceStorage-configurationData'", level: .error, category: .notification) - return nil - } - return try? JSONDecoder().decode(MBConfiguration.self, from: data) - } -} diff --git a/MindboxNotifications/Network/Model/Body+/BodyDecoder.swift b/MindboxNotifications/Network/Model/Body+/BodyDecoder.swift deleted file mode 100644 index d134eb91..00000000 --- a/MindboxNotifications/Network/Model/Body+/BodyDecoder.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// BodyDecoder.swift -// Mindbox -// -// Created by Maksim Kazachkov on 11.02.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation -import MindboxLogger - -struct BodyDecoder { - - let body: T - - init?(decodable: String) { - if let data = decodable.data(using: .utf8) { - if let body = try? JSONDecoder().decode(T.self, from: data) { - self.body = body - } else { - Logger.common(message: "BodyDecoder: Failed to decode JSON. data: \(data)", level: .error, category: .notification) - return nil - } - } else { - Logger.common(message: "BodyDecoder: Failed to decode string. decodable: \(decodable)", level: .error, category: .notification) - return nil - } - } - -} diff --git a/MindboxNotifications/Network/Model/Body+/BodyEncoder.swift b/MindboxNotifications/Network/Model/Body+/BodyEncoder.swift deleted file mode 100644 index 18966d69..00000000 --- a/MindboxNotifications/Network/Model/Body+/BodyEncoder.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// BodyEncoder.swift -// Mindbox -// -// Created by Maksim Kazachkov on 11.02.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation -import MindboxLogger - -struct BodyEncoder { - - let body: String - - init(encodable: T) { - if let encodedBody = try? JSONEncoder().encode(encodable) { - body = String(data: encodedBody, encoding: .utf8) ?? "" - } else { - body = "" - Logger.common(message: "BodyEncoder: Failed to encode JSON. encodable: \(encodable)", level: .error, category: .notification) - } - } - -} diff --git a/MindboxNotifications/Network/Model/DeliveryOperation.swift b/MindboxNotifications/Network/Model/DeliveryOperation.swift deleted file mode 100644 index be2350d3..00000000 --- a/MindboxNotifications/Network/Model/DeliveryOperation.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// DeliveryOperation.swift -// Mindbox -// -// Created by Maksim Kazachkov on 08.02.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation -import MindboxLogger - -class PushDeliveryOperation: Operation { - private let event: Event - private let service: NetworkService - - init(event: Event, service: NetworkService) { - self.event = event - self.service = service - } - - var onCompleted: ((_ event: Event, _ success: Bool) -> Void)? - - private var _isFinished: Bool = false - override var isFinished: Bool { - get { - return _isFinished - } - set { - if #available(iOS 11.0, *) { - willChangeValue(for: \.isFinished) - _isFinished = newValue - didChangeValue(for: \.isFinished) - } else { - willChangeValue(forKey: "isFinished") - _isFinished = newValue - didChangeValue(forKey: "isFinished") - } - } - } - - override func main() { - guard !isCancelled else { - return - } - - service.sendPushDelivered(event: event) { [weak self] result in - guard let self = self else { - Logger.common(message: "PushDeliveryOperation: Failed to get PushDeliveryOperation. self: \(String(describing: self))", level: .error, category: .notification) - return - } - switch result { - case true: - self.onCompleted?(self.event, true) - self.isFinished = true - case false: - self.onCompleted?(self.event, false) - self.isFinished = true - } - } - } -} diff --git a/MindboxNotifications/Network/Model/Event.swift b/MindboxNotifications/Network/Model/Event.swift deleted file mode 100644 index 7089f188..00000000 --- a/MindboxNotifications/Network/Model/Event.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// Event.swift -// Mindbox -// -// Created by Maksim Kazachkov on 03.02.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation - -struct Event { - - enum Operation: String { - case pushDelivered = "" - } - - let transactionId: String - - var dateTimeOffset: Int64 { - let enqueueDate = Date(timeIntervalSince1970: enqueueTimeStamp) - let ms = (Date().timeIntervalSince(enqueueDate) * 1000).rounded() - return Int64(ms) - } - - // The time of adding is persistent to the event queue - let enqueueTimeStamp: Double - - let serialNumber: String? - - let type: Operation - - // Data according to Operation - let body: String - - init(type: Operation, body: String) { - self.transactionId = UUID().uuidString - self.enqueueTimeStamp = Date().timeIntervalSince1970 - self.type = type - self.body = body - self.serialNumber = nil - } -} diff --git a/MindboxNotifications/Network/Model/EventWrapper.swift b/MindboxNotifications/Network/Model/EventWrapper.swift deleted file mode 100644 index 2857320b..00000000 --- a/MindboxNotifications/Network/Model/EventWrapper.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// EventWrapper.swift -// Mindbox -// -// Created by Maksim Kazachkov on 08.02.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation - -struct EventWrapper { - - let event: Event - - let endpoint: String - - let deviceUUID: String - -} diff --git a/MindboxNotifications/Network/Model/NotificationDecoder.swift b/MindboxNotifications/Network/Model/NotificationDecoder.swift deleted file mode 100644 index 4cbab13a..00000000 --- a/MindboxNotifications/Network/Model/NotificationDecoder.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// NotificationParser.swift -// Mindbox -// -// Created by Maksim Kazachkov on 31.03.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation -import UserNotifications -import MindboxLogger - -struct NotificationDecoder { - var isMindboxNotification: Bool { - userInfo["uniqueKey"] != nil - } - - private let userInfo: [AnyHashable: Any] - - init?(request: UNNotificationRequest) { - guard let userInfo = (request.content.mutableCopy() as? UNMutableNotificationContent)?.userInfo else { - Logger.common(message: "NotificationDecoder: Failed to get user info from notification content", level: .fault, category: .notification) - return nil - } - - self.init(userInfo: userInfo) - } - - init?(response: UNNotificationResponse) { - self.init(request: response.notification.request) - } - - init?(userInfo: [AnyHashable: Any]) { - if let innerUserInfo = userInfo["aps"] as? [AnyHashable: Any], innerUserInfo["uniqueKey"] != nil { - self.userInfo = innerUserInfo - Logger.common(message: "Push Notification format with one big aps object") - } else { - self.userInfo = userInfo - Logger.common(message: "Push Notification format with multiple keys") - } - } - - func decode() throws -> T { - do { - let data = try JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted) - let decoder = JSONDecoder() - do { - let payload = try decoder.decode(T.self, from: data) - return payload - } catch { - Logger.common(message: "NotificationDecoder: Failed to decode data into payload. error: \(error)", level: .error, category: .notification) - throw error - } - } catch { - Logger.common(message: "NotificationDecoder: Failed to serialize userInfo into data. error: \(error)", level: .error, category: .notification) - throw error - } - } -} diff --git a/MindboxNotifications/Network/Model/NotificationsPayloads.swift b/MindboxNotifications/Network/Model/NotificationsPayloads.swift deleted file mode 100644 index 28ddab69..00000000 --- a/MindboxNotifications/Network/Model/NotificationsPayloads.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// DeliveredNotificationPayload.swift -// Mindbox -// -// Created by Maksim Kazachkov on 31.03.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation - -enum NotificationsPayloads { - - struct Delivery: Codable, CustomDebugStringConvertible { - - let uniqueKey: String - - var debugDescription: String { - "uniqueKey: \(uniqueKey)" - } - - } - - struct Click: Codable { - - struct Buttons: Codable { - - let text: String - let uniqueKey: String - - } - - let uniqueKey: String - - let buttons: [Buttons]? - - var debugDescription: String { - "uniqueKey: \(uniqueKey)" - } - - } - -} - diff --git a/MindboxNotifications/Network/Model/PushDelivered.swift b/MindboxNotifications/Network/Model/PushDelivered.swift deleted file mode 100644 index 26e79236..00000000 --- a/MindboxNotifications/Network/Model/PushDelivered.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// PushDelivered.swift -// Mindbox -// -// Created by Maksim Kazachkov on 19.02.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation - -struct PushDelivered: Codable { - - let uniqKey: String - - init(uniqKey: String) { - self.uniqKey = uniqKey - } - -} diff --git a/MindboxNotifications/Network/Model/URLRequestBuilder.swift b/MindboxNotifications/Network/Model/URLRequestBuilder.swift deleted file mode 100644 index 91482166..00000000 --- a/MindboxNotifications/Network/Model/URLRequestBuilder.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// URLRequestBuilder.swift -// Mindbox -// -// Created by Maksim Kazachkov on 01.02.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation -import MindboxLogger - -struct URLRequestBuilder { - - let domain: String - - init(domain: String) { - self.domain = domain - } - - func asURLRequest(route: Route) throws -> URLRequest { - let components = makeURLComponents(for: route) - - guard let url = components.url else { - Logger.common(message: "URLRequestBuilder: Failed to create URL. Components: \(components)", level: .error, category: .notification) - throw URLError(.badURL) - } - - var urlRequest = URLRequest(url: url) - route.headers?.forEach { - urlRequest.addValue($0.value, forHTTPHeaderField: $0.key) - } - urlRequest.httpBody = route.body - urlRequest.httpMethod = route.method.rawValue.uppercased() - - return urlRequest - } - - private func makeURLComponents(for route: Route) -> URLComponents { - var components = URLComponents() - components.scheme = "https" - components.host = domain - components.path = route.path - components.queryItems = makeQueryItems(for: route.queryParameters) - - return components - } - - private func makeQueryItems(for parameters: QueryParameters?) -> [URLQueryItem]? { - return parameters?.compactMap { URLQueryItem(name: $0.key, value: $0.value.description) } - } - -} diff --git a/MindboxNotifications/Network/NetworkService.swift b/MindboxNotifications/Network/NetworkService.swift deleted file mode 100644 index 56156159..00000000 --- a/MindboxNotifications/Network/NetworkService.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// NetworkService.swift -// MindboxNotifications -// -// Created by Ihor Kandaurov on 22.06.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation -import MindboxLogger - -class NetworkService { - private let session: URLSession - private let configuration: MBConfiguration - - init(utilitiesFetcher: MBUtilitiesFetcher, configuration: MBConfiguration) { - let sessionConfiguration: URLSessionConfiguration = .default - let sdkVersion = utilitiesFetcher.sdkVersion ?? "unknow" - let appVersion = utilitiesFetcher.appVerson ?? "unknow" - let appName = utilitiesFetcher.hostApplicationName ?? "unknow" - let userAgent: String = "mindbox.sdk/\(sdkVersion) (\(DeviceModelHelper.os) \(DeviceModelHelper.iOSVersion); \(DeviceModelHelper.model)) \(appName)/\(appVersion)" - sessionConfiguration.httpAdditionalHeaders = [ - "Mindbox-Integration": "iOS-SDK", - "Mindbox-Integration-Version": sdkVersion, - "User-Agent": userAgent, - "Content-Type": "application/json; charset=utf-8", - ] - session = URLSession(configuration: sessionConfiguration) - self.configuration = configuration - } - - public func sendPushDelivered(event: Event, completion: @escaping ((Bool) -> Void)) { - guard let deviceUUID = configuration.previousDeviceUUID else { - completion(false) - Logger.common(message: "NetworkService: Failed to get deviceUUID. configuration.previousDeviceUUID: \(String(describing: configuration.previousDeviceUUID))", level: .error, category: .network) - return - } - - let wrapper = EventWrapper( - event: event, - endpoint: configuration.endpoint, - deviceUUID: deviceUUID - ) - - let builder = URLRequestBuilder(domain: configuration.domain) - do { - let urlRequest = try builder.asURLRequest(route: PushDeliveredEventRoute(wrapper: wrapper)) - - Logger.network(request: urlRequest, httpAdditionalHeaders: session.configuration.httpAdditionalHeaders) - - session.dataTask(with: urlRequest) { data, response, error in - Logger.response(data: data, response: response, error: error) - if let error = error { - Logger.error(.init(errorType: .server, description: error.localizedDescription)) - completion(false) - } - - if let response = response as? HTTPURLResponse { - if (200 ... 399).contains(response.statusCode) { - Logger.common(message: "Push delivered", level: .info, category: .network) - completion(true) - } else { - Logger.error(.init(errorType: .invalid, description: response.debugDescription)) - completion(false) - } - } else { - Logger.error(.init(errorType: .invalid, description: response.debugDescription)) - completion(false) - } - }.resume() - } catch let error { - Logger.error(.init(errorType: .unknown, description: error.localizedDescription)) - completion(false) - } - } -} diff --git a/MindboxNotifications/Network/PushDeliveredEventRoute.swift b/MindboxNotifications/Network/PushDeliveredEventRoute.swift deleted file mode 100644 index 65ec18a7..00000000 --- a/MindboxNotifications/Network/PushDeliveredEventRoute.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// PushDeliveredEventRoute.swift -// Mindbox -// -// Created by Maksim Kazachkov on 03.02.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation - -struct PushDeliveredEventRoute: Route { - let wrapper: EventWrapper - init(wrapper: EventWrapper) { - self.wrapper = wrapper - } - - var method: HTTPMethod { - return .get - } - - var path: String { - return "/mobile-push/delivered" - } - - var headers: HTTPHeaders? { - return nil - } - - var queryParameters: QueryParameters { - let decoded = BodyDecoder(decodable: wrapper.event.body) - return makeBasicQueryParameters(with: wrapper) - .appending(["uniqKey": decoded?.body.uniqKey ?? ""]) - .appending(["endpointId": wrapper.endpoint]) - } - - var body: Data? { - return nil - } - - func makeBasicQueryParameters(with wrapper: EventWrapper) -> QueryParameters { - ["transactionId": wrapper.event.transactionId, - "deviceUUID": wrapper.deviceUUID, - "dateTimeOffset": wrapper.event.dateTimeOffset] - } -} - -fileprivate extension QueryParameters { - func appending(_ values: @autoclosure () -> QueryParameters) -> QueryParameters { - var copy = self - values().forEach { key, value in - copy[key] = value - } - return copy - } -} diff --git a/MindboxNotifications/Network/Route.swift b/MindboxNotifications/Network/Route.swift deleted file mode 100644 index 58e9a439..00000000 --- a/MindboxNotifications/Network/Route.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Route.swift -// Mindbox -// -// Created by Maksim Kazachkov on 01.02.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation - -protocol Route { - - var method: HTTPMethod { get } - - var path: String { get } - - var headers: HTTPHeaders? { get } - - var queryParameters: QueryParameters { get } - - var body: Data? { get } - -} diff --git a/MindboxNotifications/Network/Types/HTTPMethod.swift b/MindboxNotifications/Network/Types/HTTPMethod.swift deleted file mode 100644 index 28800d06..00000000 --- a/MindboxNotifications/Network/Types/HTTPMethod.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// HTTPMethod.swift -// Mindbox -// -// Created by Maksim Kazachkov on 01.02.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation - -enum HTTPMethod: String { - - case get, post - -} diff --git a/MindboxNotifications/Network/Types/HTTPTypealiases.swift b/MindboxNotifications/Network/Types/HTTPTypealiases.swift deleted file mode 100644 index bcdacee2..00000000 --- a/MindboxNotifications/Network/Types/HTTPTypealiases.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// Typealiases.swift -// Mindbox -// -// Created by Maksim Kazachkov on 02.02.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import Foundation - -typealias HTTPHeaders = [String: String] - -typealias QueryParameters = [String: CustomStringConvertible] diff --git a/MindboxNotifications/NotificationContent.swift b/MindboxNotifications/NotificationContent.swift new file mode 100644 index 00000000..ea423b50 --- /dev/null +++ b/MindboxNotifications/NotificationContent.swift @@ -0,0 +1,110 @@ +// +// NotificationContent.swift +// MindboxNotifications +// +// Created by Sergei Semko on 8/12/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation +import UIKit +import UserNotifications +import UserNotificationsUI +import MindboxLogger + +public protocol MindboxNotificationContentProtocol: MindboxPushNotificationProtocol { + + /// Call this method in `didReceive(_ notification: UNNotification)` of `NotificationViewController` + func didReceive( + notification: UNNotification, + viewController: UIViewController, + extensionContext: NSExtensionContext? + ) +} + +// MARK: - MindboxNotificationContentProtocol + +extension MindboxNotificationService: MindboxNotificationContentProtocol { + + /// Call this method in `didReceive(_ notification: UNNotification)` of `NotificationViewController` + public func didReceive(notification: UNNotification, viewController: UIViewController, extensionContext: NSExtensionContext?) { + context = extensionContext + self.viewController = viewController + + createContent(for: notification, extensionContext: extensionContext) + } +} + +// MARK: Private methods for MindboxNotificationContentProtocol + +private extension MindboxNotificationService { + + func createContent(for notification: UNNotification, extensionContext: NSExtensionContext?) { + let request = notification.request + guard let payload = parse(request: request) else { + Logger.common(message: "MindboxNotificationService: Failed to parse payload. request: \(request)", level: .error, category: .notification) + return + } + + if let attachment = notification.request.content.attachments.first, + attachment.url.startAccessingSecurityScopedResource() { + defer { + attachment.url.stopAccessingSecurityScopedResource() + } + createImageView(with: attachment.url.path, view: viewController?.view) + } + createActions(with: payload, context: context) + } + + func createActions(with payload: Payload, context: NSExtensionContext?) { + guard let context = context, let buttons = payload.withButton?.buttons else { + Logger.common(message: "MindboxNotificationService: Failed to create actions. payload: \(payload), context: \(String(describing: context)), payload.withButton?.buttons: \(String(describing: payload.withButton?.buttons))", level: .error, category: .notification) + return + } + let actions = buttons.map { button in + UNNotificationAction( + identifier: button.uniqueKey, + title: button.text, + options: [.foreground] + ) + } + + if #available(iOS 12.0, *) { + context.notificationActions = [] + actions.forEach { + Logger.common(message: "Button title: \($0.title), id: \($0.identifier)", + level: .info, + category: .notification) + context.notificationActions.append($0) + } + } + } + + func createImageView(with imagePath: String, view: UIView?) { + guard let view = view, + let data = FileManager.default.contents(atPath: imagePath) else { + Logger.common(message: "MindboxNotificationService: Failed to create view. imagePath: \(imagePath), view: \(String(describing: view))", level: .error, category: .notification) + return + } + + let image = UIImage(data: data) + let imageView = UIImageView(image: image) + imageView.contentMode = .scaleAspectFit + imageView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(imageView) + + let imageHeight = image?.size.height ?? 0 + let imageWidth = image?.size.width ?? 0 + + let imageRatio = (imageWidth > 0) ? imageHeight / imageWidth : 0 + let imageViewHeight = view.bounds.width * imageRatio + + NSLayoutConstraint.activate([ + imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + imageView.topAnchor.constraint(equalTo: view.topAnchor), + imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + imageView.heightAnchor.constraint(lessThanOrEqualToConstant: imageViewHeight), + ]) + } +} diff --git a/MindboxNotifications/NotificationService.swift b/MindboxNotifications/NotificationService.swift new file mode 100644 index 00000000..54d001f4 --- /dev/null +++ b/MindboxNotifications/NotificationService.swift @@ -0,0 +1,121 @@ +// +// NotificationService.swift +// MindboxNotifications +// +// Created by Sergei Semko on 8/12/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation +import UserNotifications +import MindboxLogger + +public protocol MindboxNotificationServiceProtocol: MindboxPushNotificationProtocol { + + var contentHandler: ((UNNotificationContent) -> Void)? { get set } + var bestAttemptContent: UNMutableNotificationContent? { get set } + + /// Call this method in `didReceive(_ request, withContentHandler)` of `NotificationService` + func didReceive( + _ request: UNNotificationRequest, + withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void + ) + + /// Call this method in `serviceExtensionTimeWillExpire()` of `NotificationService` + func serviceExtensionTimeWillExpire() + + /// Call this method in `didReceive(_ request, withContentHandler)` of your `NotificationService` if you have implemented a custom version of `NotificationService`. + /// This is necessary as an indicator that the push notification has been delivered to Mindbox services. + /// At the moment, this method only writes a push delivery log. + func pushDelivered(_ request: UNNotificationRequest) +} + +// MARK: - MindboxNotificationServiceProtocol + +extension MindboxNotificationService: MindboxNotificationServiceProtocol { + + /// Call this method in `didReceive(_ request, withContentHandler)` of `NotificationService` + public func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.contentHandler = contentHandler + bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + guard let bestAttemptContent = bestAttemptContent else { + Logger.common(message: "MindboxNotificationService: Failed to get bestAttemptContent. bestAttemptContent: \(String(describing: bestAttemptContent))", level: .error, category: .notification) + return + } + + pushDelivered(request) + + if let imageUrl = parse(request: request)?.withImageURL?.imageUrl, + let allowedUrl = imageUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), + let url = URL(string: allowedUrl) { + downloadImage(with: url) { [weak self] in + self?.proceedFinalStage(bestAttemptContent) + } + } else { + Logger.common(message: "MindboxNotificationService: Failed to parse imageUrl", level: .error, category: .notification) + proceedFinalStage(bestAttemptContent) + } + } + + /// Call this method in `serviceExtensionTimeWillExpire()` of `NotificationService` + public func serviceExtensionTimeWillExpire() { + if let bestAttemptContent = bestAttemptContent { + Logger.common(message: "MindboxNotificationService: Failed to get bestAttemptContent. bestAttemptContent: \(bestAttemptContent)", level: .error, category: .notification) + proceedFinalStage(bestAttemptContent) + } + } + + /// Call this method in `didReceive(_ request, withContentHandler)` of your `NotificationService` if you have implemented a custom version of NotificationService. + /// This is necessary as an indicator that the push notification has been delivered to Mindbox services. + /// At the moment, this method only writes a push delivery log. + public func pushDelivered(_ request: UNNotificationRequest) { + let message = "[NotificationService]: \(#function), request id: \(request.identifier)" + Logger.common(message: message, level: .info, category: .notification) + } +} + +// MARK: Private methods for MindboxNotificationServiceProtocol + +private extension MindboxNotificationService { + func downloadImage(with url: URL, completion: @escaping () -> Void) { + Logger.common(message: "Image loading. [URL]: \(url)", level: .info, category: .notification) + URLSession.shared.dataTask(with: url) { [weak self] data, response, error in + defer { completion() } + guard let self = self, + let data = data else { + Logger.common(message: "MindboxNotificationService: Failed to get self or data. self: \(String(describing: self)), data: \(String(describing: data))", level: .error, category: .notification) + return + } + + Logger.response(data: data, response: response, error: error) + + if let attachment = self.saveImage(data) { + self.bestAttemptContent?.attachments = [attachment] + } + }.resume() + } + + func saveImage(_ data: Data) -> UNNotificationAttachment? { + let name = UUID().uuidString + guard let format = ImageFormat(data) else { + Logger.common(message: "MindboxNotificationService: Image load failed, data: \(data)", level: .error, category: .notification) + return nil + } + let url = URL(fileURLWithPath: NSTemporaryDirectory()) + let directory = url.appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString, isDirectory: true) + do { + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil) + let fileURL = directory.appendingPathComponent(name, isDirectory: true).appendingPathExtension(format.extension) + try data.write(to: fileURL, options: .atomic) + return try UNNotificationAttachment(identifier: name, url: fileURL, options: nil) + } catch { + Logger.common(message: "MindboxNotificationService: Failed to save image. data: \(data), name: \(name), url: \(url), directory: \(directory)", level: .error, category: .notification) + return nil + } + } + + func proceedFinalStage(_ bestAttemptContent: UNMutableNotificationContent) { + bestAttemptContent.categoryIdentifier = Constants.categoryIdentifier + contentHandler?(bestAttemptContent) + } +} diff --git a/MindboxNotifications/PushNotifications/NotificationFormatter.swift b/MindboxNotifications/PushNotifications/NotificationFormatter.swift new file mode 100644 index 00000000..8304ae12 --- /dev/null +++ b/MindboxNotifications/PushNotifications/NotificationFormatter.swift @@ -0,0 +1,16 @@ +// +// NotificationFormatter.swift +// MindboxNotifications +// +// Created by Sergei Semko on 8/15/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation + +final class NotificationFormatter { + static func formatNotification(_ userInfo: [AnyHashable: Any]) -> MBPushNotification? { + let strategy = NotificationStrategyFactory.strategy(for: userInfo) + return strategy.handle(userInfo: userInfo) + } +} diff --git a/MindboxNotifications/PushNotifications/PushNotificationFormatStrategies/NotificationFormatStrategy.swift b/MindboxNotifications/PushNotifications/PushNotificationFormatStrategies/NotificationFormatStrategy.swift new file mode 100644 index 00000000..6cc94955 --- /dev/null +++ b/MindboxNotifications/PushNotifications/PushNotificationFormatStrategies/NotificationFormatStrategy.swift @@ -0,0 +1,70 @@ +// +// NotificationFormatStrategy.swift +// MindboxNotifications +// +// Created by Sergei Semko on 8/15/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation +import MindboxLogger + +protocol NotificationFormatStrategy { + func handle(userInfo: [AnyHashable: Any]) -> MBPushNotification? +} + +final class LegacyFormatStrategy: NotificationFormatStrategy { + func handle(userInfo: [AnyHashable: Any]) -> MBPushNotification? { + guard let apsData = userInfo["aps"] as? [String: Any], + let alertData = apsData["alert"] as? [String: Any], + let body = alertData["body"] as? String, + let clickUrl = apsData["clickUrl"] as? String else { + Logger.common(message: "LegacyFormatStrategy: Failed to parse legacy notification format. userInfo: \(userInfo)", level: .error, category: .notification) + return nil + } + + let title = alertData["title"] as? String + let sound = apsData["sound"] as? String + let mutableContent = apsData["mutable-content"] as? Int + let contentAvailable = apsData["content-available"] as? Int + + let buttons = (apsData["buttons"] as? [[String: Any]])?.compactMap { dict -> MBPushNotificationButton? in + guard let text = dict["text"] as? String, + let url = dict["url"] as? String, + let uniqueKey = dict["uniqueKey"] as? String else { + Logger.common(message: "LegacyFormatStrategy: Error parsing button data. dictionary: \(dict)", level: .error, category: .notification) + return nil + } + return MBPushNotificationButton(text: text, url: url, uniqueKey: uniqueKey) + } + + Logger.common(message: "LegacyFormatStrategy: Successfully parsed legacy notification format.", level: .info, category: .notification) + return MBPushNotification( + aps: MBAps(alert: MBApsAlert(title: title, body: body), + sound: sound, + mutableContent: mutableContent, + contentAvailable: contentAvailable), + clickUrl: clickUrl, + imageUrl: apsData["imageUrl"] as? String, + payload: apsData["payload"] as? String, + buttons: buttons, + uniqueKey: apsData["uniqueKey"] as? String + ) + } +} + +final class CurrentFormatStrategy: NotificationFormatStrategy { + func handle(userInfo: [AnyHashable : Any]) -> MBPushNotification? { + guard let data = try? JSONSerialization.data(withJSONObject: userInfo), + let notificationModel = try? JSONDecoder().decode(MBPushNotification.self, from: data), + let clickUrl = notificationModel.clickUrl, + let alert = notificationModel.aps?.alert, + let body = alert.body else { + Logger.common(message: "CurrentFormatStrategy: Failed to parse current notification format. userInfo: \(userInfo)", level: .error, category: .notification) + return nil + } + + Logger.common(message: "CurrentFormatStrategy: Successfully parsed current notification format.", level: .info, category: .notification) + return notificationModel + } +} diff --git a/MindboxNotifications/PushNotifications/PushNotificationFormatStrategies/NotificationStrategyFactory.swift b/MindboxNotifications/PushNotifications/PushNotificationFormatStrategies/NotificationStrategyFactory.swift new file mode 100644 index 00000000..3329c047 --- /dev/null +++ b/MindboxNotifications/PushNotifications/PushNotificationFormatStrategies/NotificationStrategyFactory.swift @@ -0,0 +1,22 @@ +// +// NotificationStrategyFactory.swift +// MindboxNotifications +// +// Created by Sergei Semko on 8/15/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation +import MindboxLogger + +final class NotificationStrategyFactory { + static func strategy(for userInfo: [AnyHashable: Any]) -> NotificationFormatStrategy { + if let aps = userInfo["aps"] as? [String: Any], aps["clickUrl"] != nil && aps["uniqueKey"] != nil { + Logger.common(message: "NotificationStrategyFactory: Selected LegacyFormatStrategy for processing push notification.", level: .info, category: .notification) + return LegacyFormatStrategy() + } + + Logger.common(message: "NotificationStrategyFactory: Selected CurrentFormatStrategy for processing push notification.", level: .info, category: .notification) + return CurrentFormatStrategy() + } +} diff --git a/MindboxNotifications/PushNotifications/PushValidator.swift b/MindboxNotifications/PushNotifications/PushValidator.swift new file mode 100644 index 00000000..f517efc7 --- /dev/null +++ b/MindboxNotifications/PushNotifications/PushValidator.swift @@ -0,0 +1,26 @@ +// +// Validator.swift +// MindboxNotifications +// +// Created by Sergei Semko on 8/15/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation +import MindboxLogger + +protocol PushValidator { + func isValid(item: [AnyHashable: Any]) -> Bool +} + +final class MindboxPushValidator: PushValidator { + func isValid(item: [AnyHashable : Any]) -> Bool { + guard let pushModel = NotificationFormatter.formatNotification(item) else { + Logger.common(message: "MindboxPushValidator: Failed to convert item to Mindbox push model. Validation failed.", level: .error, category: .notification) + return false + } + + Logger.common(message: "MindboxPushValidator: Successfully validated Mindbox push model.", level: .info, category: .notification) + return true + } +} diff --git a/MindboxNotifications/SharedInternalMethods.swift b/MindboxNotifications/SharedInternalMethods.swift new file mode 100644 index 00000000..161301c0 --- /dev/null +++ b/MindboxNotifications/SharedInternalMethods.swift @@ -0,0 +1,50 @@ +// +// SharedInternalMethods.swift +// MindboxNotifications +// +// Created by Sergei Semko on 8/12/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import UIKit +import MindboxLogger + +// MARK: - Shared internal methods + +extension MindboxNotificationService { + func parse(request: UNNotificationRequest) -> Payload? { + guard let userInfo = getUserInfo(from: request) else { + Logger.common(message: "MindboxNotificationService: Failed to get userInfo", level: .error, category: .notification) + return nil + } + guard let data = try? JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted) else { + Logger.common(message: "MindboxNotificationService: Failed to get data. userInfo: \(userInfo)", level: .error, category: .notification) + return nil + } + + var payload = Payload() + + payload.withButton = try? JSONDecoder().decode(Payload.Button.self, from: data) + Logger.common(message: "MindboxNotificationService: payload.withButton: \(String(describing: payload.withButton))", level: .info, category: .notification) + + payload.withImageURL = try? JSONDecoder().decode(Payload.ImageURL.self, from: data) + Logger.common(message: "MindboxNotificationService: payload.withImageURL: \(String(describing: payload.withImageURL))", level: .info, category: .notification) + + return payload + } + + func getUserInfo(from request: UNNotificationRequest) -> [AnyHashable: Any]? { + guard let userInfo = (request.content.mutableCopy() as? UNMutableNotificationContent)?.userInfo else { + Logger.common(message: "MindboxNotificationService: Failed to get userInfo", level: .error, category: .notification) + return nil + } + + if let innerUserInfo = userInfo["aps"] as? [AnyHashable: Any], innerUserInfo["uniqueKey"] != nil { + Logger.common(message: "MindboxNotificationService: userInfo: \(innerUserInfo), userInfo.keys.count: \(userInfo.keys.count), innerUserInfo: \(innerUserInfo)", level: .info, category: .notification) + return innerUserInfo + } else { + Logger.common(message: "MindboxNotificationService: userInfo: \(userInfo)", level: .info, category: .notification) + return userInfo + } + } +} diff --git a/MindboxNotifications/Utilities/Constants.swift b/MindboxNotifications/Utilities/Constants.swift new file mode 100644 index 00000000..a083fad9 --- /dev/null +++ b/MindboxNotifications/Utilities/Constants.swift @@ -0,0 +1,15 @@ +// +// Constants.swift +// MindboxNotifications +// +// Created by Sergei Semko on 8/12/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation + +enum Constants { + + /// For `UNMutableNotificationContent.categoryIdentifier` + static var categoryIdentifier = "MindBoxCategoryIdentifier" +} diff --git a/MindboxNotificationsTests/MindboxNotificationContentTests.swift b/MindboxNotificationsTests/MindboxNotificationContentTests.swift new file mode 100644 index 00000000..285aaab6 --- /dev/null +++ b/MindboxNotificationsTests/MindboxNotificationContentTests.swift @@ -0,0 +1,142 @@ +// +// MindboxNotificationContentTests.swift +// MindboxNotificationsTests +// +// Created by Sergei Semko on 8/13/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import XCTest +@testable import MindboxNotifications + +@available(iOS 13.0, *) +final class MindboxNotificationContentTests: XCTestCase { + + var service: MindboxNotificationService! // MindboxNotificationContentProtocol + var mockViewController: UIViewController! + var mockExtensionContext: NSExtensionContext! + var mockNotificationRequest: UNNotificationRequest! + + override func setUp() { + super.setUp() + service = MindboxNotificationService() + mockViewController = UIViewController() + mockExtensionContext = MockExtensionContext() + + let aps: [AnyHashable: Any] = [ + "mutable-content": 1, + "alert": [ + "title": "Test title", + "body": "Test description" + ], + "content-available": 1, + "sound": "default" + ] + + let userInfo: [AnyHashable: Any] = [ + "clickUrl": "https://mindbox.ru/", + "payload": "{\n \"payload\": \"data\"\n}", + "uniqueKey": "4cccb64d-ba46-41eb-9699-3a706f2b910b", + "imageUrl": "https://mobpush-images.mindbox.ru/Mpush-test/63/5933f4cd-47e3-4317-9237-bc5aad291aa9.png", + "buttons": [ + [ + "url": "https://developers.mindbox.ru/docs/mindbox-sdk", + "text": "Documentation", + "uniqueKey": "1b112bcd-5eae-4914-8842-d77198466466" + ], + [ + "url": "https://google.com", + "text": "Button #1", + "uniqueKey": "cff05f38-6df4-4a10-9859-ea3bf0a65068" + ] + ], + "aps": aps + ] + + let content = UNMutableNotificationContent() + content.userInfo = userInfo + content.title = "Test title" + content.body = "Test description" + + let image = UIImage(systemName: "star")! + let imageData = image.pngData()! + let tempDirectory = FileManager.default.temporaryDirectory + let imageFileURL = tempDirectory.appendingPathComponent("testImage.png") + + try! imageData.write(to: imageFileURL) + let notificationAttachment = try! UNNotificationAttachment(identifier: "identifier", url: imageFileURL, options: nil) + + content.attachments.append(notificationAttachment) + + mockNotificationRequest = UNNotificationRequest(identifier: "test", content: content, trigger: nil) + } + + override func tearDown() { + service = nil + mockViewController = nil + mockExtensionContext = nil + mockNotificationRequest = nil + super.tearDown() + } + + func testDidReceiveFromMindboxNotificationContentProtocol() { + let mockNotification = MockUNNotification(request: mockNotificationRequest) + + XCTAssertFalse(mockNotification.request.content.attachments.isEmpty) + + service.didReceive(notification: mockNotification, + viewController: mockViewController, + extensionContext: mockExtensionContext) + + XCTAssertEqual(service.viewController, mockViewController) + XCTAssertEqual(service.context, mockExtensionContext) + + XCTAssertFalse(service.context!.notificationActions.isEmpty) + XCTAssertEqual(service.context!.notificationActions.count, 2) + + let actionTitles = service.context!.notificationActions.map { $0.title } + XCTAssertTrue(actionTitles.contains("Documentation")) + XCTAssertTrue(actionTitles.contains("Button #1")) + + let imageView = mockViewController.view.subviews.first { $0 is UIImageView } as? UIImageView + XCTAssertNotNil(imageView, "The UIImageView should be added to the ViewController") + } +} + +@available(iOS 12.0, *) +fileprivate class MockExtensionContext: NSExtensionContext { + var actions: [UNNotificationAction] = [] + + override var notificationActions: [UNNotificationAction] { + get { + return actions + } + set { + actions = newValue + } + } +} + + +@available(iOS 12.0, *) +fileprivate class MockUNNotification: UNNotification { + private let mockRequest: UNNotificationRequest + + init(request: UNNotificationRequest) { + self.mockRequest = request + + let data = try! NSKeyedArchiver.archivedData(withRootObject: request, requiringSecureCoding: true) + + let coder = try! NSKeyedUnarchiver(forReadingFrom: data) + + super.init(coder: coder)! + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var request: UNNotificationRequest { + return mockRequest + } +} diff --git a/MindboxNotificationsTests/MindboxNotificationServiceTests.swift b/MindboxNotificationsTests/MindboxNotificationServiceTests.swift new file mode 100644 index 00000000..3262b105 --- /dev/null +++ b/MindboxNotificationsTests/MindboxNotificationServiceTests.swift @@ -0,0 +1,100 @@ +// +// MindboxNotificationServiceTests.swift +// MindboxNotificationsTests +// +// Created by Sergei Semko on 8/13/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import XCTest +@testable import MindboxNotifications + +final class MindboxNotificationServiceTests: XCTestCase { + + var service: MindboxNotificationServiceProtocol! + var mockNotificationRequest: UNNotificationRequest! + + override func setUp() { + super.setUp() + service = MindboxNotificationService() + + let aps: [AnyHashable: Any] = [ + "mutable-content": 1, + "alert": [ + "title": "Test title", + "body": "Test description" + ], + "content-available": 1, + "sound": "default" + ] + + let userInfo: [AnyHashable: Any] = [ + "clickUrl": "https://mindbox.ru/", + "payload": "{\n \"payload\": \"data\"\n}", + "uniqueKey": "4cccb64d-ba46-41eb-9699-3a706f2b910b", + "imageUrl": "https://mobpush-images.mindbox.ru/Mpush-test/63/5933f4cd-47e3-4317-9237-bc5aad291aa9.png", + "buttons": [ + [ + "url": "https://developers.mindbox.ru/docs/mindbox-sdk", + "text": "Documentation", + "uniqueKey": "1b112bcd-5eae-4914-8842-d77198466466" + ], + [ + "url": "https://google.com", + "text": "Button #1", + "uniqueKey": "cff05f38-6df4-4a10-9859-ea3bf0a65068" + ] + ], + "aps": aps + ] + + let content = UNMutableNotificationContent() + content.userInfo = userInfo + content.title = "Test title" + content.body = "Test description" + mockNotificationRequest = UNNotificationRequest(identifier: "test", content: content, trigger: nil) + } + + override func tearDown() { + service = nil + mockNotificationRequest = nil + super.tearDown() + } + + func testDidReceiveWithContentHandler() { + let expectation = self.expectation(description: "Content Handler Called") + var receivedContent: UNNotificationContent? + + service.didReceive(mockNotificationRequest) { content in + receivedContent = content + expectation.fulfill() + } + + waitForExpectations(timeout: 1, handler: nil) + XCTAssertNotNil(service.contentHandler) + XCTAssertNotNil(service.bestAttemptContent) + XCTAssertEqual(service.bestAttemptContent?.userInfo["uniqueKey"] as? String, "4cccb64d-ba46-41eb-9699-3a706f2b910b") + XCTAssertNotNil(receivedContent) + + XCTAssertEqual(service.bestAttemptContent?.title, "Test title") + XCTAssertEqual(service.bestAttemptContent?.body, "Test description") + XCTAssertFalse(service.bestAttemptContent!.attachments.isEmpty) + XCTAssertTrue(service.bestAttemptContent!.attachments.count == 1) + } + + func testServiceExtensionTimeWillExpire_CallsProceedFinalStage() { + let expectation = self.expectation(description: "Content Handler Called") + var receivedContent: UNNotificationContent? + + service.didReceive(mockNotificationRequest) { content in + receivedContent = content + expectation.fulfill() + } + + service.serviceExtensionTimeWillExpire() + + waitForExpectations(timeout: 1, handler: nil) + XCTAssertNotNil(receivedContent) + XCTAssertEqual(receivedContent?.categoryIdentifier, Constants.categoryIdentifier) + } +} diff --git a/MindboxNotificationsTests/MindboxNotificationsTests.swift b/MindboxNotificationsTests/MindboxNotificationsTests.swift deleted file mode 100644 index 637f5922..00000000 --- a/MindboxNotificationsTests/MindboxNotificationsTests.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// MindboxNotificationsTests.swift -// MindboxNotificationsTests -// -// Created by Ihor Kandaurov on 22.06.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - -import XCTest -@testable import MindboxNotifications - -class MindboxNotificationsTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/MindboxNotificationsTests/SharedInternalMethodsTests.swift b/MindboxNotificationsTests/SharedInternalMethodsTests.swift new file mode 100644 index 00000000..220afa16 --- /dev/null +++ b/MindboxNotificationsTests/SharedInternalMethodsTests.swift @@ -0,0 +1,137 @@ +// +// SharedInternalMethodsTests.swift +// MindboxNotificationsTests +// +// Created by Sergei Semko on 8/13/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import XCTest +@testable import MindboxNotifications + +final class SharedInternalMethodsTests: XCTestCase { + + var service: MindboxNotificationService! + var mockNotificationRequest: UNNotificationRequest! + + override func setUp() { + super.setUp() + service = MindboxNotificationService() + + let aps: [AnyHashable: Any] = [ + "mutable-content": 1, + "alert": [ + "title": "Test title", + "body": "Test description" + ], + "content-available": 1, + "sound": "default" + ] + + let userInfo: [AnyHashable: Any] = [ + "clickUrl": "https://mindbox.ru/", + "payload": "{\n \"payload\": \"data\"\n}", + "uniqueKey": "4cccb64d-ba46-41eb-9699-3a706f2b910b", + "imageUrl": "https://mobpush-images.mindbox.ru/Mpush-test/63/5933f4cd-47e3-4317-9237-bc5aad291aa9.png", + "buttons": [ + [ + "url": "https://developers.mindbox.ru/docs/mindbox-sdk", + "text": "Documentation", + "uniqueKey": "1b112bcd-5eae-4914-8842-d77198466466" + ], + [ + "url": "https://google.com", + "text": "Button #1", + "uniqueKey": "cff05f38-6df4-4a10-9859-ea3bf0a65068" + ] + ], + "aps": aps + ] + + let content = UNMutableNotificationContent() + content.userInfo = userInfo + mockNotificationRequest = UNNotificationRequest(identifier: "test", content: content, trigger: nil) + } + + override func tearDown() { + service = nil + mockNotificationRequest = nil + super.tearDown() + } + + func testParseToPayload() { + let payload = service.parse(request: mockNotificationRequest) + XCTAssertNotNil(payload) + XCTAssertNotNil(payload?.withButton) + XCTAssertEqual(payload?.withButton?.buttons?.first?.uniqueKey, "1b112bcd-5eae-4914-8842-d77198466466") + XCTAssertEqual(payload?.withButton?.buttons?.first?.text, "Documentation") + XCTAssertEqual(payload?.withButton?.buttons?.last?.uniqueKey, "cff05f38-6df4-4a10-9859-ea3bf0a65068") + XCTAssertEqual(payload?.withButton?.buttons?.last?.text, "Button #1") + XCTAssertNotNil(payload?.withImageURL) + XCTAssertEqual(payload?.withImageURL?.imageUrl, "https://mobpush-images.mindbox.ru/Mpush-test/63/5933f4cd-47e3-4317-9237-bc5aad291aa9.png") + } + + func testGetUserInfo() { + let result = service.getUserInfo(from: mockNotificationRequest) + XCTAssertNotNil(result) + XCTAssertEqual(result?["uniqueKey"] as? String, "4cccb64d-ba46-41eb-9699-3a706f2b910b") + XCTAssertEqual(result?["clickUrl"] as? String, "https://mindbox.ru/") + + let apsResult = result?["aps"] as? [AnyHashable: Any] + XCTAssertNotNil(apsResult) + XCTAssertEqual(apsResult?["mutable-content"] as? Int, 1) + XCTAssertEqual(apsResult?["content-available"] as? Int, 1) + XCTAssertEqual(apsResult?["sound"] as? String, "default") + + let alertResult = apsResult?["alert"] as? [String: String] + XCTAssertNotNil(alertResult) + XCTAssertEqual(alertResult?["title"], "Test title") + XCTAssertEqual(alertResult?["body"], "Test description") + } + + func testGetUserInfoLegacyPushFormat() { + let aps: [AnyHashable: Any] = [ + "aps": [ + "mutable-content": 1, + "alert": [ + "title": "Test title", + "body": "Test description" + ], + "content-available": 1, + "sound": "default", + "clickUrl": "https://mindbox.ru/", + "payload": "{\n \"payload\": \"data\"\n}", + "uniqueKey": "4cccb64d-ba46-41eb-9699-3a706f2b910b", + "imageUrl": "https://mobpush-images.mindbox.ru/Mpush-test/63/5933f4cd-47e3-4317-9237-bc5aad291aa9.png", + "buttons": [ + [ + "url": "https://developers.mindbox.ru/docs/mindbox-sdk", + "text": "Documentation", + "uniqueKey": "1b112bcd-5eae-4914-8842-d77198466466" + ] + ], + ] + ] + + let userInfo: [AnyHashable: Any] = aps + + let content = UNMutableNotificationContent() + content.userInfo = userInfo + let request = UNNotificationRequest(identifier: "test", content: content, trigger: nil) + + let result = service.getUserInfo(from: request) + XCTAssertNotNil(result) + XCTAssertEqual(result?["uniqueKey"] as? String, "4cccb64d-ba46-41eb-9699-3a706f2b910b") + XCTAssertEqual(result?["clickUrl"] as? String, "https://mindbox.ru/") + + + XCTAssertEqual(result?["mutable-content"] as? Int, 1) + XCTAssertEqual(result?["content-available"] as? Int, 1) + XCTAssertEqual(result?["sound"] as? String, "default") + + let alertResult = result?["alert"] as? [String: String] + XCTAssertNotNil(alertResult) + XCTAssertEqual(alertResult?["title"], "Test title") + XCTAssertEqual(alertResult?["body"], "Test description") + } +} diff --git a/MindboxTests/ABTests/ABTestDeviceMixerTests.swift b/MindboxTests/ABTests/ABTestDeviceMixerTests.swift index e6107514..c372b843 100644 --- a/MindboxTests/ABTests/ABTestDeviceMixerTests.swift +++ b/MindboxTests/ABTests/ABTestDeviceMixerTests.swift @@ -15,7 +15,7 @@ final class ABTestDeviceMixerTests: XCTestCase { override func setUp() { super.setUp() - sut = ABTestDeviceMixer() + sut = DI.injectOrFail(ABTestDeviceMixer.self) } override func tearDown() { diff --git a/MindboxTests/ABTests/ABTestValidatorTests.swift b/MindboxTests/ABTests/ABTestValidatorTests.swift index c557f5fa..f7d819eb 100644 --- a/MindboxTests/ABTests/ABTestValidatorTests.swift +++ b/MindboxTests/ABTests/ABTestValidatorTests.swift @@ -11,7 +11,6 @@ import XCTest class ABTestValidatorTests: XCTestCase { - let sdkVersionValidator = SDKVersionValidator(sdkVersionNumeric: Constants.Versions.sdkVersionNumeric) var validator: ABTestValidator! struct TestCase { @@ -23,7 +22,7 @@ class ABTestValidatorTests: XCTestCase { override func setUp() { super.setUp() - validator = ABTestValidator(sdkVersionValidator: sdkVersionValidator) + validator = DI.injectOrFail(ABTestValidator.self) let modulus = ABTest.ABTestVariant.Modulus(lower: 0, upper: 100) let abObject = ABTest.ABTestVariant.ABTestObject(type: .inapps, kind: .all, inapps: ["inapp1"]) diff --git a/MindboxTests/ABTests/ABTestVariantsValidatorTests.swift b/MindboxTests/ABTests/ABTestVariantsValidatorTests.swift index 944e4ebb..0f28bf82 100644 --- a/MindboxTests/ABTests/ABTestVariantsValidatorTests.swift +++ b/MindboxTests/ABTests/ABTestVariantsValidatorTests.swift @@ -21,6 +21,7 @@ class ABTestVariantsValidatorTests: XCTestCase { override func setUp() { super.setUp() + variantValidator = DI.injectOrFail(ABTestVariantsValidator.self) // TODO: - Divide to few different unit tests. @@ -38,7 +39,7 @@ class ABTestVariantsValidatorTests: XCTestCase { TestCase(variant: nil, isValid: false) // variant is nil ] - variantValidator = ABTestVariantsValidator() + } override func tearDown() { diff --git a/MindboxTests/DI/DIMainModuleRegistrationTests.swift b/MindboxTests/DI/DIMainModuleRegistrationTests.swift new file mode 100644 index 00000000..28fc5c90 --- /dev/null +++ b/MindboxTests/DI/DIMainModuleRegistrationTests.swift @@ -0,0 +1,256 @@ +// +// DIMainModuleRegistrationTests.swift +// MindboxTests +// +// Created by vailence on 11.07.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import XCTest +@testable import Mindbox + +final class DIMainModuleRegistrationTests: XCTestCase { + + override func setUp() { + super.setUp() + MBInject.mode = .standard + } + + func testCoreControllerIsRegistered() { + let coreController: CoreController? = DI.inject(CoreController.self) + XCTAssertNotNil(coreController) + } + + func testGuaranteedDeliveryManagerIsRegistered() { + let manager: GuaranteedDeliveryManager? = DI.inject(GuaranteedDeliveryManager.self) + XCTAssertNotNil(manager) + } + + func testInAppConfigurationMapperIsRegistered() { + let mapper: InAppConfigurationMapperProtocol? = DI.inject(InAppConfigurationMapperProtocol.self) + XCTAssertNotNil(mapper) + } + + func testInAppConfigurationManagerIsRegistered() { + let manager: InAppConfigurationManagerProtocol? = DI.inject(InAppConfigurationManagerProtocol.self) + XCTAssertNotNil(manager) + } + + func testInAppCoreManagerIsRegistered() { + let manager: InAppCoreManagerProtocol? = DI.inject(InAppCoreManagerProtocol.self) + XCTAssertNotNil(manager) + } + + func testUUIDDebugServiceIsRegistered() { + let service: UUIDDebugService? = DI.inject(UUIDDebugService.self) + XCTAssertNotNil(service) + } + + func testUNAuthorizationStatusProviderIsRegistered() { + let provider: UNAuthorizationStatusProviding? = DI.inject(UNAuthorizationStatusProviding.self) + XCTAssertNotNil(provider) + } + + func testSDKVersionValidatorIsRegistered() { + let validator: SDKVersionValidator? = DI.inject(SDKVersionValidator.self) + XCTAssertNotNil(validator) + } + + func testPersistenceStorageIsRegistered() { + let storage: PersistenceStorage? = DI.inject(PersistenceStorage.self) + XCTAssertNotNil(storage) + } + + func testDatabaseRepositoryIsRegistered() { + let repository: MBDatabaseRepository? = DI.inject(MBDatabaseRepository.self) + XCTAssertNotNil(repository) + } + + func testImageDownloadServiceIsRegistered() { + let service: ImageDownloadServiceProtocol? = DI.inject(ImageDownloadServiceProtocol.self) + XCTAssertNotNil(service) + } + + func testNetworkFetcherIsRegistered() { + let fetcher: NetworkFetcher? = DI.inject(NetworkFetcher.self) + XCTAssertNotNil(fetcher) + } + + func testInAppConfigurationDataFacadeIsRegistered() { + let facade: InAppConfigurationDataFacadeProtocol? = DI.inject(InAppConfigurationDataFacadeProtocol.self) + XCTAssertNotNil(facade) + } + + func testSessionManagerIsRegistered() { + let manager: SessionManager? = DI.inject(SessionManager.self) + XCTAssertNotNil(manager) + } + + func testSDKLogsManagerIsRegistered() { + let manager: SDKLogsManagerProtocol? = DI.inject(SDKLogsManagerProtocol.self) + XCTAssertNotNil(manager) + } + + // Тесты для UtilitiesServices + func testUtilitiesFetcherIsRegistered() { + let fetcher: UtilitiesFetcher? = DI.inject(UtilitiesFetcher.self) + XCTAssertNotNil(fetcher) + } + + func testTimerManagerIsRegistered() { + let timerManager: TimerManager? = DI.inject(TimerManager.self) + XCTAssertNotNil(timerManager) + } + + func testUserVisitManagerIsRegistered() { + let manager: UserVisitManagerProtocol? = DI.inject(UserVisitManagerProtocol.self) + XCTAssertNotNil(manager) + } + + func testMindboxPushValidatorIsRegistered() { + let validator: MindboxPushValidator? = DI.inject(MindboxPushValidator.self) + XCTAssertNotNil(validator) + } + + func testInAppTargetingCheckerIsRegistered() { + let checker: InAppTargetingCheckerProtocol? = DI.inject(InAppTargetingCheckerProtocol.self) + XCTAssertNotNil(checker) + } + + func testDataBaseLoaderIsRegistered() { + let loader: DataBaseLoader? = DI.inject(DataBaseLoader.self) + XCTAssertNotNil(loader) + } + + func testVariantImageUrlExtractorServiceIsRegistered() { + let extractor: VariantImageUrlExtractorServiceProtocol? = DI.inject(VariantImageUrlExtractorServiceProtocol.self) + XCTAssertNotNil(extractor) + } + + func testGeoServiceIsRegistered() { + let service: GeoServiceProtocol? = DI.inject(GeoServiceProtocol.self) + XCTAssertNotNil(service) + } + + func testSegmentationServiceIsRegistered() { + let service: SegmentationServiceProtocol? = DI.inject(SegmentationServiceProtocol.self) + XCTAssertNotNil(service) + } + + func testEventRepositoryIsRegistered() { + let repository: EventRepository? = DI.inject(EventRepository.self) + XCTAssertNotNil(repository) + } + + func testTrackVisitManagerIsRegistered() { + let manager: TrackVisitManager? = DI.inject(TrackVisitManager.self) + XCTAssertNotNil(manager) + } + + func testInappMessageEventSenderIsRegistered() { + let sender: InappMessageEventSender? = DI.inject(InappMessageEventSender.self) + XCTAssertNotNil(sender) + } + + func testClickNotificationManagerIsRegistered() { + let manager: ClickNotificationManager? = DI.inject(ClickNotificationManager.self) + XCTAssertNotNil(manager) + } + + func testABTestDeviceMixerIsRegistered() { + let mixer: ABTestDeviceMixer? = DI.inject(ABTestDeviceMixer.self) + XCTAssertNotNil(mixer) + } + + func testABTestVariantsValidatorIsRegistered() { + let validator: ABTestVariantsValidator? = DI.inject(ABTestVariantsValidator.self) + XCTAssertNotNil(validator) + } + + func testABTestValidatorIsRegistered() { + let validator: ABTestValidator? = DI.inject(ABTestValidator.self) + XCTAssertNotNil(validator) + } + + func testLayerActionFilterIsRegistered() { + let filter: LayerActionFilterProtocol? = DI.inject(LayerActionFilterProtocol.self) + XCTAssertNotNil(filter) + } + + func testLayersSourceFilterIsRegistered() { + let filter: LayersSourceFilterProtocol? = DI.inject(LayersSourceFilterProtocol.self) + XCTAssertNotNil(filter) + } + + func testLayersFilterIsRegistered() { + let filter: LayersFilterProtocol? = DI.inject(LayersFilterProtocol.self) + XCTAssertNotNil(filter) + } + + func testElementsSizeFilterIsRegistered() { + let filter: ElementsSizeFilterProtocol? = DI.inject(ElementsSizeFilterProtocol.self) + XCTAssertNotNil(filter) + } + + func testElementsColorFilterIsRegistered() { + let filter: ElementsColorFilterProtocol? = DI.inject(ElementsColorFilterProtocol.self) + XCTAssertNotNil(filter) + } + + func testElementsPositionFilterIsRegistered() { + let filter: ElementsPositionFilterProtocol? = DI.inject(ElementsPositionFilterProtocol.self) + XCTAssertNotNil(filter) + } + + func testElementsFilterIsRegistered() { + let filter: ElementsFilterProtocol? = DI.inject(ElementsFilterProtocol.self) + XCTAssertNotNil(filter) + } + + func testContentPositionFilterIsRegistered() { + let filter: ContentPositionFilterProtocol? = DI.inject(ContentPositionFilterProtocol.self) + XCTAssertNotNil(filter) + } + + func testVariantFilterIsRegistered() { + let filter: VariantFilterProtocol? = DI.inject(VariantFilterProtocol.self) + XCTAssertNotNil(filter) + } + + func testInappFilterIsRegistered() { + let filter: InappFilterProtocol? = DI.inject(InappFilterProtocol.self) + XCTAssertNotNil(filter) + } + + // Тесты для InappPresentation + func testInAppMessagesTrackerIsRegistered() { + let tracker: InAppMessagesTracker? = DI.inject(InAppMessagesTracker.self) + XCTAssertNotNil(tracker) + } + + func testPresentationDisplayUseCaseIsRegistered() { + let useCase: PresentationDisplayUseCase? = DI.inject(PresentationDisplayUseCase.self) + XCTAssertNotNil(useCase) + } + + func testUseCaseFactoryIsRegistered() { + let factory: UseCaseFactoryProtocol? = DI.inject(UseCaseFactoryProtocol.self) + XCTAssertNotNil(factory) + } + + func testInAppActionHandlerIsRegistered() { + let handler: InAppActionHandlerProtocol? = DI.inject(InAppActionHandlerProtocol.self) + XCTAssertNotNil(handler) + } + + func testInAppPresentationManagerIsRegistered() { + let manager: InAppPresentationManagerProtocol? = DI.inject(InAppPresentationManagerProtocol.self) + XCTAssertNotNil(manager) + } + + func testMigrationManagerIsRegistered() { + let manager: MigrationManagerProtocol? = DI.inject(MigrationManagerProtocol.self) + XCTAssertNotNil(manager) + XCTAssert(manager is MigrationManager) + } +} diff --git a/MindboxTests/DI/DIMainModuleReplaceableTests.swift b/MindboxTests/DI/DIMainModuleReplaceableTests.swift new file mode 100644 index 00000000..23b7a20c --- /dev/null +++ b/MindboxTests/DI/DIMainModuleReplaceableTests.swift @@ -0,0 +1,81 @@ +// +// DIMainModuleReplaceableTests.swift +// MindboxTests +// +// Created by vailence on 11.07.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import XCTest +@testable import Mindbox + +class DIMainModuleReplaceableTests: XCTestCase { + override func setUp() { + super.setUp() + MBInject.mode = .standard + } + + func testUUIDDebugServiceIsRegistered() { + let service: UUIDDebugService? = DI.inject(UUIDDebugService.self) + XCTAssertNotNil(service) + XCTAssert(service is PasteboardUUIDDebugService) + } + + func testUNAuthorizationStatusProviderIsRegistered() { + let provider: UNAuthorizationStatusProviding? = DI.inject(UNAuthorizationStatusProviding.self) + XCTAssertNotNil(provider) + XCTAssert(provider is UNAuthorizationStatusProvider) + } + + func testSDKVersionValidatorIsRegistered() { + let validator: SDKVersionValidator? = DI.inject(SDKVersionValidator.self) + XCTAssertNotNil(validator) + } + + func testPersistenceStorageIsRegistered() { + let storage: PersistenceStorage? = DI.inject(PersistenceStorage.self) + XCTAssertNotNil(storage) + XCTAssert(storage is MBPersistenceStorage) + } + + func testDatabaseRepositoryIsRegistered() { + let repository: MBDatabaseRepository? = DI.inject(MBDatabaseRepository.self) + XCTAssertNotNil(repository) + } + + func testImageDownloadServiceIsRegistered() { + let service: ImageDownloadServiceProtocol? = DI.inject(ImageDownloadServiceProtocol.self) + XCTAssertNotNil(service) + XCTAssert(service is ImageDownloadService) + } + + func testNetworkFetcherIsRegistered() { + let fetcher: NetworkFetcher? = DI.inject(NetworkFetcher.self) + XCTAssertNotNil(fetcher) + XCTAssert(fetcher is MBNetworkFetcher) + } + + func testInAppConfigurationDataFacadeIsRegistered() { + let facade: InAppConfigurationDataFacadeProtocol? = DI.inject(InAppConfigurationDataFacadeProtocol.self) + XCTAssertNotNil(facade) + XCTAssert(facade is InAppConfigurationDataFacade) + } + + func testSessionManagerIsRegistered() { + let manager: SessionManager? = DI.inject(SessionManager.self) + XCTAssertNotNil(manager) + XCTAssert(manager is MBSessionManager) + } + + func testSDKLogsManagerIsRegistered() { + let manager: SDKLogsManagerProtocol? = DI.inject(SDKLogsManagerProtocol.self) + XCTAssertNotNil(manager) + XCTAssert(manager is SDKLogsManager) + } + + func testInAppCoreManagerIsRegistered() { + let manager: InAppCoreManagerProtocol? = DI.inject(InAppCoreManagerProtocol.self) + XCTAssertNotNil(manager) + XCTAssert(manager is InAppCoreManager) + } +} diff --git a/MindboxTests/DI/DITestModuleReplaceableTests.swift b/MindboxTests/DI/DITestModuleReplaceableTests.swift new file mode 100644 index 00000000..166b9da4 --- /dev/null +++ b/MindboxTests/DI/DITestModuleReplaceableTests.swift @@ -0,0 +1,82 @@ +// +// DITestModuleReplaceableTests.swift +// MindboxTests +// +// Created by vailence on 11.07.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import XCTest +@testable import Mindbox + +class DITestModuleReplaceableTests: XCTestCase { + override func setUp() { + super.setUp() + MBInject.mode = .test + } + + func testUUIDDebugServiceIsRegistered() { + let service: UUIDDebugService? = DI.inject(UUIDDebugService.self) + XCTAssertNotNil(service) + XCTAssert(service is MockUUIDDebugService) + } + + func testUNAuthorizationStatusProviderIsRegistered() { + let provider: UNAuthorizationStatusProviding? = DI.inject(UNAuthorizationStatusProviding.self) + XCTAssertNotNil(provider) + XCTAssert(provider is MockUNAuthorizationStatusProvider) + } + + func testSDKVersionValidatorIsRegistered() { + let validator: SDKVersionValidator? = DI.inject(SDKVersionValidator.self) + XCTAssertNotNil(validator) + } + + func testPersistenceStorageIsRegistered() { + let storage: PersistenceStorage? = DI.inject(PersistenceStorage.self) + XCTAssertNotNil(storage) + XCTAssert(storage is MockPersistenceStorage) + } + + func testDatabaseRepositoryIsRegistered() { + let repository: MBDatabaseRepository? = DI.inject(MBDatabaseRepository.self) + XCTAssertNotNil(repository) + XCTAssert(repository is MockDatabaseRepository) + } + + func testImageDownloadServiceIsRegistered() { + let service: ImageDownloadServiceProtocol? = DI.inject(ImageDownloadServiceProtocol.self) + XCTAssertNotNil(service) + XCTAssert(service is MockImageDownloadService) + } + + func testNetworkFetcherIsRegistered() { + let fetcher: NetworkFetcher? = DI.inject(NetworkFetcher.self) + XCTAssertNotNil(fetcher) + XCTAssert(fetcher is MockNetworkFetcher) + } + + func testInAppConfigurationDataFacadeIsRegistered() { + let facade: InAppConfigurationDataFacadeProtocol? = DI.inject(InAppConfigurationDataFacadeProtocol.self) + XCTAssertNotNil(facade) + XCTAssert(facade is MockInAppConfigurationDataFacade) + } + + func testSessionManagerIsRegistered() { + let manager: SessionManager? = DI.inject(SessionManager.self) + XCTAssertNotNil(manager) + XCTAssert(manager is MockSessionManager) + } + + func testSDKLogsManagerIsRegistered() { + let manager: SDKLogsManagerProtocol? = DI.inject(SDKLogsManagerProtocol.self) + XCTAssertNotNil(manager) + XCTAssert(manager is SDKLogsManager) + } + + func testInAppCoreManagerIsRegistered() { + let manager: InAppCoreManagerProtocol? = DI.inject(InAppCoreManagerProtocol.self) + XCTAssertNotNil(manager) + XCTAssert(manager is InAppCoreManagerMock) + } +} diff --git a/MindboxTests/DI/DITests.swift b/MindboxTests/DI/DITests.swift new file mode 100644 index 00000000..535175ad --- /dev/null +++ b/MindboxTests/DI/DITests.swift @@ -0,0 +1,85 @@ +// +// DITests.swift +// MindboxTests +// +// Created by vailence on 11.07.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import XCTest +@testable import Mindbox + + +class TestModeRegistrationTests: XCTestCase { + override func setUp() { + super.setUp() + MBInject.mode = .test + } + + func testUUIDDebugServiceIsRegistered() { + let service: UUIDDebugService? = DI.inject(UUIDDebugService.self) + XCTAssertNotNil(service) + XCTAssert(service is MockUUIDDebugService) + } + + func testUNAuthorizationStatusProviderIsRegistered() { + let provider: UNAuthorizationStatusProviding? = DI.inject(UNAuthorizationStatusProviding.self) + XCTAssertNotNil(provider) + XCTAssert(provider is MockUNAuthorizationStatusProvider) + } + + func testSDKVersionValidatorIsRegistered() { + let validator: SDKVersionValidator? = DI.inject(SDKVersionValidator.self) + XCTAssertNotNil(validator) + } + + func testPersistenceStorageIsRegistered() { + let storage: PersistenceStorage? = DI.inject(PersistenceStorage.self) + XCTAssertNotNil(storage) + XCTAssert(storage is MockPersistenceStorage) + } + + func testDatabaseRepositoryIsRegistered() { + let repository: MBDatabaseRepository? = DI.inject(MBDatabaseRepository.self) + XCTAssertNotNil(repository) + XCTAssert(repository is MockDatabaseRepository) + } + + func testImageDownloadServiceIsRegistered() { + let service: ImageDownloadServiceProtocol? = DI.inject(ImageDownloadServiceProtocol.self) + XCTAssertNotNil(service) + XCTAssert(service is MockImageDownloadService) + } + + func testNetworkFetcherIsRegistered() { + let fetcher: NetworkFetcher? = DI.inject(NetworkFetcher.self) + XCTAssertNotNil(fetcher) + XCTAssert(fetcher is MockNetworkFetcher) + } + + func testInAppConfigurationDataFacadeIsRegistered() { + let facade: InAppConfigurationDataFacadeProtocol? = DI.inject(InAppConfigurationDataFacadeProtocol.self) + XCTAssertNotNil(facade) + XCTAssert(facade is MockInAppConfigurationDataFacade) + } + + func testSessionManagerIsRegistered() { + let manager: SessionManager? = DI.inject(SessionManager.self) + XCTAssertNotNil(manager) + XCTAssert(manager is MockSessionManager) + } + + func testSDKLogsManagerIsRegistered() { + let manager: SDKLogsManagerProtocol? = DI.inject(SDKLogsManagerProtocol.self) + XCTAssertNotNil(manager) + XCTAssert(manager is SDKLogsManager) + } + + func testInAppCoreManagerIsRegistered() { + let manager: InAppCoreManagerProtocol? = DI.inject(InAppCoreManagerProtocol.self) + XCTAssertNotNil(manager) + XCTAssert(manager is InAppCoreManagerMock) + } + + // Добавьте тесты для всех остальных классов +} diff --git a/MindboxTests/DI/Injections/InjectionMocks.swift b/MindboxTests/DI/Injections/InjectionMocks.swift new file mode 100644 index 00000000..0003a262 --- /dev/null +++ b/MindboxTests/DI/Injections/InjectionMocks.swift @@ -0,0 +1,75 @@ +// +// InjectionMocks.swift +// MindboxTests +// +// Created by vailence on 21.06.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation +@testable import Mindbox + +extension MBContainer { + func registerMocks() -> Self { + register(UUIDDebugService.self) { + MockUUIDDebugService() + } + + register(UNAuthorizationStatusProviding.self, scope: .transient) { + MockUNAuthorizationStatusProvider(status: .authorized) + } + + register(SDKVersionValidator.self) { + SDKVersionValidator(sdkVersionNumeric: Constants.Versions.sdkVersionNumeric) + } + + register(PersistenceStorage.self) { + MockPersistenceStorage() + } + + register(MBDatabaseRepository.self) { + let databaseLoader = DI.injectOrFail(DataBaseLoader.self) + let persistentContainer = try! databaseLoader.loadPersistentContainer() + return try! MockDatabaseRepository(persistentContainer: persistentContainer) + } + + register(ImageDownloadServiceProtocol.self, scope: .container) { + MockImageDownloadService() + } + + register(NetworkFetcher.self) { + MockNetworkFetcher() + } + + register(InAppConfigurationDataFacadeProtocol.self) { + let segmentationService = DI.injectOrFail(SegmentationServiceProtocol.self) + let targetingChecker = DI.injectOrFail(InAppTargetingCheckerProtocol.self) + let imageService = DI.injectOrFail(ImageDownloadServiceProtocol.self) + let tracker = DI.injectOrFail(InAppMessagesTracker.self) + return MockInAppConfigurationDataFacade(segmentationService: segmentationService, + targetingChecker: targetingChecker, + imageService: imageService, + tracker: tracker) + } + + register(SessionManager.self) { + MockSessionManager() + } + + register(EventRepositoryMock.self) { + EventRepositoryMock() + } + + register(SDKLogsManagerProtocol.self) { + let persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + let eventRepository = DI.injectOrFail(EventRepositoryMock.self) + return SDKLogsManager(persistenceStorage: persistenceStorage, eventRepository: eventRepository) + } + + register(InAppCoreManagerProtocol.self) { + InAppCoreManagerMock() + } + + return self + } +} diff --git a/MindboxTests/DI/StubContainer.swift b/MindboxTests/DI/StubContainer.swift new file mode 100644 index 00000000..c06c6e56 --- /dev/null +++ b/MindboxTests/DI/StubContainer.swift @@ -0,0 +1,27 @@ +// +// StubContainer.swift +// MindboxTests +// +// Created by vailence on 21.06.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation +@testable import Mindbox + +enum TestConfiguration { + static func configure() { + MBInject.buildTestContainer = { + let container = MBContainer() + return container + .registerCore() + .registerUtilitiesServices() + .registerABTestUtilities() + .registerInappTools() + .registerInappPresentation() + .registerMocks() + } + + MBInject.mode = .test + } +} diff --git a/MindboxTests/DI/TestDependencyProvider.swift b/MindboxTests/DI/TestDependencyProvider.swift deleted file mode 100644 index bb435132..00000000 --- a/MindboxTests/DI/TestDependencyProvider.swift +++ /dev/null @@ -1,128 +0,0 @@ -// -// DITest.swift -// MindboxTests -// -// Created by Mikhail Barilov on 28.01.2021. -// Copyright © 2021 Mindbox. All rights reserved. -// - - -import XCTest -@testable import Mindbox - -final class TestDependencyProvider: DependencyContainer { - var inAppTargetingChecker: InAppTargetingChecker - let inAppMessagesManager: InAppCoreManagerProtocol - let utilitiesFetcher: UtilitiesFetcher - let persistenceStorage: PersistenceStorage - let databaseLoader: DataBaseLoader - let databaseRepository: MBDatabaseRepository - let guaranteedDeliveryManager: GuaranteedDeliveryManager - let authorizationStatusProvider: UNAuthorizationStatusProviding - let sessionManager: SessionManager - let instanceFactory: InstanceFactory - let uuidDebugService: UUIDDebugService - var inappMessageEventSender: InappMessageEventSender - let sdkVersionValidator: SDKVersionValidator - var geoService: GeoServiceProtocol - var segmentationSevice: SegmentationServiceProtocol - var imageDownloadService: ImageDownloadServiceProtocol - var abTestDeviceMixer: ABTestDeviceMixer - var urlExtractorService: VariantImageUrlExtractorService - var inappFilterService: InappFilterProtocol - var pushValidator: MindboxPushValidator - var inAppConfigurationDataFacade: InAppConfigurationDataFacadeProtocol - var userVisitManager: UserVisitManagerProtocol - var ttlValidationService: TTLValidationProtocol - var frequencyValidator: InappFrequencyValidator - - init() throws { - utilitiesFetcher = MBUtilitiesFetcher() - persistenceStorage = MockPersistenceStorage() - databaseLoader = try DataBaseLoader() - frequencyValidator = InappFrequencyValidator(persistenceStorage: persistenceStorage) - let persistentContainer = try databaseLoader.loadPersistentContainer() - databaseRepository = try MockDatabaseRepository(persistentContainer: persistentContainer) - instanceFactory = MockInstanceFactory( - persistenceStorage: persistenceStorage, - utilitiesFetcher: utilitiesFetcher, - databaseRepository: databaseRepository - ) - guaranteedDeliveryManager = GuaranteedDeliveryManager( - persistenceStorage: persistenceStorage, - databaseRepository: databaseRepository, - eventRepository: instanceFactory.makeEventRepository() - ) - authorizationStatusProvider = MockUNAuthorizationStatusProvider(status: .authorized) - sessionManager = MockSessionManager() - inAppTargetingChecker = InAppTargetingChecker(persistenceStorage: persistenceStorage) - inAppMessagesManager = InAppCoreManagerMock() - uuidDebugService = MockUUIDDebugService() - inappMessageEventSender = InappMessageEventSender(inAppMessagesManager: inAppMessagesManager) - sdkVersionValidator = SDKVersionValidator(sdkVersionNumeric: 8) - geoService = GeoService(fetcher: instanceFactory.makeNetworkFetcher(), - targetingChecker: inAppTargetingChecker) - segmentationSevice = SegmentationService(customerSegmentsAPI: .live, - targetingChecker: inAppTargetingChecker) - imageDownloadService = MockImageDownloadService() - abTestDeviceMixer = ABTestDeviceMixer() - urlExtractorService = VariantImageUrlExtractorService() - ttlValidationService = TTLValidationService(persistenceStorage: persistenceStorage) - let tracker = InAppMessagesTracker(databaseRepository: databaseRepository) - inAppConfigurationDataFacade = InAppConfigurationDataFacade(geoService: geoService, - segmentationService: segmentationSevice, - targetingChecker: inAppTargetingChecker, - imageService: imageDownloadService, - tracker: tracker) - - let actionFilter = LayerActionFilterService() - let sourceFilter = LayersSourceFilterService() - let layersFilterService = LayersFilterService(actionFilter: actionFilter, sourceFilter: sourceFilter) - let sizeFilter = ElementSizeFilterService() - let colorFilter = ElementsColorFilterService() - let positionFilter = ElementsPositionFilterService() - let elementsFilterService = ElementsFilterService(sizeFilter: sizeFilter, positionFilter: positionFilter, colorFilter: colorFilter) - let contentPositionFilterService = ContentPositionFilterService() - let variantsFilterService = VariantFilterService(layersFilter: layersFilterService, - elementsFilter: elementsFilterService, - contentPositionFilter: contentPositionFilterService) - inappFilterService = InappsFilterService(persistenceStorage: persistenceStorage, - abTestDeviceMixer: abTestDeviceMixer, - variantsFilter: variantsFilterService, - sdkVersionValidator: sdkVersionValidator, - frequencyValidator: frequencyValidator) - pushValidator = MindboxPushValidator() - userVisitManager = UserVisitManager(persistenceStorage: persistenceStorage) - } -} - -class MockInstanceFactory: InstanceFactory { - - private let persistenceStorage: PersistenceStorage - private let utilitiesFetcher: UtilitiesFetcher - private let databaseRepository: MBDatabaseRepository - - var isFailureNetworkFetcher: Bool = false - - init(persistenceStorage: PersistenceStorage, utilitiesFetcher: UtilitiesFetcher, databaseRepository: MBDatabaseRepository) { - self.persistenceStorage = persistenceStorage - self.utilitiesFetcher = utilitiesFetcher - self.databaseRepository = databaseRepository - } - - func makeNetworkFetcher() -> NetworkFetcher { - return isFailureNetworkFetcher ? MockFailureNetworkFetcher() : MockNetworkFetcher() - } - - func makeEventRepository() -> EventRepository { - return MBEventRepository( - fetcher: makeNetworkFetcher(), - persistenceStorage: persistenceStorage - ) - } - - func makeTrackVisitManager() -> TrackVisitManager { - return TrackVisitManager(databaseRepository: databaseRepository) - } -} - diff --git a/MindboxTests/Database/DatabaseLoaderTest.swift b/MindboxTests/Database/DatabaseLoaderTest.swift index b5ab67e8..774ddb80 100644 --- a/MindboxTests/Database/DatabaseLoaderTest.swift +++ b/MindboxTests/Database/DatabaseLoaderTest.swift @@ -12,26 +12,25 @@ import CoreData class DatabaseLoaderTest: XCTestCase { - var persistentContainer: NSPersistentContainer { - container.databaseRepository.persistentContainer - } - - var container: TestDependencyProvider! + var persistentContainer: NSPersistentContainer! + var databaseLoader: DataBaseLoader! override func setUp() { super.setUp() - container = try! TestDependencyProvider() + databaseLoader = DI.injectOrFail(DataBaseLoader.self) + persistentContainer = DI.injectOrFail(MBDatabaseRepository.self).persistentContainer } override func tearDown() { - - container = nil + databaseLoader = nil + persistentContainer = nil super.tearDown() } func testDestroyDatabase() { - XCTAssertNotNil(persistentContainer.persistentStoreCoordinator.persistentStore(for: container.databaseLoader.persistentStoreURL!)) - try! container.databaseLoader.destroy() - XCTAssertNil(persistentContainer.persistentStoreCoordinator.persistentStore(for: container.databaseLoader.persistentStoreURL!)) + let persistentStoreURL = databaseLoader.persistentStoreURL! + XCTAssertNotNil(persistentContainer.persistentStoreCoordinator.persistentStore(for: persistentStoreURL)) + try! databaseLoader.destroy() + XCTAssertNil(persistentContainer.persistentStoreCoordinator.persistentStore(for: persistentStoreURL)) } } diff --git a/MindboxTests/Database/DatabaseRepositoryTestCase.swift b/MindboxTests/Database/DatabaseRepositoryTestCase.swift index 5ff3f098..f3a7b1ff 100644 --- a/MindboxTests/Database/DatabaseRepositoryTestCase.swift +++ b/MindboxTests/Database/DatabaseRepositoryTestCase.swift @@ -12,16 +12,12 @@ import CoreData class DatabaseRepositoryTestCase: XCTestCase { - var container: DependencyContainer! var databaseRepository: MBDatabaseRepository! var eventGenerator: EventGenerator! - - override func setUp() { super.setUp() - container = try! TestDependencyProvider() - databaseRepository = container.databaseRepository + databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) eventGenerator = EventGenerator() try! databaseRepository.erase() @@ -31,8 +27,6 @@ class DatabaseRepositoryTestCase: XCTestCase { } override func tearDown() { - - container = nil databaseRepository = nil eventGenerator = nil super.tearDown() diff --git a/MindboxTests/EventRepository/EventRepositoryTestCase.swift b/MindboxTests/EventRepository/EventRepositoryTestCase.swift index 72ef4a64..9e727cac 100644 --- a/MindboxTests/EventRepository/EventRepositoryTestCase.swift +++ b/MindboxTests/EventRepository/EventRepositoryTestCase.swift @@ -11,34 +11,22 @@ import XCTest class EventRepositoryTestCase: XCTestCase { var coreController: CoreController! - var container: DependencyContainer! var controllerQueue: DispatchQueue! + var persistenceStorage: PersistenceStorage! override func setUp() { super.setUp() - container = try! TestDependencyProvider() - controllerQueue = DispatchQueue(label: "test-core-controller-queue") - coreController = CoreController( - persistenceStorage: container.persistenceStorage, - utilitiesFetcher: container.utilitiesFetcher, - notificationStatusProvider: container.authorizationStatusProvider, - databaseRepository: container.databaseRepository, - guaranteedDeliveryManager: container.guaranteedDeliveryManager, - trackVisitManager: container.instanceFactory.makeTrackVisitManager(), - sessionManager: container.sessionManager, - inAppMessagesManager: InAppCoreManagerMock(), - uuidDebugService: MockUUIDDebugService(), - controllerQueue: controllerQueue, - userVisitManager: container.userVisitManager - ) - container.persistenceStorage.reset() - try! container.databaseRepository.erase() + persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + coreController = DI.injectOrFail(CoreController.self) + controllerQueue = coreController.controllerQueue + persistenceStorage.reset() + let databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) + try! databaseRepository.erase() } override func tearDown() { coreController = nil - container = nil controllerQueue = nil super.tearDown() } @@ -47,7 +35,7 @@ class EventRepositoryTestCase: XCTestCase { let configuration = try! MBConfiguration(plistName: "TestEventConfig") coreController.initialization(configuration: configuration) waitForInitializationFinished() - let repository: EventRepository = container.instanceFactory.makeEventRepository() + let repository = DI.injectOrFail(EventRepository.self) let event = Event( type: .installed, body: "" @@ -68,7 +56,7 @@ class EventRepositoryTestCase: XCTestCase { let configuration = try! MBConfiguration(plistName: "TestEventConfig") coreController.initialization(configuration: configuration) waitForInitializationFinished() - let repository: EventRepository = container.instanceFactory.makeEventRepository() + let repository = DI.injectOrFail(EventRepository.self) let event = Event(type: .syncEvent, body: "") let expectation = self.expectation(description: "send event") repository.send(type: SuccessCase.self, event: event) { result in diff --git a/MindboxTests/Extensions/XCTestCase+Extensions.swift b/MindboxTests/Extensions/XCTestCase+Extensions.swift new file mode 100644 index 00000000..48f55a80 --- /dev/null +++ b/MindboxTests/Extensions/XCTestCase+Extensions.swift @@ -0,0 +1,18 @@ +// +// XCTestCase+Extensions.swift +// MindboxTests +// +// Created by vailence on 21.06.2024. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation +import XCTest +@testable import Mindbox + +extension XCTestCase { + open override func setUp() { + super.setUp() + TestConfiguration.configure() + } +} diff --git a/MindboxTests/GuaranteedDelivery/GuaranteedDeliveryTestCase.swift b/MindboxTests/GuaranteedDelivery/GuaranteedDeliveryTestCase.swift index 314e4da8..5a8b96c5 100644 --- a/MindboxTests/GuaranteedDelivery/GuaranteedDeliveryTestCase.swift +++ b/MindboxTests/GuaranteedDelivery/GuaranteedDeliveryTestCase.swift @@ -12,7 +12,6 @@ import XCTest class GuaranteedDeliveryTestCase: XCTestCase { - var container: TestDependencyProvider! var databaseRepository: MBDatabaseRepository! var guaranteedDeliveryManager: GuaranteedDeliveryManager! var persistenceStorage: PersistenceStorage! @@ -23,10 +22,9 @@ class GuaranteedDeliveryTestCase: XCTestCase { super.setUp() Mindbox.logger.logLevel = .none - container = try! TestDependencyProvider() - databaseRepository = container.databaseRepository - guaranteedDeliveryManager = container.guaranteedDeliveryManager - persistenceStorage = container.persistenceStorage + databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) + guaranteedDeliveryManager = DI.injectOrFail(GuaranteedDeliveryManager.self) + persistenceStorage = DI.injectOrFail(PersistenceStorage.self) eventGenerator = EventGenerator() isDelivering = guaranteedDeliveryManager.state.isDelivering @@ -35,13 +33,10 @@ class GuaranteedDeliveryTestCase: XCTestCase { persistenceStorage.configuration?.previousDeviceUUID = configuration.previousDeviceUUID persistenceStorage.deviceUUID = "0593B5CC-1479-4E45-A7D3-F0E8F9B40898" try! databaseRepository.erase() - updateInstanceFactory(withFailureNetworkFetcher: false) // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { - - container = nil databaseRepository = nil guaranteedDeliveryManager = nil persistenceStorage = nil @@ -50,16 +45,12 @@ class GuaranteedDeliveryTestCase: XCTestCase { super.tearDown() } - private func updateInstanceFactory(withFailureNetworkFetcher: Bool) { - (container.instanceFactory as! MockInstanceFactory).isFailureNetworkFetcher = withFailureNetworkFetcher - } - func testDeliverMultipleEvents() { let retryDeadline: TimeInterval = 3 guaranteedDeliveryManager = GuaranteedDeliveryManager( - persistenceStorage: container.persistenceStorage, - databaseRepository: container.databaseRepository, - eventRepository: container.instanceFactory.makeEventRepository(), + persistenceStorage: DI.injectOrFail(PersistenceStorage.self), + databaseRepository: DI.injectOrFail(MBDatabaseRepository.self), + eventRepository: DI.injectOrFail(EventRepository.self), retryDeadline: retryDeadline ) let events = eventGenerator.generateEvents(count: 10) @@ -107,11 +98,12 @@ class GuaranteedDeliveryTestCase: XCTestCase { } func testScheduleByTimer() { + // Может понадобиться MockFailureNetworkFetcher (Check later) let retryDeadline: TimeInterval = 2 guaranteedDeliveryManager = GuaranteedDeliveryManager( - persistenceStorage: container.persistenceStorage, - databaseRepository: container.databaseRepository, - eventRepository: container.instanceFactory.makeEventRepository(), + persistenceStorage: DI.injectOrFail(PersistenceStorage.self), + databaseRepository: DI.injectOrFail(MBDatabaseRepository.self), + eventRepository: DI.injectOrFail(EventRepository.self), retryDeadline: retryDeadline ) let simpleCase: [GuaranteedDeliveryManager.State] = [.delivering, .idle] @@ -152,12 +144,11 @@ class GuaranteedDeliveryTestCase: XCTestCase { } func testFailureScheduleByTimer() { - updateInstanceFactory(withFailureNetworkFetcher: true) let retryDeadline: TimeInterval = 2 guaranteedDeliveryManager = GuaranteedDeliveryManager( - persistenceStorage: container.persistenceStorage, - databaseRepository: container.databaseRepository, - eventRepository: container.instanceFactory.makeEventRepository(), + persistenceStorage: DI.injectOrFail(PersistenceStorage.self), + databaseRepository: DI.injectOrFail(MBDatabaseRepository.self), + eventRepository: DI.injectOrFail(EventRepository.self), retryDeadline: retryDeadline ) let errorCase: [GuaranteedDeliveryManager.State] = [ diff --git a/MindboxTests/InApp/Tests/ABTesting/ABTests.swift b/MindboxTests/InApp/Tests/ABTesting/ABTests.swift index 20ad9811..bb451788 100644 --- a/MindboxTests/InApp/Tests/ABTesting/ABTests.swift +++ b/MindboxTests/InApp/Tests/ABTesting/ABTests.swift @@ -45,7 +45,6 @@ // persistenceStorage: persistenceStorage, // sdkVersionValidator: sdkVersionValidator, // imageDownloadService: container.imageDownloadService, -// urlExtractorService: container.urlExtractorService, // abTestDeviceMixer: container.abTestDeviceMixer) // shownInAppsIds = Set(persistenceStorage.shownInAppsIds ?? []) // } diff --git a/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppTargetingRequestsTests.swift b/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppTargetingRequestsTests.swift index 6989ddfd..6767db10 100644 --- a/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppTargetingRequestsTests.swift +++ b/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppTargetingRequestsTests.swift @@ -11,35 +11,26 @@ import XCTest class InAppTargetingRequestsTests: XCTestCase { - var container: TestDependencyProvider! - private var mockDataFacade: MockInAppConfigurationDataFacade! private var mapper: InAppConfigurationMapperProtocol! + private var persistenceStorage: PersistenceStorage! private var targetingChecker: InAppTargetingCheckerProtocol! override func setUp() { super.setUp() - container = try! TestDependencyProvider() SessionTemporaryStorage.shared.erase() - targetingChecker = container.inAppTargetingChecker - try! container.databaseRepository.erase() - let tracker = InAppMessagesTracker(databaseRepository: container.databaseRepository) - mockDataFacade = MockInAppConfigurationDataFacade(geoService: container.geoService, - segmentationService: container.segmentationSevice, - targetingChecker: targetingChecker, - imageService: container.imageDownloadService, - tracker: tracker) + targetingChecker = DI.injectOrFail(InAppTargetingCheckerProtocol.self) + let databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) + try! databaseRepository.erase() + mockDataFacade = DI.injectOrFail(InAppConfigurationDataFacadeProtocol.self) as? MockInAppConfigurationDataFacade mockDataFacade.clean() - mapper = InAppConfigutationMapper(inappFilterService: container.inappFilterService, - targetingChecker: targetingChecker, - urlExtractorService: container.urlExtractorService, - dataFacade: mockDataFacade) + mapper = DI.injectOrFail(InAppConfigurationMapperProtocol.self) + persistenceStorage = DI.injectOrFail(PersistenceStorage.self) } override func tearDown() { - container = nil mockDataFacade = nil targetingChecker = nil mapper = nil @@ -65,7 +56,7 @@ class InAppTargetingRequestsTests: XCTestCase { func test_TwoInappsTrue_FirstShownBefore() { let expectation = XCTestExpectation(description: "Waiting for sendRemainingInappsTargeting to complete") - container.persistenceStorage.shownInAppsIds = ["1"] + persistenceStorage.shownInAppsIds = ["1"] do { let config = try getConfig(name: "3-4-5-TargetingRequests") mapper.mapConfigResponse(nil, config) { _ in @@ -124,7 +115,7 @@ class InAppTargetingRequestsTests: XCTestCase { let expectationForsendRemainingInappsTargeting = XCTestExpectation(description: "Waiting for first sendRemainingInappsTargeting to complete") let expectationForMapConfigResponse = XCTestExpectation(description: "Waiting for mapConfigResponse to complete") - container.persistenceStorage.shownInAppsIds = ["1"] + persistenceStorage.shownInAppsIds = ["1"] do { let config = try getConfig(name: "9-TargetingRequests") targetingChecker.geoModels = .init(city: 1, region: 2, country: 3) @@ -194,7 +185,7 @@ class InAppTargetingRequestsTests: XCTestCase { let expectationTest = XCTestExpectation(description: "Operation test") let expectationTestAgain = XCTestExpectation(description: "Operation test again") - container.persistenceStorage.shownInAppsIds = ["1"] + persistenceStorage.shownInAppsIds = ["1"] do { let config = try getConfig(name: "16-17-TargetingRequests") @@ -257,7 +248,7 @@ class InAppTargetingRequestsTests: XCTestCase { do { let config = try getConfig(name: "31-TargetingRequests") - container.persistenceStorage.deviceUUID = "40909d27-4bef-4a8d-9164-6bfcf58ecc76" // 1 вариант + persistenceStorage.deviceUUID = "40909d27-4bef-4a8d-9164-6bfcf58ecc76" // 1 вариант targetingChecker.geoModels = .init(city: 1, region: 2, country: 3) SessionTemporaryStorage.shared.geoRequestCompleted = true @@ -300,7 +291,7 @@ class InAppTargetingRequestsTests: XCTestCase { do { let config = try getConfig(name: "31-TargetingRequests") - container.persistenceStorage.deviceUUID = "b4e0f767-fe8f-4825-9772-f1162f2db52d" // 2 вариант + persistenceStorage.deviceUUID = "b4e0f767-fe8f-4825-9772-f1162f2db52d" // 2 вариант targetingChecker.geoModels = .init(city: 1, region: 2, country: 3) SessionTemporaryStorage.shared.geoRequestCompleted = true @@ -343,7 +334,7 @@ class InAppTargetingRequestsTests: XCTestCase { do { let config = try getConfig(name: "31-TargetingRequests") - container.persistenceStorage.deviceUUID = "55fbd965-c658-47a8-8786-d72ba79b38a2" // 3 вариант + persistenceStorage.deviceUUID = "55fbd965-c658-47a8-8786-d72ba79b38a2" // 3 вариант targetingChecker.geoModels = .init(city: 1, region: 2, country: 3) SessionTemporaryStorage.shared.geoRequestCompleted = true diff --git a/MindboxTests/InApp/Tests/InAppConfigResponseTests/InappTTLTests.swift b/MindboxTests/InApp/Tests/InAppConfigResponseTests/InappTTLTests.swift index 63050e2b..3e357b2a 100644 --- a/MindboxTests/InApp/Tests/InAppConfigResponseTests/InappTTLTests.swift +++ b/MindboxTests/InApp/Tests/InAppConfigResponseTests/InappTTLTests.swift @@ -10,19 +10,16 @@ import XCTest @testable import Mindbox class InappTTLTests: XCTestCase { - var container: TestDependencyProvider! var persistenceStorage: PersistenceStorage! var service: TTLValidationProtocol! override func setUp() { super.setUp() - container = try! TestDependencyProvider() - persistenceStorage = container.persistenceStorage - service = container.ttlValidationService + persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + service = TTLValidationService(persistenceStorage: persistenceStorage) } override func tearDown() { - container = nil persistenceStorage = nil service = nil super.tearDown() @@ -30,7 +27,6 @@ class InappTTLTests: XCTestCase { func testNeedResetInapps_WithTTL_Exceeds() throws { persistenceStorage.configDownloadDate = Calendar.current.date(byAdding: .hour, value: -2, to: Date()) - let service = TTLValidationService(persistenceStorage: persistenceStorage) let settings = Settings(operations: nil, ttl: .init(inapps: "01:00:00")) let config = ConfigResponse(settings: settings) let result = service.needResetInapps(config: config) @@ -39,7 +35,6 @@ class InappTTLTests: XCTestCase { func testNeedResetInapps_WithTTL_NotExceeded() throws { persistenceStorage.configDownloadDate = Calendar.current.date(byAdding: .second, value: -1, to: Date()) - let service = TTLValidationService(persistenceStorage: persistenceStorage) let settings = Settings(operations: nil, ttl: .init(inapps: "00:00:02")) let config = ConfigResponse(settings: settings) let result = service.needResetInapps(config: config) @@ -48,7 +43,6 @@ class InappTTLTests: XCTestCase { func testNeedResetInapps_WithoutTTL() throws { persistenceStorage.configDownloadDate = Date() - let service = TTLValidationService(persistenceStorage: persistenceStorage) let settings = Settings(operations: nil, ttl: nil) let config = ConfigResponse(settings: settings) let result = service.needResetInapps(config: config) @@ -57,7 +51,6 @@ class InappTTLTests: XCTestCase { func testNeedResetInapps_WithTTLHalfHourAgo_NotExceeded() throws { persistenceStorage.configDownloadDate = Calendar.current.date(byAdding: .minute, value: -30, to: Date()) - let service = TTLValidationService(persistenceStorage: persistenceStorage) let settings = Settings(operations: nil, ttl: .init(inapps: "01:00:00")) let config = ConfigResponse(settings: settings) let result = service.needResetInapps(config: config) @@ -66,7 +59,6 @@ class InappTTLTests: XCTestCase { func testNeedResetInapps_WithTTLHalfMinutesAgo_NotExceeded() throws { persistenceStorage.configDownloadDate = Calendar.current.date(byAdding: .second, value: -30, to: Date()) - let service = TTLValidationService(persistenceStorage: persistenceStorage) let settings = Settings(operations: nil, ttl: .init(inapps: "00:01:00")) let config = ConfigResponse(settings: settings) let result = service.needResetInapps(config: config) @@ -75,7 +67,6 @@ class InappTTLTests: XCTestCase { func testNeedResetInapps_WithTTLOneDayAgo_NotExceeded() throws { persistenceStorage.configDownloadDate = Calendar.current.date(byAdding: .day, value: -1, to: Date()) - let service = TTLValidationService(persistenceStorage: persistenceStorage) let settings = Settings(operations: nil, ttl: .init(inapps: "2.00:00:00")) let config = ConfigResponse(settings: settings) let result = service.needResetInapps(config: config) @@ -84,7 +75,6 @@ class InappTTLTests: XCTestCase { func testNeedResetInapps_WithMinusTTL_NotExceeded() throws { persistenceStorage.configDownloadDate = Calendar.current.date(byAdding: .day, value: 1, to: Date()) - let service = TTLValidationService(persistenceStorage: persistenceStorage) let settings = Settings(operations: nil, ttl: .init(inapps: "-2.00:00:00")) let config = ConfigResponse(settings: settings) let result = service.needResetInapps(config: config) @@ -93,7 +83,6 @@ class InappTTLTests: XCTestCase { func testNeedResetInapps_WithMinusOneDayTTL_NotExceeded() throws { persistenceStorage.configDownloadDate = Calendar.current.date(byAdding: .day, value: -2, to: Date()) - let service = TTLValidationService(persistenceStorage: persistenceStorage) let settings = Settings(operations: nil, ttl: .init(inapps: "-1.00:00:00")) let config = ConfigResponse(settings: settings) let result = service.needResetInapps(config: config) diff --git a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/GeoServiceTests.swift b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/GeoServiceTests.swift index 663a7334..6ab776d0 100644 --- a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/GeoServiceTests.swift +++ b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/GeoServiceTests.swift @@ -14,15 +14,12 @@ final class GeoServiceTests: XCTestCase { var sut: GeoServiceProtocol! var networkFetcher: MockNetworkFetcher! var targetingChecker: InAppTargetingCheckerProtocol! - var container: TestDependencyProvider! override func setUp() { super.setUp() - container = try! TestDependencyProvider() - networkFetcher = MockNetworkFetcher() - targetingChecker = container.inAppTargetingChecker - sut = GeoService(fetcher: networkFetcher, - targetingChecker: targetingChecker) + networkFetcher = DI.injectOrFail(NetworkFetcher.self) as? MockNetworkFetcher + targetingChecker = DI.injectOrFail(InAppTargetingCheckerProtocol.self) + sut = DI.injectOrFail(GeoServiceProtocol.self) } override func tearDown() { @@ -30,7 +27,6 @@ final class GeoServiceTests: XCTestCase { networkFetcher = nil targetingChecker = nil SessionTemporaryStorage.shared.erase() - container = nil super.tearDown() } diff --git a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/ImageDownloadService/ImageDownloadServiceTests.swift b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/ImageDownloadService/ImageDownloadServiceTests.swift index 866f1a5c..2d4651d4 100644 --- a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/ImageDownloadService/ImageDownloadServiceTests.swift +++ b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/ImageDownloadService/ImageDownloadServiceTests.swift @@ -14,7 +14,7 @@ class ImageDownloadServiceTests: XCTestCase { override func setUp() { super.setUp() - sut = MockImageDownloadService() + sut = DI.injectOrFail(ImageDownloadServiceProtocol.self) } override func tearDown() { diff --git a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/InappFilterServiceTests/InappFilterServiceTests.swift b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/InappFilterServiceTests/InappFilterServiceTests.swift index f477d04e..cf143b58 100644 --- a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/InappFilterServiceTests/InappFilterServiceTests.swift +++ b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/InappFilterServiceTests/InappFilterServiceTests.swift @@ -19,12 +19,10 @@ final class InappFilterServiceTests: XCTestCase { } var sut: InappFilterProtocol! - var container: TestDependencyProvider! override func setUp() { super.setUp() - container = try! TestDependencyProvider() - sut = container.inappFilterService + sut = DI.injectOrFail(InappFilterProtocol.self) } override func tearDown() { diff --git a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/SegmentationServiceTests.swift b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/SegmentationServiceTests.swift index 880dd8ca..2b0153c2 100644 --- a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/SegmentationServiceTests.swift +++ b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/SegmentationServiceTests.swift @@ -11,26 +11,27 @@ import XCTest final class SegmentationServiceTests: XCTestCase { - var container: TestDependencyProvider! - var sut: SegmentationServiceProtocol! + var sut: SegmentationService! var targetingChecker: InAppTargetingCheckerProtocol! override func setUp() { super.setUp() - container = try! TestDependencyProvider() - targetingChecker = container.inAppTargetingChecker - sut = SegmentationService(customerSegmentsAPI: .init(fetchSegments: { segmentationCheckRequest, completion in + targetingChecker = DI.injectOrFail(InAppTargetingCheckerProtocol.self) + + sut = DI.injectOrFail(SegmentationServiceProtocol.self) as? SegmentationService + let customerSegmentAPI = CustomerSegmentsAPI { segmentationCheckRequest, completion in completion(.init(status: .success, customerSegmentations: [.init(segmentation: .init(ids: .init(externalId: "1")), segment: .init(ids: .init(externalId: "2")))])) - }, fetchProductSegments: { segmentationCheckRequest, completion in + } fetchProductSegments: { segmentationCheckRequest, completion in completion(.init(status: .success, products: [.init(ids: ["Hello": "World"], segmentations: [.init(ids: .init(externalId: "123"), segment: .init(ids: .init(externalId: "456")))])])) - }), targetingChecker: targetingChecker) + } + + sut.customerSegmentsAPI = customerSegmentAPI } override func tearDown() { - container = nil SessionTemporaryStorage.shared.erase() targetingChecker = nil sut = nil diff --git a/MindboxTests/InApp/Tests/InAppTargetingCheckerTests/InAppTargetingCheckerTests.swift b/MindboxTests/InApp/Tests/InAppTargetingCheckerTests/InAppTargetingCheckerTests.swift index 69a93569..1a26791a 100644 --- a/MindboxTests/InApp/Tests/InAppTargetingCheckerTests/InAppTargetingCheckerTests.swift +++ b/MindboxTests/InApp/Tests/InAppTargetingCheckerTests/InAppTargetingCheckerTests.swift @@ -11,24 +11,21 @@ import XCTest final class InAppTargetingCheckerTests: XCTestCase { - var container: TestDependencyProvider! let inAppStub = InAppStub() let trueTargeting: Targeting = .true(TrueTargeting()) - var targetingChecker: InAppTargetingChecker! + var targetingChecker: InAppTargetingCheckerProtocol! var storage: PersistenceStorage! override func setUp() { super.setUp() - container = try! TestDependencyProvider() - storage = container.persistenceStorage - targetingChecker = container.inAppTargetingChecker + storage = DI.injectOrFail(PersistenceStorage.self) + targetingChecker = DI.injectOrFail(InAppTargetingCheckerProtocol.self) targetingChecker.geoModels = InAppGeoResponse(city: 123, region: 456, country: 789) } override func tearDown() { targetingChecker = nil storage = nil - container = nil super.tearDown() } diff --git a/MindboxTests/MigrationsTests/MigrationManagerTests.swift b/MindboxTests/MigrationsTests/MigrationManagerTests.swift new file mode 100644 index 00000000..bbf39603 --- /dev/null +++ b/MindboxTests/MigrationsTests/MigrationManagerTests.swift @@ -0,0 +1,247 @@ +// +// MigrationManagerTests.swift +// MindboxTests +// +// Created by Sergei Semko on 7/30/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import XCTest +@testable import Mindbox + +final class MigrationManagerTests: XCTestCase { + + private var migrationManager: MigrationManagerProtocol! + private var persistenceStorageMock: PersistenceStorage! + + override func setUp() { + super.setUp() + persistenceStorageMock = DI.injectOrFail(PersistenceStorage.self) + persistenceStorageMock.deviceUUID = "00000000-0000-0000-0000-000000000000" + persistenceStorageMock.installationDate = Date() + persistenceStorageMock.configDownloadDate = Date() + persistenceStorageMock.userVisitCount = 1 + persistenceStorageMock.handledlogRequestIds = ["37db8697-ace9-4d1f-99b6-7e303d6c874f"] + persistenceStorageMock.shownInappsDictionary = [ + "36920d7e-3c42-4194-9a11-b0b5c550460c": Date(), + "37bed734-aa34-4c10-918b-873f67505d46": Date() + ] + + let testMigrations: [MigrationProtocol] = [ + TestBaseMigration_1() + ] + + migrationManager = MigrationManager( + persistenceStorage: persistenceStorageMock, + migrations: testMigrations, + sdkVersionCode: 1 + ) + } + + override func tearDown() { + migrationManager = nil + persistenceStorageMock = nil + super.tearDown() + } + + func testGeneralProductionMigrations() { + migrationManager = MigrationManager(persistenceStorage: persistenceStorageMock) + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == 0) + migrationManager.migrate() + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == Constants.Migration.sdkVersionCode) + XCTAssertNotNil(persistenceStorageMock.configDownloadDate, "Must NOT `softReset()` `persistenceStorage`") + } + + func testPerformTestMigrationsButFirstInstallationAndSkipMigrations() { + let testMigrations: [MigrationProtocol] = [ + TestBaseMigration_1() + ] + + persistenceStorageMock.installationDate = nil + + migrationManager = MigrationManager(persistenceStorage: persistenceStorageMock, + migrations: testMigrations, sdkVersionCode: 1) + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == 0) + migrationManager.migrate() + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == 1) + XCTAssertNotNil(persistenceStorageMock.configDownloadDate, "Must NOT `softReset()` `persistenceStorage`") + } +} + +// MARK: - Base Migrations Tests + +extension MigrationManagerTests { + + func testPerformOneTestBaseMigrationFromSetUp() { + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == 0) + migrationManager.migrate() + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == 1) + XCTAssertNotNil(persistenceStorageMock.configDownloadDate, "Must NOT `softReset()` `persistenceStorage`") + } + + func testPerformTwoTestBaseMigrations() { + let testMigrations: [MigrationProtocol] = [ + TestBaseMigration_1(), + TestBaseMigration_2() + ] + + let expectedSdkVersionCodeAfterMigrations = 2 + + migrationManager = MigrationManager(persistenceStorage: persistenceStorageMock, + migrations: testMigrations, sdkVersionCode: expectedSdkVersionCodeAfterMigrations) + + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == 0) + migrationManager.migrate() + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == expectedSdkVersionCodeAfterMigrations) + XCTAssertNotNil(persistenceStorageMock.configDownloadDate, "Must NOT `softReset()` `persistenceStorage`") + } + + func testPerformThreeTestBaseMigrationsWithOneIsNeededEqualFalse() { + let testMigrations: [MigrationProtocol] = [ + TestBaseMigration_1(), + TestBaseMigration_2(), + TestBaseMigration_3_IsNeeded_False() // IsNeeded == false -> No auto increment in BaseMigration + ] + + let expectedSdkVersionCodeAfterMigrations = 2 + + migrationManager = MigrationManager( + persistenceStorage: persistenceStorageMock, + migrations: testMigrations, + sdkVersionCode: expectedSdkVersionCodeAfterMigrations + ) + + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == 0) + migrationManager.migrate() + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == expectedSdkVersionCodeAfterMigrations) + XCTAssertNotNil(persistenceStorageMock.configDownloadDate, "Must NOT `softReset()` `persistenceStorage`") + } + + func testSortPerformThreeTestMigrationsThatAreDecalredInARandomOrder() { + let testMigrations: [MigrationProtocol] = [ + TestBaseMigration_2(), + TestBaseMigration_3_IsNeeded_False(), + TestBaseMigration_1(), + ] + + let expectedSdkVersionCodeAfterMigrations = 2 + + migrationManager = MigrationManager(persistenceStorage: persistenceStorageMock, + migrations: testMigrations, sdkVersionCode: expectedSdkVersionCodeAfterMigrations) + + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == 0) + migrationManager.migrate() + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == expectedSdkVersionCodeAfterMigrations) + XCTAssertNotNil(persistenceStorageMock.configDownloadDate, "Must NOT `softReset()` `persistenceStorage`") + } + + func testPerformTestBaseMigrationsWhenOneThrowError() { + let testMigrations: [MigrationProtocol] = [ + TestBaseMigration_2(), + TestBaseMigration_1(), + TestBaseMigration_4_WithPerfomError(), + ] + + let expectedSdkVersionCodeAfterMigrations = 3 + + migrationManager = MigrationManager(persistenceStorage: persistenceStorageMock, + migrations: testMigrations, sdkVersionCode: expectedSdkVersionCodeAfterMigrations) + + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == 0) + migrationManager.migrate() + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == expectedSdkVersionCodeAfterMigrations) + + XCTAssertNil(persistenceStorageMock.configDownloadDate, "Must softReset() persistenceStorage") + XCTAssertNil(persistenceStorageMock.shownInappsDictionary, "Must softReset() persistenceStorage") + XCTAssertNil(persistenceStorageMock.handledlogRequestIds, "Must softReset() persistenceStorage") + let expectedUserVisitCountAfterSoftReset = 0 + XCTAssertEqual(persistenceStorageMock.userVisitCount, expectedUserVisitCountAfterSoftReset, "Must softReset() persistenceStorage") + } +} + +// MARK: - Protocol Migrations Tests + +extension MigrationManagerTests { + + func testPerformOneTestProtocolMigration() { + let testMigrations: [MigrationProtocol] = [ + TestProtocolMigration_1() + ] + + let expectedSdkVersionCodeAfterMigrations = 0 + + migrationManager = MigrationManager(persistenceStorage: persistenceStorageMock, + migrations: testMigrations, sdkVersionCode: expectedSdkVersionCodeAfterMigrations) + + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == 0) + migrationManager.migrate() + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == expectedSdkVersionCodeAfterMigrations) + XCTAssertNotNil(persistenceStorageMock.configDownloadDate, "Must NOT `softReset()` `persistenceStorage`") + } + + func testPerformTwoTestProtocolMigrations() { + let testMigrations: [MigrationProtocol] = [ + TestProtocolMigration_1(), + TestProtocolMigration_2(persistenceStorage: persistenceStorageMock) // Used increment sdkVersionCode into `run` + ] + + let expectedSdkVersionCodeAfterMigrations = 1 + + migrationManager = MigrationManager(persistenceStorage: persistenceStorageMock, + migrations: testMigrations, sdkVersionCode: expectedSdkVersionCodeAfterMigrations) + + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == 0) + migrationManager.migrate() + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == expectedSdkVersionCodeAfterMigrations) + XCTAssertNotNil(persistenceStorageMock.configDownloadDate, "Must NOT `softReset()` `persistenceStorage`") + } + + func testPerformThreeTestProtocolMigrations() { + let testMigrations: [MigrationProtocol] = [ + TestProtocolMigration_1(), + TestProtocolMigration_2(persistenceStorage: persistenceStorageMock), // Used increment sdkVersionCode into `run` + TestProtocolMigration_3(persistenceStorage: persistenceStorageMock) // Throw error into `run` + ] + + let expectedSdkVersionCodeAfterMigrations = 2 + + migrationManager = MigrationManager(persistenceStorage: persistenceStorageMock, + migrations: testMigrations, sdkVersionCode: expectedSdkVersionCodeAfterMigrations) + + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == 0) + migrationManager.migrate() + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == expectedSdkVersionCodeAfterMigrations) + + XCTAssertNil(persistenceStorageMock.configDownloadDate, "Must softReset() persistenceStorage") + XCTAssertNil(persistenceStorageMock.shownInappsDictionary, "Must softReset() persistenceStorage") + XCTAssertNil(persistenceStorageMock.handledlogRequestIds, "Must softReset() persistenceStorage") + let expectedUserVisitCountAfterSoftReset = 0 + XCTAssertEqual(persistenceStorageMock.userVisitCount, expectedUserVisitCountAfterSoftReset, "Must softReset() persistenceStorage") + } +} + +// MARK: - Mixed Migrations Tests + +extension MigrationManagerTests { + + func testPerformMixedTestMigrations() { + let testMigrations: [MigrationProtocol] = [ + TestBaseMigration_1(), // Auto Increment sdkVersionCode + TestBaseMigration_2(), // Auto Increment sdkVersionCode + TestProtocolMigration_1(), + TestProtocolMigration_2(persistenceStorage: persistenceStorageMock), // Used increment sdkVersionCode into `run` + ] + + let expectedSdkVersionCodeAfterMigrations = 3 + + migrationManager = MigrationManager(persistenceStorage: persistenceStorageMock, + migrations: testMigrations, sdkVersionCode: expectedSdkVersionCodeAfterMigrations) + + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == 0) + migrationManager.migrate() + XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == expectedSdkVersionCodeAfterMigrations) + XCTAssertNotNil(persistenceStorageMock.configDownloadDate, "Must NOT `softReset()` `persistenceStorage`") + } +} + + diff --git a/MindboxTests/MigrationsTests/TestMigrations/TestBaseMigrations.swift b/MindboxTests/MigrationsTests/TestMigrations/TestBaseMigrations.swift new file mode 100644 index 00000000..c8e704d5 --- /dev/null +++ b/MindboxTests/MigrationsTests/TestMigrations/TestBaseMigrations.swift @@ -0,0 +1,83 @@ +// +// TestBaseMigrations.swift +// MindboxTests +// +// Created by Sergei Semko on 8/6/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation +@testable import Mindbox + +final class TestBaseMigration_1: BaseMigration { + override var description: String { + "TestBaseMigration number 1" + } + + override var isNeeded: Bool { + true + } + + override var version: Int { + 1 + } + + override func performMigration() throws { + // Do some code + } +} + +final class TestBaseMigration_2: BaseMigration { + override var description: String { + "TestBaseMigration number 2" + } + + override var isNeeded: Bool { + true + } + + override var version: Int { + 2 + } + + override func performMigration() throws { + // Do some code + } +} + +final class TestBaseMigration_3_IsNeeded_False: BaseMigration { + override var description: String { + "TestBaseMigration number 3. isNeeded == false" + } + + override var isNeeded: Bool { + false + } + + override var version: Int { + 3 + } + + override func performMigration() throws { + // Do some code + } +} + +final class TestBaseMigration_4_WithPerfomError: BaseMigration { + override var description: String { + "TestBaseMigration number 4. perfromMigration throw error" + } + + override var isNeeded: Bool { + true + } + + override var version: Int { + 4 + } + + override func performMigration() throws { + // Do some code + throw NSError(domain: "com.sdk.migration", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid version for migration"]) + } +} diff --git a/MindboxTests/MigrationsTests/TestMigrations/TestProtocolMigrations.swift b/MindboxTests/MigrationsTests/TestMigrations/TestProtocolMigrations.swift new file mode 100644 index 00000000..7dad412c --- /dev/null +++ b/MindboxTests/MigrationsTests/TestMigrations/TestProtocolMigrations.swift @@ -0,0 +1,83 @@ +// +// TestProtocolMigrations.swift +// MindboxTests +// +// Created by Sergei Semko on 8/6/24. +// Copyright © 2024 Mindbox. All rights reserved. +// + +import Foundation +@testable import Mindbox + +final class TestProtocolMigration_1: MigrationProtocol { + var description: String { + "TestProtocolMigration number 1 with migration version 5" + } + + var isNeeded: Bool { + return true + } + + var version: Int { + return 5 + } + + func run() throws { + // Do some code + } +} + +final class TestProtocolMigration_2: MigrationProtocol { + + private var persistenceStorage: PersistenceStorage + + var description: String { + "TestProtocolMigration number 2 with migration version 6" + } + + var isNeeded: Bool { + return true + } + + var version: Int { + return 6 + } + + func run() throws { + // Do some code + let versionCodeForMigration = persistenceStorage.versionCodeForMigration! + persistenceStorage.versionCodeForMigration = versionCodeForMigration + 1 + } + + init(persistenceStorage: PersistenceStorage) { + self.persistenceStorage = persistenceStorage + } +} + +final class TestProtocolMigration_3: MigrationProtocol { + + private var persistenceStorage: PersistenceStorage + + var description: String { + "TestProtocolMigration number 3 with migration version 7" + } + + var isNeeded: Bool { + return true + } + + var version: Int { + return 7 + } + + func run() throws { + // Do some code + throw NSError(domain: "com.sdk.migration", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid version for migration"]) + let versionCodeForMigration = persistenceStorage.versionCodeForMigration! + persistenceStorage.versionCodeForMigration = versionCodeForMigration + 1 + } + + init(persistenceStorage: PersistenceStorage) { + self.persistenceStorage = persistenceStorage + } +} diff --git a/MindboxTests/MindboxLogger/SDKLogManagerTests.swift b/MindboxTests/MindboxLogger/SDKLogManagerTests.swift index 7ccc83e5..378fff2f 100644 --- a/MindboxTests/MindboxLogger/SDKLogManagerTests.swift +++ b/MindboxTests/MindboxLogger/SDKLogManagerTests.swift @@ -14,14 +14,14 @@ final class SDKLogManagerTests: XCTestCase { var eventRepositoryMock: EventRepositoryMock! var logsManager: SDKLogsManager! - var persistenceStorageMock: MockPersistenceStorage! + var persistenceStorageMock: PersistenceStorage! override func setUp() { super.setUp() - persistenceStorageMock = MockPersistenceStorage() + persistenceStorageMock = DI.injectOrFail(PersistenceStorage.self) persistenceStorageMock.deviceUUID = "2" - eventRepositoryMock = EventRepositoryMock() - logsManager = SDKLogsManager(persistenceStorage: persistenceStorageMock, eventRepository: eventRepositoryMock) + eventRepositoryMock = DI.injectOrFail(EventRepositoryMock.self) + logsManager = DI.injectOrFail(SDKLogsManagerProtocol.self) as? SDKLogsManager } override func tearDown() { diff --git a/MindboxTests/MindboxTests.swift b/MindboxTests/MindboxTests.swift index c971431d..0ebfe6b3 100644 --- a/MindboxTests/MindboxTests.swift +++ b/MindboxTests/MindboxTests.swift @@ -13,15 +13,16 @@ class MindboxTests: XCTestCase { var mindBoxDidInstalledFlag: Bool = false var apnsTokenDidUpdatedFlag: Bool = false - var container: DependencyContainer! var coreController: CoreController! var controllerQueue = DispatchQueue(label: "test-core-controller-queue") + var persistenceStorage: PersistenceStorage! override func setUp() { - container = try! TestDependencyProvider() - container.persistenceStorage.reset() - try! container.databaseRepository.erase() - Mindbox.shared.assembly(with: container) + persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + persistenceStorage.reset() + let databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) + try! databaseRepository.erase() + Mindbox.shared.assembly() Mindbox.shared.coreController?.controllerQueue = self.controllerQueue // Put setup code here. This method is called before the invocation of each test method in the class. } @@ -31,19 +32,7 @@ class MindboxTests: XCTestCase { } func testInitialization() { - coreController = CoreController( - persistenceStorage: container.persistenceStorage, - utilitiesFetcher: container.utilitiesFetcher, - notificationStatusProvider: container.authorizationStatusProvider, - databaseRepository: container.databaseRepository, - guaranteedDeliveryManager: container.guaranteedDeliveryManager, - trackVisitManager: container.instanceFactory.makeTrackVisitManager(), - sessionManager: container.sessionManager, - inAppMessagesManager: InAppCoreManagerMock(), - uuidDebugService: MockUUIDDebugService(), - controllerQueue: controllerQueue, - userVisitManager: container.userVisitManager - ) + coreController = DI.injectOrFail(CoreController.self) // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. let configuration1 = try! MBConfiguration(plistName: "TestConfig1") @@ -51,7 +40,7 @@ class MindboxTests: XCTestCase { waitForInitializationFinished() - XCTAssertTrue(container.persistenceStorage.isInstalled) + XCTAssertTrue(persistenceStorage.isInstalled) var deviceUUID: String? Mindbox.shared.getDeviceUUID { value in deviceUUID = value @@ -64,8 +53,8 @@ class MindboxTests: XCTestCase { waitForInitializationFinished() - XCTAssertTrue(container.persistenceStorage.isInstalled) - XCTAssertNotNil(container.persistenceStorage.apnsToken) + XCTAssertTrue(persistenceStorage.isInstalled) + XCTAssertNotNil(persistenceStorage.apnsToken) var deviceUUID2: String? Mindbox.shared.getDeviceUUID { value in deviceUUID2 = value @@ -73,21 +62,10 @@ class MindboxTests: XCTestCase { XCTAssertNotNil(deviceUUID2) XCTAssert(deviceUUID == deviceUUID2) - container.persistenceStorage.reset() - try! container.databaseRepository.erase() - coreController = CoreController( - persistenceStorage: container.persistenceStorage, - utilitiesFetcher: container.utilitiesFetcher, - notificationStatusProvider: container.authorizationStatusProvider, - databaseRepository: container.databaseRepository, - guaranteedDeliveryManager: container.guaranteedDeliveryManager, - trackVisitManager: container.instanceFactory.makeTrackVisitManager(), - sessionManager: container.sessionManager, - inAppMessagesManager: InAppCoreManagerMock(), - uuidDebugService: MockUUIDDebugService(), - controllerQueue: controllerQueue, - userVisitManager: container.userVisitManager - ) + persistenceStorage.reset() + let databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) + try! databaseRepository.erase() + coreController = DI.injectOrFail(CoreController.self) // // // // // // // // // // // // @@ -97,8 +75,8 @@ class MindboxTests: XCTestCase { waitForInitializationFinished() - XCTAssertTrue(container.persistenceStorage.isInstalled) - XCTAssertNotNil(container.persistenceStorage.apnsToken) + XCTAssertTrue(persistenceStorage.isInstalled) + XCTAssertNotNil(persistenceStorage.apnsToken) } func testGetDeviceUUID() { diff --git a/MindboxTests/Mock/MockInAppConfigurationDataFacade.swift b/MindboxTests/Mock/MockInAppConfigurationDataFacade.swift index 487b0d46..b7ed99ce 100644 --- a/MindboxTests/Mock/MockInAppConfigurationDataFacade.swift +++ b/MindboxTests/Mock/MockInAppConfigurationDataFacade.swift @@ -12,7 +12,6 @@ import UIKit class MockInAppConfigurationDataFacade: InAppConfigurationDataFacadeProtocol { - let geoService: GeoServiceProtocol let segmentationService: SegmentationServiceProtocol var targetingChecker: InAppTargetingCheckerProtocol let imageService: ImageDownloadServiceProtocol @@ -21,12 +20,10 @@ class MockInAppConfigurationDataFacade: InAppConfigurationDataFacadeProtocol { public var showArray: [String] = [] public var targetingArray: [String] = [] - init(geoService: GeoServiceProtocol, - segmentationService: SegmentationServiceProtocol, + init(segmentationService: SegmentationServiceProtocol, targetingChecker: InAppTargetingCheckerProtocol, imageService: ImageDownloadServiceProtocol, tracker: InappTargetingTrackProtocol) { - self.geoService = geoService self.segmentationService = segmentationService self.targetingChecker = targetingChecker self.imageService = imageService diff --git a/MindboxTests/Mock/MockPersistenceStorage.swift b/MindboxTests/Mock/MockPersistenceStorage.swift index 2e98549e..9468bcdb 100644 --- a/MindboxTests/Mock/MockPersistenceStorage.swift +++ b/MindboxTests/Mock/MockPersistenceStorage.swift @@ -12,7 +12,7 @@ import Foundation class MockPersistenceStorage: PersistenceStorage { var onDidChange: (() -> Void)? - private var _userVisitCount: Int? = 0 + init() { @@ -89,10 +89,19 @@ class MockPersistenceStorage: PersistenceStorage { var imageLoadingMaxTimeInSeconds: Double? + private var _userVisitCount: Int? = 0 + var userVisitCount: Int? { get { return _userVisitCount } set { _userVisitCount = newValue } } + + private var _versionCodeForMigration: Int? = 0 + + var versionCodeForMigration: Int? { + get { return _versionCodeForMigration } + set { _versionCodeForMigration = newValue } + } var configDownloadDate: Date? { didSet { @@ -112,6 +121,13 @@ class MockPersistenceStorage: PersistenceStorage { resetBackgroundExecutions() } + func softReset() { + configDownloadDate = nil + shownInappsDictionary = nil + handledlogRequestIds = nil + userVisitCount = 0 + resetBackgroundExecutions() + } func setBackgroundExecution(_ value: BackgroudExecution) { backgroundExecutions.append(value) diff --git a/MindboxTests/PushNotificationTests/MindboxPushValidatorTests.swift b/MindboxTests/PushNotificationTests/MindboxPushValidatorTests.swift index c63c24b0..9aaa563f 100644 --- a/MindboxTests/PushNotificationTests/MindboxPushValidatorTests.swift +++ b/MindboxTests/PushNotificationTests/MindboxPushValidatorTests.swift @@ -11,6 +11,18 @@ import XCTest final class MindboxPushValidatorTests: XCTestCase { + var validator: MindboxPushValidator! + + override func setUp() { + super.setUp() + validator = DI.injectOrFail(MindboxPushValidator.self) + } + + override func tearDown() { + super.tearDown() + validator = nil + } + func testValidatorWithValidNotification() { let validNotification: [AnyHashable: Any] = [ "aps": [ @@ -34,8 +46,6 @@ final class MindboxPushValidatorTests: XCTestCase { ], "uniqueKey": "guid-for-message" ] - - let validator = MindboxPushValidator() XCTAssertTrue(validator.isValid(item: validNotification), "Validator should return true for a valid notification") } @@ -62,8 +72,6 @@ final class MindboxPushValidatorTests: XCTestCase { ], "uniqueKey": "guid-for-message" ] - - let validator = MindboxPushValidator() XCTAssertFalse(validator.isValid(item: invalidNotification), "Validator should return false for an invalid notification") } @@ -90,8 +98,6 @@ final class MindboxPushValidatorTests: XCTestCase { ], "uniqueKey": "guid-for-message" ] - - let validator = MindboxPushValidator() XCTAssertFalse(validator.isValid(item: invalidNotification), "Validator should return false for an invalid notification") } diff --git a/MindboxTests/UserVisitManagerTests/UserVisitManagerTests.swift b/MindboxTests/UserVisitManagerTests/UserVisitManagerTests.swift index 425f53d2..c07f38a1 100644 --- a/MindboxTests/UserVisitManagerTests/UserVisitManagerTests.swift +++ b/MindboxTests/UserVisitManagerTests/UserVisitManagerTests.swift @@ -14,13 +14,11 @@ final class UserVisitManagerTests: XCTestCase { private var persistenceStorageMock: PersistenceStorage! private var userVisitManager: UserVisitManagerProtocol! private var sessionManagerMock: MockSessionManager! - private var container: TestDependencyProvider! override func setUp() { super.setUp() - container = try! TestDependencyProvider() - persistenceStorageMock = MockPersistenceStorage() - userVisitManager = UserVisitManager(persistenceStorage: persistenceStorageMock) + persistenceStorageMock = DI.injectOrFail(PersistenceStorage.self) + userVisitManager = DI.injectOrFail(UserVisitManagerProtocol.self) persistenceStorageMock.deviceUUID = "00000000-0000-0000-0000-000000000000" } @@ -36,7 +34,7 @@ final class UserVisitManagerTests: XCTestCase { userVisitManager.saveUserVisit() XCTAssertEqual(persistenceStorageMock.userVisitCount, 2) - userVisitManager = UserVisitManager(persistenceStorage: persistenceStorageMock) + userVisitManager = DI.injectOrFail(UserVisitManagerProtocol.self) userVisitManager.saveUserVisit() XCTAssertEqual(persistenceStorageMock.userVisitCount, 3) diff --git a/MindboxTests/Validators/InappFrequencyTests.swift b/MindboxTests/Validators/InappFrequencyTests.swift index a27820c4..0e17d56d 100644 --- a/MindboxTests/Validators/InappFrequencyTests.swift +++ b/MindboxTests/Validators/InappFrequencyTests.swift @@ -11,28 +11,25 @@ import XCTest class InappFrequencyTests: XCTestCase { - var container: DependencyContainer! var validator: InappFrequencyValidator! var persistenceStorage: PersistenceStorage! override func setUp() { super.setUp() - container = try! TestDependencyProvider() - validator = container.frequencyValidator - persistenceStorage = container.persistenceStorage + persistenceStorage = DI.injectOrFail(PersistenceStorage.self) persistenceStorage.shownInappsDictionary = [:] + validator = InappFrequencyValidator(persistenceStorage: persistenceStorage) } override func tearDown() { validator = nil persistenceStorage = nil - container = nil super.tearDown() } func test_once_lifetime_firstTime_shown() throws { let onceFrequency = OnceFrequency(kind: .lifetime) - let inappFrequency: InappFrequency = .once(.init(kind: .lifetime)) + let inappFrequency: InappFrequency = .once(onceFrequency) let inapp = getInapp(frequency: inappFrequency) XCTAssertTrue(validator.isValid(item: inapp)) } diff --git a/MindboxTests/Validators/SDKVersionValidatorTests.swift b/MindboxTests/Validators/SDKVersionValidatorTests.swift index 475a8c53..4fa4139a 100644 --- a/MindboxTests/Validators/SDKVersionValidatorTests.swift +++ b/MindboxTests/Validators/SDKVersionValidatorTests.swift @@ -15,6 +15,8 @@ class SDKVersionValidatorTests: XCTestCase { override func setUp() { super.setUp() + validator = DI.injectOrFail(SDKVersionValidator.self) + validator.sdkVersionNumeric = 6 } override func tearDown() { @@ -24,30 +26,25 @@ class SDKVersionValidatorTests: XCTestCase { func test_whenMinVersionIsTooHigh_returnsFalse() { let sdkVersion = SdkVersion(min: 10, max: nil) - validator = SDKVersionValidator(sdkVersionNumeric: 6) XCTAssertFalse(validator.isValid(item: sdkVersion)) } func testValidator_whenMaxVersionIsTooLow_returnsFalse() { let sdkVersion = SdkVersion(min: nil, max: 5) - validator = SDKVersionValidator(sdkVersionNumeric: 6) XCTAssertFalse(validator.isValid(item: sdkVersion)) } func testValidator_whenVersionIsWithinBounds_returnsTrue() { let sdkVersion = SdkVersion(min: 5, max: 7) - validator = SDKVersionValidator(sdkVersionNumeric: 6) XCTAssertTrue(validator.isValid(item: sdkVersion)) } func testValidator_whenMinAndMaxAreNil_returnsTrue() { let sdkVersion = SdkVersion(min: nil, max: nil) - validator = SDKVersionValidator(sdkVersionNumeric: 6) XCTAssertFalse(validator.isValid(item: sdkVersion)) } func testValidator_whenVersionIsNull_returnsFalse() { - validator = SDKVersionValidator(sdkVersionNumeric: 6) XCTAssertFalse(validator.isValid(item: nil)) } } diff --git a/MindboxTests/Versioning/VersioningTestCase.swift b/MindboxTests/Versioning/VersioningTestCase.swift index b5930eac..730c72a2 100644 --- a/MindboxTests/Versioning/VersioningTestCase.swift +++ b/MindboxTests/Versioning/VersioningTestCase.swift @@ -12,15 +12,17 @@ import XCTest class VersioningTestCase: XCTestCase { private var queues: [DispatchQueue] = [] - var container: DependencyContainer! + var persistenceStorage: PersistenceStorage! override func setUp() { super.setUp() - container = try! TestDependencyProvider() - container.persistenceStorage.reset() - try! container.databaseRepository.erase() - Mindbox.shared.assembly(with: container) - TimerManager.shared.invalidate() + persistenceStorage = DI.injectOrFail(PersistenceStorage.self) + persistenceStorage.reset() + let databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) + try! databaseRepository.erase() + Mindbox.shared.assembly() + let timer = DI.injectOrFail(TimerManager.self) + timer.invalidate() queues = [] // Put setup code here. This method is called before the invocation of each test method in the class. } diff --git a/Package.swift b/Package.swift index ea60423d..d29fdd11 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "Mindbox", - platforms: [.iOS(.v10)], + platforms: [.iOS(.v12)], products: [ .library( name: "Mindbox", diff --git a/SDKVersionProvider/SDKVersionConfig.xcconfig b/SDKVersionProvider/SDKVersionConfig.xcconfig index 48537767..4f883452 100644 --- a/SDKVersionProvider/SDKVersionConfig.xcconfig +++ b/SDKVersionProvider/SDKVersionConfig.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 2.10.3-rc +MARKETING_VERSION = 2.11.0 diff --git a/SDKVersionProvider/SDKVersionProvider.swift b/SDKVersionProvider/SDKVersionProvider.swift index 81f34aae..ab8dfdfd 100644 --- a/SDKVersionProvider/SDKVersionProvider.swift +++ b/SDKVersionProvider/SDKVersionProvider.swift @@ -8,6 +8,6 @@ import Foundation public class SDKVersionProvider { - public static let sdkVersion = "2.10.3-rc" + public static let sdkVersion = "2.11.0" }