diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 00000000..9c1bd906 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,145 @@ +# Directory and file filters +included: # paths to include during linting. `--path` is ignored if present. + - Mindbox + - MindboxLogger + - MindboxNotifications + - MindboxTests + - MindboxNotificationsTests +excluded: # paths to ignore during linting. Takes precedence over `included`. + +only_rules: # default rules + - closing_brace + - closure_parameter_position + - colon + - comma + - comment_spacing + - compiler_protocol_init + - computed_accessors_order + - control_statement + - cyclomatic_complexity + - deployment_target + - discouraged_direct_init + - duplicate_conditions + - duplicate_enum_cases + - duplicate_imports + - empty_enum_arguments + - empty_parameters + - empty_parentheses_with_trailing_closure + - file_length + - for_where + - force_cast + - force_try + - function_body_length + - function_parameter_count + - generic_type_name + - implicit_getter + - inclusive_language + - invalid_swiftlint_command + - is_disjoint + - large_tuple + - leading_whitespace + - legacy_cggeometry_functions + - legacy_constant + - legacy_constructor + - legacy_hashing + - legacy_nsgeometry_functions + - legacy_random + - line_length + - mark + - multiple_closures_with_trailing_closure + - no_space_in_method_call + - non_optional_string_data_conversion + - notification_center_detachment + - ns_number_init_as_function_reference + - nsobject_prefer_isequal + - opening_brace + - operator_whitespace + - optional_data_string_conversion + - orphaned_doc_comment + - prefer_type_checking + - private_unit_test + - protocol_property_accessors_order + - reduce_boolean + - redundant_discardable_let + - redundant_objc_attribute + - redundant_optional_initialization + - redundant_set_access_control + - redundant_string_enum_value + - redundant_void_return + - return_arrow_whitespace + - self_in_property_initialization + - shorthand_operator + - statement_position + - static_over_final_class + - superfluous_disable_command + - syntactic_sugar + - trailing_comma + - trailing_newline + - trailing_semicolon + - trailing_whitespace + - unavailable_condition + - unneeded_break_in_switch + - unneeded_override + - unneeded_synthesized_initializer + - unused_closure_parameter + - unused_control_flow_label + - unused_enumerated + - unused_optional_binding + - unused_setter_value + - vertical_parameter_alignment + - vertical_whitespace + - void_function_in_ternary + - void_return + - xctfail_message + + # opt_in_rules + - attributes + - closure_body_length + - closure_end_indentation + - closure_spacing + - discouraged_object_literal + - empty_count + - empty_string + - fatal_error_message + - first_where + - force_unwrapping + - last_where + - modifier_order + - operator_usage_whitespace + - private_outlet + - redundant_type_annotation + - toggle_bool + - unneeded_parentheses_in_closure_argument + - vertical_whitespace_closing_braces + - weak_delegate + - yoda_condition + +# Default Rules Setup + +cyclomatic_complexity: + ignores_case_statements: true + +file_length: + warning: 400 + error: 800 + ignore_comment_only_lines: true + +function_body_length: + warning: 150 + error: 250 + +large_tuple: + warning: 3 + error: 4 + +line_length: + warning: 220 + error: 300 + ignores_comments: true + ignores_function_declarations: true + +# Opt-in Rules Setup + +closure_body_length: + warning: 30 + error: 50 diff --git a/Mindbox.xcodeproj/project.pbxproj b/Mindbox.xcodeproj/project.pbxproj index f476e53d..aab1f0d6 100644 --- a/Mindbox.xcodeproj/project.pbxproj +++ b/Mindbox.xcodeproj/project.pbxproj @@ -3162,6 +3162,7 @@ buildPhases = ( 313B232B25ADEA0F00A1CB72 /* Headers */, 313B232C25ADEA0F00A1CB72 /* Sources */, + 47DCEB3C2CBD154E0096593C /* Run Script SwiftLint */, 313B232D25ADEA0F00A1CB72 /* Frameworks */, 313B232E25ADEA0F00A1CB72 /* Resources */, 3159FFB425B5AAF300EE80E9 /* Increment Build Number Script */, @@ -3499,6 +3500,26 @@ shellPath = /bin/sh; shellScript = "buildNumber=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\")\nbuildNumber=$(($buildNumber + 1))\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\"\n"; }; + 47DCEB3C2CBD154E0096593C /* Run Script SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script SwiftLint"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/zsh; + shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n CONFIG_FILE=\"${SRCROOT}/.swiftlint.yml\"\n if [ -f $CONFIG_FILE ]; then\n swiftlint --config $CONFIG_FILE\n else\n echo \"warning: SwiftLint configuration file not found at ${CONFIG_FILE}\"\n swiftlint\n fi\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -4146,6 +4167,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Mindbox/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( @@ -4178,6 +4200,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_MODULE_VERIFIER = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Mindbox/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/Mindbox/ClickNotificationManager/ClickNotificationManager.swift b/Mindbox/ClickNotificationManager/ClickNotificationManager.swift index ab755b6d..9c0a2ee9 100644 --- a/Mindbox/ClickNotificationManager/ClickNotificationManager.swift +++ b/Mindbox/ClickNotificationManager/ClickNotificationManager.swift @@ -10,15 +10,15 @@ import Foundation import UserNotifications final class ClickNotificationManager { - + private let databaseRepository: MBDatabaseRepository - + init( databaseRepository: MBDatabaseRepository ) { self.databaseRepository = databaseRepository } - + func track(uniqueKey: String, buttonUniqueKey: String? = nil) throws { var trackMobilePushClick: TrackClick? if let buttonUniqueKey = buttonUniqueKey { @@ -36,11 +36,10 @@ final class ClickNotificationManager { let event = Event(type: .trackClick, body: BodyEncoder(encodable: encodable).body) try databaseRepository.create(event: event) } - + func track(response: UNNotificationResponse) throws { let decoder = try NotificationDecoder(response: response) let payload = try decoder.decode() try track(uniqueKey: payload.uniqueKey, buttonUniqueKey: response.actionIdentifier) } - } diff --git a/Mindbox/CoreController/CoreController.swift b/Mindbox/CoreController/CoreController.swift index 4fdc9afb..f6bf4e14 100644 --- a/Mindbox/CoreController/CoreController.swift +++ b/Mindbox/CoreController/CoreController.swift @@ -25,13 +25,13 @@ final class CoreController { var controllerQueue: DispatchQueue func initialization(configuration: MBConfiguration) { - + controllerQueue.async { 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 { @@ -39,13 +39,13 @@ final class CoreController { } else { self.repeatInitialization(with: configuration) } - + self.guaranteedDeliveryManager.canScheduleOperations = true - + let appStateMessage = "[App State]: \(UIApplication.shared.appStateDescription)" Logger.common(message: appStateMessage, level: .info, category: .general) } - + Logger.common(message: "[Configuration]: \(configuration)", level: .info, category: .general) Logger.common(message: "[SDK Version]: \(self.utilitiesFetcher.sdkVersion ?? "null")", level: .info, category: .general) Logger.common(message: "[APNS Token]: \(self.persistenceStorage.apnsToken ?? "null")", level: .info, category: .general) @@ -55,7 +55,7 @@ final class CoreController { func apnsTokenDidUpdate(token: String) { controllerQueue.async { let isNotificationsEnabled = self.notificationStatus() - + if self.persistenceStorage.needUpdateInfoOnce ?? true { self.updateInfo(apnsToken: token, isNotificationsEnabled: isNotificationsEnabled) self.persistenceStorage.isNotificationsEnabled = isNotificationsEnabled @@ -63,7 +63,7 @@ final class CoreController { self.persistenceStorage.needUpdateInfoOnce = false return } - + if self.persistenceStorage.isInstalled { self.updateInfo( apnsToken: token, @@ -107,7 +107,7 @@ final class CoreController { private func generateDeviceUUID() -> String { let lock = DispatchSemaphore(value: 0) - var deviceUUID: String? + var deviceUUID = String() let start = CFAbsoluteTimeGetCurrent() utilitiesFetcher.getDeviceUUID { deviceUUID = $0 @@ -115,7 +115,7 @@ final class CoreController { } lock.wait() Logger.common(message: "It took \(CFAbsoluteTimeGetCurrent() - start) seconds to generate deviceUUID", level: .debug, category: .general) - return deviceUUID! + return deviceUUID } private func primaryInitialization(with configutaion: MBConfiguration) { @@ -133,7 +133,7 @@ final class CoreController { Logger.common(message: "Unable to find deviceUUID in persistenceStorage", level: .error, category: .general) return } - + if configValidation.changedState != .none { Logger.common(message: "Mindbox Configuration changed", level: .info, category: .general) install( @@ -272,13 +272,13 @@ final class CoreController { } } } - + let timer = DI.injectOrFail(TimerManager.self) timer.configurate(trackEvery: 20 * 60) { Logger.common(message: "Scheduled Time tracker started") sessionManager.trackForeground() } - + timer.setupTimer() } } diff --git a/Mindbox/CoreController/TimerManager.swift b/Mindbox/CoreController/TimerManager.swift index 2b4d6747..ccbb0984 100644 --- a/Mindbox/CoreController/TimerManager.swift +++ b/Mindbox/CoreController/TimerManager.swift @@ -10,10 +10,10 @@ import UIKit import MindboxLogger public final class TimerManager { - + internal var didEnterBackgroundApplication: NSObjectProtocol? internal var didBecomeActiveApplication: NSObjectProtocol? - + internal var timer: Timer? { didSet { if didBecomeActiveApplication == nil && didEnterBackgroundApplication == nil { @@ -21,9 +21,9 @@ public final class TimerManager { } } } - - internal var deadline: TimeInterval? = nil - + + internal var deadline: TimeInterval? + internal var seconds: TimeInterval = 0 { didSet { if seconds >= deadline ?? TimeInterval(Int.max) { @@ -32,50 +32,48 @@ public final class TimerManager { } } } - - internal var block: (() -> ())? - + + internal var block: (() -> Void)? + internal func invalidate() { timer?.invalidate() timer = nil seconds = 0 Logger.common(message: "The timer is stopped") } - + internal func removeObservers() { - if didBecomeActiveApplication != nil && didEnterBackgroundApplication != nil { - NotificationCenter.default.removeObserver(didEnterBackgroundApplication!) - NotificationCenter.default.removeObserver(didBecomeActiveApplication!) + if let didEnterBackground = didEnterBackgroundApplication, + let didBecomeActive = didBecomeActiveApplication { + NotificationCenter.default.removeObserver(didEnterBackground) + NotificationCenter.default.removeObserver(didBecomeActive) didBecomeActiveApplication = nil didEnterBackgroundApplication = nil } - } - + internal func setupObservers() { didEnterBackgroundApplication = NotificationCenter.default.addObserver( forName: UIApplication.didEnterBackgroundNotification, object: nil, - queue: nil) { [weak self] (_) in - + queue: nil) { [weak self] _ in + self?.invalidate() } - - + didBecomeActiveApplication = NotificationCenter.default.addObserver( forName: UIApplication.didBecomeActiveNotification, object: nil, - queue: nil) { [weak self] (_) in - + queue: nil) { [weak self] _ in + if self?.timer == nil { - + self?.setupTimer() } } - } - - public func configurate(trackEvery seconds: TimeInterval?, block: (() -> ())?) { + + public func configurate(trackEvery seconds: TimeInterval?, block: (() -> Void)?) { if seconds != nil { self.deadline = seconds } @@ -83,23 +81,22 @@ public final class TimerManager { self.block = block } self.seconds = 0 - } - + public func setupTimer(trackEvery newDeadline: TimeInterval? = nil) { - if newDeadline != nil { - self.deadline = newDeadline! + if let newDeadline { + self.deadline = newDeadline } self.seconds = 0 - + timer = Timer(timeInterval: 1, repeats: true) { [weak self] _ in self?.seconds += 1 } - + guard let timer = timer else { return } - + RunLoop.main.add(timer, forMode: .common) Logger.common(message: "The timer is running") } diff --git a/Mindbox/DI/Injections/InjectABTestUtilities.swift b/Mindbox/DI/Injections/InjectABTestUtilities.swift index c9414226..990c8be8 100644 --- a/Mindbox/DI/Injections/InjectABTestUtilities.swift +++ b/Mindbox/DI/Injections/InjectABTestUtilities.swift @@ -13,17 +13,17 @@ extension MBContainer { 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 index a9deb56d..37bc5225 100644 --- a/Mindbox/DI/Injections/InjectCore.swift +++ b/Mindbox/DI/Injections/InjectCore.swift @@ -21,7 +21,7 @@ extension MBContainer { 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) @@ -31,7 +31,7 @@ extension MBContainer { databaseRepository: databaseRepository, eventRepository: eventRepository) } - + register(InAppConfigurationMapperProtocol.self) { let inappFilterService = DI.injectOrFail(InappFilterProtocol.self) let targetingChecker = DI.injectOrFail(InAppTargetingCheckerProtocol.self) @@ -40,7 +40,7 @@ extension MBContainer { targetingChecker: targetingChecker, dataFacade: dataFacade) } - + register(InAppConfigurationManagerProtocol.self) { let persistenceStorage = DI.injectOrFail(PersistenceStorage.self) return InAppConfigurationManager( @@ -49,7 +49,7 @@ extension MBContainer { inAppConfigurationMapper: DI.injectOrFail(InAppConfigurationMapperProtocol.self), persistenceStorage: persistenceStorage) } - + return self } } diff --git a/Mindbox/DI/Injections/InjectInappTools.swift b/Mindbox/DI/Injections/InjectInappTools.swift index 2df6b8c9..4946b71c 100644 --- a/Mindbox/DI/Injections/InjectInappTools.swift +++ b/Mindbox/DI/Injections/InjectInappTools.swift @@ -14,49 +14,49 @@ extension MBContainer { 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) @@ -65,37 +65,37 @@ extension MBContainer { 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 index f759618b..3d1ebdc4 100644 --- a/Mindbox/DI/Injections/InjectReplaceable.swift +++ b/Mindbox/DI/Injections/InjectReplaceable.swift @@ -18,39 +18,45 @@ extension MBContainer { 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)! + guard let defaults = UserDefaults(suiteName: utilitiesFetcher.applicationGroupIdentifier) else { + fatalError("Failed to create UserDefaults with suite name: \(utilitiesFetcher.applicationGroupIdentifier). Check and set up your AppGroups correctly.") + } return MBPersistenceStorage(defaults: defaults) } - + register(MBDatabaseRepository.self) { let databaseLoader = DI.injectOrFail(DataBaseLoader.self) - let persistentContainer = try! databaseLoader.loadPersistentContainer() - return try! MBDatabaseRepository(persistentContainer: persistentContainer) + + guard let persistentContainer = try? databaseLoader.loadPersistentContainer(), + let dbRepository = try? MBDatabaseRepository(persistentContainer: persistentContainer) else { + fatalError("Failed to create MBDatabaseRepository") + } + return dbRepository } - + 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) @@ -62,18 +68,18 @@ extension MBContainer { 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) @@ -82,7 +88,7 @@ extension MBContainer { presentationManager: presentationManager, persistenceStorage: persistenceStorage) } - + return self } } diff --git a/Mindbox/DI/Injections/InjectUtilities.swift b/Mindbox/DI/Injections/InjectUtilities.swift index c74123c2..7110a933 100644 --- a/Mindbox/DI/Injections/InjectUtilities.swift +++ b/Mindbox/DI/Injections/InjectUtilities.swift @@ -17,7 +17,7 @@ extension MBContainer { register(TimerManager.self) { TimerManager() } - + register(MigrationManagerProtocol.self, scope: .transient) { let persistenceStorage = DI.injectOrFail(PersistenceStorage.self) return MigrationManager(persistenceStorage: persistenceStorage) @@ -27,19 +27,23 @@ extension MBContainer { 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) + + guard let dbLoader = try? DataBaseLoader(applicationGroupIdentifier: utilitiesFetcher.applicationGroupIdentifier) else { + fatalError("Failed to create DataBaseLoader") + } + return dbLoader } register(VariantImageUrlExtractorServiceProtocol.self, scope: .transient) { @@ -52,29 +56,29 @@ extension MBContainer { 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) diff --git a/Mindbox/DI/MBContainer.swift b/Mindbox/DI/MBContainer.swift index c303ea4a..b798c12e 100644 --- a/Mindbox/DI/MBContainer.swift +++ b/Mindbox/DI/MBContainer.swift @@ -16,15 +16,15 @@ enum ObjectScope { 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: @@ -40,7 +40,7 @@ class MBContainer { } return nil } - + func resolveOrFail(_ serviceType: T.Type) -> T { guard let service = self.resolve(serviceType) else { fatalError("Service \(serviceType) not found") diff --git a/Mindbox/DI/MBInject.swift b/Mindbox/DI/MBInject.swift index 66c3c3d3..27c6a731 100644 --- a/Mindbox/DI/MBInject.swift +++ b/Mindbox/DI/MBInject.swift @@ -17,7 +17,7 @@ extension MBContainer: ModuleInjecting { func inject(_ serviceType: Dependency.Type) -> Dependency? { return self.resolve(serviceType) } - + func injectOrFail(_ serviceType: Dependency.Type) -> Dependency { return self.resolveOrFail(serviceType) } @@ -28,9 +28,9 @@ enum MBInject { case standard case test } - + static var container: MBContainer = MBInject.buildDefaulContainer() - + static var mode: InjectionMode = .standard { didSet { switch mode { @@ -41,7 +41,7 @@ enum MBInject { } } } - + fileprivate static func buildDefaulContainer() -> MBContainer { let container = MBContainer() return container @@ -52,7 +52,7 @@ enum MBInject { .registerInappTools() .registerInappPresentation() } - + public static var buildTestContainer: () -> MBContainer = { let container = MBContainer() return container diff --git a/Mindbox/Database/DatabaseLoader.swift b/Mindbox/Database/DatabaseLoader.swift index 6b53dced..dbba17f3 100644 --- a/Mindbox/Database/DatabaseLoader.swift +++ b/Mindbox/Database/DatabaseLoader.swift @@ -11,14 +11,14 @@ import CoreData import MindboxLogger class DataBaseLoader { - + private let persistentStoreDescriptions: [NSPersistentStoreDescription]? private let persistentContainer: NSPersistentContainer var persistentStoreDescription: NSPersistentStoreDescription? - + var loadPersistentStoresError: Error? var persistentStoreURL: URL? - + init(persistentStoreDescriptions: [NSPersistentStoreDescription]? = nil, applicationGroupIdentifier: String? = nil) throws { MBPersistentContainer.applicationGroupIdentifier = applicationGroupIdentifier let momdName = Constants.Database.mombName @@ -38,7 +38,7 @@ class DataBaseLoader { } #endif - + guard let modelURL = modelURL else { Logger.common(message: MBDatabaseError.unableCreateDatabaseModel.errorDescription, level: .error, category: .database) throw MBDatabaseError.unableCreateDatabaseModel @@ -52,7 +52,7 @@ class DataBaseLoader { name: momdName, managedObjectModel: managedObjectModel ) - + self.persistentStoreDescriptions = persistentStoreDescriptions if let persistentStoreDescriptions = persistentStoreDescriptions { persistentContainer.persistentStoreDescriptions = persistentStoreDescriptions @@ -63,7 +63,7 @@ class DataBaseLoader { $0.shouldInferMappingModelAutomatically = true } } - + func loadPersistentContainer() throws -> NSPersistentContainer { do { return try loadPersistentStores() @@ -74,9 +74,9 @@ class DataBaseLoader { } } } - + private func loadPersistentStores() throws -> NSPersistentContainer { - persistentContainer.loadPersistentStores { [weak self] (persistentStoreDescription, error) in + persistentContainer.loadPersistentStores { [weak self] persistentStoreDescription, error in if let url = persistentStoreDescription.url { Logger.common(message: "Persistent store url: \(url.description)", level: .info, category: .database) } else { @@ -92,7 +92,7 @@ class DataBaseLoader { } return persistentContainer } - + func destroy() throws { guard let persistentStoreURL = persistentStoreURL else { Logger.common(message: MBDatabaseError.persistentStoreURLNotFound.errorDescription, level: .error, category: .database) @@ -100,7 +100,7 @@ class DataBaseLoader { } Logger.common(message: "Removing database at url: \(persistentStoreURL.absoluteString)", level: .info, category: .database) - + guard FileManager.default.fileExists(atPath: persistentStoreURL.path) else { Logger.common(message: MBDatabaseError.persistentStoreNotExistsAtURL(path: persistentStoreURL.path).errorDescription, level: .error, category: .database) throw MBDatabaseError.persistentStoreNotExistsAtURL(path: persistentStoreURL.path) @@ -113,5 +113,4 @@ class DataBaseLoader { throw error } } - } diff --git a/Mindbox/Database/Entities/CDEvent+CoreDataClass.swift b/Mindbox/Database/Entities/CDEvent+CoreDataClass.swift index 33c7d23a..09a915b1 100644 --- a/Mindbox/Database/Entities/CDEvent+CoreDataClass.swift +++ b/Mindbox/Database/Entities/CDEvent+CoreDataClass.swift @@ -11,6 +11,4 @@ import Foundation import CoreData @objc(CDEvent) -public class CDEvent: NSManagedObject { - -} +public class CDEvent: NSManagedObject {} diff --git a/Mindbox/Database/Entities/CDEvent+CoreDataProperties.swift b/Mindbox/Database/Entities/CDEvent+CoreDataProperties.swift index 1b5d3bef..d4d989eb 100644 --- a/Mindbox/Database/Entities/CDEvent+CoreDataProperties.swift +++ b/Mindbox/Database/Entities/CDEvent+CoreDataProperties.swift @@ -10,9 +10,8 @@ import Foundation import CoreData - extension CDEvent { - + public class func fetchRequestForDelete(lifeLimitDate: Date? = nil) -> NSFetchRequest { let request = NSFetchRequest(entityName: "CDEvent") if let monthLimitDateStamp = lifeLimitDate?.timeIntervalSince1970 { @@ -22,11 +21,11 @@ extension CDEvent { ) } request.sortDescriptors = [ - NSSortDescriptor(key: #keyPath(CDEvent.timestamp), ascending: true), + NSSortDescriptor(key: #keyPath(CDEvent.timestamp), ascending: true) ] return request } - + public class func fetchRequestForSend(lifeLimitDate: Date?, retryDeadLine: TimeInterval = 60) -> NSFetchRequest { let request = NSFetchRequest(entityName: "CDEvent") var subpredicates: [NSPredicate] = [] @@ -53,17 +52,17 @@ extension CDEvent { request.predicate = NSCompoundPredicate(type: .or, subpredicates: subpredicates) request.sortDescriptors = [ NSSortDescriptor(key: #keyPath(CDEvent.retryTimestamp), ascending: true), - NSSortDescriptor(key: #keyPath(CDEvent.timestamp), ascending: true), + NSSortDescriptor(key: #keyPath(CDEvent.timestamp), ascending: true) ] return request } - + public class func fetchRequest(by transactionId: String) -> NSFetchRequest { let request = NSFetchRequest(entityName: "CDEvent") request.predicate = NSPredicate(format: "%K == %@", argumentArray: [#keyPath(CDEvent.transactionId), transactionId]) return request } - + public class func countEventsFetchRequest(lifeLimitDate: Date? = nil) -> NSFetchRequest { let request = NSFetchRequest(entityName: "CDEvent") if let monthLimitDateStamp = lifeLimitDate?.timeIntervalSince1970 { @@ -74,7 +73,7 @@ extension CDEvent { } return request } - + public class func deprecatedEventsFetchRequest(lifeLimitDate: Date?) -> NSFetchRequest { let request = NSFetchRequest(entityName: "CDEvent") if let monthLimitDateStamp = lifeLimitDate?.timeIntervalSince1970 { @@ -82,11 +81,10 @@ extension CDEvent { } return request } - + @NSManaged public var body: String? @NSManaged public var timestamp: Double @NSManaged public var transactionId: String? @NSManaged public var type: String? @NSManaged public var retryTimestamp: Double - } diff --git a/Mindbox/Database/MBDatabaseError.swift b/Mindbox/Database/MBDatabaseError.swift index f5067c34..558120c4 100644 --- a/Mindbox/Database/MBDatabaseError.swift +++ b/Mindbox/Database/MBDatabaseError.swift @@ -9,7 +9,7 @@ import Foundation public enum MBDatabaseError: LocalizedError { - + case unableCreateDatabaseModel case unableCreateManagedObjectModel(with: URL) case unableToLoadPeristentStore(localizedDescription: String) diff --git a/Mindbox/Database/MBDatabaseRepository.swift b/Mindbox/Database/MBDatabaseRepository.swift index bcb008c1..79aafac7 100644 --- a/Mindbox/Database/MBDatabaseRepository.swift +++ b/Mindbox/Database/MBDatabaseRepository.swift @@ -17,18 +17,18 @@ class MBDatabaseRepository { case infoUpdate = "ApplicationInfoUpdatedVersion" case instanceId = "ApplicationInstanceId" } - + let persistentContainer: NSPersistentContainer private let context: NSManagedObjectContext private let store: NSPersistentStore - + var limit: Int { return 10000 } let deprecatedLimit = 500 - + var onObjectsDidChange: (() -> Void)? - + var lifeLimitDate: Date? { let calendar: Calendar = .current guard let monthLimitDate = calendar.date(byAdding: .month, value: -6, to: Date()) else { @@ -78,7 +78,7 @@ class MBDatabaseRepository { try saveEvent(withContext: context) } } - + func read(by transactionId: String) throws -> CDEvent? { try context.mindboxPerformAndWait { Logger.common(message: "Reading event with transactionId: \(transactionId)", level: .info, category: .database) @@ -91,7 +91,7 @@ class MBDatabaseRepository { return entity } } - + func update(event: Event) throws { try context.mindboxPerformAndWait { Logger.common(message: "Updating event with transactionId: \(event.transactionId)", level: .info, category: .database) @@ -104,7 +104,7 @@ class MBDatabaseRepository { try saveEvent(withContext: context) } } - + func delete(event: Event) throws { try context.mindboxPerformAndWait { Logger.common(message: "Deleting event with transactionId: \(event.transactionId)", level: .info, category: .database) @@ -117,8 +117,8 @@ class MBDatabaseRepository { try saveEvent(withContext: context) } } - - func query(fetchLimit: Int, retryDeadline: TimeInterval = 60) throws -> [Event] { + + func query(fetchLimit: Int, retryDeadline: TimeInterval = 60) throws -> [Event] { try context.mindboxPerformAndWait { Logger.common(message: "Quering events with fetchLimit: \(fetchLimit)", level: .info, category: .database) let request: NSFetchRequest = CDEvent.fetchRequestForSend(lifeLimitDate: lifeLimitDate, retryDeadLine: retryDeadline) @@ -135,17 +135,17 @@ class MBDatabaseRepository { } } } - - func query(by request: NSFetchRequest) throws -> [CDEvent] { + + func query(by request: NSFetchRequest) throws -> [CDEvent] { try context.fetch(request) } - + func removeDeprecatedEventsIfNeeded() throws { let request: NSFetchRequest = CDEvent.deprecatedEventsFetchRequest(lifeLimitDate: lifeLimitDate) let context = persistentContainer.newBackgroundContext() try delete(by: request, withContext: context) } - + func countDeprecatedEvents() throws -> Int { let context = persistentContainer.newBackgroundContext() let request: NSFetchRequest = CDEvent.deprecatedEventsFetchRequest(lifeLimitDate: lifeLimitDate) @@ -161,7 +161,7 @@ class MBDatabaseRepository { } } } - + func erase() throws { let fetchRequest = NSFetchRequest(entityName: "CDEvent") let eraseRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) @@ -173,7 +173,7 @@ class MBDatabaseRepository { try countEvents() } } - + @discardableResult func countEvents() throws -> Int { let request: NSFetchRequest = CDEvent.countEventsFetchRequest() @@ -191,7 +191,7 @@ class MBDatabaseRepository { } } } - + private func cleanUp(count: Int) { let fetchLimit = count - limit guard fetchLimit > .zero else { return } @@ -225,12 +225,9 @@ class MBDatabaseRepository { private func findEvent(by request: NSFetchRequest) throws -> CDEvent? { try context.registeredObjects .compactMap { $0 as? CDEvent } - .filter { !$0.isFault } - .filter { request.predicate?.evaluate(with: $0) ?? false } - .first + .first(where: { !$0.isFault && request.predicate?.evaluate(with: $0) ?? false }) ?? context.fetch(request).first } - } // MARK: - ManagedObjectContext save processing @@ -259,7 +256,6 @@ private extension MBDatabaseRepository { throw error } } - } // MARK: - Metadata processing @@ -277,10 +273,10 @@ private extension MBDatabaseRepository { do { try context.mindboxPerformAndWait { try saveContext(context) - Logger.common(message: "Did save metadata of \(key.rawValue) to: \(String(describing: value))", level: .info, category: .database) } + Logger.common(message: "Did save metadata of \(key.rawValue) to: \(String(describing: value))", level: .info, category: .database) + } } catch { Logger.common(message: "Did save metadata of \(key.rawValue) failed with error: \(error.localizedDescription)", level: .error, category: .database) } } - } diff --git a/Mindbox/Extensions/UIApplication+Extensions.swift b/Mindbox/Extensions/UIApplication+Extensions.swift index 9a426484..7fec6248 100644 --- a/Mindbox/Extensions/UIApplication+Extensions.swift +++ b/Mindbox/Extensions/UIApplication+Extensions.swift @@ -18,7 +18,7 @@ extension UIApplication { } } } - + private var describeApplicationState: String { switch applicationState { case .active: diff --git a/Mindbox/Extensions/UIColor+Extensions.swift b/Mindbox/Extensions/UIColor+Extensions.swift index 9e23c800..a28e4a9c 100644 --- a/Mindbox/Extensions/UIColor+Extensions.swift +++ b/Mindbox/Extensions/UIColor+Extensions.swift @@ -10,17 +10,17 @@ import UIKit extension UIColor { convenience init(hex: String) { var cString: String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() - + if cString.hasPrefix("#") { cString.remove(at: cString.startIndex) } - + if cString.count != 6 { self.init(white: 1.0, alpha: 1.0) } else { var rgbValue: UInt32 = 0 Scanner(string: cString).scanHexInt32(&rgbValue) - + self.init( red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, diff --git a/Mindbox/GuaranteedDeliveryManager/Background/BGTaskManager.swift b/Mindbox/GuaranteedDeliveryManager/Background/BGTaskManager.swift index f6b2cf47..ac8c6d75 100644 --- a/Mindbox/GuaranteedDeliveryManager/Background/BGTaskManager.swift +++ b/Mindbox/GuaranteedDeliveryManager/Background/BGTaskManager.swift @@ -11,10 +11,9 @@ import UIKit import BackgroundTasks import MindboxLogger - @available(iOS 13.0, *) class BGTaskManager: BackgroundTaskManagerType { - + weak var gdManager: GuaranteedDeliveryManager? private var appGDRefreshIdentifier: String? @@ -26,12 +25,12 @@ class BGTaskManager: BackgroundTaskManagerType { private let persistenceStorage: PersistenceStorage private let databaseRepository: MBDatabaseRepository - + init(persistenceStorage: PersistenceStorage, databaseRepository: MBDatabaseRepository) { self.persistenceStorage = persistenceStorage self.databaseRepository = databaseRepository } - + func registerBGTasks( appGDRefreshIdentifier: String, appGDProcessingIdentifier: String, @@ -40,7 +39,7 @@ class BGTaskManager: BackgroundTaskManagerType { self.appGDRefreshIdentifier = appGDRefreshIdentifier self.appGDProcessingIdentifier = appGDProcessingIdentifier self.appDBCleanProcessingIdentifire = appDBCleanProcessingIdentifire - + BGTaskScheduler.shared.register( forTaskWithIdentifier: appGDRefreshIdentifier, using: nil, @@ -57,28 +56,28 @@ class BGTaskManager: BackgroundTaskManagerType { launchHandler: appDBCleanProcessingHandler ) } - + func endBackgroundTask(success: Bool) { guard appGDRefreshTask != nil, appGDProcessingTask != nil else { return } - + Logger.common(message: "Did call EndBackgroundTask", level: .info, category: .background) appGDRefreshTask?.setTaskCompleted(success: success) appGDProcessingTask?.setTaskCompleted(success: success) } - + func applicationDidEnterBackground() { scheduleAppGDRefreshTask() scheduleAppGDProcessingTask() scheduleAppDBCleanProcessingTaskIfNeeded() } - + func applicationDidBecomeActive() { appGDRefreshTask?.setTaskCompleted(success: false) appGDProcessingTask?.setTaskCompleted(success: false) } - + // MARK: - Shedulers private func scheduleAppGDRefreshTask() { guard let identifier = appGDRefreshIdentifier else { @@ -98,7 +97,7 @@ class BGTaskManager: BackgroundTaskManagerType { #endif } } - + private func scheduleAppGDProcessingTask() { guard let identifier = appGDProcessingIdentifier else { Logger.common(message: "appGDProcessingIdentifier is nil", level: .error, category: .background) @@ -118,7 +117,7 @@ class BGTaskManager: BackgroundTaskManagerType { #endif } } - + private func scheduleAppDBCleanProcessingTaskIfNeeded() { guard let identifier = appDBCleanProcessingIdentifire else { Logger.common(message: "appDBCleanProcessingIdentifire is nil", level: .error, category: .background) @@ -146,10 +145,9 @@ class BGTaskManager: BackgroundTaskManagerType { #else Logger.common(message: "Could not schedule app processing task with error: \(error.localizedDescription)", level: .info, category: .background) #endif - } } - + // MARK: - Handlers private func appGDRefreshHandler(task: BGTask) { Logger.common(message: "Invoked appGDRefreshHandler", level: .info, category: .background) @@ -183,7 +181,7 @@ class BGTaskManager: BackgroundTaskManagerType { Mindbox.shared.coreController?.checkNotificationStatus() Logger.common(message: "GDAppRefresh task started", level: .info, category: .background) } - + private func appGDProcessingHandler(task: BGTask) { Logger.common(message: "Invoked appGDAppProcessingHandler", level: .info, category: .background) guard let task = task as? BGProcessingTask else { @@ -215,7 +213,7 @@ class BGTaskManager: BackgroundTaskManagerType { Mindbox.shared.coreController?.checkNotificationStatus() Logger.common(message: "GDAppProcessing task started", level: .info, category: .background) } - + private func appDBCleanProcessingHandler(task: BGTask) { Logger.common(message: "Invoked removeDeprecatedEventsProcessing", level: .info, category: .background) guard let task = task as? BGProcessingTask else { @@ -240,5 +238,4 @@ class BGTaskManager: BackgroundTaskManagerType { Mindbox.shared.coreController?.checkNotificationStatus() Logger.common(message: "removeDeprecatedEventsProcessing task started", level: .info, category: .background) } - } diff --git a/Mindbox/GuaranteedDeliveryManager/Background/BackgroundTaskManagerProxy.swift b/Mindbox/GuaranteedDeliveryManager/Background/BackgroundTaskManagerProxy.swift index 9ead8025..2ef2e7c4 100644 --- a/Mindbox/GuaranteedDeliveryManager/Background/BackgroundTaskManagerProxy.swift +++ b/Mindbox/GuaranteedDeliveryManager/Background/BackgroundTaskManagerProxy.swift @@ -11,7 +11,7 @@ import UIKit import MindboxLogger class BackgroundTaskManagerProxy { - + weak var gdManager: GuaranteedDeliveryManager? { didSet { taskManagers.forEach { @@ -19,26 +19,26 @@ class BackgroundTaskManagerProxy { } } } - + private var taskManagers: [BackgroundTaskManagerType] = [] - + private let persistenceStorage: PersistenceStorage private let databaseRepository: MBDatabaseRepository - + init(persistenceStorage: PersistenceStorage, databaseRepository: MBDatabaseRepository) { self.persistenceStorage = persistenceStorage self.databaseRepository = databaseRepository NotificationCenter.default.addObserver( forName: UIApplication.didEnterBackgroundNotification, object: nil, - queue: nil) { [weak self] (_) in + queue: nil) { [weak self] _ in Logger.common(message: "UIApplication.didEnterBackgroundNotification", level: .info, category: .general) self?.taskManagers.forEach { $0.applicationDidEnterBackground() } } NotificationCenter.default.addObserver( forName: UIApplication.didBecomeActiveNotification, object: nil, - queue: nil) { [weak self] (_) in + queue: nil) { [weak self] _ in self?.taskManagers.forEach { $0.applicationDidBecomeActive() } } if #available(iOS 13, *) { @@ -59,11 +59,11 @@ class BackgroundTaskManagerProxy { ] } } - + func endBackgroundTask(success: Bool) { taskManagers.forEach { $0.endBackgroundTask(success: success) } } - + func registerBGTasks( appGDRefreshIdentifier: String, appGDProcessingIdentifier: String, @@ -77,11 +77,10 @@ class BackgroundTaskManagerProxy { ) } } - + func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { taskManagers.forEach { $0.application(application, performFetchWithCompletionHandler: completionHandler) } } - } diff --git a/Mindbox/GuaranteedDeliveryManager/Background/UIBackgroundTaskManager.swift b/Mindbox/GuaranteedDeliveryManager/Background/UIBackgroundTaskManager.swift index 8fade682..fbac895f 100644 --- a/Mindbox/GuaranteedDeliveryManager/Background/UIBackgroundTaskManager.swift +++ b/Mindbox/GuaranteedDeliveryManager/Background/UIBackgroundTaskManager.swift @@ -11,19 +11,19 @@ import UIKit import MindboxLogger class UIBackgroundTaskManager: BackgroundTaskManagerType { - + weak var gdManager: GuaranteedDeliveryManager? - + private let persistenceStorage: PersistenceStorage private let databaseRepository: MBDatabaseRepository - + init(persistenceStorage: PersistenceStorage, databaseRepository: MBDatabaseRepository) { self.persistenceStorage = persistenceStorage self.databaseRepository = databaseRepository } - + private var observationToken: NSKeyValueObservation? - + private var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid { didSet { if backgroundTaskID != .invalid { @@ -33,21 +33,19 @@ class UIBackgroundTaskManager: BackgroundTaskManagerType { } } } - + private var removingDeprecatedEventsInProgress = false - + func applicationDidEnterBackground() { - if #available(iOS 13.0, *) { - // Do nothing cause BGProcessingTask will be called - } else { + if #unavailable(iOS 13.0) { beginBackgroundTask() } } - + func applicationDidBecomeActive() { endBackgroundTask(success: false) } - + private func beginBackgroundTask() { guard backgroundTaskID == .invalid else { Logger.common(message: "BackgroundTask already in progress. Skip call of beginBackgroundTask", level: .info, category: .background) @@ -67,7 +65,7 @@ class UIBackgroundTaskManager: BackgroundTaskManagerType { removeDeprecatedEventsIfNeeded() } - + private func removeDeprecatedEventsIfNeeded() { let deprecatedEventsRemoveDate = persistenceStorage.deprecatedEventsRemoveDate ?? .distantPast guard !removingDeprecatedEventsInProgress, @@ -93,7 +91,7 @@ class UIBackgroundTaskManager: BackgroundTaskManagerType { queue.addOperation(operation) Logger.common(message: "removeDeprecatedEventsProcessing task started", level: .info, category: .background) } - + func endBackgroundTask(success: Bool) { guard backgroundTaskID != .invalid else { return } guard !removingDeprecatedEventsInProgress else { return } @@ -101,9 +99,9 @@ class UIBackgroundTaskManager: BackgroundTaskManagerType { UIApplication.shared.endBackgroundTask(self.backgroundTaskID) self.backgroundTaskID = .invalid } - + private typealias CompletionHandler = (UIBackgroundFetchResult) -> Void - + func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { Mindbox.shared.coreController?.checkNotificationStatus() guard let gdManager = gdManager else { @@ -127,7 +125,7 @@ class UIBackgroundTaskManager: BackgroundTaskManagerType { waitingForRetry(taskID: taskID, completionHandler: completionHandler) } } - + private func idle(taskID: String, completionHandler: @escaping CompletionHandler) { Logger.common(message: "completionHandler(.noData): idle", level: .info, category: .background) let backgroudExecution = BackgroudExecution( @@ -139,9 +137,9 @@ class UIBackgroundTaskManager: BackgroundTaskManagerType { persistenceStorage.setBackgroundExecution(backgroudExecution) completionHandler(.noData) } - + private func delivering(taskID: String, completionHandler: @escaping CompletionHandler) { - observationToken = gdManager?.observe(\.stateObserver, options: [.new]) { [weak self] (observed, change) in + observationToken = gdManager?.observe(\.stateObserver, options: [.new]) { [weak self] _, change in Logger.common(message: "change.newValue \(String(describing: change.newValue))", level: .info, category: .background) let idleString = NSString(string: GuaranteedDeliveryManager.State.idle.rawValue) if change.newValue == idleString { @@ -159,7 +157,7 @@ class UIBackgroundTaskManager: BackgroundTaskManagerType { } } } - + private func waitingForRetry(taskID: String, completionHandler: @escaping CompletionHandler) { Logger.common(message: "completionHandler(.newData): waitingForRetry", level: .info, category: .background) let backgroudExecution = BackgroudExecution( @@ -171,5 +169,4 @@ class UIBackgroundTaskManager: BackgroundTaskManagerType { persistenceStorage.setBackgroundExecution(backgroudExecution) completionHandler(.newData) } - } diff --git a/Mindbox/GuaranteedDeliveryManager/DeliveryOperation.swift b/Mindbox/GuaranteedDeliveryManager/DeliveryOperation.swift index 299c8763..c5fe0d7d 100644 --- a/Mindbox/GuaranteedDeliveryManager/DeliveryOperation.swift +++ b/Mindbox/GuaranteedDeliveryManager/DeliveryOperation.swift @@ -44,5 +44,4 @@ class DeliveryOperation: AsyncOperation, @unchecked Sendable { self.finish() } } - } diff --git a/Mindbox/GuaranteedDeliveryManager/GuaranteedDeliveryManager+State.swift b/Mindbox/GuaranteedDeliveryManager/GuaranteedDeliveryManager+State.swift index 29f5e9fd..2153706b 100644 --- a/Mindbox/GuaranteedDeliveryManager/GuaranteedDeliveryManager+State.swift +++ b/Mindbox/GuaranteedDeliveryManager/GuaranteedDeliveryManager+State.swift @@ -9,27 +9,25 @@ import Foundation extension GuaranteedDeliveryManager { - + enum State: String, CustomStringConvertible { - + case idle, delivering, waitingForRetry - + var isDelivering: Bool { self == .delivering } - + var isIdle: Bool { self == .idle } - + var isWaitingForRetry: Bool { self == .waitingForRetry } - + var description: String { rawValue } - } - } diff --git a/Mindbox/GuaranteedDeliveryManager/GuaranteedDeliveryManager.swift b/Mindbox/GuaranteedDeliveryManager/GuaranteedDeliveryManager.swift index dd6c05f7..8652f6a5 100644 --- a/Mindbox/GuaranteedDeliveryManager/GuaranteedDeliveryManager.swift +++ b/Mindbox/GuaranteedDeliveryManager/GuaranteedDeliveryManager.swift @@ -13,12 +13,12 @@ import BackgroundTasks import MindboxLogger final class GuaranteedDeliveryManager: NSObject { - + private let databaseRepository: MBDatabaseRepository private let eventRepository: EventRepository - + let backgroundTaskManager: BackgroundTaskManagerProxy - + private let queue: OperationQueue = { let queue = OperationQueue() queue.qualityOfService = .utility @@ -26,29 +26,29 @@ final class GuaranteedDeliveryManager: NSObject { queue.name = "Mindbox-GuaranteedDeliveryQueue" return queue }() - + private let semaphore = DispatchSemaphore(value: 1) - + var onCompletedEvent: ((_ event: Event, _ error: MindboxError?) -> Void)? - + @objc dynamic var stateObserver: NSString - + private(set) var state: State = .idle { didSet { stateObserver = NSString(string: state.rawValue) Logger.common(message: "State didSet to value: \(state.description)", level: .info, category: .delivery) } } - + var canScheduleOperations = false { didSet { Logger.common(message: "canScheduleOperation didSet to value: \(canScheduleOperations)", level: .info, category: .delivery) performScheduleIfNeeded() } } - + private let fetchLimit: Int - + init( persistenceStorage: PersistenceStorage, databaseRepository: MBDatabaseRepository, @@ -72,27 +72,27 @@ final class GuaranteedDeliveryManager: NSObject { NotificationCenter.default.addObserver( forName: UIApplication.didBecomeActiveNotification, object: nil, - queue: nil) { [weak self] (_) in + queue: nil) { [weak self] _ in self?.performScheduleIfNeeded() } } - + private let retryDeadline: TimeInterval - + func performScheduleIfNeeded() { guard canScheduleOperations else { return } - guard let count = try? databaseRepository.countEvents() else { + guard let events = try? databaseRepository.countEvents() else { return } - guard count != 0 else { + guard events != 0 else { backgroundTaskManager.endBackgroundTask(success: true) return } - scheduleOperations(fetchLimit: count <= fetchLimit ? count : fetchLimit) + scheduleOperations(fetchLimit: events <= fetchLimit ? events : fetchLimit) } - + private func scheduleOperations(fetchLimit: Int) { semaphore.wait() guard !state.isDelivering else { @@ -135,21 +135,20 @@ final class GuaranteedDeliveryManager: NSObject { let operations = delivery + [completion] queue.addOperations(operations, waitUntilFinished: false) } - + /// Cancels all queued and executing operations func cancelAllOperations() { queue.cancelAllOperations() } - } class AsyncOperation: Operation, @unchecked Sendable { private let lockQueue = DispatchQueue(label: "com.mindbox.asyncoperation", attributes: .concurrent) - + override var isAsynchronous: Bool { return true } - + private var _isExecuting: Bool = false override private(set) var isExecuting: Bool { get { @@ -173,7 +172,7 @@ class AsyncOperation: Operation, @unchecked Sendable { } } } - + private var _isFinished: Bool = false override private(set) var isFinished: Bool { get { @@ -197,19 +196,19 @@ class AsyncOperation: Operation, @unchecked Sendable { } } } - + override func start() { guard !isCancelled else { return finish() } - + isFinished = false isExecuting = true main() } - + override func main() { fatalError("Subclasses must implement `main` without overriding super.") } - + func finish() { isExecuting = false isFinished = true diff --git a/Mindbox/InAppMessages/Configuration/API/InAppConfigurationAPI.swift b/Mindbox/InAppMessages/Configuration/API/InAppConfigurationAPI.swift index fa58e556..478e18b3 100644 --- a/Mindbox/InAppMessages/Configuration/API/InAppConfigurationAPI.swift +++ b/Mindbox/InAppMessages/Configuration/API/InAppConfigurationAPI.swift @@ -58,7 +58,7 @@ class InAppConfigurationAPI { Logger.error(error.asLoggerError()) return .error(error) } - + Logger.response(data: data, response: response, error: error) if httpResponse.statusCode == 404 { @@ -66,7 +66,7 @@ class InAppConfigurationAPI { } if let data = data { return .data(data) - } else if let error = error { + } else if let error = error { return .error(error) } else { let error = MindboxError.invalidResponse(response) @@ -76,7 +76,6 @@ class InAppConfigurationAPI { } } - private struct FetchInAppConfigRoute: Route { let endpoint: String @@ -95,5 +94,3 @@ private struct FetchInAppConfigRoute: Route { var body: Data? { nil } } - - diff --git a/Mindbox/InAppMessages/Configuration/API/TargetingModel.swift b/Mindbox/InAppMessages/Configuration/API/TargetingModel.swift index 641c9732..ae236a23 100644 --- a/Mindbox/InAppMessages/Configuration/API/TargetingModel.swift +++ b/Mindbox/InAppMessages/Configuration/API/TargetingModel.swift @@ -30,7 +30,7 @@ enum InAppTargetingType: String, Decodable { case visit case pushEnabled case unknown - + init(from decoder: Decoder) throws { let container: SingleValueDecodingContainer = try decoder.singleValueContainer() let type: String = try container.decode(String.self) @@ -54,11 +54,11 @@ enum Targeting: Decodable, Hashable, Equatable { case visit(VisitTargeting) case pushEnabled(PushEnabledTargeting) case unknown - + enum CodingKeys: String, CodingKey { case type = "$type" } - + static func == (lhs: Targeting, rhs: Targeting) -> Bool { switch (lhs, rhs) { case (.true, .true): return true @@ -79,7 +79,7 @@ enum Targeting: Decodable, Hashable, Equatable { default: return false } } - + func hash(into hasher: inout Hasher) { switch self { case .true: hasher.combine("true") @@ -99,7 +99,7 @@ enum Targeting: Decodable, Hashable, Equatable { case .unknown: hasher.combine("unknown") } } - + init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container( keyedBy: CodingKeys.self) @@ -107,9 +107,9 @@ enum Targeting: Decodable, Hashable, Equatable { self = .unknown return } - + let targetingContainer: SingleValueDecodingContainer = try decoder.singleValueContainer() - + switch type { case .true: let trueTargeting = try targetingContainer.decode(TrueTargeting.self) diff --git a/Mindbox/InAppMessages/Configuration/API/Targetings/CategoryIDInTargeting.swift b/Mindbox/InAppMessages/Configuration/API/Targetings/CategoryIDInTargeting.swift index 3580171c..74be2321 100644 --- a/Mindbox/InAppMessages/Configuration/API/Targetings/CategoryIDInTargeting.swift +++ b/Mindbox/InAppMessages/Configuration/API/Targetings/CategoryIDInTargeting.swift @@ -8,19 +8,19 @@ import Foundation -struct CategoryIDInTargeting: ITargeting, Decodable { +struct CategoryIDInTargeting: ITargeting, Decodable { let kind: CategoryKind let values: [CategoryIDInValue] - + enum CategoryKind: String, Codable { case any case none } - + struct CategoryIDInValue: Codable { let id: String let name: String - + enum CodingKeys: String, CodingKey { case id = "externalId" case name = "externalSystemName" diff --git a/Mindbox/InAppMessages/Configuration/API/Targetings/CategoryIDTargeting.swift b/Mindbox/InAppMessages/Configuration/API/Targetings/CategoryIDTargeting.swift index 21fcc4d2..6a8ceb3b 100644 --- a/Mindbox/InAppMessages/Configuration/API/Targetings/CategoryIDTargeting.swift +++ b/Mindbox/InAppMessages/Configuration/API/Targetings/CategoryIDTargeting.swift @@ -8,17 +8,17 @@ import Foundation -struct CategoryIDTargeting: ITargeting, Decodable { +struct CategoryIDTargeting: ITargeting, Decodable { let kind: CategoryKind let value: String - + enum CategoryKind: String, Codable { case substring case notSubstring case startsWith case endsWith } - + var name: String { return value.uppercased() } diff --git a/Mindbox/InAppMessages/Configuration/API/Targetings/GeoTargeting.swift b/Mindbox/InAppMessages/Configuration/API/Targetings/GeoTargeting.swift index 7fcead28..e01b5d28 100644 --- a/Mindbox/InAppMessages/Configuration/API/Targetings/GeoTargeting.swift +++ b/Mindbox/InAppMessages/Configuration/API/Targetings/GeoTargeting.swift @@ -7,17 +7,17 @@ import Foundation -struct CityTargeting: ITargeting, Decodable { +struct CityTargeting: ITargeting, Decodable { let kind: TargetingNegationConditionKindType let ids: [Int] } -struct RegionTargeting: ITargeting, Decodable { +struct RegionTargeting: ITargeting, Decodable { let kind: TargetingNegationConditionKindType let ids: [Int] } -struct CountryTargeting: ITargeting, Decodable { +struct CountryTargeting: ITargeting, Decodable { let kind: TargetingNegationConditionKindType let ids: [Int] } diff --git a/Mindbox/InAppMessages/Configuration/API/Targetings/ProductIDTargeting.swift b/Mindbox/InAppMessages/Configuration/API/Targetings/ProductIDTargeting.swift index 9f098090..a15bde5a 100644 --- a/Mindbox/InAppMessages/Configuration/API/Targetings/ProductIDTargeting.swift +++ b/Mindbox/InAppMessages/Configuration/API/Targetings/ProductIDTargeting.swift @@ -8,17 +8,17 @@ import Foundation -struct ProductIDTargeting: ITargeting, Decodable { +struct ProductIDTargeting: ITargeting, Decodable { let kind: ProductKind let value: String - + enum ProductKind: String, Codable { case substring case notSubstring case startsWith case endsWith } - + var name: String { return value.uppercased() } diff --git a/Mindbox/InAppMessages/Configuration/API/Targetings/TrueTargeting.swift b/Mindbox/InAppMessages/Configuration/API/Targetings/TrueTargeting.swift index 12841c10..bd4f5e14 100644 --- a/Mindbox/InAppMessages/Configuration/API/Targetings/TrueTargeting.swift +++ b/Mindbox/InAppMessages/Configuration/API/Targetings/TrueTargeting.swift @@ -7,6 +7,4 @@ import Foundation -struct TrueTargeting: ITargeting, Decodable { - -} +struct TrueTargeting: ITargeting, Decodable {} diff --git a/Mindbox/InAppMessages/Configuration/InAppConfigurationManager.swift b/Mindbox/InAppMessages/Configuration/InAppConfigurationManager.swift index d014057b..4b1a68f2 100644 --- a/Mindbox/InAppMessages/Configuration/InAppConfigurationManager.swift +++ b/Mindbox/InAppMessages/Configuration/InAppConfigurationManager.swift @@ -24,7 +24,7 @@ protocol InAppConfigurationManagerProtocol: AnyObject { /// Prepares in-apps configation (loads from network, stores in cache, cache invalidation). /// Also builds domain models on the base of configuration: in-app requests, in-app message models. class InAppConfigurationManager: InAppConfigurationManagerProtocol { - + private let jsonDecoder = JSONDecoder() private let queue = DispatchQueue(label: "com.Mindbox.configurationManager") private var inapp: InAppFormData? @@ -59,17 +59,17 @@ class InAppConfigurationManager: InAppConfigurationManagerProtocol { defer { inapp = nil } - + return inapp } } - + func recalculateInapps(with event: ApplicationEvent) { queue.sync { guard let rawConfigurationResponse = rawConfigurationResponse else { return } - + setConfigPrepared(rawConfigurationResponse, event: event) } } @@ -113,13 +113,13 @@ class InAppConfigurationManager: InAppConfigurationManagerProtocol { Logger.common(message: "Failed to apply configuration from cache: No cached configuration found.") 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.") } - + setConfigPrepared(cachedConfig) } @@ -142,7 +142,7 @@ class InAppConfigurationManager: InAppConfigurationManagerProtocol { Logger.common(message: "[TTL] Config download date successfully updated to: \(now.asDateTimeWithSeconds).") inAppConfigRepository.saveConfigToCache(data) } - + private func setConfigPrepared(_ configResponse: ConfigResponse, event: ApplicationEvent? = nil) { rawConfigurationResponse = configResponse inAppConfigurationMapper.mapConfigResponse(event, configResponse, { inapp in @@ -154,12 +154,12 @@ class InAppConfigurationManager: InAppConfigurationManagerProtocol { } }) } - + private func setupSettingsFromConfig(_ settings: Settings?) { guard let settings = settings else { return } - + if let viewCategory = settings.operations?.viewCategory { SessionTemporaryStorage.shared.operationsFromSettings.insert(viewCategory.systemName.lowercased()) } @@ -168,7 +168,7 @@ class InAppConfigurationManager: InAppConfigurationManagerProtocol { SessionTemporaryStorage.shared.operationsFromSettings.insert(viewProduct.systemName.lowercased()) } } - + private func createTTLValidationService() -> TTLValidationProtocol { return TTLValidationService(persistenceStorage: self.persistenceStorage) } diff --git a/Mindbox/InAppMessages/Configuration/InAppConfigurationRepository.swift b/Mindbox/InAppMessages/Configuration/InAppConfigurationRepository.swift index b570390d..32900938 100644 --- a/Mindbox/InAppMessages/Configuration/InAppConfigurationRepository.swift +++ b/Mindbox/InAppMessages/Configuration/InAppConfigurationRepository.swift @@ -52,5 +52,4 @@ class InAppConfigurationRepository { .urls(for: .documentDirectory, in: .userDomainMask)[0] .appendingPathComponent("InAppMessagesConfiguration.json") } - } diff --git a/Mindbox/InAppMessages/Configuration/Services/TTLValidationService.swift b/Mindbox/InAppMessages/Configuration/Services/TTLValidationService.swift index 0232e1d0..950aaa8b 100644 --- a/Mindbox/InAppMessages/Configuration/Services/TTLValidationService.swift +++ b/Mindbox/InAppMessages/Configuration/Services/TTLValidationService.swift @@ -14,26 +14,26 @@ protocol TTLValidationProtocol { } class TTLValidationService: TTLValidationProtocol { - + let persistenceStorage: PersistenceStorage - + init(persistenceStorage: PersistenceStorage) { self.persistenceStorage = persistenceStorage } - + func needResetInapps(config: ConfigResponse) -> Bool { guard let configDownloadDate = persistenceStorage.configDownloadDate else { Logger.common(message: "[TTL] Config download date is nil. Unable to proceed with inapps reset validation.") return false } - + guard let ttl = config.settings?.ttl?.inapps, let ttlMilliseconds = try? ttl.parseTimeSpanToMillis(), ttlMilliseconds >= 0 else { Logger.common(message: "[TTL] Variables are missing or corrupted. Inapps reset will not be performed.") return false } - + let now = Date() let nowMilliseconds = Int64(now.timeIntervalSince1970 * 1000) let configDownloadMilliseconds = configDownloadDate.timeIntervalSince1970 * 1000 @@ -41,13 +41,13 @@ class TTLValidationService: TTLValidationProtocol { let isNeedResetInapps = nowMilliseconds > expiredTimeTtlMilliseconds let expiredTimeTtlDate = Date(timeIntervalSince1970: TimeInterval(expiredTimeTtlMilliseconds) / 1000.0) - + let message = """ [TTL] Current date: \(now.asDateTimeWithSeconds). Config with TTL valid until: \(expiredTimeTtlDate.asDateTimeWithSeconds). Need to reset inapps: \(isNeedResetInapps). """ - + Logger.common(message: message) return isNeedResetInapps } diff --git a/Mindbox/InAppMessages/Images/ImageFormat.swift b/Mindbox/InAppMessages/Images/ImageFormat.swift index 5510dc00..ea518482 100644 --- a/Mindbox/InAppMessages/Images/ImageFormat.swift +++ b/Mindbox/InAppMessages/Images/ImageFormat.swift @@ -24,9 +24,9 @@ enum ImageFormat: String { extension ImageFormat { private static func get(from data: Data?) -> ImageFormat? { - + guard let firstByte = data?.first else { return nil } - + switch firstByte { case 0x89: return .png @@ -38,34 +38,34 @@ extension ImageFormat { return nil } } - + private static func animatedImage(withGIFData data: Data) -> UIImage? { - + guard let source = CGImageSourceCreateWithData(data as CFData, nil) else { return nil } - + let frameCount = CGImageSourceGetCount(source) var frames: [UIImage] = [] var gifDuration = 0.0 - + for i in 0.. UIImage? { - + guard let imageData else { return nil } let imageFormat = ImageFormat.get(from: imageData) diff --git a/Mindbox/InAppMessages/Images/InAppImagesStorage.swift b/Mindbox/InAppMessages/Images/InAppImagesStorage.swift index e4b6983c..0653bd2a 100644 --- a/Mindbox/InAppMessages/Images/InAppImagesStorage.swift +++ b/Mindbox/InAppMessages/Images/InAppImagesStorage.swift @@ -15,33 +15,33 @@ protocol ImageDownloader { } class URLSessionImageDownloader: ImageDownloader { - + private let persistenceStorage: PersistenceStorage - + init(persistenceStorage: PersistenceStorage) { self.persistenceStorage = persistenceStorage } - + private var task: URLSessionDownloadTask? - + func downloadImage(withUrl imageUrl: String, completion: @escaping (URL?, HTTPURLResponse?, Error?) -> Void) { guard let url = URL(string: imageUrl) else { completion(nil, nil, NSError(domain: "Invalid URL", code: -1, userInfo: nil)) return } - + let configuration = URLSessionConfiguration.default configuration.timeoutIntervalForResource = persistenceStorage.imageLoadingMaxTimeInSeconds ?? 3 let session = URLSession(configuration: configuration) - let downloadTask = session.downloadTask(with: url) { (localURL, response, error) in + let downloadTask = session.downloadTask(with: url) { localURL, response, error in completion(localURL, response as? HTTPURLResponse, error) } - + task = downloadTask downloadTask.resume() } - + func cancel() { task?.cancel() } diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/InAppConfigutationMapper.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/InAppConfigutationMapper.swift index 6f1d14f8..e5581081 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/InAppConfigutationMapper.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/InAppConfigutationMapper.swift @@ -10,19 +10,19 @@ import MindboxLogger import UIKit protocol InAppConfigurationMapperProtocol { - func mapConfigResponse(_ event: ApplicationEvent?, _ response: ConfigResponse,_ completion: @escaping (InAppFormData?) -> Void) -> Void + func mapConfigResponse(_ event: ApplicationEvent?, _ response: ConfigResponse, _ completion: @escaping (InAppFormData?) -> Void) var targetingChecker: InAppTargetingCheckerProtocol { get set } func sendRemainingInappsTargeting() } final class InAppConfigutationMapper: InAppConfigurationMapperProtocol { - + var targetingChecker: InAppTargetingCheckerProtocol var filteredInAppsByEvent: [InAppMessageTriggerEvent: [InAppTransitionData]] = [:] var filteredInappsByEventForTargeting: [InAppMessageTriggerEvent: [InAppTransitionData]] = [:] - + let dataFacade: InAppConfigurationDataFacadeProtocol - + private let inappFilterService: InappFilterProtocol private var validInapps: [InApp] = [] private var savedEventForTargeting: ApplicationEvent? @@ -42,17 +42,17 @@ final class InAppConfigutationMapper: InAppConfigurationMapperProtocol { _ completion: @escaping (InAppFormData?) -> Void) { savedEventForTargeting = event self.targetingChecker.event = nil - + let filteredInapps = inappFilterService.filter(inapps: response.inapps?.elements, abTests: response.abtests) validInapps = inappFilterService.validInapps - + targetingChecker.event = event prepareTargetingChecker(for: filteredInapps) - + prepareForRemainingTargeting() - + dataFacade.setObservedOperation() - + if filteredInapps.isEmpty { Logger.common(message: "No inapps to show", level: .debug, category: .inAppMessages) completion(nil) @@ -79,17 +79,17 @@ final class InAppConfigutationMapper: InAppConfigurationMapperProtocol { } } } - + func prepareForRemainingTargeting() { let estimatedInapps = validInapps prepareTargetingChecker(for: estimatedInapps) } - + func sendRemainingInappsTargeting() { self.dataFacade.fetchDependencies(model: savedEventForTargeting?.model) { self.filterByInappsEvents(inapps: self.validInapps, filteredInAppsByEvent: &self.filteredInappsByEventForTargeting) - + let logMessage = """ TR | Initiating processing of remaining in-app targeting requests. Full list of in-app messages: \(self.validInapps.map { $0.id }) @@ -97,14 +97,18 @@ final class InAppConfigutationMapper: InAppConfigurationMapperProtocol { """ Logger.common(message: logMessage, level: .debug, category: .inAppMessages) - let targetedEventKey: InAppMessageTriggerEvent = self.savedEventForTargeting != nil - ? .applicationEvent(self.savedEventForTargeting!) - : .start - + var targetedEventKey: InAppMessageTriggerEvent + + if let savedEventForTargeting = self.savedEventForTargeting { + targetedEventKey = .applicationEvent(savedEventForTargeting) + } else { + targetedEventKey = .start + } + guard let inappsByEvent = self.filteredInappsByEventForTargeting[targetedEventKey] else { return } - + let preparedForTrackTargetingInapps: Set = Set(self.validInapps.compactMap { inapp -> String? in guard inapp.id != self.shownInnapId, inappsByEvent.contains(where: { $0.inAppId == inapp.id }), @@ -119,7 +123,7 @@ final class InAppConfigutationMapper: InAppConfigurationMapperProtocol { preparedForTrackTargetingInapps.forEach { id in self.dataFacade.trackTargeting(id: id) } - + self.shownInnapId = "" } } @@ -129,26 +133,26 @@ final class InAppConfigutationMapper: InAppConfigurationMapperProtocol { targetingChecker.prepare(targeting: $0.targeting) }) } - + func filterByInappsEvents(inapps: [InApp], filteredInAppsByEvent: inout [InAppMessageTriggerEvent: [InAppTransitionData]]) { for inapp in inapps { var triggerEvent: InAppMessageTriggerEvent = .start - + let inAppAlreadyAddedForEvent = filteredInAppsByEvent[triggerEvent]?.contains(where: { $0.inAppId == inapp.id }) ?? false - + // If the in-app message has already been added, continue to the next message guard !inAppAlreadyAddedForEvent else { continue } - + guard targetingChecker.check(targeting: inapp.targeting) else { continue } - + if let event = targetingChecker.event { triggerEvent = .applicationEvent(event) } - + var inAppsForEvent = filteredInAppsByEvent[triggerEvent] ?? [InAppTransitionData]() if let inAppFormVariants = inapp.form.variants.first { let formData = InAppTransitionData(inAppId: inapp.id, @@ -158,30 +162,32 @@ final class InAppConfigutationMapper: InAppConfigurationMapperProtocol { } } } - + private func buildInAppByEvent(inapps: [InAppTransitionData], completion: @escaping (InAppFormData?) -> Void) { var formData: InAppFormData? let group = DispatchGroup() let imageDictQueue = DispatchQueue(label: "com.mindbox.imagedict.queue", attributes: .concurrent) + // FIXME: Rewrite this closure in the future + // swiftlint:disable:next closure_body_length DispatchQueue.global().async { for inapp in inapps { - + guard formData == nil else { break } - + var imageDict: [String: UIImage] = [:] var gotError = false - - if let _ = self.inappFilterService.shownInAppDictionary[inapp.inAppId] { + + if self.inappFilterService.shownInAppDictionary[inapp.inAppId] != nil { continue } - + 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 { group.enter() @@ -190,7 +196,7 @@ final class InAppConfigutationMapper: InAppConfigurationMapperProtocol { defer { group.leave() } - + switch result { case .success(let image): imageDictQueue.async(flags: .barrier) { @@ -201,9 +207,9 @@ final class InAppConfigutationMapper: InAppConfigurationMapperProtocol { } } } - + group.wait() - + imageDictQueue.sync { if !imageDict.isEmpty && !gotError { let firstImageValue = imageValues.first ?? "" @@ -211,14 +217,14 @@ final class InAppConfigutationMapper: InAppConfigurationMapperProtocol { } } } - + group.notify(queue: .main) { DispatchQueue.main.async { [weak self] in if !SessionTemporaryStorage.shared.isPresentingInAppMessage { self?.shownInnapId = formData?.inAppId ?? "" self?.dataFacade.trackTargeting(id: formData?.inAppId) } - + completion(formData) } } diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InAppConfigurationDataFacade.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InAppConfigurationDataFacade.swift index 495ee5c4..0e27c8c7 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InAppConfigurationDataFacade.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InAppConfigurationDataFacade.swift @@ -18,13 +18,13 @@ protocol InAppConfigurationDataFacadeProtocol { } class InAppConfigurationDataFacade: InAppConfigurationDataFacadeProtocol { - + var geoService: GeoServiceProtocol? let segmentationService: SegmentationServiceProtocol var targetingChecker: InAppTargetingCheckerProtocol let imageService: ImageDownloadServiceProtocol let tracker: InappTargetingTrackProtocol - + init(segmentationService: SegmentationServiceProtocol, targetingChecker: InAppTargetingCheckerProtocol, imageService: ImageDownloadServiceProtocol, @@ -34,30 +34,30 @@ class InAppConfigurationDataFacade: InAppConfigurationDataFacadeProtocol { self.imageService = imageService self.tracker = tracker } - + private let dispatchGroup = DispatchGroup() private var fetchedProductIdsCache: [String: String] = [:] - + func fetchDependencies(model: InappOperationJSONModel?, _ completion: @escaping () -> Void) { fetchSegmentationIfNeeded() fetchGeoIfNeeded() fetchProductSegmentationIfNeeded(products: model?.viewProduct?.product) - + dispatchGroup.notify(queue: .main) { completion() } } - + func setObservedOperation() { SessionTemporaryStorage.shared.observedCustomOperations = Set(targetingChecker.context.operationsName) } - + func downloadImage(withUrl url: String, completion: @escaping (Result) -> Void) { imageService.downloadImage(withUrl: url) { result in completion(result) } } - + func trackTargeting(id: String?) { if let id = id { do { @@ -93,21 +93,21 @@ private extension InAppConfigurationDataFacade { } } } - + private func fetchProductSegmentationIfNeeded(products: ProductCategory?) { guard let products = products else { return } - + let productIds = products.ids let allMatch = productIds.allSatisfy { key, value in fetchedProductIdsCache[key] == value } - + if allMatch { return } - + dispatchGroup.enter() segmentationService.checkProductSegmentationRequest(products: products) { response in self.fetchedProductIdsCache = productIds diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ContentPositionFilter.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ContentPositionFilter.swift index 134729b3..cdd2e18f 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ContentPositionFilter.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ContentPositionFilter.swift @@ -14,19 +14,19 @@ protocol ContentPositionFilterProtocol { } final class ContentPositionFilterService: ContentPositionFilterProtocol { - + enum Constants { static let defaultGravity = ContentPositionGravity(vertical: .bottom, horizontal: .center) static let defaultMargin = ContentPositionMargin(kind: .dp, top: 0, right: 0, left: 0, bottom: 0) static let defaultContentPosition = ContentPosition(gravity: defaultGravity, margin: defaultMargin) } - + func filter(_ contentPosition: ContentPositionDTO?) throws -> ContentPosition { guard let contentPosition = contentPosition else { Logger.common(message: "Content position is invalid or missing. Default value set: [\(Constants.defaultContentPosition)].", level: .debug, category: .inAppMessages) return Constants.defaultContentPosition } - + var customGravity: ContentPositionGravity if let gravity = contentPosition.gravity { let vertical = gravity.vertical ?? .bottom @@ -36,7 +36,7 @@ final class ContentPositionFilterService: ContentPositionFilterProtocol { Logger.common(message: "Gravity is invalid or missing. Default value set: [\(Constants.defaultGravity)].", level: .debug, category: .inAppMessages) customGravity = Constants.defaultGravity } - + var customMargin: ContentPositionMargin? if let margin = contentPosition.margin { switch margin.kind { @@ -59,11 +59,11 @@ final class ContentPositionFilterService: ContentPositionFilterProtocol { Logger.common(message: "Content position margin is invalid or missing. Default value set: [\(Constants.defaultMargin)].", level: .debug, category: .inAppMessages) customMargin = Constants.defaultMargin } - + guard let customMargin = customMargin else { throw CustomDecodingError.unknownType("ContentPositionFilterService validation not passed. Inapp will be skipped.") } - + return ContentPosition(gravity: customGravity, margin: customMargin) } } diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsColorFilter.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsColorFilter.swift index 8de5a184..6d44e8fc 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsColorFilter.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsColorFilter.swift @@ -17,13 +17,13 @@ final class ElementsColorFilterService: ElementsColorFilterProtocol { enum Constants { static let defaultColor = "#FFFFFF" } - + func filter(_ color: String?) throws -> String { guard let color = color, color.isHexValid() else { Logger.common(message: "Color is invalid or missing. Default value set: [\(Constants.defaultColor)]", level: .debug, category: .inAppMessages) return Constants.defaultColor } - + return color } } diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsFilter.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsFilter.swift index 3091772e..77b01ed2 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsFilter.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsFilter.swift @@ -14,34 +14,34 @@ protocol ElementsFilterProtocol { } final class ElementsFilterService: ElementsFilterProtocol { - + enum Constants { static let lineWidth = 2 } - + private let sizeFilter: ElementsSizeFilterProtocol private let positionFilter: ElementsPositionFilterProtocol private let colorFilter: ElementsColorFilterProtocol - + init(sizeFilter: ElementsSizeFilterProtocol, positionFilter: ElementsPositionFilterProtocol, colorFilter: ElementsColorFilterProtocol) { self.sizeFilter = sizeFilter self.positionFilter = positionFilter self.colorFilter = colorFilter } - + func filter(_ elements: [ContentElementDTO]?) throws -> [ContentElement] { guard let elements = elements, !elements.isEmpty else { Logger.common(message: "Elements are missing or empty.", level: .debug, category: .inAppMessages) return [] } - + var filteredElements: [ContentElement] = [] - + elementsLoop: for element in elements { if element.elementType == .unknown { continue } - + switch element { case .closeButton(let closeButtonElementDTO): let size = try sizeFilter.filter(closeButtonElementDTO.size) diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsPositionFilter.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsPositionFilter.swift index 672d9a7c..409a3be2 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsPositionFilter.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsPositionFilter.swift @@ -14,21 +14,20 @@ protocol ElementsPositionFilterProtocol { } final class ElementsPositionFilterService: ElementsPositionFilterProtocol { - + enum Constants { static let defaultMargin = ContentElementPositionMargin(kind: .proportion, top: 0.02, right: 0.02, left: 0.02, bottom: 0.02) - } - + func filter(_ position: ContentElementPositionDTO?) throws -> ContentElementPosition { guard let position = position, let margin = position.margin else { Logger.common(message: "Position or margin is invalid or missing. Default value set: [\(Constants.defaultMargin)].", level: .debug, category: .inAppMessages) return ContentElementPosition(margin: Constants.defaultMargin) } - + let marginRange: ClosedRange = 0...1 - + switch margin.kind { case .proportion: if let top = margin.top, @@ -50,7 +49,7 @@ final class ElementsPositionFilterService: ElementsPositionFilterProtocol { Logger.common(message: "Unknown type of ContentElementPosition. Default value set: [\(Constants.defaultMargin)].", level: .debug, category: .inAppMessages) return ContentElementPosition(margin: Constants.defaultMargin) } - + throw CustomDecodingError.unknownType("ElementsPositionFilterService validation not passed. In-app will be ignored.") } } diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsSizeFilter.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsSizeFilter.swift index 3dd87127..b25842ab 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsSizeFilter.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/ElementsFiter/ElementsSizeFilter.swift @@ -17,13 +17,13 @@ final class ElementSizeFilterService: ElementsSizeFilterProtocol { enum Constants { static let defaultSize = ContentElementSize(kind: .dp, width: 24, height: 24) } - + func filter(_ size: ContentElementSizeDTO?) throws -> ContentElementSize { guard let size = size else { Logger.common(message: "Size is invalid or missing. Default value set: [\(Constants.defaultSize)].", level: .debug, category: .inAppMessages) return Constants.defaultSize } - + switch size.kind { case .dp: if let height = size.height, @@ -36,7 +36,7 @@ final class ElementSizeFilterService: ElementsSizeFilterProtocol { Logger.common(message: "Unknown type of ContentElementSize. Default value set: [\(Constants.defaultSize)].", level: .debug, category: .inAppMessages) return Constants.defaultSize } - + throw CustomDecodingError.unknownType("ElementSizeFilterService validation not passed. In-app will be ignored.") } } diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/InappFilter.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/InappFilter.swift index 56509115..1f8a5201 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/InappFilter.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/InappFilter.swift @@ -16,10 +16,10 @@ protocol InappFilterProtocol { } final class InappsFilterService: InappFilterProtocol { - + var validInapps: [InApp] = [] var shownInAppDictionary: [String: Date] = [:] - + private let persistenceStorage: PersistenceStorage private let variantsFilter: VariantFilterProtocol private let sdkVersionValidator: SDKVersionValidator @@ -29,19 +29,19 @@ final class InappsFilterService: InappFilterProtocol { self.variantsFilter = variantsFilter self.sdkVersionValidator = sdkVersionValidator } - + func filter(inapps: [InAppDTO]?, abTests: [ABTest]?) -> [InApp] { guard var inapps = inapps else { Logger.common(message: "Received nil for in-apps. Returning an empty array.", level: .debug, category: .inAppMessages) return [] } - + inapps = filterInappsBySDKVersion(inapps) Logger.common(message: "Processing \(inapps.count) in-app(s).", level: .debug, category: .inAppMessages) let validInapps = filterValidInAppMessages(inapps) let filteredByABTestInapps = filterInappsByABTests(abTests, responseInapps: validInapps) let filteredByAlreadyShown = filterInappsByAlreadyShown(filteredByABTestInapps) - + return filteredByAlreadyShown } } @@ -56,7 +56,7 @@ private extension InappsFilterService { return filteredInapps } - + func filterValidInAppMessages(_ inapps: [InAppDTO]) -> [InApp] { var filteredInapps: [InApp] = [] for inapp in inapps { @@ -66,7 +66,7 @@ private extension InappsFilterService { let formModel = InAppForm(variants: variants) let inappModel = InApp(id: inapp.id, sdkVersion: inapp.sdkVersion, - targeting: inapp.targeting, + targeting: inapp.targeting, frequency: inapp.frequency, form: formModel) filteredInapps.append(inappModel) @@ -75,39 +75,41 @@ private extension InappsFilterService { Logger.common(message: "In-app [ID:] \(inapp.id)\n[Error]: \(error)", level: .error, category: .inAppMessages) } } - + Logger.common(message: "Filtering process completed. \(filteredInapps.count) valid in-app(s) found.", level: .debug, category: .inAppMessages) validInapps = filteredInapps return filteredInapps } - + + // FIXME: Rewrite this func in the future + // swiftlint:disable:next cyclomatic_complexity func filterInappsByABTests(_ abTests: [ABTest]?, responseInapps: [InApp]?) -> [InApp] { let responseInapps = responseInapps ?? [] guard let abTests = abTests, !abTests.isEmpty else { return responseInapps } - + var result: [InApp] = responseInapps let abTestDeviceMixer = DI.injectOrFail(ABTestDeviceMixer.self) - + for abTest in abTests { guard let uuid = UUID(uuidString: persistenceStorage.deviceUUID ?? "" ), let salt = abTest.salt, let variants = abTest.variants else { continue } - + let hashValue = try? abTestDeviceMixer.modulusGuidHash(identifier: uuid, salt: salt) - + guard let hashValue = hashValue else { continue } - + Logger.common(message: "[Hash Value]: \(hashValue) for [UUID]: \(persistenceStorage.deviceUUID ?? "nil")") Logger.common(message: "[AB-test ID]: \(abTest.id)") - + var allInappsInVariantsExceptCurrentBranch: [String] = [] - + for variant in variants { if let objects = variant.objects { for object in objects { @@ -121,9 +123,9 @@ private extension InappsFilterService { } } } - + var setInapps = Set(allInappsInVariantsExceptCurrentBranch) - + for variant in variants { if let modulus = variant.modulus, let objects = variant.objects, let upper = modulus.upper { let range = modulus.lower.. [InApp] { let shownInAppDictionary = persistenceStorage.shownInappsDictionary ?? [:] Logger.common(message: "Shown in-apps ids: [\(shownInAppDictionary.keys)]", level: .info, category: .inAppMessages) @@ -163,7 +165,7 @@ private extension InappsFilterService { return filteredInapps } - + private func createFrequencyValidator() -> InappFrequencyValidator { InappFrequencyValidator(persistenceStorage: persistenceStorage) } diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/LayersFilter/LayerActionFilter.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/LayersFilter/LayerActionFilter.swift index be6478c6..155ecf4f 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/LayersFilter/LayerActionFilter.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/LayersFilter/LayerActionFilter.swift @@ -18,7 +18,7 @@ final class LayerActionFilterService: LayerActionFilterProtocol { action.actionType != .unknown else { throw CustomDecodingError.unknownType("LayerActionFilterService validation not passed.") } - + switch action { case .pushPermission(let pushPermissionAction): if let payload = pushPermissionAction.intentPayload { @@ -33,7 +33,7 @@ final class LayerActionFilterService: LayerActionFilterProtocol { case .unknown: break } - + throw CustomDecodingError.unknownType("LayerActionFilterService validation not passed.") } } diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/LayersFilter/LayersFilter.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/LayersFilter/LayersFilter.swift index a4eb6df3..7329c8ab 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/LayersFilter/LayersFilter.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/LayersFilter/LayersFilter.swift @@ -16,19 +16,19 @@ protocol LayersFilterProtocol { final class LayersFilterService: LayersFilterProtocol { private let actionFilter: LayerActionFilterProtocol private let sourceFilter: LayersSourceFilterProtocol - + init(actionFilter: LayerActionFilterProtocol, sourceFilter: LayersSourceFilterProtocol) { self.actionFilter = actionFilter self.sourceFilter = sourceFilter } - + func filter(_ layers: [ContentBackgroundLayerDTO]?) throws -> [ContentBackgroundLayer] { var filteredLayers: [ContentBackgroundLayer] = [] - + guard let layers = layers else { throw CustomDecodingError.unknownType("LayersFilterService validation not passed.") } - + for layer in layers { switch layer { case .image(let imageContentBackgroundLayerDTO): @@ -39,14 +39,13 @@ final class LayersFilterService: LayersFilterProtocol { filteredLayers.append(newLayer) case .unknown: Logger.common(message: "Unknown type of layer. Layer will be skipped.", level: .debug, category: .inAppMessages) - break } } - + if filteredLayers.isEmpty { throw CustomDecodingError.unknownType("Layers cannot be empty. In-app will be skipped.") } - + return filteredLayers.filter { $0.layerType != .unknown } } } diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/LayersFilter/LayersSourceFilter.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/LayersFilter/LayersSourceFilter.swift index 65a0e5d7..528b02b0 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/LayersFilter/LayersSourceFilter.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/LayersFilter/LayersSourceFilter.swift @@ -18,7 +18,7 @@ final class LayersSourceFilterService: LayersSourceFilterProtocol { source.sourceType != .unknown else { throw CustomDecodingError.unknownType("LayersSourceFilterService validation not passed.") } - + switch source { case .url(let urlLayerSource): if let value = urlLayerSource.value, !value.isEmpty { @@ -28,7 +28,7 @@ final class LayersSourceFilterService: LayersSourceFilterProtocol { case .unknown: break } - + throw CustomDecodingError.unknownType("LayersSourceFilterService validation not passed.") } } diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/VariantsFilter.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/VariantsFilter.swift index d52c9476..c46ccfd7 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/VariantsFilter.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/InappFilterService/VariantsFilter.swift @@ -14,34 +14,34 @@ protocol VariantFilterProtocol { } final class VariantFilterService: VariantFilterProtocol { - + private let layersFilter: LayersFilterProtocol private let elementsFilter: ElementsFilterProtocol private let contentPositionFilter: ContentPositionFilterProtocol - + init(layersFilter: LayersFilterProtocol, elementsFilter: ElementsFilterProtocol, contentPositionFilter: ContentPositionFilterProtocol) { self.layersFilter = layersFilter self.elementsFilter = elementsFilter self.contentPositionFilter = contentPositionFilter } - + func filter(_ variants: [MindboxFormVariantDTO]?) throws -> [MindboxFormVariant] { var resultVariants: [MindboxFormVariant] = [] guard let variants = variants else { throw CustomDecodingError.unknownType("VariantFilterService validation not passed.") } - - variantsLoop: for variant in variants { + + for variant in variants { switch variant { case .modal(let modalFormVariantDTO): guard let content = modalFormVariantDTO.content, let background = content.background else { throw CustomDecodingError.unknownType("VariantFilterService validation not passed.") } - + let filteredLayers = try layersFilter.filter(background.layers) let fileterdElements = try elementsFilter.filter(content.elements) - + let backgroundModel = ContentBackground(layers: filteredLayers) let contentModel = InappFormVariantContent(background: backgroundModel, elements: fileterdElements) @@ -53,11 +53,11 @@ final class VariantFilterService: VariantFilterProtocol { let background = content.background else { throw CustomDecodingError.unknownType("VariantFilterService validation not passed.") } - + let filteredLayers = try layersFilter.filter(background.layers) let filteredElements = try elementsFilter.filter(content.elements) let contentPosition = try contentPositionFilter.filter(content.position) - + let backgroundModel = ContentBackground(layers: filteredLayers) let contentModel = SnackbarFormVariantContent(background: backgroundModel, position: contentPosition, @@ -70,7 +70,7 @@ final class VariantFilterService: VariantFilterProtocol { continue } } - + return resultVariants } } diff --git a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/VariantImageUrlExtractorService.swift b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/VariantImageUrlExtractorService.swift index 7876a33c..6355deaf 100644 --- a/Mindbox/InAppMessages/InAppConfigurationMapper/Services/VariantImageUrlExtractorService.swift +++ b/Mindbox/InAppMessages/InAppConfigurationMapper/Services/VariantImageUrlExtractorService.swift @@ -15,7 +15,7 @@ protocol VariantImageUrlExtractorServiceProtocol { class VariantImageUrlExtractorService: VariantImageUrlExtractorServiceProtocol { func extractImageURL(from variant: MindboxFormVariant) -> [String] { var urlString: [String] = [] - + let elements: [ContentBackgroundLayer] switch variant { @@ -31,7 +31,7 @@ class VariantImageUrlExtractorService: VariantImageUrlExtractorServiceProtocol { return urlString } - + private func extractImageURLs(from elements: [ContentBackgroundLayer], into urlString: inout [String]) { for element in elements { switch element { diff --git a/Mindbox/InAppMessages/InAppCoreManager.swift b/Mindbox/InAppMessages/InAppCoreManager.swift index 668fd9d6..402f0613 100644 --- a/Mindbox/InAppMessages/InAppCoreManager.swift +++ b/Mindbox/InAppMessages/InAppCoreManager.swift @@ -21,7 +21,7 @@ enum InAppMessageTriggerEvent: Hashable { return false } } - + /// Application start event. Fires after SDK configurated case start // All inapps by now is Start /// Any other event sent to SDK @@ -31,7 +31,7 @@ enum InAppMessageTriggerEvent: Hashable { struct ApplicationEvent: Hashable, Equatable { let name: String let model: InappOperationJSONModel? - + init(name: String, model: InappOperationJSONModel?) { self.name = name.lowercased() self.model = model @@ -77,7 +77,7 @@ final class InAppCoreManager: InAppCoreManagerProtocol { Logger.common(message: "Skip launching InAppManager because it is already launched", level: .info, category: .visit) return } - + isInAppManagerLaunched = true sendEvent(.start) configManager.delegate = self @@ -90,13 +90,13 @@ final class InAppCoreManager: InAppCoreManagerProtocol { isConfigurationReady = false configManager.recalculateInapps(with: event) } - + serialQueue.async { guard self.isConfigurationReady else { self.unhandledEvents.append(event) return } - + Logger.common(message: "Received event: \(event)", level: .debug, category: .inAppMessages) self.handleEvent(event) } @@ -109,7 +109,7 @@ final class InAppCoreManager: InAppCoreManagerProtocol { guard !SessionTemporaryStorage.shared.isPresentingInAppMessage else { return } - + onReceivedInAppResponse() } @@ -150,7 +150,7 @@ final class InAppCoreManager: InAppCoreManagerProtocol { private func handleQueuedEvents() { Logger.common(message: "Start handling waiting events. Count: \(unhandledEvents.count)", level: .debug, category: .inAppMessages) - while unhandledEvents.count > 0 { + while !unhandledEvents.isEmpty { let event = unhandledEvents.removeFirst() handleEvent(event) } diff --git a/Mindbox/InAppMessages/InAppMessagesTracker.swift b/Mindbox/InAppMessages/InAppMessagesTracker.swift index 812896e0..767f7332 100644 --- a/Mindbox/InAppMessages/InAppMessagesTracker.swift +++ b/Mindbox/InAppMessages/InAppMessagesTracker.swift @@ -40,7 +40,7 @@ class InAppMessagesTracker: InAppMessagesTrackerProtocol, InappTargetingTrackPro let event = Event(type: .inAppClickEvent, body: BodyEncoder(encodable: encodable).body) try databaseRepository.create(event: event) } - + func trackTargeting(id: String) throws { let encodable = InAppBody(inappId: id) let event = Event(type: .inAppTargetingEvent, body: BodyEncoder(encodable: encodable).body) diff --git a/Mindbox/InAppMessages/InAppTargetingChecker/CheckerFactory/CheckerFactory.swift b/Mindbox/InAppMessages/InAppTargetingChecker/CheckerFactory/CheckerFactory.swift index 9e9da6f3..49be0ad5 100644 --- a/Mindbox/InAppMessages/InAppTargetingChecker/CheckerFactory/CheckerFactory.swift +++ b/Mindbox/InAppMessages/InAppTargetingChecker/CheckerFactory/CheckerFactory.swift @@ -245,7 +245,7 @@ final class VisitTargetingFactory: CheckerFactory { guard case let .visit(targeting) = targetType else { return checkerFunctions } - + let visitChecker = VisitTargetingChecker() visitChecker.checker = checker return CheckerFunctions( diff --git a/Mindbox/InAppMessages/InAppTargetingChecker/InternalTargetingChecker.swift b/Mindbox/InAppMessages/InAppTargetingChecker/InternalTargetingChecker.swift index 8429085f..807566ed 100644 --- a/Mindbox/InAppMessages/InAppTargetingChecker/InternalTargetingChecker.swift +++ b/Mindbox/InAppMessages/InAppTargetingChecker/InternalTargetingChecker.swift @@ -16,23 +16,30 @@ struct PreparationContext { } protocol ITargetingChecker: AnyObject { - func prepare(targeting: ITargeting, context: inout PreparationContext) -> Void + func prepare(targeting: ITargeting, context: inout PreparationContext) func check(targeting: ITargeting) -> Bool } class InternalTargetingChecker: ITargetingChecker { func prepare(targeting: ITargeting, context: inout PreparationContext) { - prepareInternal(targeting: targeting as! T, context: &context) + guard let specificTargeting = targeting as? T else { + fatalError("Failed to cast targeting to type \(T.self)") + } + prepareInternal(targeting: specificTargeting, context: &context) } - - func prepareInternal(targeting: T, context: inout PreparationContext) -> Void { + + func prepareInternal(targeting: T, context: inout PreparationContext) { return } - + func check(targeting: ITargeting) -> Bool { - return checkInternal(targeting: targeting as! T) + guard let specificTargeting = targeting as? T else { + fatalError("Failed to cast targeting to type \(T.self)") + } + + return checkInternal(targeting: specificTargeting) } - + func checkInternal(targeting: T) -> Bool { assertionFailure("This method must be overridden") return false diff --git a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingChecker.swift b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingChecker.swift index 4ac366f3..337f1229 100644 --- a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingChecker.swift +++ b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingChecker.swift @@ -44,36 +44,36 @@ class CheckerFunctions { protocol InAppTargetingCheckerProtocol: TargetingCheckerContextProtocol, TargetingCheckerActionProtocol, TargetingCheckerMap, TargetingCheckerPersistenceStorageProtocol { } final class InAppTargetingChecker: InAppTargetingCheckerProtocol { - + init(persistenceStorage: PersistenceStorage) { self.persistenceStorage = persistenceStorage setupCheckerMap() } - + var context = PreparationContext() - var checkedSegmentations: [SegmentationCheckResponse.CustomerSegmentation]? = nil - var checkedProductSegmentations: [InAppProductSegmentResponse.CustomerSegmentation]? = nil + var checkedSegmentations: [SegmentationCheckResponse.CustomerSegmentation]? + var checkedProductSegmentations: [InAppProductSegmentResponse.CustomerSegmentation]? var geoModels: InAppGeoResponse? var event: ApplicationEvent? var persistenceStorage: PersistenceStorage - + var checkerMap: [Targeting: (Targeting) -> CheckerFunctions] = [:] - + func prepare(targeting: Targeting) { guard let target = checkerMap[targeting] else { Logger.common(message: "target not exist in checkerMap. Targeting: \(targeting)", level: .error, category: .inAppMessages) return } - + target(targeting).prepare(&context) } - + func check(targeting: Targeting) -> Bool { guard let target = checkerMap[targeting] else { Logger.common(message: "target not exist in checkerMap. Targeting: \(targeting)", level: .error, category: .inAppMessages) return false } - + return target(targeting).check() } @@ -136,11 +136,11 @@ final class InAppTargetingChecker: InAppTargetingCheckerProtocol { segmentExternalId: "") let productSegmentTargetingFactory = ProductSegmentTargetingFactory(checker: self) checkerMap[.viewProductSegment(productSegmentTargeting)] = productSegmentTargetingFactory.makeChecker(for:) - + let visitTargeting = VisitTargeting(kind: .equals, value: 1) let visitTargetingFactory = VisitTargetingFactory(checker: self) checkerMap[.visit(visitTargeting)] = visitTargetingFactory.makeChecker(for:) - + let pushEnabledTargeting = PushEnabledTargeting(value: false) let pushEnabledFactory = PushEnabledTargetingFactory() checkerMap[.pushEnabled(pushEnabledTargeting)] = pushEnabledFactory.makeChecker(for:) diff --git a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/AndTargetingChecker.swift b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/AndTargetingChecker.swift index c79fdea6..e142ffc3 100644 --- a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/AndTargetingChecker.swift +++ b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/AndTargetingChecker.swift @@ -9,8 +9,8 @@ import Foundation final class AndTargetingChecker: InternalTargetingChecker { weak var checker: InAppTargetingCheckerProtocol? - - override func prepareInternal(targeting: AndTargeting, context: inout PreparationContext) -> Void { + + override func prepareInternal(targeting: AndTargeting, context: inout PreparationContext) { for node in targeting.nodes { guard let checker = checker, let target = checker.checkerMap[node] else { @@ -20,7 +20,7 @@ final class AndTargetingChecker: InternalTargetingChecker { target(node).prepare(&context) } } - + override func checkInternal(targeting: AndTargeting) -> Bool { guard let checker = checker else { return false @@ -30,16 +30,16 @@ final class AndTargetingChecker: InternalTargetingChecker { if node == .unknown { return false } - + guard let target = checker.checkerMap[node] else { return false } - + if target(node).check() == false { return false } } - + return true } } diff --git a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/CategoryIDChecker.swift b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/CategoryIDChecker.swift index a8747537..82e1a5f2 100644 --- a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/CategoryIDChecker.swift +++ b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/CategoryIDChecker.swift @@ -10,11 +10,9 @@ import Foundation final class CategoryIDChecker: InternalTargetingChecker { weak var checker: TargetingCheckerContextProtocol? - - override func prepareInternal(targeting: CategoryIDTargeting, context: inout PreparationContext) { - - } - + + override func prepareInternal(targeting: CategoryIDTargeting, context: inout PreparationContext) {} + override func checkInternal(targeting: CategoryIDTargeting) -> Bool { guard let checker = checker, let event = checker.event, @@ -37,7 +35,7 @@ final class CategoryIDChecker: InternalTargetingChecker { if lowercaseValue.hasSuffix(lowercaseName) { return true } } } - + return false } } diff --git a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/CategoryIDInChecker.swift b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/CategoryIDInChecker.swift index 0e8380d0..86b30917 100644 --- a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/CategoryIDInChecker.swift +++ b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/CategoryIDInChecker.swift @@ -10,11 +10,9 @@ import Foundation final class CategoryIDInChecker: InternalTargetingChecker { weak var checker: TargetingCheckerContextProtocol? - - override func prepareInternal(targeting: CategoryIDInTargeting, context: inout PreparationContext) { - - } - + + override func prepareInternal(targeting: CategoryIDInTargeting, context: inout PreparationContext) {} + override func checkInternal(targeting: CategoryIDInTargeting) -> Bool { guard let checker = checker, let event = checker.event, @@ -22,18 +20,16 @@ final class CategoryIDInChecker: InternalTargetingChecker !ids.isEmpty else { return false } - - for i in targeting.values { - if ids.contains(where: { $0.key.lowercased() == i.name.lowercased() && $0.value.lowercased() == i.id.lowercased() }) { - switch targeting.kind { - case .any: - return true - case .none: - return false - } + + for i in targeting.values where ids.contains(where: { $0.key.lowercased() == i.name.lowercased() && $0.value.lowercased() == i.id.lowercased() }) { + switch targeting.kind { + case .any: + return true + case .none: + return false } } - + return targeting.kind == .any ? false : true } } diff --git a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/CustomOperationChecker.swift b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/CustomOperationChecker.swift index 4558f245..e64d1854 100644 --- a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/CustomOperationChecker.swift +++ b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/CustomOperationChecker.swift @@ -10,18 +10,18 @@ import Foundation final class CustomOperationChecker: InternalTargetingChecker { weak var checker: TargetingCheckerContextProtocol? - + override func prepareInternal(targeting: CustomOperationTargeting, context: inout PreparationContext) { context.operationsName.append(targeting.systemName.lowercased()) } - + override func checkInternal(targeting: CustomOperationTargeting) -> Bool { guard let checker = checker, let operationName = checker.event?.name, !targeting.systemName.isEmpty else { return false } - + return operationName.lowercased() == targeting.systemName.lowercased() } } diff --git a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/GeoTargetingChecker.swift b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/GeoTargetingChecker.swift index 38992129..51c8570c 100644 --- a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/GeoTargetingChecker.swift +++ b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/GeoTargetingChecker.swift @@ -9,24 +9,24 @@ import Foundation final class CityTargetingChecker: InternalTargetingChecker { weak var checker: TargetingCheckerContextProtocol? - - override func prepareInternal(targeting: CityTargeting, context: inout PreparationContext) -> Void { + + override func prepareInternal(targeting: CityTargeting, context: inout PreparationContext) { context.isNeedGeoRequest = true } - + override func checkInternal(targeting: CityTargeting) -> Bool { guard let checker = checker else { return false } - + guard let geoModel = checker.geoModels else { return false } - + let segment = targeting.ids.first(where: { $0 == geoModel.city }) - + switch targeting.kind { case .positive: return segment != nil @@ -38,24 +38,24 @@ final class CityTargetingChecker: InternalTargetingChecker { final class RegionTargetingChecker: InternalTargetingChecker { weak var checker: TargetingCheckerContextProtocol? - - override func prepareInternal(targeting: RegionTargeting, context: inout PreparationContext) -> Void { + + override func prepareInternal(targeting: RegionTargeting, context: inout PreparationContext) { context.isNeedGeoRequest = true } - + override func checkInternal(targeting: RegionTargeting) -> Bool { guard let checker = checker else { return false } - + guard let geoModel = checker.geoModels else { return false } - + let segment = targeting.ids.first(where: { $0 == geoModel.region }) - + switch targeting.kind { case .positive: return segment != nil @@ -67,24 +67,24 @@ final class RegionTargetingChecker: InternalTargetingChecker { final class CountryTargetingChecker: InternalTargetingChecker { weak var checker: TargetingCheckerContextProtocol? - - override func prepareInternal(targeting: CountryTargeting, context: inout PreparationContext) -> Void { + + override func prepareInternal(targeting: CountryTargeting, context: inout PreparationContext) { context.isNeedGeoRequest = true } - + override func checkInternal(targeting: CountryTargeting) -> Bool { guard let checker = checker else { return false } - + guard let geoModel = checker.geoModels else { return false } - + let segment = targeting.ids.first(where: { $0 == geoModel.country }) - + switch targeting.kind { case .positive: return segment != nil @@ -93,4 +93,3 @@ final class CountryTargetingChecker: InternalTargetingChecker } } } - diff --git a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/OrTargetingChecker.swift b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/OrTargetingChecker.swift index 37ed85e1..927e2d23 100644 --- a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/OrTargetingChecker.swift +++ b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/OrTargetingChecker.swift @@ -9,28 +9,28 @@ import Foundation final class OrTargetingChecker: InternalTargetingChecker { weak var checker: InAppTargetingCheckerProtocol? - - override func prepareInternal(targeting: OrTargeting, context: inout PreparationContext) -> Void { + + override func prepareInternal(targeting: OrTargeting, context: inout PreparationContext) { for node in targeting.nodes { guard let checker = checker, let target = checker.checkerMap[node] else { return } - + target(node).prepare(&context) } } - + override func checkInternal(targeting: OrTargeting) -> Bool { guard let checker = checker else { return false } - + for node in targeting.nodes { if node == .unknown { return false } - + guard let target = checker.checkerMap[node] else { return false } @@ -38,7 +38,7 @@ final class OrTargetingChecker: InternalTargetingChecker { return true } } - + return false } } diff --git a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/ProductIDChecker.swift b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/ProductIDChecker.swift index 734fc731..496219b5 100644 --- a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/ProductIDChecker.swift +++ b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/ProductIDChecker.swift @@ -11,9 +11,7 @@ import Foundation final class ProductIDChecker: InternalTargetingChecker { weak var checker: TargetingCheckerContextProtocol? - override func prepareInternal(targeting: ProductIDTargeting, context: inout PreparationContext) { - - } + override func prepareInternal(targeting: ProductIDTargeting, context: inout PreparationContext) {} override func checkInternal(targeting: ProductIDTargeting) -> Bool { guard let checker = checker, diff --git a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/ProductSegmentChecker.swift b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/ProductSegmentChecker.swift index 7e772e6c..a61ed508 100644 --- a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/ProductSegmentChecker.swift +++ b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/ProductSegmentChecker.swift @@ -12,7 +12,7 @@ final class ProductSegmentChecker: InternalTargetingChecker Void { + override func prepareInternal(targeting: ProductSegmentTargeting, context: inout PreparationContext) { context.productSegments.append(targeting.segmentationExternalId) } diff --git a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/PushEnabledTargetingChecker.swift b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/PushEnabledTargetingChecker.swift index 6ca555f1..230f1753 100644 --- a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/PushEnabledTargetingChecker.swift +++ b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/PushEnabledTargetingChecker.swift @@ -13,7 +13,7 @@ final class PushEnabledTargetingChecker: InternalTargetingChecker Bool { let lock = DispatchSemaphore(value: 0) var isNotificationsEnabled = true - + UNUserNotificationCenter.current().getNotificationSettings { settings in switch settings.authorizationStatus { case .notDetermined, .denied: @@ -25,7 +25,7 @@ final class PushEnabledTargetingChecker: InternalTargetingChecker { weak var checker: TargetingCheckerContextProtocol? - - override func prepareInternal(targeting: SegmentTargeting, context: inout PreparationContext) -> Void { + + override func prepareInternal(targeting: SegmentTargeting, context: inout PreparationContext) { context.segments.append(targeting.segmentationExternalId) } - + override func checkInternal(targeting: SegmentTargeting) -> Bool { guard let checker = checker else { return false } - + guard let checkedSegmentations = checker.checkedSegmentations, !checkedSegmentations.isEmpty else { return false } - + let segment = checkedSegmentations.first(where: { $0.segment?.ids?.externalId == targeting.segmentExternalId }) - + switch targeting.kind { case .positive: return segment != nil diff --git a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/VisitTargetingChecker.swift b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/VisitTargetingChecker.swift index 13b2e0ca..f2ef8aa3 100644 --- a/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/VisitTargetingChecker.swift +++ b/Mindbox/InAppMessages/InAppTargetingChecker/TargetingCheckerTypes/VisitTargetingChecker.swift @@ -10,19 +10,19 @@ import Foundation import MindboxLogger final class VisitTargetingChecker: InternalTargetingChecker { - + weak var checker: TargetingCheckerPersistenceStorageProtocol? - + override func checkInternal(targeting: VisitTargeting) -> Bool { guard let checker = checker else { return false } - + guard let count = checker.persistenceStorage.userVisitCount else { Logger.common(message: "VisitTargetingChecker. userVisitCount doesn't exists.", level: .error, category: .inAppMessages) return false } - + switch targeting.kind { case .gte: return count >= targeting.value diff --git a/Mindbox/InAppMessages/InappMessagesDelegate/CommonProtocols/MindboxURLHandlerDelegate.swift b/Mindbox/InAppMessages/InappMessagesDelegate/CommonProtocols/MindboxURLHandlerDelegate.swift index d9742fe1..d82acf21 100644 --- a/Mindbox/InAppMessages/InappMessagesDelegate/CommonProtocols/MindboxURLHandlerDelegate.swift +++ b/Mindbox/InAppMessages/InappMessagesDelegate/CommonProtocols/MindboxURLHandlerDelegate.swift @@ -20,7 +20,7 @@ public extension MindboxURLHandlerDelegate { Logger.common(message: "The URL does not exist or is invalid.", category: .inAppMessages) return } - + UIApplication.shared.open(url, options: [:], completionHandler: nil) } } diff --git a/Mindbox/InAppMessages/InappMessagesDelegate/CompositeInappMessageDelegate.swift b/Mindbox/InAppMessages/InappMessagesDelegate/CompositeInappMessageDelegate.swift index 4cc7640d..8c4f2990 100644 --- a/Mindbox/InAppMessages/InappMessagesDelegate/CompositeInappMessageDelegate.swift +++ b/Mindbox/InAppMessages/InappMessagesDelegate/CompositeInappMessageDelegate.swift @@ -27,7 +27,7 @@ public extension CompositeInappMessageDelegate { $0.inAppMessageTapAction(id: id, url: url, payload: payload) } } - + func inAppMessageDismissed(id: String) { Logger.common(message: "CompositeInappMessageDelegate inAppMessageDismissed called.") delegates.forEach { diff --git a/Mindbox/InAppMessages/InappMessagesDelegate/CopyInappMessageDelegate.swift b/Mindbox/InAppMessages/InappMessagesDelegate/CopyInappMessageDelegate.swift index 02eb8c76..8f1e2b43 100644 --- a/Mindbox/InAppMessages/InappMessagesDelegate/CopyInappMessageDelegate.swift +++ b/Mindbox/InAppMessages/InappMessagesDelegate/CopyInappMessageDelegate.swift @@ -24,6 +24,6 @@ public extension CopyInappMessageDelegate { Logger.common(message: "CopyInappMessageDelegte inAppMessageTapAction called.") copyPayload(payload) } - + func inAppMessageDismissed(id: String) { } } diff --git a/Mindbox/InAppMessages/InappMessagesDelegate/DefaultInappMessageDelegate.swift b/Mindbox/InAppMessages/InappMessagesDelegate/DefaultInappMessageDelegate.swift index 796bf62a..7044cbe9 100644 --- a/Mindbox/InAppMessages/InappMessagesDelegate/DefaultInappMessageDelegate.swift +++ b/Mindbox/InAppMessages/InappMessagesDelegate/DefaultInappMessageDelegate.swift @@ -27,6 +27,6 @@ public extension DefaultInappMessageDelegate { openURL(url) copyPayload(payload) } - + func inAppMessageDismissed(id: String) { } } diff --git a/Mindbox/InAppMessages/InappMessagesDelegate/InAppMessagesDelegate.swift b/Mindbox/InAppMessages/InappMessagesDelegate/InAppMessagesDelegate.swift index 1ba2afee..ca6de5e5 100644 --- a/Mindbox/InAppMessages/InappMessagesDelegate/InAppMessagesDelegate.swift +++ b/Mindbox/InAppMessages/InappMessagesDelegate/InAppMessagesDelegate.swift @@ -72,6 +72,6 @@ public extension InAppMessagesDelegate { func inAppMessageTapAction(id: String, url: URL?, payload: String) { Logger.common(message: "InAppMessagesDelegate inAppMessageTapAction called.") } - + func inAppMessageDismissed(id: String) { } } diff --git a/Mindbox/InAppMessages/InappMessagesDelegate/URLInappMessageDelegate.swift b/Mindbox/InAppMessages/InappMessagesDelegate/URLInappMessageDelegate.swift index 98bc1ff4..d785de73 100644 --- a/Mindbox/InAppMessages/InappMessagesDelegate/URLInappMessageDelegate.swift +++ b/Mindbox/InAppMessages/InappMessagesDelegate/URLInappMessageDelegate.swift @@ -24,6 +24,6 @@ public extension URLInappMessageDelegate { Logger.common(message: "URLInappMessageDelegate inAppMessageTapAction called.") openURL(url) } - + func inAppMessageDismissed(id: String) { } } diff --git a/Mindbox/InAppMessages/Models/Config/ABTestModel.swift b/Mindbox/InAppMessages/Models/Config/ABTestModel.swift index 8104c8f2..6d1a2a8a 100644 --- a/Mindbox/InAppMessages/Models/Config/ABTestModel.swift +++ b/Mindbox/InAppMessages/Models/Config/ABTestModel.swift @@ -13,17 +13,17 @@ struct ABTest: Decodable, Equatable { let sdkVersion: SdkVersion? let salt: String? let variants: [ABTestVariant]? - + struct ABTestVariant: Decodable, Equatable { let id: String let modulus: Modulus? let objects: [ABTestObject]? - + struct Modulus: Decodable, Equatable { let lower: Int let upper: Int? } - + struct ABTestObject: Decodable, Equatable { let type: ABTestType let kind: ABTestKind @@ -58,7 +58,7 @@ struct ABTest: Decodable, Equatable { kind = try container.decode(ABTestKind.self, forKey: .kind) inapps = try container.decodeIfPresent([String].self, forKey: .inapps) } - + init(type: ABTestType, kind: ABTestKind, inapps: [String]? = nil) { self.type = type self.kind = kind diff --git a/Mindbox/InAppMessages/Models/Config/ConfigResponse.swift b/Mindbox/InAppMessages/Models/Config/ConfigResponse.swift index 83011bf0..44756842 100644 --- a/Mindbox/InAppMessages/Models/Config/ConfigResponse.swift +++ b/Mindbox/InAppMessages/Models/Config/ConfigResponse.swift @@ -13,18 +13,18 @@ struct ConfigResponse: Decodable { let monitoring: Monitoring? let settings: Settings? let abtests: [ABTest]? - + enum CodingKeys: String, CodingKey { case inapps, monitoring, settings, abtests } - + init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - + self.inapps = try? container.decodeIfPresent(FailableDecodableArray.self, forKey: .inapps) self.monitoring = ConfigResponse.decodeIfPresent(container, forKey: .monitoring, errorDesc: "Cannot decode Monitoring") self.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({ @@ -35,7 +35,7 @@ struct ConfigResponse: Decodable { self.abtests = nil } } - + private static func decodeIfPresent(_ container: KeyedDecodingContainer, forKey key: CodingKeys, errorDesc: String) -> T? where T: Decodable { diff --git a/Mindbox/InAppMessages/Models/Config/InappModel/InAppModel.swift b/Mindbox/InAppMessages/Models/Config/InappModel/InAppModel.swift index 8f93d913..eaa5247f 100644 --- a/Mindbox/InAppMessages/Models/Config/InappModel/InAppModel.swift +++ b/Mindbox/InAppMessages/Models/Config/InappModel/InAppModel.swift @@ -14,7 +14,7 @@ struct InAppDTO: Decodable, Equatable { var frequency: InappFrequency? let targeting: Targeting let form: InAppFormDTO - + enum CodingKeys: CodingKey { case id case sdkVersion @@ -22,21 +22,21 @@ struct InAppDTO: Decodable, Equatable { case targeting case form } - + init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.id = try container.decode(String.self, forKey: .id) self.sdkVersion = try container.decode(SdkVersion.self, forKey: .sdkVersion) self.frequency = try container.decodeIfPresent(InappFrequency.self, forKey: .frequency) - + if frequency == .unknown { throw CustomDecodingError.unknownType("Frequency has unknown type. Inapp will be ignored.") } - + if frequency == nil { frequency = .once(OnceFrequency(kind: .lifetime)) } - + self.targeting = try container.decode(Targeting.self, forKey: .targeting) self.form = try container.decode(InAppFormDTO.self, forKey: .form) } diff --git a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/ContentBackgroundLayer.swift b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/ContentBackgroundLayer.swift index c0c3a907..ee47a426 100644 --- a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/ContentBackgroundLayer.swift +++ b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/ContentBackgroundLayer.swift @@ -13,7 +13,7 @@ protocol ContentBackgroundLayerProtocol: Decodable, Equatable { } enum ContentBackgroundLayerType: String, Decodable { case image case unknown - + init(from decoder: Decoder) throws { let container: SingleValueDecodingContainer = try decoder.singleValueContainer() let type: String = try container.decode(String.self) @@ -24,11 +24,11 @@ enum ContentBackgroundLayerType: String, Decodable { enum ContentBackgroundLayerDTO: Decodable, Hashable, Equatable { case image(ImageContentBackgroundLayerDTO) case unknown - + enum CodingKeys: String, CodingKey { case type = "$type" } - + static func == (lhs: ContentBackgroundLayerDTO, rhs: ContentBackgroundLayerDTO) -> Bool { switch (lhs, rhs) { case (.image, .image): return true @@ -36,23 +36,23 @@ enum ContentBackgroundLayerDTO: Decodable, Hashable, Equatable { default: return false } } - + func hash(into hasher: inout Hasher) { switch self { case .image: hasher.combine("image") case .unknown: hasher.combine("unknown") } } - + init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container( keyedBy: CodingKeys.self) guard let type = try? container.decode(ContentBackgroundLayerType.self, forKey: .type) else { throw CustomDecodingError.decodingError("The layer type could not be decoded. The layer will be ignored.") } - + let layerContainer: SingleValueDecodingContainer = try decoder.singleValueContainer() - + switch type { case .image: let imageLayer = try layerContainer.decode(ImageContentBackgroundLayerDTO.self) @@ -77,11 +77,11 @@ extension ContentBackgroundLayerDTO { enum ContentBackgroundLayer: Decodable, Hashable, Equatable { case image(ImageContentBackgroundLayer) case unknown - + enum CodingKeys: String, CodingKey { case type = "$type" } - + static func == (lhs: ContentBackgroundLayer, rhs: ContentBackgroundLayer) -> Bool { switch (lhs, rhs) { case (.image, .image): return true @@ -89,23 +89,23 @@ enum ContentBackgroundLayer: Decodable, Hashable, Equatable { default: return false } } - + func hash(into hasher: inout Hasher) { switch self { case .image: hasher.combine("image") case .unknown: hasher.combine("unknown") } } - + init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container( keyedBy: CodingKeys.self) guard let type = try? container.decode(ContentBackgroundLayerType.self, forKey: .type) else { throw CustomDecodingError.decodingError("The layer type could not be decoded. The layer will be ignored.") } - + let layerContainer: SingleValueDecodingContainer = try decoder.singleValueContainer() - + switch type { case .image: let imageLayer = try layerContainer.decode(ImageContentBackgroundLayer.self) diff --git a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/ContentBackgroundLayerAction/ContentBackgroundLayerAction.swift b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/ContentBackgroundLayerAction/ContentBackgroundLayerAction.swift index d595b70a..8c38622b 100644 --- a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/ContentBackgroundLayerAction/ContentBackgroundLayerAction.swift +++ b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/ContentBackgroundLayerAction/ContentBackgroundLayerAction.swift @@ -15,7 +15,7 @@ enum ContentBackgroundLayerActionType: String, Decodable { case pushPermission case redirectUrl case unknown - + init(from decoder: Decoder) throws { let container: SingleValueDecodingContainer = try decoder.singleValueContainer() let type: String = try container.decode(String.self) @@ -27,11 +27,11 @@ enum ContentBackgroundLayerActionDTO: Decodable, Hashable, Equatable { case pushPermission(PushPermissionLayerActionDTO) case redirectUrl(RedirectUrlLayerActionDTO) case unknown - + enum CodingKeys: String, CodingKey { case type = "$type" } - + static func == (lhs: ContentBackgroundLayerActionDTO, rhs: ContentBackgroundLayerActionDTO) -> Bool { switch (lhs, rhs) { case (.pushPermission, .pushPermission): return true @@ -40,7 +40,7 @@ enum ContentBackgroundLayerActionDTO: Decodable, Hashable, Equatable { default: return false } } - + func hash(into hasher: inout Hasher) { switch self { case .pushPermission: hasher.combine("pushPermission") @@ -48,16 +48,16 @@ enum ContentBackgroundLayerActionDTO: Decodable, Hashable, Equatable { case .unknown: hasher.combine("unknown") } } - + init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container( keyedBy: CodingKeys.self) guard let type = try? container.decode(ContentBackgroundLayerActionType.self, forKey: .type) else { throw CustomDecodingError.decodingError("The action type could not be decoded. The action will be ignored.") } - + let actionContainer: SingleValueDecodingContainer = try decoder.singleValueContainer() - + switch type { case .pushPermission: let pushPermissionAction = try actionContainer.decode(PushPermissionLayerActionDTO.self) @@ -84,17 +84,16 @@ extension ContentBackgroundLayerActionDTO { } } - // MARK: - Real model enum ContentBackgroundLayerAction: Decodable, Hashable, Equatable { case pushPermission(PushPermissionLayerAction) case redirectUrl(RedirectUrlLayerAction) case unknown - + enum CodingKeys: String, CodingKey { case type = "$type" } - + static func == (lhs: ContentBackgroundLayerAction, rhs: ContentBackgroundLayerAction) -> Bool { switch (lhs, rhs) { case (.redirectUrl, .redirectUrl): return true @@ -102,7 +101,7 @@ enum ContentBackgroundLayerAction: Decodable, Hashable, Equatable { default: return false } } - + func hash(into hasher: inout Hasher) { switch self { case .pushPermission: hasher.combine("pushPermission") @@ -110,16 +109,16 @@ enum ContentBackgroundLayerAction: Decodable, Hashable, Equatable { case .unknown: hasher.combine("unknown") } } - + init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container( keyedBy: CodingKeys.self) guard let type = try? container.decode(ContentBackgroundLayerActionType.self, forKey: .type) else { throw CustomDecodingError.decodingError("The action type could not be decoded. The action will be ignored.") } - + let actionContainer: SingleValueDecodingContainer = try decoder.singleValueContainer() - + switch type { case .pushPermission: let pushPermissionAction = try actionContainer.decode(PushPermissionLayerAction.self) diff --git a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/ContentBackgroundSource/ContentBackgroundLayerSource.swift b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/ContentBackgroundSource/ContentBackgroundLayerSource.swift index 8f0c58de..75b403d6 100644 --- a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/ContentBackgroundSource/ContentBackgroundLayerSource.swift +++ b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/ContentBackgroundSource/ContentBackgroundLayerSource.swift @@ -8,13 +8,12 @@ import Foundation - protocol ContentBackgroundLayerSourceProtocol: Decodable, Equatable { } enum ContentBackgroundLayerSourceType: String, Decodable { case url case unknown - + init(from decoder: Decoder) throws { let container: SingleValueDecodingContainer = try decoder.singleValueContainer() let type: String = try container.decode(String.self) @@ -25,11 +24,11 @@ enum ContentBackgroundLayerSourceType: String, Decodable { enum ContentBackgroundLayerSourceDTO: Decodable, Hashable, Equatable { case url(UrlLayerSourceDTO) case unknown - + enum CodingKeys: String, CodingKey { case type = "$type" } - + static func == (lhs: ContentBackgroundLayerSourceDTO, rhs: ContentBackgroundLayerSourceDTO) -> Bool { switch (lhs, rhs) { case (.url, .url): return true @@ -37,23 +36,23 @@ enum ContentBackgroundLayerSourceDTO: Decodable, Hashable, Equatable { default: return false } } - + func hash(into hasher: inout Hasher) { switch self { case .url: hasher.combine("url") case .unknown: hasher.combine("unknown") } } - + init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container( keyedBy: CodingKeys.self) guard let type = try? container.decode(ContentBackgroundLayerSourceType.self, forKey: .type) else { throw CustomDecodingError.decodingError("The source type could not be decoded. The source will be ignored.") } - + let sourceContainer: SingleValueDecodingContainer = try decoder.singleValueContainer() - + switch type { case .url: let urlSource = try sourceContainer.decode(UrlLayerSourceDTO.self) @@ -78,11 +77,11 @@ extension ContentBackgroundLayerSourceDTO { enum ContentBackgroundLayerSource: Decodable, Hashable, Equatable { case url(UrlLayerSource) case unknown - + enum CodingKeys: String, CodingKey { case type = "$type" } - + static func == (lhs: ContentBackgroundLayerSource, rhs: ContentBackgroundLayerSource) -> Bool { switch (lhs, rhs) { case (.url, .url): return true @@ -90,23 +89,23 @@ enum ContentBackgroundLayerSource: Decodable, Hashable, Equatable { default: return false } } - + func hash(into hasher: inout Hasher) { switch self { case .url: hasher.combine("url") case .unknown: hasher.combine("unknown") } } - + init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container( keyedBy: CodingKeys.self) guard let type = try? container.decode(ContentBackgroundLayerSourceType.self, forKey: .type) else { throw CustomDecodingError.decodingError("The source type could not be decoded. The source will be ignored.") } - + let sourceContainer: SingleValueDecodingContainer = try decoder.singleValueContainer() - + switch type { case .url: let urlSource = try sourceContainer.decode(UrlLayerSource.self) diff --git a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/Layers/ImageContentBackgroundLayer.swift b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/Layers/ImageContentBackgroundLayer.swift index dca5190f..ee7957d4 100644 --- a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/Layers/ImageContentBackgroundLayer.swift +++ b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentBackground/ContentBackgroundLayer/Layers/ImageContentBackgroundLayer.swift @@ -13,7 +13,6 @@ struct ImageContentBackgroundLayerDTO: ContentBackgroundLayerProtocol { let source: ContentBackgroundLayerSourceDTO? } - struct ImageContentBackgroundLayer: ContentBackgroundLayerProtocol { let action: ContentBackgroundLayerAction let source: ContentBackgroundLayerSource diff --git a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElement.swift b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElement.swift index 8c802b9d..4a328326 100644 --- a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElement.swift +++ b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElement.swift @@ -13,7 +13,7 @@ protocol ContentElementProtocol: Decodable, Equatable { } enum ContentElementType: String, Decodable { case closeButton case unknown - + init(from decoder: Decoder) throws { let container: SingleValueDecodingContainer = try decoder.singleValueContainer() let type: String = try container.decode(String.self) @@ -24,11 +24,11 @@ enum ContentElementType: String, Decodable { enum ContentElementDTO: Decodable, Hashable, Equatable { case closeButton(CloseButtonElementDTO) case unknown - + enum CodingKeys: String, CodingKey { case type = "$type" } - + static func == (lhs: ContentElementDTO, rhs: ContentElementDTO) -> Bool { switch (lhs, rhs) { case (.closeButton, .closeButton): return true @@ -36,23 +36,23 @@ enum ContentElementDTO: Decodable, Hashable, Equatable { default: return false } } - + func hash(into hasher: inout Hasher) { switch self { case .closeButton: hasher.combine("closeButton") case .unknown: hasher.combine("unknown") } } - + init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container( keyedBy: CodingKeys.self) guard let type = try? container.decode(ContentElementType.self, forKey: .type) else { throw CustomDecodingError.decodingError("The variant type could not be decoded. The variant will be ignored.") } - + let elementContainer: SingleValueDecodingContainer = try decoder.singleValueContainer() - + switch type { case .closeButton: let closeButtonElement = try elementContainer.decode(CloseButtonElementDTO.self) @@ -77,11 +77,11 @@ extension ContentElementDTO { enum ContentElement: Decodable, Hashable, Equatable { case closeButton(CloseButtonElement) case unknown - + enum CodingKeys: String, CodingKey { case type = "$type" } - + static func == (lhs: ContentElement, rhs: ContentElement) -> Bool { switch (lhs, rhs) { case (.closeButton, .closeButton): return true @@ -89,23 +89,23 @@ enum ContentElement: Decodable, Hashable, Equatable { default: return false } } - + func hash(into hasher: inout Hasher) { switch self { case .closeButton: hasher.combine("closeButton") case .unknown: hasher.combine("unknown") } } - + init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container( keyedBy: CodingKeys.self) guard let type = try? container.decode(ContentElementType.self, forKey: .type) else { throw CustomDecodingError.decodingError("The variant type could not be decoded. The variant will be ignored.") } - + let elementContainer: SingleValueDecodingContainer = try decoder.singleValueContainer() - + switch type { case .closeButton: let closeButtonElement = try elementContainer.decode(CloseButtonElement.self) diff --git a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElementPosition/ContentElementPosition.swift b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElementPosition/ContentElementPosition.swift index 1f644a14..101a0e97 100644 --- a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElementPosition/ContentElementPosition.swift +++ b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElementPosition/ContentElementPosition.swift @@ -12,7 +12,6 @@ struct ContentElementPositionDTO: Decodable, Equatable { let margin: ContentElementPositionMarginDTO? } - struct ContentElementPosition: Decodable, Equatable { let margin: ContentElementPositionMargin } diff --git a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElementPosition/ContentElementPositionMargin/ContentElementPositionMargin.swift b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElementPosition/ContentElementPositionMargin/ContentElementPositionMargin.swift index f9b560f9..666e5f93 100644 --- a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElementPosition/ContentElementPositionMargin/ContentElementPositionMargin.swift +++ b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElementPosition/ContentElementPositionMargin/ContentElementPositionMargin.swift @@ -27,7 +27,7 @@ struct ContentElementPositionMargin: Decodable, Equatable { enum ElementPositionMarginKind: String, Decodable, Equatable { case proportion case unknown - + init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let rawValue = try container.decode(RawValue.self) diff --git a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElementSize/Kind/ContentElementSizeType.swift b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElementSize/Kind/ContentElementSizeType.swift index 346831b3..c21d3b01 100644 --- a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElementSize/Kind/ContentElementSizeType.swift +++ b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentElement/ContentElementSize/Kind/ContentElementSizeType.swift @@ -11,7 +11,7 @@ import Foundation enum ContentElementSizeKind: String, Decodable, Equatable { case dp case unknown - + init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let rawValue = try container.decode(RawValue.self) diff --git a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentPosition/ContentPositionMargin/ContentPositionMargin.swift b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentPosition/ContentPositionMargin/ContentPositionMargin.swift index 2c0ada89..3efea0bf 100644 --- a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentPosition/ContentPositionMargin/ContentPositionMargin.swift +++ b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/InappFormVariantContent/ContentPosition/ContentPositionMargin/ContentPositionMargin.swift @@ -27,7 +27,7 @@ struct ContentPositionMargin: Decodable, Equatable { enum ContentPositionMarginKind: String, Decodable, Equatable { case dp case unknown - + init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let rawValue = try container.decode(RawValue.self) diff --git a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/iFormVariant.swift b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/iFormVariant.swift index 8a95948d..b531ecea 100644 --- a/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/iFormVariant.swift +++ b/Mindbox/InAppMessages/Models/Config/InappModel/InappForm/InappFormVariant/iFormVariant.swift @@ -14,7 +14,7 @@ enum MindboxFormVariantType: String, Decodable { case modal case snackbar case unknown - + init(from decoder: Decoder) throws { let container: SingleValueDecodingContainer = try decoder.singleValueContainer() let type: String = try container.decode(String.self) @@ -26,11 +26,11 @@ enum MindboxFormVariantDTO: Decodable, Hashable, Equatable { case modal(ModalFormVariantDTO) case snackbar(SnackbarFormVariantDTO) case unknown - + enum CodingKeys: String, CodingKey { case type = "$type" } - + static func == (lhs: MindboxFormVariantDTO, rhs: MindboxFormVariantDTO) -> Bool { switch (lhs, rhs) { case (.modal, .modal): return true @@ -39,7 +39,7 @@ enum MindboxFormVariantDTO: Decodable, Hashable, Equatable { default: return false } } - + func hash(into hasher: inout Hasher) { switch self { case .modal: hasher.combine("modal") @@ -47,16 +47,16 @@ enum MindboxFormVariantDTO: Decodable, Hashable, Equatable { case .unknown: hasher.combine("unknown") } } - + init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container( keyedBy: CodingKeys.self) guard let type = try? container.decode(MindboxFormVariantType.self, forKey: .type) else { throw CustomDecodingError.decodingError("The variant type could not be decoded. The variant will be ignored.") } - + let variantContainer: SingleValueDecodingContainer = try decoder.singleValueContainer() - + switch type { case .modal: let modalVariant = try variantContainer.decode(ModalFormVariantDTO.self) @@ -74,11 +74,11 @@ enum MindboxFormVariant: Decodable, Hashable, Equatable { case modal(ModalFormVariant) case snackbar(SnackbarFormVariant) case unknown - + enum CodingKeys: String, CodingKey { case type = "$type" } - + static func == (lhs: MindboxFormVariant, rhs: MindboxFormVariant) -> Bool { switch (lhs, rhs) { case (.modal, .modal): return true @@ -87,7 +87,7 @@ enum MindboxFormVariant: Decodable, Hashable, Equatable { default: return false } } - + func hash(into hasher: inout Hasher) { switch self { case .modal: hasher.combine("modal") @@ -95,16 +95,16 @@ enum MindboxFormVariant: Decodable, Hashable, Equatable { case .unknown: hasher.combine("unknown") } } - + init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container( keyedBy: CodingKeys.self) guard let type = try? container.decode(MindboxFormVariantType.self, forKey: .type) else { throw CustomDecodingError.decodingError("The variant type could not be decoded. The variant will be ignored.") } - + let variantContainer: SingleValueDecodingContainer = try decoder.singleValueContainer() - + switch type { case .modal: let modalVariant = try variantContainer.decode(ModalFormVariant.self) @@ -127,13 +127,13 @@ extension MindboxFormVariant { throw CustomDecodingError.unknownType("The variant type could not be decoded. The variant will be ignored.") } self = .modal(modalVariant) - + case .snackbar: guard let snackbarVariant = snackbarVariant else { throw CustomDecodingError.unknownType("The variant type could not be decoded. The variant will be ignored.") } self = .snackbar(snackbarVariant) - + case .unknown: self = .unknown } diff --git a/Mindbox/InAppMessages/Models/Config/InappModel/InappFrequency/InappFrequency.swift b/Mindbox/InAppMessages/Models/Config/InappModel/InappFrequency/InappFrequency.swift index 45f0a29a..c65c8cb3 100644 --- a/Mindbox/InAppMessages/Models/Config/InappModel/InappFrequency/InappFrequency.swift +++ b/Mindbox/InAppMessages/Models/Config/InappModel/InappFrequency/InappFrequency.swift @@ -14,7 +14,7 @@ enum InappFrequencyType: String, Decodable { case periodic case once case unknown - + init(from decoder: Decoder) throws { let container: SingleValueDecodingContainer = try decoder.singleValueContainer() let type: String = try container.decode(String.self) @@ -26,11 +26,11 @@ enum InappFrequency: Decodable, Equatable, Hashable { case periodic(PeriodicFrequency) case once(OnceFrequency) case unknown - + enum CodingKeys: String, CodingKey { case type = "$type" } - + static func == (lhs: InappFrequency, rhs: InappFrequency) -> Bool { switch (lhs, rhs) { case (.periodic, .periodic): return true @@ -39,7 +39,7 @@ enum InappFrequency: Decodable, Equatable, Hashable { default: return false } } - + func hash(into hasher: inout Hasher) { switch self { case .periodic: hasher.combine("periodic") @@ -47,16 +47,16 @@ enum InappFrequency: Decodable, Equatable, Hashable { case .unknown: hasher.combine("unknown") } } - + init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container( keyedBy: CodingKeys.self) guard let type = try? container.decode(InappFrequencyType.self, forKey: .type) else { throw CustomDecodingError.decodingError("The variant type could not be decoded. The variant will be ignored.") } - + let variantContainer: SingleValueDecodingContainer = try decoder.singleValueContainer() - + switch type { case .periodic: let periodicFrequency = try variantContainer.decode(PeriodicFrequency.self) diff --git a/Mindbox/InAppMessages/Models/Config/InappModel/InappFrequency/Types/OnceFrequency.swift b/Mindbox/InAppMessages/Models/Config/InappModel/InappFrequency/Types/OnceFrequency.swift index f227d7b7..22f4b072 100644 --- a/Mindbox/InAppMessages/Models/Config/InappModel/InappFrequency/Types/OnceFrequency.swift +++ b/Mindbox/InAppMessages/Models/Config/InappModel/InappFrequency/Types/OnceFrequency.swift @@ -10,18 +10,18 @@ import Foundation struct OnceFrequency: iFormVariant, Decodable, Equatable { let kind: OnceFrequencyKind - + enum OnceFrequencyKind: String, Decodable { case lifetime case session - + init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let decodedString = try container.decode(String.self).lowercased() guard let value = OnceFrequencyKind(rawValue: decodedString) else { throw CustomDecodingError.unknownType("Cannot decode OnceFrequency. Inapp will be ignored.") } - + self = value } } diff --git a/Mindbox/InAppMessages/Models/Config/InappModel/InappFrequency/Types/PeriodicFrequency.swift b/Mindbox/InAppMessages/Models/Config/InappModel/InappFrequency/Types/PeriodicFrequency.swift index 3af84a17..aee3cc2b 100644 --- a/Mindbox/InAppMessages/Models/Config/InappModel/InappFrequency/Types/PeriodicFrequency.swift +++ b/Mindbox/InAppMessages/Models/Config/InappModel/InappFrequency/Types/PeriodicFrequency.swift @@ -11,21 +11,21 @@ import Foundation struct PeriodicFrequency: Decodable, Equatable { let unit: Unit let value: Int - + enum Unit: String, Decodable { - case seconds = "seconds" - case minutes = "minutes" - case hours = "hours" - case days = "days" + case seconds + case minutes + case hours + case days init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let decodedString = try container.decode(String.self).lowercased() - + guard let value = Unit(rawValue: decodedString) else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Неизвестная единица времени") } - + self = value } } diff --git a/Mindbox/InAppMessages/Models/Config/SettingsModel.swift b/Mindbox/InAppMessages/Models/Config/SettingsModel.swift index fde610ff..a8e4b205 100644 --- a/Mindbox/InAppMessages/Models/Config/SettingsModel.swift +++ b/Mindbox/InAppMessages/Models/Config/SettingsModel.swift @@ -11,7 +11,7 @@ import Foundation struct Settings: Decodable, Equatable { let operations: SettingsOperations? let ttl: TimeToLive? - + enum CodingKeys: CodingKey { case operations, ttl } diff --git a/Mindbox/InAppMessages/Models/Config/SettingsOperationsModel.swift b/Mindbox/InAppMessages/Models/Config/SettingsOperationsModel.swift index 1ad16fd9..d96588dc 100644 --- a/Mindbox/InAppMessages/Models/Config/SettingsOperationsModel.swift +++ b/Mindbox/InAppMessages/Models/Config/SettingsOperationsModel.swift @@ -10,17 +10,17 @@ import Foundation extension Settings { struct SettingsOperations: Decodable, Equatable { - + let viewProduct: Operation? let viewCategory: Operation? let setCart: Operation? - + enum CodingKeys: CodingKey { case viewProduct case viewCategory case setCart } - + struct Operation: Decodable, Equatable { let systemName: String } @@ -33,7 +33,7 @@ extension Settings.SettingsOperations { self.viewProduct = try? container.decodeIfPresent(Operation.self, forKey: .viewProduct) self.viewCategory = try? container.decodeIfPresent(Operation.self, forKey: .viewCategory) self.setCart = try? container.decodeIfPresent(Operation.self, forKey: .setCart) - + if viewProduct == nil && viewCategory == nil && setCart == nil { // Will never be caught because of `try?` in `Settings.init` throw DecodingError.dataCorruptedError(forKey: .viewProduct, in: container, debugDescription: "The `operation` type could not be decoded because all operations are nil") diff --git a/Mindbox/InAppMessages/Models/Config/TimeToLiveModel.swift b/Mindbox/InAppMessages/Models/Config/TimeToLiveModel.swift index 29b9cd08..824a25c2 100644 --- a/Mindbox/InAppMessages/Models/Config/TimeToLiveModel.swift +++ b/Mindbox/InAppMessages/Models/Config/TimeToLiveModel.swift @@ -11,7 +11,7 @@ import Foundation extension Settings { struct TimeToLive: Decodable, Equatable { let inapps: String? - + enum CodingKeys: CodingKey { case inapps } diff --git a/Mindbox/InAppMessages/Models/InAppGeoResponse.swift b/Mindbox/InAppMessages/Models/InAppGeoResponse.swift index 1b815222..d0f60bcb 100644 --- a/Mindbox/InAppMessages/Models/InAppGeoResponse.swift +++ b/Mindbox/InAppMessages/Models/InAppGeoResponse.swift @@ -20,7 +20,7 @@ struct InAppGeoResponse: Codable, Equatable { } struct FetchInAppGeoRoute: Route { - + var method: HTTPMethod { .get } var path: String { "/geo" } diff --git a/Mindbox/InAppMessages/Models/InAppOperationJSONModel.swift b/Mindbox/InAppMessages/Models/InAppOperationJSONModel.swift index 450c6bee..5abc9209 100644 --- a/Mindbox/InAppMessages/Models/InAppOperationJSONModel.swift +++ b/Mindbox/InAppMessages/Models/InAppOperationJSONModel.swift @@ -48,13 +48,13 @@ struct ProductCategory: Codable, Equatable, Hashable { init(ids: [String: String]) { self.ids = ids } - + init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let rawIds = try container.decode([String: CodableValue].self, forKey: .ids) - + var convertedIds = [String: String]() - + for (key, value) in rawIds { switch value { case .string(let stringValue): @@ -65,10 +65,10 @@ struct ProductCategory: Codable, Equatable, Hashable { convertedIds[key] = String(doubleValue) } } - + self.ids = convertedIds } - + func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.ids, forKey: .ids) @@ -79,10 +79,10 @@ enum CodableValue: Codable { case string(String) case int(Int) case double(Double) - + init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() - + if let stringValue = try? container.decode(String.self) { self = .string(stringValue) } else if let intValue = try? container.decode(Int.self) { @@ -99,7 +99,7 @@ enum CodableValue: Codable { ) } } - + func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() switch self { diff --git a/Mindbox/InAppMessages/Presentation/ActionHandler/ActionUseCaseFactory.swift b/Mindbox/InAppMessages/Presentation/ActionHandler/ActionUseCaseFactory.swift index d3b7f4e4..3533925d 100644 --- a/Mindbox/InAppMessages/Presentation/ActionHandler/ActionUseCaseFactory.swift +++ b/Mindbox/InAppMessages/Presentation/ActionHandler/ActionUseCaseFactory.swift @@ -14,11 +14,11 @@ protocol UseCaseFactoryProtocol { class ActionUseCaseFactory: UseCaseFactoryProtocol { private let clickTracker: PresentationClickTracker - + init(tracker: InAppMessagesTrackerProtocol) { clickTracker = PresentationClickTracker(tracker: tracker) } - + func createUseCase(action: ContentBackgroundLayerAction) -> PresentationActionUseCaseProtocol? { switch action { case .pushPermission(let pushPermissionModel): diff --git a/Mindbox/InAppMessages/Presentation/ActionHandler/InappActionHandler.swift b/Mindbox/InAppMessages/Presentation/ActionHandler/InappActionHandler.swift index 22c9485e..108014db 100644 --- a/Mindbox/InAppMessages/Presentation/ActionHandler/InappActionHandler.swift +++ b/Mindbox/InAppMessages/Presentation/ActionHandler/InappActionHandler.swift @@ -14,13 +14,13 @@ protocol InAppActionHandlerProtocol { for id: String, onTap: @escaping InAppMessageTapAction, close: @escaping () -> Void - ) -> Void + ) } final class InAppActionHandler: InAppActionHandlerProtocol { - + private let actionUseCaseFactory: UseCaseFactoryProtocol - + init(actionUseCaseFactory: UseCaseFactoryProtocol) { self.actionUseCaseFactory = actionUseCaseFactory } @@ -29,11 +29,11 @@ final class InAppActionHandler: InAppActionHandlerProtocol { for id: String, onTap: @escaping InAppMessageTapAction, close: @escaping () -> Void) { - + guard let action = actionUseCaseFactory.createUseCase(action: action) else { - return + return } - + action.onTapAction(id: id, onTap: onTap, close: close) } } diff --git a/Mindbox/InAppMessages/Presentation/InAppPresentationManager.swift b/Mindbox/InAppMessages/Presentation/InAppPresentationManager.swift index 222ad8ef..df8b2e8f 100644 --- a/Mindbox/InAppMessages/Presentation/InAppPresentationManager.swift +++ b/Mindbox/InAppMessages/Presentation/InAppPresentationManager.swift @@ -39,16 +39,16 @@ enum InAppPresentationError { typealias InAppMessageTapAction = (_ tapLink: URL?, _ payload: String) -> Void final class InAppPresentationManager: InAppPresentationManagerProtocol { - + private let actionHandler: InAppActionHandlerProtocol private let displayUseCase: PresentationDisplayUseCase - + init(actionHandler: InAppActionHandlerProtocol, displayUseCase: PresentationDisplayUseCase) { self.actionHandler = actionHandler self.displayUseCase = displayUseCase } - + func present( inAppFormData: InAppFormData, onPresented: @escaping () -> Void, @@ -61,7 +61,7 @@ final class InAppPresentationManager: InAppPresentationManagerProtocol { onError(.failed("Self guard not passed.")) return } - + self.displayUseCase.presentInAppUIModel(model: inAppFormData, onPresented: { self.displayUseCase.onPresented(id: inAppFormData.inAppId, onPresented) @@ -70,7 +70,7 @@ final class InAppPresentationManager: InAppPresentationManagerProtocol { let action = action else { return } - + self.actionHandler.handleAction(action, for: inAppFormData.inAppId, onTap: onTapAction, close: { self.displayUseCase.dismissInAppUIModel(onClose: onPresentationCompleted) }) diff --git a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/PresentationClickTracker.swift b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/PresentationClickTracker.swift index 2065c644..09a45aa4 100644 --- a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/PresentationClickTracker.swift +++ b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/PresentationClickTracker.swift @@ -20,15 +20,15 @@ protocol PresentationActionUseCaseProtocol { class PresentationClickTracker { private let tracker: InAppMessagesTrackerProtocol private var clickTracked = false - + init(tracker: InAppMessagesTrackerProtocol) { self.tracker = tracker } - + func trackClick(id: String) { Logger.common(message: "Presentation completed", level: .debug, category: .inAppMessages) guard !clickTracked else { return } - + do { try tracker.trackClick(id: id) clickTracked = true diff --git a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/PushPermissionActionUseCase.swift b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/PushPermissionActionUseCase.swift index cdf24aca..54f9f8bd 100644 --- a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/PushPermissionActionUseCase.swift +++ b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/PushPermissionActionUseCase.swift @@ -12,15 +12,15 @@ import UserNotifications import UIKit final class PushPermissionActionUseCase: PresentationActionUseCaseProtocol { - + private let tracker: PresentationClickTracker private let model: PushPermissionLayerAction - + init(tracker: PresentationClickTracker, model: PushPermissionLayerAction) { self.tracker = tracker self.model = model } - + func onTapAction( id: String, onTap: @escaping InAppMessageTapAction, @@ -32,7 +32,7 @@ final class PushPermissionActionUseCase: PresentationActionUseCaseProtocol { onTap(nil, model.intentPayload) close() } - + func requestOrOpenSettingsForNotifications() { UNUserNotificationCenter.current().getNotificationSettings { settings in Logger.common(message: "Status of notification permission: \(settings.authorizationStatus.description)", level: .debug, category: .inAppMessages) @@ -49,14 +49,14 @@ final class PushPermissionActionUseCase: PresentationActionUseCaseProtocol { } } } - + private func pushNotificationRequest() { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in if let error = error { Logger.common(message: "Error requesting notification permissions: \(error)", level: .error, category: .inAppMessages) return } - + if granted { DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() @@ -67,7 +67,7 @@ final class PushPermissionActionUseCase: PresentationActionUseCaseProtocol { } } } - + private func openPushNotificationSettings() { DispatchQueue.main.async { let settingsUrl: URL? @@ -76,15 +76,15 @@ final class PushPermissionActionUseCase: PresentationActionUseCaseProtocol { } else { settingsUrl = URL(string: UIApplication.openSettingsURLString) } - + guard let settingsUrl = settingsUrl, UIApplication.shared.canOpenURL(settingsUrl) else { Logger.common(message: "Failed to parse the settings URL or encountered an issue opening it.", level: .debug, category: .inAppMessages) return } - + if UIApplication.shared.canOpenURL(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/PresentationActionUseCase/RedirectURLActionUseCase.swift b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/RedirectURLActionUseCase.swift index 25a0e8ed..dc299866 100644 --- a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/RedirectURLActionUseCase.swift +++ b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationActionUseCase/RedirectURLActionUseCase.swift @@ -18,7 +18,7 @@ final class RedirectURLActionUseCase: PresentationActionUseCaseProtocol { self.tracker = tracker self.model = model } - + func onTapAction( id: String, onTap: @escaping InAppMessageTapAction, diff --git a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/PresentationDisplayUseCase.swift b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/PresentationDisplayUseCase.swift index 165416fd..ed75d06a 100644 --- a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/PresentationDisplayUseCase.swift +++ b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/PresentationDisplayUseCase.swift @@ -22,37 +22,39 @@ final class PresentationDisplayUseCase { } func presentInAppUIModel(model: InAppFormData, onPresented: @escaping () -> Void, onTapAction: @escaping (ContentBackgroundLayerAction?) -> Void, onClose: @escaping () -> Void) { - + changeType(model: model.content) - + guard let window = presentationStrategy?.getWindow() else { Logger.common(message: "In-app window creating failed") return } - + Logger.common(message: "PresentationDisplayUseCase window: \(window)") - + guard let factory = self.factory else { Logger.common(message: "Factory does not exists.", level: .error, category: .general) return } - - guard let viewController = factory.create(model: model.content, - id: model.inAppId, - imagesDict: model.imagesDict, - firstImageValue: model.firstImageValue, - onPresented: onPresented, - onTapAction: onTapAction, - onClose: onClose) else { + + let parameters = ViewFactoryParameters(model: model.content, + id: model.inAppId, + imagesDict: model.imagesDict, + firstImageValue: model.firstImageValue, + onPresented: onPresented, + onTapAction: onTapAction, + onClose: onClose) + + guard let viewController = factory.create(with: parameters) else { return } - + presentedVC = viewController - + if let image = model.imagesDict[model.firstImageValue] { presentationStrategy?.setupWindowFrame(model: model.content, imageSize: image.size) } - + presentationStrategy?.present(id: model.inAppId, in: window, using: viewController) } @@ -67,7 +69,7 @@ final class PresentationDisplayUseCase { self.presentationStrategy = nil self.factory = nil } - + func onPresented(id: String, _ completion: @escaping () -> Void) { do { try tracker.trackView(id: id) @@ -77,7 +79,7 @@ final class PresentationDisplayUseCase { } completion() } - + private func changeType(model: MindboxFormVariant) { switch model { case .modal: diff --git a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/ModalPresentationStrategy.swift b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/ModalPresentationStrategy.swift index 2d5c690a..0fa1676c 100644 --- a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/ModalPresentationStrategy.swift +++ b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/ModalPresentationStrategy.swift @@ -11,11 +11,11 @@ import MindboxLogger final class ModalPresentationStrategy: PresentationStrategyProtocol { var window: UIWindow? - + func getWindow() -> UIWindow? { return makeInAppMessageWindow() } - + func present(id: String, in window: UIWindow, using viewController: UIViewController) { window.rootViewController = viewController window.isHidden = false @@ -27,11 +27,11 @@ final class ModalPresentationStrategy: PresentationStrategyProtocol { viewController.view.window?.rootViewController = nil Logger.common(message: "In-app modal presentation dismissed", level: .debug, category: .inAppMessages) } - + func setupWindowFrame(model: MindboxFormVariant, imageSize: CGSize) { // Not need to setup. } - + private func makeInAppMessageWindow() -> UIWindow? { let window: UIWindow? if #available(iOS 13.0, *) { @@ -52,7 +52,7 @@ final class ModalPresentationStrategy: PresentationStrategyProtocol { return windowScene } } - + return UIApplication.shared.connectedScenes.first as? UIWindowScene } diff --git a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/PresentationStrategyProtocol.swift b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/PresentationStrategyProtocol.swift index 806b8424..76c0e724 100644 --- a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/PresentationStrategyProtocol.swift +++ b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/PresentationStrategyProtocol.swift @@ -10,7 +10,7 @@ import UIKit protocol PresentationStrategyProtocol { var window: UIWindow? { get set } - + func getWindow() -> UIWindow? func present(id: String, in window: UIWindow, using viewController: UIViewController) func dismiss(viewController: UIViewController) diff --git a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/SnackbarPresentationStrategy.swift b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/SnackbarPresentationStrategy.swift index afe0b83c..27e8d647 100644 --- a/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/SnackbarPresentationStrategy.swift +++ b/Mindbox/InAppMessages/Presentation/Manager/UseCases/PresentationDisplayUseCase/Strategy/SnackbarPresentationStrategy.swift @@ -10,13 +10,13 @@ import UIKit import MindboxLogger final class SnackbarPresentationStrategy: PresentationStrategyProtocol { - + enum Constants { static let oneThirdScreenHeight: CGFloat = UIScreen.main.bounds.height / 3.0 } - + var window: UIWindow? - + func getWindow() -> UIWindow? { let window: UIWindow let screenBounds = UIScreen.main.bounds @@ -31,7 +31,7 @@ final class SnackbarPresentationStrategy: PresentationStrategyProtocol { } else { window = UIWindow(frame: UIScreen.main.bounds) } - + window.frame = windowFrame window.backgroundColor = .clear window.windowLevel = .normal + 3 @@ -52,7 +52,7 @@ final class SnackbarPresentationStrategy: PresentationStrategyProtocol { viewController.view.frame = topController.view.frame topController.addChild(viewController) topController.view.addSubview(viewController.view) - + viewController.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ viewController.view.topAnchor.constraint(equalTo: topController.view.topAnchor), @@ -60,7 +60,7 @@ final class SnackbarPresentationStrategy: PresentationStrategyProtocol { viewController.view.leadingAnchor.constraint(equalTo: topController.view.leadingAnchor), viewController.view.trailingAnchor.constraint(equalTo: topController.view.trailingAnchor) ]) - + viewController.didMove(toParent: topController) Logger.common(message: "In-app snackbar with id \(id) presented", level: .info, category: .inAppMessages) } else { @@ -73,7 +73,7 @@ final class SnackbarPresentationStrategy: PresentationStrategyProtocol { viewController.removeFromParent() Logger.common(message: "In-app snackbar presentation dismissed", level: .debug, category: .inAppMessages) } - + func setupWindowFrame(model: MindboxFormVariant, imageSize: CGSize) { switch model { case .snackbar(let snackbarFormVariant): @@ -97,7 +97,7 @@ final class SnackbarPresentationStrategy: PresentationStrategyProtocol { private extension SnackbarPresentationStrategy { func getSafeAreaInset(gravity: GravityVerticalType) -> CGFloat { var safeAreaInset: CGFloat = 0 - + if gravity == .bottom { safeAreaInset = window?.safeAreaInsets.bottom ?? 0 } else if gravity == .top { @@ -106,7 +106,7 @@ private extension SnackbarPresentationStrategy { return safeAreaInset } - + func getYPosition(gravity: GravityVerticalType, finalHeight: CGFloat, safeAreaInset: CGFloat) -> CGFloat { var y = UIScreen.main.bounds.height - finalHeight if gravity == .bottom { @@ -114,7 +114,7 @@ private extension SnackbarPresentationStrategy { } else if gravity == .top { y = safeAreaInset } - + return y } } diff --git a/Mindbox/InAppMessages/Presentation/Views/CommonViews/CrossView.swift b/Mindbox/InAppMessages/Presentation/Views/CommonViews/CrossView.swift index 7f6ebad0..9130a568 100644 --- a/Mindbox/InAppMessages/Presentation/Views/CommonViews/CrossView.swift +++ b/Mindbox/InAppMessages/Presentation/Views/CommonViews/CrossView.swift @@ -9,7 +9,7 @@ import UIKit class CrossView: UIView { - + var lineColor: UIColor = .black var lineWidth: CGFloat = 1.0 var padding: CGFloat = 2.0 @@ -18,21 +18,21 @@ class CrossView: UIView { super.init(frame: frame) setupDefaults() } - + required init?(coder: NSCoder) { super.init(coder: coder) setupDefaults() } - + convenience init(lineColorHex: String?, lineWidth: Int?) { self.init() setupView(lineColorHex: lineColorHex, lineWidth: lineWidth) } - + private func setupDefaults() { backgroundColor = .clear } - + private func setupView(lineColorHex: String?, lineWidth: Int?) { let color = lineColorHex ?? "#000000" let width = lineWidth ?? 1 @@ -44,7 +44,7 @@ class CrossView: UIView { let path = drawCross(in: rect) strokePath(path) } - + private func drawCross(in rect: CGRect) -> UIBezierPath { let path = UIBezierPath() path.lineWidth = lineWidth @@ -64,16 +64,16 @@ class CrossView: UIView { path.addLine(to: rightBottom) path.move(to: rightTop) path.addLine(to: leftBottom) - + return path } - + private func strokePath(_ path: UIBezierPath) { lineColor.setStroke() path.stroke() addShapeLayer(for: path) } - + private func addShapeLayer(for path: UIBezierPath) { let shapeLayer = CAShapeLayer() shapeLayer.path = path.cgPath diff --git a/Mindbox/InAppMessages/Presentation/Views/CommonViews/ElementFactory/CloseButtonFactory/CloseButtonElementFactory.swift b/Mindbox/InAppMessages/Presentation/Views/CommonViews/ElementFactory/CloseButtonFactory/CloseButtonElementFactory.swift index 0f4eed2a..2abed279 100644 --- a/Mindbox/InAppMessages/Presentation/Views/CommonViews/ElementFactory/CloseButtonFactory/CloseButtonElementFactory.swift +++ b/Mindbox/InAppMessages/Presentation/Views/CommonViews/ElementFactory/CloseButtonFactory/CloseButtonElementFactory.swift @@ -17,22 +17,22 @@ class CloseButtonElementFactory: ElementFactory { let closeRecognizer = UILongPressGestureRecognizer(target: controller, action: #selector(controller.onCloseButton)) closeRecognizer.minimumPressDuration = 0 closeButton.addGestureRecognizer(closeRecognizer) - + return closeButton } - + return nil } - + func setupConstraints(for view: UIView, from element: ContentElement, in parentView: UIView) { if case .closeButton(let closeButtonElement) = element { let size = closeButtonElement.size let top = closeButtonElement.position.margin.top let right = closeButtonElement.position.margin.right - + let horizontalOffset = (parentView.frame.width - CGFloat(size.width)) * right let verticalOffset = (parentView.frame.height - CGFloat(size.height)) * top - + if size.kind == .dp { view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ diff --git a/Mindbox/InAppMessages/Presentation/Views/CommonViews/InAppImageOnlyView.swift b/Mindbox/InAppMessages/Presentation/Views/CommonViews/InAppImageOnlyView.swift index d991fa97..7758c465 100644 --- a/Mindbox/InAppMessages/Presentation/Views/CommonViews/InAppImageOnlyView.swift +++ b/Mindbox/InAppMessages/Presentation/Views/CommonViews/InAppImageOnlyView.swift @@ -33,9 +33,9 @@ final class InAppImageOnlyView: UIView { Logger.common(message: "[Error]: \(#function) at line \(#line) of \(#file)", level: .error) return } - + Logger.common(message: "InAppImageOnlyView custom init started.") - + imageView.contentMode = .scaleAspectFill imageView.image = image diff --git a/Mindbox/InAppMessages/Presentation/Views/CommonViews/LayersFactory/ImageLayer/ImageLayerFactory.swift b/Mindbox/InAppMessages/Presentation/Views/CommonViews/LayersFactory/ImageLayer/ImageLayerFactory.swift index eda52d9d..a85d3738 100644 --- a/Mindbox/InAppMessages/Presentation/Views/CommonViews/LayersFactory/ImageLayer/ImageLayerFactory.swift +++ b/Mindbox/InAppMessages/Presentation/Views/CommonViews/LayersFactory/ImageLayer/ImageLayerFactory.swift @@ -22,7 +22,7 @@ class ImageLayerFactory: LayerFactory { Logger.common(message: "ImageLayerFactory return nil.") return nil } - + func setupConstraints(for view: UIView, in parentView: UIView) { view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ @@ -32,7 +32,7 @@ class ImageLayerFactory: LayerFactory { view.widthAnchor.constraint(equalTo: view.heightAnchor, multiplier: 3 / 4) ]) } - + func setupConstraintsSnackbar(for view: UIView, in parentView: UIView) { view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ diff --git a/Mindbox/InAppMessages/Presentation/Views/ModalView/ModalViewController.swift b/Mindbox/InAppMessages/Presentation/Views/ModalView/ModalViewController.swift index a7e00e1e..72c61a1b 100644 --- a/Mindbox/InAppMessages/Presentation/Views/ModalView/ModalViewController.swift +++ b/Mindbox/InAppMessages/Presentation/Views/ModalView/ModalViewController.swift @@ -15,37 +15,42 @@ protocol InappViewControllerProtocol { var layersFactories: [ContentBackgroundLayerType: LayerFactory] { get } } -@objc protocol GestureHandler { - @objc func imageTapped(_ sender: UITapGestureRecognizer) - @objc func onCloseButton(_ gesture: UILongPressGestureRecognizer) +@objc +protocol GestureHandler { + + @objc + func imageTapped(_ sender: UITapGestureRecognizer) + + @objc + func onCloseButton(_ gesture: UILongPressGestureRecognizer) } final class ModalViewController: UIViewController, InappViewControllerProtocol { - + // MARK: InappViewControllerProtocol - + var layers = [UIView]() var elements = [UIView]() let elementFactories: [ContentElementType: ElementFactory] = [ .closeButton: CloseButtonElementFactory() ] - + let layersFactories: [ContentBackgroundLayerType: LayerFactory] = [ .image: ImageLayerFactory() ] - + // MARK: Private properties - + private let model: ModalFormVariant private let id: String private let imagesDict: [String: UIImage] - + private let onPresented: () -> Void private let onClose: () -> Void private let onTapAction: (ContentBackgroundLayerAction?) -> Void - + private var viewWillAppearWasCalled = false - + private enum Constants { static let defaultAlphaBackgroundColor: CGFloat = 0.2 } @@ -84,13 +89,13 @@ final class ModalViewController: UIViewController, InappViewControllerProtocol { ) view.addGestureRecognizer(onTapDimmedViewGesture) view.isUserInteractionEnabled = true - + setupLayers() } - + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - + if let inappView = layers.first(where: { $0 is InAppImageOnlyView }) { Logger.common(message: "In-app modal height: [\(inappView.frame.height) pt]") Logger.common(message: "In-app modal width: [\(inappView.frame.width) pt]") @@ -99,7 +104,7 @@ final class ModalViewController: UIViewController, InappViewControllerProtocol { elements.forEach({ $0.removeFromSuperview() }) - + setupElements() } @@ -112,10 +117,11 @@ final class ModalViewController: UIViewController, InappViewControllerProtocol { // MARK: Private methods - @objc private func onTapDimmedView() { + @objc + private func onTapDimmedView() { onClose() } - + private func setupLayers() { let layers = model.content.background.layers for layer in layers { @@ -133,7 +139,7 @@ final class ModalViewController: UIViewController, InappViewControllerProtocol { } } } - + private func setupElements() { guard let elements = model.content.elements, let inappView = layers.first(where: { $0 is InAppImageOnlyView }) else { @@ -156,20 +162,22 @@ final class ModalViewController: UIViewController, InappViewControllerProtocol { // MARK: - GestureHandler extension ModalViewController: GestureHandler { - @objc func imageTapped(_ sender: UITapGestureRecognizer) { + @objc + func imageTapped(_ sender: UITapGestureRecognizer) { guard let imageView = sender.view as? InAppImageOnlyView else { return } - + let action = imageView.action onTapAction(action) } - - @objc func onCloseButton(_ gesture: UILongPressGestureRecognizer) { + + @objc + func onCloseButton(_ gesture: UILongPressGestureRecognizer) { guard let crossView = gesture.view else { return } - + let location = gesture.location(in: crossView) let isInsideCrossView = crossView.bounds.contains(location) if gesture.state == .ended && isInsideCrossView { diff --git a/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarView.swift b/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarView.swift index 09937358..e7ef1054 100644 --- a/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarView.swift +++ b/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarView.swift @@ -18,14 +18,14 @@ class SnackbarView: UIView { private let onClose: () -> Void private let animationTime: TimeInterval - + private var safeAreaInset: (top: CGFloat, bottom: CGFloat) { ( top: window?.safeAreaInsets.top ?? Constants.defaultSafeAreaTopInset, bottom: window?.safeAreaInsets.bottom ?? Constants.defaultSafeAreaBottomInset ) } - + private enum Constants { static let defaultAnimationTime: TimeInterval = 0.3 static let swipeThresholdFraction: CGFloat = 0.5 @@ -46,14 +46,14 @@ class SnackbarView: UIView { Logger.common(message: "SnackbarView inited.") setupPanGesture() } - + required init?(coder: NSCoder) { Logger.common(message: "SnackbarView init(coder:) has not been implemented.") fatalError("init(coder:) has not been implemented") } - + // MARK: Public methods - + public func hide(animated: Bool = true, completion: (() -> Void)? = nil) { animateHide(completion: { self.onClose() @@ -68,7 +68,8 @@ class SnackbarView: UIView { self.addGestureRecognizer(panGesture) } - @objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) { + @objc + private func handlePanGesture(_ gesture: UIPanGestureRecognizer) { let translation = gesture.translation(in: self) switch gesture.state { case .changed: @@ -90,7 +91,7 @@ class SnackbarView: UIView { private func finalizeGesture(translation: CGPoint) { let threshold = frame.height * Constants.swipeThresholdFraction + (swipeDirection == .up ? safeAreaInset.top : safeAreaInset.bottom) - + if ((swipeDirection == .up && translation.y < Constants.verticalMovementThreshold) || (swipeDirection == .down && translation.y > Constants.verticalMovementThreshold) ) && abs(translation.y) > threshold { @@ -104,9 +105,9 @@ class SnackbarView: UIView { private func animateHide(completion: @escaping () -> Void, animated: Bool) { if animated { - UIView.animate(withDuration: animationTime, animations: { + UIView.animate(withDuration: animationTime) { self.setHiddenTransform() - }) { _ in + } completion: { _ in completion() } } else { @@ -117,7 +118,7 @@ class SnackbarView: UIView { private func setHiddenTransform() { let yOffset: CGFloat - + switch swipeDirection { case .up: yOffset = -(frame.height + safeAreaInset.top) @@ -126,7 +127,7 @@ class SnackbarView: UIView { default: yOffset = Constants.noVerticalTranslation } - + self.transform = CGAffineTransform(translationX: Constants.noHorizontalTranslation, y: yOffset) } } diff --git a/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarViewController.swift b/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarViewController.swift index c6341c87..66b6c41e 100644 --- a/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarViewController.swift +++ b/Mindbox/InAppMessages/Presentation/Views/SnackbarView/SnackbarViewController.swift @@ -10,39 +10,39 @@ import UIKit import MindboxLogger class SnackbarViewController: UIViewController, InappViewControllerProtocol { - + // MARK: InappViewControllerProtocol - + var layers = [UIView]() var elements = [UIView]() - + let elementFactories: [ContentElementType: ElementFactory] = [ .closeButton: CloseButtonElementFactory() ] - + let layersFactories: [ContentBackgroundLayerType: LayerFactory] = [ .image: ImageLayerFactory() ] - + // MARK: Internal properties var edgeConstraint: NSLayoutConstraint? - + let snackbarView: SnackbarView let model: SnackbarFormVariant - + var leftOffset: CGFloat { return model.content.position.margin.left } - + var rightOffset: CGFloat { return model.content.position.margin.right } - + var swipeDirection: UISwipeGestureRecognizer.Direction { return .down } - + enum Constants { static let animationDuration: TimeInterval = 0.5 static let screenPart: CGFloat = 3.0 @@ -51,15 +51,15 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { static let noTopOffset: CGFloat = .zero static let noSize: CGSize = .zero } - + // MARK: Private properties private let imagesDict: [String: UIImage] private let firstImageValue: String - + private let onPresented: () -> Void private let onTapAction: (ContentBackgroundLayerAction?) -> Void - + private var hasSetupLayers = false private var hasSetupElements = false @@ -87,7 +87,7 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { Logger.common(message: "SnackbarViewController init(coder:) has not been implemented.") fatalError("init(coder:) has not been implemented") } - + // MARK: Life cycle override func viewDidLoad() { @@ -97,10 +97,10 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { snackbarView.translatesAutoresizingMaskIntoConstraints = false snackbarView.isUserInteractionEnabled = true } - + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - + if !hasSetupLayers { hasSetupLayers = true setupConstraints() @@ -120,28 +120,28 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { } } } - + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) animateConstraints(withDuration: Constants.animationDuration) onPresented() } - + // MARK: Internal methods - + func setViewFrame(with height: CGFloat) { Logger.common(message: "Method setViewFrame must be overridden in subclass.") } - + func setupEdgeConstraint(with height: CGFloat) { Logger.common(message: "Method setupEdgeConstraint must be overridden in subclass.") } - + // MARK: Private methods private func setupLayers() { let layers = model.content.background.layers - + for layer in layers { if let factory = layersFactories[layer.layerType] { if case .image(let imageContentBackgroundLayer) = layer { @@ -157,8 +157,8 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { } } } - - private func setupElements() { + + private func setupElements() { for element in model.content.elements { if let factory = elementFactories[element.elementType] { let elementView = factory.create(from: element, with: self) @@ -170,32 +170,32 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { } } } - + private func animateConstraints(withDuration duration: TimeInterval) { view.layoutIfNeeded() - + UIView.animate(withDuration: duration) { self.edgeConstraint?.constant = Constants.defaultEdgeConstraint self.view.layoutIfNeeded() } } - + private func setupConstraints() { guard let image = imagesDict[firstImageValue] else { Logger.common(message: "[Error]: \(#function) at line \(#line) of \(#file)", level: .error) return } - + let width = view.layer.frame.width let heightMultiplier = width / image.size.width let imageHeight = image.size.height * heightMultiplier let finalHeight = (imageHeight < Constants.oneThirdScreenHeight) ? imageHeight : Constants.oneThirdScreenHeight - + setViewFrame(with: finalHeight) - + snackbarView.swipeDirection = swipeDirection snackbarView.translatesAutoresizingMaskIntoConstraints = false - + setupLayoutConstraints(with: finalHeight) setupEdgeConstraint(with: finalHeight) } @@ -204,7 +204,7 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { NSLayoutConstraint.activate([ snackbarView.leadingAnchor.constraint(equalTo: view.leadingAnchor), snackbarView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - snackbarView.heightAnchor.constraint(equalToConstant: height), + snackbarView.heightAnchor.constraint(equalToConstant: height) ]) } } @@ -212,22 +212,24 @@ class SnackbarViewController: UIViewController, InappViewControllerProtocol { // MARK: - GestureHandler extension SnackbarViewController: GestureHandler { - @objc func imageTapped(_ sender: UITapGestureRecognizer) { + @objc + func imageTapped(_ sender: UITapGestureRecognizer) { guard let imageView = sender.view as? InAppImageOnlyView else { Logger.common(message: "[Error]: \(#function) at line \(#line) of \(#file)", level: .error) return } - + let action = imageView.action onTapAction(action) } - - @objc func onCloseButton(_ gesture: UILongPressGestureRecognizer) { + + @objc + func onCloseButton(_ gesture: UILongPressGestureRecognizer) { guard let crossView = gesture.view else { Logger.common(message: "[Error]: \(#function) at line \(#line) of \(#file)", level: .error) return } - + let location = gesture.location(in: crossView) let isInsideCrossView = crossView.bounds.contains(location) if gesture.state == .ended && isInsideCrossView { @@ -262,11 +264,11 @@ class BottomSnackbarViewController: SnackbarViewController { override var swipeDirection: UISwipeGestureRecognizer.Direction { return .down } - + override func setViewFrame(with height: CGFloat) { let screenHeight = UIScreen.main.bounds.height let safeAreaBottomOffset: CGFloat = view.safeAreaInsets.bottom - + let finalHeight = height + safeAreaBottomOffset self.view.frame = CGRect(x: leftOffset, y: screenHeight - finalHeight, diff --git a/Mindbox/InAppMessages/Presentation/Views/ViewFactory/ModalViewFactory.swift b/Mindbox/InAppMessages/Presentation/Views/ViewFactory/ModalViewFactory.swift index 07a6a449..b52a632e 100644 --- a/Mindbox/InAppMessages/Presentation/Views/ViewFactory/ModalViewFactory.swift +++ b/Mindbox/InAppMessages/Presentation/Views/ViewFactory/ModalViewFactory.swift @@ -9,26 +9,21 @@ import UIKit class ModalViewFactory: ViewFactoryProtocol { + weak var myViewController: UIViewController? - - func create(model: MindboxFormVariant, - id: String, - imagesDict: [String: UIImage], - firstImageValue: String, - onPresented: @escaping () -> Void, - onTapAction: @escaping (ContentBackgroundLayerAction?) -> Void, - onClose: @escaping () -> Void) -> UIViewController? { - if case .modal(let modalFormVariant) = model { + + func create(with params: ViewFactoryParameters) -> UIViewController? { + if case .modal(let modalFormVariant) = params.model { let viewController = ModalViewController(model: modalFormVariant, - id: id, - imagesDict: imagesDict, - onPresented: onPresented, - onTapAction: onTapAction, - onClose: onClose) + id: params.id, + imagesDict: params.imagesDict, + onPresented: params.onPresented, + onTapAction: params.onTapAction, + onClose: params.onClose) myViewController = viewController return viewController } - + return nil } } diff --git a/Mindbox/InAppMessages/Presentation/Views/ViewFactory/SnackbarViewFactory.swift b/Mindbox/InAppMessages/Presentation/Views/ViewFactory/SnackbarViewFactory.swift index b009d91f..f691cd91 100644 --- a/Mindbox/InAppMessages/Presentation/Views/ViewFactory/SnackbarViewFactory.swift +++ b/Mindbox/InAppMessages/Presentation/Views/ViewFactory/SnackbarViewFactory.swift @@ -13,28 +13,38 @@ import MindboxLogger class SnackbarViewFactory: ViewFactoryProtocol { weak var viewController: UIViewController? - - func create(model: MindboxFormVariant, id: String, imagesDict: [String: UIImage], firstImageValue: String, onPresented: @escaping () -> Void, onTapAction: @escaping (ContentBackgroundLayerAction?) -> Void, onClose: @escaping () -> Void) -> UIViewController? { - if case .snackbar(let snackbarFormVariant) = model { + + func create(with params: ViewFactoryParameters) -> UIViewController? { + if case .snackbar(let snackbarFormVariant) = params.model { if let gravity = snackbarFormVariant.content.position.gravity?.vertical { var snackbarViewController: UIViewController? - let snackbarView = SnackbarView(onClose: onClose) + let snackbarView = SnackbarView(onClose: params.onClose) switch gravity { case .top: - snackbarViewController = TopSnackbarViewController(model: snackbarFormVariant, imagesDict: imagesDict, snackbarView: snackbarView, firstImageValue: firstImageValue, onPresented: onPresented, onTapAction: onTapAction) + snackbarViewController = TopSnackbarViewController(model: snackbarFormVariant, + imagesDict: params.imagesDict, + snackbarView: snackbarView, + firstImageValue: params.firstImageValue, + onPresented: params.onPresented, + onTapAction: params.onTapAction) case .bottom: - snackbarViewController = BottomSnackbarViewController(model: snackbarFormVariant, imagesDict: imagesDict, snackbarView: snackbarView, firstImageValue: firstImageValue, onPresented: onPresented, onTapAction: onTapAction) + snackbarViewController = BottomSnackbarViewController(model: snackbarFormVariant, + imagesDict: params.imagesDict, + snackbarView: snackbarView, + firstImageValue: params.firstImageValue, + onPresented: params.onPresented, + onTapAction: params.onTapAction) default: Logger.common(message: "SnackbarViewFactory controller is nil.") return nil } - + self.viewController = snackbarViewController - + return viewController } } - + Logger.common(message: "SnackbarViewFactory create returns nil.") return nil } diff --git a/Mindbox/InAppMessages/Presentation/Views/ViewFactory/ViewFactoryProtocol.swift b/Mindbox/InAppMessages/Presentation/Views/ViewFactory/ViewFactoryProtocol.swift index edbbb402..7074f1c3 100644 --- a/Mindbox/InAppMessages/Presentation/Views/ViewFactory/ViewFactoryProtocol.swift +++ b/Mindbox/InAppMessages/Presentation/Views/ViewFactory/ViewFactoryProtocol.swift @@ -9,12 +9,16 @@ import UIKit import Foundation +struct ViewFactoryParameters { + let model: MindboxFormVariant + let id: String + let imagesDict: [String: UIImage] + let firstImageValue: String + let onPresented: () -> Void + let onTapAction: (ContentBackgroundLayerAction?) -> Void + let onClose: () -> Void +} + protocol ViewFactoryProtocol { - func create(model: MindboxFormVariant, - id: String, - imagesDict: [String: UIImage], - firstImageValue: String, - onPresented: @escaping () -> Void, - onTapAction: @escaping (ContentBackgroundLayerAction?) -> Void, - onClose: @escaping () -> Void) -> UIViewController? + func create(with params: ViewFactoryParameters) -> UIViewController? } diff --git a/Mindbox/MBConfiguration.swift b/Mindbox/MBConfiguration.swift index 15962a6d..f4490f6c 100644 --- a/Mindbox/MBConfiguration.swift +++ b/Mindbox/MBConfiguration.swift @@ -75,7 +75,7 @@ public struct MBConfiguration: Codable { self.previousDeviceUUID = "" } } - + self.subscribeCustomerIfCreated = subscribeCustomerIfCreated self.shouldCreateCustomer = shouldCreateCustomer self.imageLoadingMaxTimeInSeconds = imageLoadingMaxTimeInSeconds @@ -200,5 +200,4 @@ struct ConfigValidation { changedState = .none } } - } diff --git a/Mindbox/Mindbox.swift b/Mindbox/Mindbox.swift index dedcb431..97ed7593 100644 --- a/Mindbox/Mindbox.swift +++ b/Mindbox/Mindbox.swift @@ -55,8 +55,7 @@ public class Mindbox: NSObject { delegate?.mindBox(self, failedWithError: error) } } - - + /** A delegate for handling in-app messages. @@ -66,7 +65,7 @@ public class Mindbox: NSObject { - However, if the user wishes to customize the handling of in-app messages, it is mandatory to subscribe to this delegate. Customization can include handling specific user interactions, presenting messages in a custom format, or integrating more complex in-app message logic. */ - + public weak var inAppMessagesDelegate: InAppMessagesDelegate? { didSet { inAppMessagesManager?.delegate = inAppMessagesDelegate @@ -164,13 +163,13 @@ public class Mindbox: NSObject { .joined() Logger.common(message: "Did register for remote notifications with token: \(token)", level: .info, category: .notification) if let persistenceAPNSToken = persistenceStorage?.apnsToken { - + if persistenceStorage?.needUpdateInfoOnce ?? true { Logger.common(message: "APNS Token forced to update") coreController?.apnsTokenDidUpdate(token: token) return } - + guard persistenceAPNSToken != token else { Logger.common(message: "persistenceAPNSToken is equal to deviceToken. persistenceAPNSToken: \(persistenceAPNSToken)", level: .error, category: .notification) return @@ -236,7 +235,7 @@ public class Mindbox: NSObject { 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) @@ -283,7 +282,7 @@ public class Mindbox: NSObject { return } guard let jsonData = json.data(using: .utf8), - let _ = try? JSONSerialization.jsonObject(with: jsonData) else { + (try? JSONSerialization.jsonObject(with: jsonData)) != nil else { Logger.common(message: "Operation body is not valid JSON", level: .error, category: .notification) return } @@ -342,7 +341,7 @@ public class Mindbox: NSObject { return } guard let jsonData = json.data(using: .utf8), - let _ = try? JSONSerialization.jsonObject(with: jsonData) else { + (try? JSONSerialization.jsonObject(with: jsonData)) != nil else { Logger.common(message: "Operation body is not valid JSON", level: .error, category: .notification) return } @@ -447,7 +446,7 @@ public class Mindbox: NSObject { Logger.common(message: "Track Visit failed with error: \(error)", level: .error, category: .visit) } } - + /** Objc method for tracking application activities. @@ -508,7 +507,7 @@ public class Mindbox: NSObject { ) { guaranteedDeliveryManager?.backgroundTaskManager.application(application, performFetchWithCompletionHandler: completionHandler) } - + /** Determines whether the given notification is a Mindbox push notification. @@ -522,7 +521,7 @@ public class Mindbox: NSObject { let pushValidator = DI.injectOrFail(MindboxPushValidator.self) return pushValidator.isValid(item: userInfo) } - + /** Converts a `UNNotification` to a `MBPushNotification` model for Mindbox push notifications. @@ -540,10 +539,10 @@ public class Mindbox: NSObject { private var initError: Error? - private override init() { + override private init() { super.init() self.assembly() - self.persistenceStorage?.storeToFileBackgroundExecution() + self.persistenceStorage?.storeToFileBackgroundExecution() } func assembly() { @@ -560,19 +559,19 @@ public class Mindbox: NSObject { guard let inappMessageEventSender = DI.inject(InappMessageEventSender.self) else { return } - + inappMessageEventSender.sendEventIfEnabled(operationSystemName, jsonString: jsonString) } - @objc private func resetShownInApps() { + @objc + private func resetShownInApps() { persistenceStorage?.shownInappsDictionary = [:] } - - @objc private func eraseSessionStorage() { + + @objc + private func eraseSessionStorage() { sessionTemporaryStorage?.erase() } } -extension Mindbox: DefaultInappMessageDelegate { - -} +extension Mindbox: DefaultInappMessageDelegate {} diff --git a/Mindbox/MindboxAppDelegate.swift b/Mindbox/MindboxAppDelegate.swift index e1009b7c..3ba7d831 100644 --- a/Mindbox/MindboxAppDelegate.swift +++ b/Mindbox/MindboxAppDelegate.swift @@ -69,19 +69,19 @@ open class MindboxAppDelegate: NSObject, UNUserNotificationCenterDelegate, UIApp ) { Mindbox.shared.application(application, performFetchWithCompletionHandler: completionHandler) } - + open func applicationWillEnterForeground(_ application: UIApplication) { Logger.common(message: "Enter foreground", level: .info, category: .general) } - + open func applicationDidEnterBackground(_ application: UIApplication) { Logger.common(message: "Enter background", level: .info, category: .general) } - + open func applicationWillTerminate(_ application: UIApplication) { Logger.common(message: "App is closed", level: .info, category: .general) } - + open func applicationDidBecomeActive(_ application: UIApplication) { Logger.common(message: "App is active", level: .info, category: .general) } diff --git a/Mindbox/MindboxLogger/SDKLogsManager.swift b/Mindbox/MindboxLogger/SDKLogsManager.swift index 13d25e0f..196270ec 100644 --- a/Mindbox/MindboxLogger/SDKLogsManager.swift +++ b/Mindbox/MindboxLogger/SDKLogsManager.swift @@ -13,20 +13,20 @@ protocol SDKLogsManagerProtocol { } class SDKLogsManager: SDKLogsManagerProtocol { - + private enum Constants { static let logsSizeLimit = 800000 // In bytes } - + private let persistenceStorage: PersistenceStorage private let eventRepository: EventRepository - + init(persistenceStorage: PersistenceStorage, eventRepository: EventRepository) { self.persistenceStorage = persistenceStorage self.eventRepository = eventRepository } - + func sendLogs(logs: [Monitoring.Logs]) { guard !logs.isEmpty else { return } var handledLogsRequestIds = persistenceStorage.handledlogRequestIds ?? [] @@ -37,20 +37,18 @@ class SDKLogsManager: SDKLogsManagerProtocol { let to = log.to.toDate(withFormat: .utc) else { return } - + do { let body = try getBody(from: from, to: to, requestID: log.requestId) let event = Event(type: .sdkLogs, body: BodyEncoder(encodable: body).body) eventRepository.send(event: event) { _ in } - } catch { - - } + } catch {} } } - + self.persistenceStorage.handledlogRequestIds = handledLogsRequestIds } - + func getBody(from: Date, to: Date, requestID: String) throws -> SDKLogsRequest { let firstLog = try MBLoggerCoreDataManager.shared.getFirstLog() let lastLog = try MBLoggerCoreDataManager.shared.getLastLog() @@ -61,34 +59,34 @@ class SDKLogsManager: SDKLogsManagerProtocol { from: from, to: to) let actualLogs = actualLogs(allLogs: fetchedLogs) - + return SDKLogsRequest(status: status.value, requestId: requestID, content: actualLogs) } - + func getStatus(firstLog: LogMessage?, lastLog: LogMessage?, logs: [LogMessage], from: Date, to: Date) -> SDKLogsStatus { if let firstLog = firstLog, firstLog.timestamp > to { return .elderLog(date: firstLog.timestamp.toString(withFormat: .utc)) } else if let lastLog = lastLog, lastLog.timestamp < from { return .latestLog(date: lastLog.timestamp.toString(withFormat: .utc)) - } else if getLogsSize(logs) > Constants.logsSizeLimit { + } else if getLogsSize(logs) > Constants.logsSizeLimit { return .largeSize } else if logs.isEmpty { return .noData } - + return .ok } - + private func getLogsSize(_ logs: [LogMessage]) -> Int { return logs.reduce(0) { $0 + $1.description.utf8.count } } - + func actualLogs(allLogs: [LogMessage]) -> [String] { var logs = allLogs var totalSize = getLogsSize(logs) - + while totalSize > Constants.logsSizeLimit { for index in (0.. QueryParameters { ["transactionId": wrapper.event.transactionId, "deviceUUID": wrapper.deviceUUID, diff --git a/Mindbox/MindboxSceneDelegate.swift b/Mindbox/MindboxSceneDelegate.swift index e6fbb769..c434b8da 100644 --- a/Mindbox/MindboxSceneDelegate.swift +++ b/Mindbox/MindboxSceneDelegate.swift @@ -17,7 +17,7 @@ open class MindboxSceneDelegate: UIResponder, UIWindowSceneDelegate { options connectionOptions: UIScene.ConnectionOptions ) { Mindbox.shared.track(.launchScene(connectionOptions)) - guard let _ = (scene as? UIWindowScene) else { return } + guard (scene as? UIWindowScene) != nil else { return } } open func scene( diff --git a/Mindbox/Model/Bodies/CustomEvent.swift b/Mindbox/Model/Bodies/CustomEvent.swift index 398fe8f9..7b4608b4 100644 --- a/Mindbox/Model/Bodies/CustomEvent.swift +++ b/Mindbox/Model/Bodies/CustomEvent.swift @@ -11,9 +11,4 @@ import Foundation struct CustomEvent: Codable { let name: String let payload: String - - init(name: String, payload: String) { - self.name = name - self.payload = payload - } } diff --git a/Mindbox/Model/Bodies/MobileApplicationInfoUpdated.swift b/Mindbox/Model/Bodies/MobileApplicationInfoUpdated.swift index b30ff854..349e2794 100644 --- a/Mindbox/Model/Bodies/MobileApplicationInfoUpdated.swift +++ b/Mindbox/Model/Bodies/MobileApplicationInfoUpdated.swift @@ -13,15 +13,15 @@ struct MobileApplicationInfoUpdated: Codable { let notificationProvider: String let token: String - + var isTokenAvailable: Bool - + let isNotificationsEnabled: Bool - + let version: Int - + let instanceId: String - + init(token: String?, isNotificationsEnabled: Bool, version: Int, @@ -34,5 +34,4 @@ struct MobileApplicationInfoUpdated: Codable { self.instanceId = instanceId self.notificationProvider = notificationProvider } - } diff --git a/Mindbox/Model/Bodies/MobileApplicationInstalled.swift b/Mindbox/Model/Bodies/MobileApplicationInstalled.swift index a504e818..5afd37b8 100644 --- a/Mindbox/Model/Bodies/MobileApplicationInstalled.swift +++ b/Mindbox/Model/Bodies/MobileApplicationInstalled.swift @@ -13,24 +13,24 @@ struct MobileApplicationInstalled: Codable { let notificationProvider: String let token: String - + var isTokenAvailable: Bool - + let isNotificationsEnabled: Bool - + let installationId: String - + let subscribe: Bool - + let externalDeviceUUID: String? - + let version: Int - + let instanceId: String /// ID текущая таймзона устройства в формате IANA, например "Asia/Krasnoyarsk", null если недоступно let ianaTimeZone: String? - + init( token: String? = nil, isNotificationsEnabled: Bool, @@ -53,5 +53,4 @@ struct MobileApplicationInstalled: Codable { self.notificationProvider = notificationProvider self.ianaTimeZone = ianaTimeZone } - } diff --git a/Mindbox/Model/Bodies/TrackClick.swift b/Mindbox/Model/Bodies/TrackClick.swift index ebf59605..a1f9b590 100644 --- a/Mindbox/Model/Bodies/TrackClick.swift +++ b/Mindbox/Model/Bodies/TrackClick.swift @@ -9,14 +9,13 @@ import Foundation struct TrackClick: Codable { - + let messageUniqueKey: String - + let buttonUniqueKey: String? - + init(messageUniqueKey: String, buttonUniqueKey: String? = nil) { self.messageUniqueKey = messageUniqueKey self.buttonUniqueKey = buttonUniqueKey } - } diff --git a/Mindbox/Model/Bodies/TrackVisit.swift b/Mindbox/Model/Bodies/TrackVisit.swift index c66b5b8e..815056d0 100644 --- a/Mindbox/Model/Bodies/TrackVisit.swift +++ b/Mindbox/Model/Bodies/TrackVisit.swift @@ -16,7 +16,7 @@ struct TrackVisit: Codable { let requestUrl: URL? let source: TrackVisitSource? - + let sdkVersionNumeric: Int init(url: URL? = nil, source: TrackVisitSource? = nil) { diff --git a/Mindbox/Model/Common/IDS.swift b/Mindbox/Model/Common/IDS.swift index 42624c21..3e5a4951 100644 --- a/Mindbox/Model/Common/IDS.swift +++ b/Mindbox/Model/Common/IDS.swift @@ -13,7 +13,7 @@ import Foundation public struct IDS: Codable { public typealias DictionaryType = [String: String] - private var value: Dictionary = [:] + private var value: [String: String] = [:] public init(_ value: [String: String]? = nil, mindboxId: Int? = nil) { self.value = value ?? [:] diff --git a/Mindbox/Model/Common/MBDate.swift b/Mindbox/Model/Common/MBDate.swift index ec622458..938c3fc5 100644 --- a/Mindbox/Model/Common/MBDate.swift +++ b/Mindbox/Model/Common/MBDate.swift @@ -79,7 +79,7 @@ extension Date { public var asDateTime: DateTime { return DateTime(self) } - + public var asDateTimeWithSeconds: String { return DateTimeWithSeconds(self).string } diff --git a/Mindbox/Model/Event.swift b/Mindbox/Model/Event.swift index 87658ea1..b30c2b28 100644 --- a/Mindbox/Model/Event.swift +++ b/Mindbox/Model/Event.swift @@ -17,13 +17,13 @@ protocol EventProtocol { var type: Event.Operation { get } var isRetry: Bool { get } var body: String { get } - + init(type: Event.Operation, body: String) init?(_ event: CDEvent) } struct Event: EventProtocol { - + enum Operation: String { case installed = "MobilePush.ApplicationInstalled" case installedWithoutCustomer = "MobilePush.ApplicationInstalledWithoutCustomer" @@ -36,12 +36,12 @@ struct Event: EventProtocol { case inAppViewEvent = "Inapp.Show" case inAppClickEvent = "Inapp.Click" case inAppTargetingEvent = "Inapp.Targeting" - + case sdkLogs = "MobileSdk.Logs" } - + let transactionId: String - + var dateTimeOffset: Int64 { guard isRetry else { return 0 @@ -50,18 +50,18 @@ struct Event: EventProtocol { let ms = (Date().timeIntervalSince(enqueueDate) * 1000).rounded() return Int64(ms) } - + // Время добавляения персистентно в очередь событий let enqueueTimeStamp: Double - + let serialNumber: String? - + let type: Operation // True if first attempt to send was failed let isRetry: Bool // Data according to Operation let body: String - + init(type: Operation, body: String) { self.transactionId = UUID().uuidString self.enqueueTimeStamp = Date().timeIntervalSince1970 @@ -70,7 +70,7 @@ struct Event: EventProtocol { self.serialNumber = nil self.isRetry = false } - + init?(_ event: CDEvent) { guard let transactionId = event.transactionId else { Logger.common(message: "Event with transactionId: nil", level: .error, category: .database) diff --git a/Mindbox/Model/Helpers/SwiftyJSON.swift b/Mindbox/Model/Helpers/SwiftyJSON.swift index a4b8a47e..07957929 100644 --- a/Mindbox/Model/Helpers/SwiftyJSON.swift +++ b/Mindbox/Model/Helpers/SwiftyJSON.swift @@ -23,6 +23,8 @@ import Foundation import MindboxLogger +// swiftlint:disable all + // MARK: - Error // swiftlint:disable line_length diff --git a/Mindbox/Model/MindboxError/MindboxError.swift b/Mindbox/Model/MindboxError/MindboxError.swift index 3077c85a..33db0f4c 100644 --- a/Mindbox/Model/MindboxError/MindboxError.swift +++ b/Mindbox/Model/MindboxError/MindboxError.swift @@ -25,7 +25,7 @@ public enum MindboxError: LocalizedError { case connectionError /// Unhandled errors case unknown(Error) - + /// Return a formatted error message public var errorDescription: String? { switch self { @@ -46,7 +46,7 @@ public enum MindboxError: LocalizedError { return "No response received from the server, please check your internet connection." } } - + public var failureReason: String? { switch self { case let .serverError(error): @@ -65,7 +65,7 @@ public enum MindboxError: LocalizedError { return "Connection error" } } - + public var errorKey: String? { switch self { case let .internalError(error): @@ -74,7 +74,7 @@ public enum MindboxError: LocalizedError { return nil } } - + public init(_ error: InternalError) { self = .internalError(error) } @@ -183,7 +183,6 @@ public extension MindboxError { errorMessage: error.localizedDescription).convertToString() } } - } public struct InternalError: CustomStringConvertible { @@ -202,7 +201,7 @@ public struct InternalError: CustomStringConvertible { self.rawError = rawError self.statusCode = statusCode } - + public init( errorKey: ErrorKey, rawError: Error? = nil, @@ -212,7 +211,7 @@ public struct InternalError: CustomStringConvertible { self.rawError = rawError self.statusCode = statusCode } - + public init( errorKey: ErrorKey, reason: String? = nil, @@ -222,12 +221,12 @@ public struct InternalError: CustomStringConvertible { self.reason = reason self.suggestion = suggestion } - + public var description: String { var string: String = "" - + string += "\nError Key: \(errorKey)" - + if let rawError = rawError { if let rawError = rawError as? DecodingError { switch rawError { @@ -248,19 +247,19 @@ public struct InternalError: CustomStringConvertible { string += "\nError description: \(rawError.localizedDescription)" } } - + if let statusCode = statusCode { string += "\nStatus code: \(statusCode)" } - + if let reason = reason { string += "\nReason: \(reason)" } - + if let suggestion = suggestion { string += "\nSuggestion: \(suggestion)" } - + return string } } diff --git a/Mindbox/Model/MindboxError/ProtocolError.swift b/Mindbox/Model/MindboxError/ProtocolError.swift index 661aeb2c..217b3018 100644 --- a/Mindbox/Model/MindboxError/ProtocolError.swift +++ b/Mindbox/Model/MindboxError/ProtocolError.swift @@ -35,7 +35,7 @@ public struct ProtocolError: Codable, CustomStringConvertible { self.httpStatusCode = httpStatusCode self.errorId = errorId } - + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: Keys.self) httpStatusCode = try container.decode(Int.self, forKey: .httpStatusCode) diff --git a/Mindbox/Model/MindboxError/UnknownDecodable.swift b/Mindbox/Model/MindboxError/UnknownDecodable.swift index 93a533eb..5a9fdf06 100644 --- a/Mindbox/Model/MindboxError/UnknownDecodable.swift +++ b/Mindbox/Model/MindboxError/UnknownDecodable.swift @@ -23,7 +23,7 @@ public extension UnknownDecodable where Self: RawRepresentable, Self.RawValue == self = value } else if let unknownCase = unknownCase { self = unknownCase - + let error = MindboxError(InternalError(errorKey: .parsing, reason: "No match with value \(parsed). Set to .unknown", suggestion: "Add an .\(parsed) case")) Logger.error(error.asLoggerError()) } else { diff --git a/Mindbox/Model/NotificationPayload/NotificationsPayloads.swift b/Mindbox/Model/NotificationPayload/NotificationsPayloads.swift index 28ddab69..da19d480 100644 --- a/Mindbox/Model/NotificationPayload/NotificationsPayloads.swift +++ b/Mindbox/Model/NotificationPayload/NotificationsPayloads.swift @@ -9,35 +9,30 @@ 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/Mindbox/Model/OperationResponse.swift b/Mindbox/Model/OperationResponse.swift index 66bdb7e7..ca647a85 100644 --- a/Mindbox/Model/OperationResponse.swift +++ b/Mindbox/Model/OperationResponse.swift @@ -78,7 +78,6 @@ public extension OperationResponse { } } """ - } return jsonString } diff --git a/Mindbox/Model/Request/RecommendationRequest.swift b/Mindbox/Model/Request/RecommendationRequest.swift index d7ba166b..05781b14 100644 --- a/Mindbox/Model/Request/RecommendationRequest.swift +++ b/Mindbox/Model/Request/RecommendationRequest.swift @@ -15,7 +15,7 @@ open class RecommendationRequest: Encodable { self.area = area self.productCategory = productCategory } - + public init( limit: Int, area: AreaRequest? = nil, diff --git a/Mindbox/Model/Response/PoolResponse.swift b/Mindbox/Model/Response/PoolResponse.swift index 3d0aeef5..0e695829 100644 --- a/Mindbox/Model/Response/PoolResponse.swift +++ b/Mindbox/Model/Response/PoolResponse.swift @@ -4,7 +4,7 @@ open class PoolResponse: Codable { public let ids: IDS? public let name: String? public let poolDescription: String? - + enum CodingKeys: String, CodingKey { case ids case name diff --git a/Mindbox/Model/Response/PromoCodeResponse.swift b/Mindbox/Model/Response/PromoCodeResponse.swift index 22f9edd6..581a1574 100644 --- a/Mindbox/Model/Response/PromoCodeResponse.swift +++ b/Mindbox/Model/Response/PromoCodeResponse.swift @@ -25,5 +25,3 @@ public enum IssueStatusResponse: String, UnknownCodable { case issued = "Issued" case unknown } - - diff --git a/Mindbox/Network/Abstract/Route.swift b/Mindbox/Network/Abstract/Route.swift index 58e9a439..9712ef43 100644 --- a/Mindbox/Network/Abstract/Route.swift +++ b/Mindbox/Network/Abstract/Route.swift @@ -9,15 +9,14 @@ 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/Mindbox/Network/Helpers/DeviceModelHelper.swift b/Mindbox/Network/Helpers/DeviceModelHelper.swift index 13015332..8a37512d 100644 --- a/Mindbox/Network/Helpers/DeviceModelHelper.swift +++ b/Mindbox/Network/Helpers/DeviceModelHelper.swift @@ -10,7 +10,7 @@ import Foundation import UIKit.UIDevice struct DeviceModelHelper { - + static let os = UIDevice.current.systemName static let iOSVersion = UIDevice.current.systemVersion @@ -24,5 +24,4 @@ struct DeviceModelHelper { } return identifier }() - } diff --git a/Mindbox/Network/Helpers/URLRequestBuilder.swift b/Mindbox/Network/Helpers/URLRequestBuilder.swift index 25a862f6..0b4d53b6 100644 --- a/Mindbox/Network/Helpers/URLRequestBuilder.swift +++ b/Mindbox/Network/Helpers/URLRequestBuilder.swift @@ -10,13 +10,9 @@ 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) @@ -24,29 +20,28 @@ struct URLRequestBuilder { Logger.common(message: "Bad url. [URL]: \(String(describing: components.url))", level: .error, category: .network) 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/Mindbox/Network/MBNetworkFetcher.swift b/Mindbox/Network/MBNetworkFetcher.swift index 39169383..bd5851c2 100644 --- a/Mindbox/Network/MBNetworkFetcher.swift +++ b/Mindbox/Network/MBNetworkFetcher.swift @@ -22,7 +22,7 @@ class MBNetworkFetcher: NetworkFetcher { 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, @@ -47,7 +47,7 @@ class MBNetworkFetcher: NetworkFetcher { completion(.failure(error)) return } - + let builder = URLRequestBuilder(domain: configuration.domain) do { let urlRequest = try builder.asURLRequest(route: route) @@ -124,7 +124,7 @@ class MBNetworkFetcher: NetworkFetcher { completion: @escaping ((Result) -> Void) ) { Logger.response(data: data, response: response, error: error) - + // Check if we have any response at all guard let response = response else { completion(.failure(.connectionError)) diff --git a/Mindbox/Network/Types/HTTPMethod.swift b/Mindbox/Network/Types/HTTPMethod.swift index 28800d06..d9a1bcdf 100644 --- a/Mindbox/Network/Types/HTTPMethod.swift +++ b/Mindbox/Network/Types/HTTPMethod.swift @@ -9,7 +9,6 @@ import Foundation enum HTTPMethod: String { - + case get, post - } diff --git a/Mindbox/Network/Validators/HTTPURLResponseStatusCodeValidator.swift b/Mindbox/Network/Validators/HTTPURLResponseStatusCodeValidator.swift index 24a824da..72053adc 100644 --- a/Mindbox/Network/Validators/HTTPURLResponseStatusCodeValidator.swift +++ b/Mindbox/Network/Validators/HTTPURLResponseStatusCodeValidator.swift @@ -9,13 +9,13 @@ import Foundation struct HTTPURLResponseStatusCodeValidator { - + let statusCode: Int - + enum StatusCodes { - + case success, redirection, clientError, serverError - + var range: ClosedRange { switch self { case .success: @@ -28,7 +28,7 @@ struct HTTPURLResponseStatusCodeValidator { return 500...599 } } - + init?(statusCode: Int) { switch statusCode { case StatusCodes.success.range: @@ -43,13 +43,12 @@ struct HTTPURLResponseStatusCodeValidator { return nil } } - } - + var isClientError: Bool { return StatusCodes(statusCode: statusCode) == .clientError } - + func evaluate() -> Bool { guard let statusCode = StatusCodes(statusCode: statusCode) else { return false @@ -57,5 +56,4 @@ struct HTTPURLResponseStatusCodeValidator { let evaluateCodes: [StatusCodes] = [.success, .redirection] return evaluateCodes.contains(statusCode) } - } diff --git a/Mindbox/NetworkRepository/Event/EventRoute.swift b/Mindbox/NetworkRepository/Event/EventRoute.swift index 133a6b24..940962eb 100644 --- a/Mindbox/NetworkRepository/Event/EventRoute.swift +++ b/Mindbox/NetworkRepository/Event/EventRoute.swift @@ -101,11 +101,6 @@ enum EventRoute: Route { fileprivate struct TrackVisitBodyProxy: Codable { let ianaTimeZone: String let endpointId: String - - init(ianaTimeZone: String, endpointId: String) { - self.ianaTimeZone = ianaTimeZone - self.endpointId = endpointId - } } fileprivate extension QueryParameters { diff --git a/Mindbox/NetworkRepository/Event/EventWrapper.swift b/Mindbox/NetworkRepository/Event/EventWrapper.swift index 2857320b..a4e4450a 100644 --- a/Mindbox/NetworkRepository/Event/EventWrapper.swift +++ b/Mindbox/NetworkRepository/Event/EventWrapper.swift @@ -9,11 +9,10 @@ import Foundation struct EventWrapper { - + let event: Event - + let endpoint: String - + let deviceUUID: String - } diff --git a/Mindbox/PersistenceStorage/MBPersistenceStorage.swift b/Mindbox/PersistenceStorage/MBPersistenceStorage.swift index c12778b7..4d9da231 100644 --- a/Mindbox/PersistenceStorage/MBPersistenceStorage.swift +++ b/Mindbox/PersistenceStorage/MBPersistenceStorage.swift @@ -10,7 +10,7 @@ import Foundation import MindboxLogger class MBPersistenceStorage: PersistenceStorage { - + var onDidChange: (() -> Void)? // MARK: - Dependency @@ -27,7 +27,7 @@ class MBPersistenceStorage: PersistenceStorage { var isInstalled: Bool { installationDate != nil } - + var installationDate: Date? { get { if let dateString = installationDateString { @@ -44,7 +44,7 @@ class MBPersistenceStorage: PersistenceStorage { } } } - + var apnsTokenSaveDate: Date? { get { if let dateString = apnsTokenSaveDateString { @@ -61,7 +61,7 @@ class MBPersistenceStorage: PersistenceStorage { } } } - + var deprecatedEventsRemoveDate: Date? { get { if let dateString = deprecatedEventsRemoveDateString { @@ -78,7 +78,7 @@ class MBPersistenceStorage: PersistenceStorage { } } } - + var configuration: MBConfiguration? { get { guard let data = configurationData else { @@ -94,7 +94,7 @@ class MBPersistenceStorage: PersistenceStorage { } } } - + var configDownloadDate: Date? { get { if let dateString = configDownloadDateString { @@ -111,30 +111,28 @@ class MBPersistenceStorage: PersistenceStorage { } } } - + var backgroundExecutions: [BackgroudExecution] { - get { - if let data = MBPersistenceStorage.defaults.value(forKey:"backgroundExecution") as? Data { - return (try? PropertyListDecoder().decode(Array.self, from: data)) ?? [] - } else { - return [] - } + if let data = MBPersistenceStorage.defaults.value(forKey: "backgroundExecution") as? Data { + return (try? PropertyListDecoder().decode(Array.self, from: data)) ?? [] + } else { + return [] } } - + func setBackgroundExecution(_ value: BackgroudExecution) { var tasks = backgroundExecutions tasks.append(value) - MBPersistenceStorage.defaults.set(try? PropertyListEncoder().encode(tasks), forKey:"backgroundExecution") + MBPersistenceStorage.defaults.set(try? PropertyListEncoder().encode(tasks), forKey: "backgroundExecution") MBPersistenceStorage.defaults.synchronize() onDidChange?() } - + func storeToFileBackgroundExecution() { let path = FileManager.default .urls(for: .documentDirectory, in: .userDomainMask)[0] .appendingPathComponent("BackgroundExecution.plist") - + // Swift Dictionary To Data. let encoder = PropertyListEncoder() encoder.outputFormat = .xml @@ -146,8 +144,7 @@ class MBPersistenceStorage: PersistenceStorage { Logger.common(message: "StoreToFileBackgroundExecution did failed with error: \(error.localizedDescription)") } } - - + init(defaults: UserDefaults) { MBPersistenceStorage.defaults = defaults } @@ -177,51 +174,51 @@ class MBPersistenceStorage: PersistenceStorage { @UserDefaultsWrapper(key: .shownInAppsIds, defaultValue: nil) var shownInAppsIds: [String]? - + @UserDefaultsWrapper(key: .shownInAppsDictionary, defaultValue: [:]) - var shownInappsDictionary: [String : Date]? - + var shownInappsDictionary: [String: Date]? + @UserDefaultsWrapper(key: .handledlogRequestIds, defaultValue: nil) var handledlogRequestIds: [String]? - + @UserDefaultsWrapper(key: .imageLoadingMaxTimeInSeconds, defaultValue: nil) var imageLoadingMaxTimeInSeconds: Double? - + @UserDefaultsWrapper(key: .apnsTokenSaveDate, defaultValue: nil) private var apnsTokenSaveDateString: String? { didSet { onDidChange?() } } - + @UserDefaultsWrapper(key: .deprecatedEventsRemoveDate, defaultValue: nil) private var deprecatedEventsRemoveDateString: String? { didSet { onDidChange?() } } - + @UserDefaultsWrapper(key: .configurationData, defaultValue: nil) private var configurationData: Data? { didSet { onDidChange?() } } - + @UserDefaultsWrapper(key: .isNotificationsEnabled, defaultValue: nil) var isNotificationsEnabled: Bool? { didSet { onDidChange?() } } - + @UserDefaultsWrapper(key: .installationData, defaultValue: nil) private var installationDateString: String? { didSet { onDidChange?() } } - + @UserDefaultsWrapper(key: .needUpdateInfoOnce, defaultValue: nil) var needUpdateInfoOnce: Bool? { didSet { @@ -235,17 +232,17 @@ class MBPersistenceStorage: PersistenceStorage { onDidChange?() } } - + @UserDefaultsWrapper(key: .versionCodeForMigration, defaultValue: 0) var versionCodeForMigration: Int? - + @UserDefaultsWrapper(key: .configDownloadDate, defaultValue: nil) private var configDownloadDateString: String? { didSet { onDidChange?() } } - + func softReset() { configDownloadDate = nil shownInappsDictionary = nil @@ -253,7 +250,7 @@ class MBPersistenceStorage: PersistenceStorage { userVisitCount = 0 resetBackgroundExecutions() } - + func resetBackgroundExecutions() { MBPersistenceStorage.defaults.removeObject(forKey: "backgroundExecution") MBPersistenceStorage.defaults.synchronize() @@ -263,7 +260,7 @@ class MBPersistenceStorage: PersistenceStorage { // MARK: - Functions for unit testing extension MBPersistenceStorage { - + func reset() { installationDate = nil deviceUUID = nil @@ -279,24 +276,23 @@ extension MBPersistenceStorage { } struct BackgroudExecution: Codable { - + let taskID: String - + let taskName: String - + let dateString: String - + let info: String - } extension MBPersistenceStorage { - + @propertyWrapper struct UserDefaultsWrapper { - + enum Key: String { - + case installationId = "MBPersistenceStorage-installationId" case deviceUUID = "MBPersistenceStorage-deviceUUID" case apnsToken = "MBPersistenceStorage-apnsToken" @@ -314,15 +310,15 @@ extension MBPersistenceStorage { case configDownloadDate = "MBPersistenceStorage-configDownloadDate" case versionCodeForMigration = "MBPersistenceStorage-versionCodeForMigration" } - + private let key: Key private let defaultValue: T? - + init(key: Key, defaultValue: T?) { self.key = key self.defaultValue = defaultValue } - + var wrappedValue: T? { get { // Read value from UserDefaults @@ -336,9 +332,7 @@ extension MBPersistenceStorage { MBPersistenceStorage.defaults.synchronize() } } - } - } fileprivate extension UserDefaults { @@ -346,5 +340,4 @@ fileprivate extension UserDefaults { func isValueExists(forKey key: String) -> Bool { return object(forKey: key) != nil } - } diff --git a/Mindbox/PersistenceStorage/PersistenceStorage.swift b/Mindbox/PersistenceStorage/PersistenceStorage.swift index a8333007..fe03732a 100644 --- a/Mindbox/PersistenceStorage/PersistenceStorage.swift +++ b/Mindbox/PersistenceStorage/PersistenceStorage.swift @@ -31,11 +31,11 @@ protocol PersistenceStorage: AnyObject { @available(*, deprecated, renamed: "shownInappsDictionary", message: "Use shownInappsDictionary since version 2.10.0") var shownInAppsIds: [String]? { get set } - + var shownInappsDictionary: [String: Date]? { get set } - + var handledlogRequestIds: [String]? { get set } - + var imageLoadingMaxTimeInSeconds: Double? { get set } func setBackgroundExecution(_ value: BackgroudExecution) @@ -45,28 +45,27 @@ protocol PersistenceStorage: AnyObject { func storeToFileBackgroundExecution() var onDidChange: (() -> Void)? { get set } - + var needUpdateInfoOnce: Bool? { get set } 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/PublicModels/MBPushNotification.swift b/Mindbox/PublicModels/MBPushNotification.swift index 131476b3..ae98e895 100644 --- a/Mindbox/PublicModels/MBPushNotification.swift +++ b/Mindbox/PublicModels/MBPushNotification.swift @@ -8,7 +8,6 @@ import Foundation - public struct MBPushNotification: Codable { public let aps: MBAps? public let clickUrl: String? diff --git a/Mindbox/PushNotifications/PushNotificationFormatStrategies/NotificationFormatFactory.swift b/Mindbox/PushNotifications/PushNotificationFormatStrategies/NotificationFormatFactory.swift index 6d23aaa4..23382a7e 100644 --- a/Mindbox/PushNotifications/PushNotificationFormatStrategies/NotificationFormatFactory.swift +++ b/Mindbox/PushNotifications/PushNotificationFormatStrategies/NotificationFormatFactory.swift @@ -16,7 +16,7 @@ class NotificationStrategyFactory { 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/Mindbox/PushNotifications/PushNotificationFormatStrategies/NotificationFormatStrategy.swift b/Mindbox/PushNotifications/PushNotificationFormatStrategies/NotificationFormatStrategy.swift index c5681c65..e1fc3ebb 100644 --- a/Mindbox/PushNotifications/PushNotificationFormatStrategies/NotificationFormatStrategy.swift +++ b/Mindbox/PushNotifications/PushNotificationFormatStrategies/NotificationFormatStrategy.swift @@ -22,12 +22,12 @@ class LegacyFormatStrategy: NotificationFormatStrategy { 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, @@ -37,7 +37,7 @@ class LegacyFormatStrategy: NotificationFormatStrategy { } 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), @@ -54,7 +54,7 @@ class LegacyFormatStrategy: NotificationFormatStrategy { } class CurrentFormatStrategy: NotificationFormatStrategy { - func handle(userInfo: [AnyHashable : Any]) -> MBPushNotification? { + func handle(userInfo: [AnyHashable: Any]) -> MBPushNotification? { guard let data = try? JSONSerialization.data(withJSONObject: userInfo), let notificationModel = try? JSONDecoder().decode(MBPushNotification.self, from: data), notificationModel.clickUrl != nil, @@ -63,7 +63,7 @@ class CurrentFormatStrategy: NotificationFormatStrategy { 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/Mindbox/SessionManager/MBSessionManager.swift b/Mindbox/SessionManager/MBSessionManager.swift index a11a61aa..045b69d2 100644 --- a/Mindbox/SessionManager/MBSessionManager.swift +++ b/Mindbox/SessionManager/MBSessionManager.swift @@ -10,12 +10,12 @@ import UIKit import MindboxLogger final class MBSessionManager { - + var sessionHandler: ((Bool) -> Void)? var isActiveNow: Bool { return isActive } - + private let trackVisitManager: TrackVisitManager - + init(trackVisitManager: TrackVisitManager) { self.trackVisitManager = trackVisitManager subscribe() @@ -37,7 +37,7 @@ final class MBSessionManager { ) { [weak self] _ in self?.isActive = true } - + NotificationCenter.default.addObserver( forName: UIApplication.didEnterBackgroundNotification, object: nil, @@ -45,7 +45,7 @@ final class MBSessionManager { ) { [weak self] _ in self?.isActive = false } - + NotificationCenter.default.addObserver( forName: .initializationCompleted, object: nil, diff --git a/Mindbox/SessionManager/SessionManager.swift b/Mindbox/SessionManager/SessionManager.swift index ef0144d1..6e9c1a4a 100644 --- a/Mindbox/SessionManager/SessionManager.swift +++ b/Mindbox/SessionManager/SessionManager.swift @@ -9,12 +9,11 @@ import Foundation protocol SessionManager: AnyObject { - + var isActiveNow: Bool { get } var sessionHandler: ((Bool) -> Void)? { get set } - + func trackDirect() - + func trackForeground() - } diff --git a/Mindbox/TrackVisitManager/TrackVisitManager.swift b/Mindbox/TrackVisitManager/TrackVisitManager.swift index cd3fec44..d79372ce 100644 --- a/Mindbox/TrackVisitManager/TrackVisitManager.swift +++ b/Mindbox/TrackVisitManager/TrackVisitManager.swift @@ -29,7 +29,8 @@ final class TrackVisitManager { } } - @objc func track(data: TrackVisitData) throws { + @objc + func track(data: TrackVisitData) throws { if let userActivity = data.universalLink { try handleUniversalLink(userActivity) } else if let response = data.push { diff --git a/Mindbox/UserVisitManager/UserVisitManager.swift b/Mindbox/UserVisitManager/UserVisitManager.swift index 7ab4dcce..722099ec 100644 --- a/Mindbox/UserVisitManager/UserVisitManager.swift +++ b/Mindbox/UserVisitManager/UserVisitManager.swift @@ -16,7 +16,7 @@ protocol UserVisitManagerProtocol { final class UserVisitManager { private let persistenceStorage: PersistenceStorage private var isVisitSaved: Bool = false - + init(persistenceStorage: PersistenceStorage) { self.persistenceStorage = persistenceStorage } @@ -30,21 +30,21 @@ extension UserVisitManager: UserVisitManagerProtocol { Logger.common(message: "Skip changing userVisit because it is already saved", level: .info, category: .visit) return } - + isVisitSaved = true - + var previousUserVisitCount = persistenceStorage.userVisitCount ?? UVMConstants.noAppVisits - + // Handling the first launch when SDK version has been updated to 2.9.0 or higher from versions below 2.9.0 let isInstalled = SessionTemporaryStorage.shared.isInstalledFromPersistenceStorageBeforeInitSDK - if (isInstalled && previousUserVisitCount == UVMConstants.noAppVisits) { + if isInstalled && previousUserVisitCount == UVMConstants.noAppVisits { previousUserVisitCount = UVMConstants.appVisitsWhenSDKHasBeenUpdated } - + let userVisitCount = previousUserVisitCount + 1 - + persistenceStorage.userVisitCount = userVisitCount - + let message = "UserVisit has been changed from \(previousUserVisitCount) to \(userVisitCount)" Logger.common(message: message, level: .info, category: .visit) } diff --git a/Mindbox/Utilities/ABTestDeviceMixer/ABTestDeviceMixer.swift b/Mindbox/Utilities/ABTestDeviceMixer/ABTestDeviceMixer.swift index 017c2428..2b92a8a6 100644 --- a/Mindbox/Utilities/ABTestDeviceMixer/ABTestDeviceMixer.swift +++ b/Mindbox/Utilities/ABTestDeviceMixer/ABTestDeviceMixer.swift @@ -17,13 +17,13 @@ class ABTestDeviceMixer { return try stringModulusHash(identifier: identifier.uuidString.uppercased(), saltUpper: salt.uppercased()) } - + private func stringModulusHash(identifier: String, saltUpper: String) throws -> Int { let saltedId = identifier + saltUpper guard let saltedData = saltedId.data(using: .utf8) else { throw MindboxError.internalError(.init(errorKey: .general, reason: "SaltedData failed.")) } - + let hash = sha256.hash(data: saltedData) guard hash.count >= 32 else { throw MindboxError.internalError(.init(errorKey: .general, reason: "Hash count failed.")) @@ -34,7 +34,7 @@ class ABTestDeviceMixer { | (Int(hash[29]) << 16) | (Int(hash[30]) << 8) | Int(hash[31]) - + let unsigned = UInt(bitPattern: bigEndianLastBytesAsInt) return Int(unsigned % 100) } @@ -43,8 +43,8 @@ class ABTestDeviceMixer { struct SHA256 { func hash(data: Data) -> Data { var hashData = Data(count: Int(CC_SHA256_DIGEST_LENGTH)) - data.withUnsafeBytes { (messageBytes) in - hashData.withUnsafeMutableBytes { (hashBytes) in + data.withUnsafeBytes { messageBytes in + hashData.withUnsafeMutableBytes { hashBytes in _ = CC_SHA256(messageBytes.baseAddress, CC_LONG(data.count), hashBytes.bindMemory(to: UInt8.self).baseAddress) } } diff --git a/Mindbox/Utilities/Body+/BodyDecoder.swift b/Mindbox/Utilities/Body+/BodyDecoder.swift index 23a6b20b..65ded8fc 100644 --- a/Mindbox/Utilities/Body+/BodyDecoder.swift +++ b/Mindbox/Utilities/Body+/BodyDecoder.swift @@ -9,9 +9,9 @@ import Foundation 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) { @@ -23,5 +23,4 @@ struct BodyDecoder { return nil } } - } diff --git a/Mindbox/Utilities/Body+/BodyEncoder.swift b/Mindbox/Utilities/Body+/BodyEncoder.swift index 8b28539f..a14d7464 100644 --- a/Mindbox/Utilities/Body+/BodyEncoder.swift +++ b/Mindbox/Utilities/Body+/BodyEncoder.swift @@ -9,9 +9,9 @@ import Foundation struct BodyEncoder { - + let body: String - + init(encodable: T) { if let encodedBody = try? JSONEncoder().encode(encodable) { body = String(data: encodedBody, encoding: .utf8) ?? "" @@ -19,5 +19,4 @@ struct BodyEncoder { body = "" } } - } diff --git a/Mindbox/Utilities/Constants.swift b/Mindbox/Utilities/Constants.swift index 16cff023..3a6c4734 100644 --- a/Mindbox/Utilities/Constants.swift +++ b/Mindbox/Utilities/Constants.swift @@ -9,36 +9,33 @@ import Foundation enum Constants { - + enum Background { - + static let removeDeprecatedEventsInterval = TimeInterval(7 * 24 * 60 * 60) - + static let refreshTaskInterval = TimeInterval(2 * 60) } - + enum Database { - + static let mombName = "MBDatabase" - } - + enum Notification { - + static let mindBoxIdentifireKey = "uniqueKey" - } - + /// 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/DispatchSemaphore.swift b/Mindbox/Utilities/DispatchSemaphore.swift index a43a8d04..5b2116dd 100644 --- a/Mindbox/Utilities/DispatchSemaphore.swift +++ b/Mindbox/Utilities/DispatchSemaphore.swift @@ -9,11 +9,10 @@ import Foundation extension DispatchSemaphore { - + func lock(execute task: () throws -> T) rethrows -> T { wait() defer { signal() } return try task() } - } diff --git a/Mindbox/Utilities/IDFetcher/IDFAFetcher.swift b/Mindbox/Utilities/IDFetcher/IDFAFetcher.swift index ddd430b3..a98b45cf 100644 --- a/Mindbox/Utilities/IDFetcher/IDFAFetcher.swift +++ b/Mindbox/Utilities/IDFetcher/IDFAFetcher.swift @@ -26,7 +26,7 @@ struct IDFAFetcher { return extract() } } - + private func extract() -> UUID? { let udid = ASIdentifierManager.shared().advertisingIdentifier guard isValid(udid: udid.uuidString) else { @@ -34,7 +34,7 @@ struct IDFAFetcher { } return udid } - + private func isValid(udid: String) -> Bool { return UDIDValidator(udid: udid).evaluate() } diff --git a/Mindbox/Utilities/IDFetcher/IDFVFetcher.swift b/Mindbox/Utilities/IDFetcher/IDFVFetcher.swift index ef45f8a5..ce26f812 100644 --- a/Mindbox/Utilities/IDFetcher/IDFVFetcher.swift +++ b/Mindbox/Utilities/IDFetcher/IDFVFetcher.swift @@ -10,13 +10,13 @@ import Foundation import UIKit.UIDevice struct IDFVFetcher { - + typealias Completion = (UUID?) -> Void - + func fetch(tryCount: Int, completion: @escaping Completion) { var countdown = tryCount - let timer = Timer(timeInterval: 1, repeats: true) { (timer) in + let timer = Timer(timeInterval: 1, repeats: true) { timer in guard countdown > 0 else { completion(nil) timer.invalidate() @@ -31,9 +31,8 @@ struct IDFVFetcher { timer.fire() RunLoop.current.add(timer, forMode: .common) } - + private func isValid(udid: String) -> Bool { return UDIDValidator(udid: udid).evaluate() } - } diff --git a/Mindbox/Utilities/Migrations/ImplementationOfMigrations/ShownInAppsIdsMigration.swift b/Mindbox/Utilities/Migrations/ImplementationOfMigrations/ShownInAppsIdsMigration.swift index 39839072..b7134dd0 100644 --- a/Mindbox/Utilities/Migrations/ImplementationOfMigrations/ShownInAppsIdsMigration.swift +++ b/Mindbox/Utilities/Migrations/ImplementationOfMigrations/ShownInAppsIdsMigration.swift @@ -9,35 +9,35 @@ import Foundation final class MigrationShownInAppsIds: MigrationProtocol { - + private var persistenceStorage: PersistenceStorage = DI.injectOrFail(PersistenceStorage.self) - + var description: String { "Migration shownInAppsIds to shownInappsDictionary. Starting with SDK 2.10.0." } - + @available(*, deprecated, message: "Suppress `deprecated` shownInAppsIds warning") var isNeeded: Bool { persistenceStorage.shownInAppsIds?.isEmpty == false } - + var version: Int { 0 } - + @available(*, deprecated, message: "Suppress deprecated `shownInAppsIds` warning") func run() throws { guard let oldShownInAppsIds = persistenceStorage.shownInAppsIds else { return } - + let migrationTimestamp = Date(timeIntervalSince1970: 0) var newFormatShownInapps = [String: Date]() - + for id in oldShownInAppsIds { newFormatShownInapps[id] = migrationTimestamp } - + persistenceStorage.shownInappsDictionary = newFormatShownInapps persistenceStorage.shownInAppsIds = nil } diff --git a/Mindbox/Utilities/Migrations/MigrationAbstractions/BaseMigration.swift b/Mindbox/Utilities/Migrations/MigrationAbstractions/BaseMigration.swift index e40cd590..438d8dd2 100644 --- a/Mindbox/Utilities/Migrations/MigrationAbstractions/BaseMigration.swift +++ b/Mindbox/Utilities/Migrations/MigrationAbstractions/BaseMigration.swift @@ -20,38 +20,38 @@ import Foundation /// - 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. diff --git a/Mindbox/Utilities/Migrations/MigrationAbstractions/MigrationProtocol.swift b/Mindbox/Utilities/Migrations/MigrationAbstractions/MigrationProtocol.swift index db7bab3d..9dd1ae17 100644 --- a/Mindbox/Utilities/Migrations/MigrationAbstractions/MigrationProtocol.swift +++ b/Mindbox/Utilities/Migrations/MigrationAbstractions/MigrationProtocol.swift @@ -15,19 +15,19 @@ import Foundation /// 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 index 2736fb82..6c1443cf 100644 --- a/Mindbox/Utilities/Migrations/MigrationManager/MigrationManager.swift +++ b/Mindbox/Utilities/Migrations/MigrationManager/MigrationManager.swift @@ -12,20 +12,20 @@ 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 @@ -49,9 +49,9 @@ final class MigrationManager { init(persistenceStorage: PersistenceStorage) { self.persistenceStorage = persistenceStorage self.localSdkVersionCode = Constants.Migration.sdkVersionCode - + self.migrations = [ - MigrationShownInAppsIds(), + MigrationShownInAppsIds() ] } } @@ -59,21 +59,21 @@ final class MigrationManager { // 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 } @@ -89,25 +89,24 @@ extension MigrationManager: MigrationManagerProtocol { 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: diff --git a/Mindbox/Utilities/Migrations/MigrationManager/MigrationManagerProtocol.swift b/Mindbox/Utilities/Migrations/MigrationManager/MigrationManagerProtocol.swift index 0c822463..60b8a9b8 100644 --- a/Mindbox/Utilities/Migrations/MigrationManager/MigrationManagerProtocol.swift +++ b/Mindbox/Utilities/Migrations/MigrationManager/MigrationManagerProtocol.swift @@ -12,7 +12,7 @@ import Foundation /// 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. diff --git a/Mindbox/Utilities/NotificationDecoder.swift b/Mindbox/Utilities/NotificationDecoder.swift index b4f3bcf9..ad2a5e0b 100644 --- a/Mindbox/Utilities/NotificationDecoder.swift +++ b/Mindbox/Utilities/NotificationDecoder.swift @@ -11,13 +11,13 @@ import UserNotifications import MindboxLogger struct NotificationDecoder { - + var isMindboxNotification: Bool { userInfo[Constants.Notification.mindBoxIdentifireKey] != nil } - + private let userInfo: [AnyHashable: Any] - + init(request: UNNotificationRequest) throws { guard let userInfo = (request.content.mutableCopy() as? UNMutableNotificationContent)?.userInfo else { let error = MindboxError.internalError(InternalError(errorKey: "unableToFetchUserInfo")) @@ -26,11 +26,11 @@ struct NotificationDecoder { } try self.init(userInfo: userInfo) } - + init(response: UNNotificationResponse) throws { try self.init(request: response.notification.request) } - + init(userInfo: [AnyHashable: Any]) throws { if let jsonData = try? JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted), let jsonString = String(data: jsonData, encoding: .utf8) { @@ -38,7 +38,7 @@ struct NotificationDecoder { } else { Logger.common(message: "NotificationDecoder: Unable to serialize userInfo to JSON", level: .error) } - + 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") @@ -47,7 +47,7 @@ struct NotificationDecoder { Logger.common(message: "Push Notification format with multiple keys") } } - + func decode() throws -> T { do { let data = try JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted) @@ -65,5 +65,4 @@ struct NotificationDecoder { throw error } } - } diff --git a/Mindbox/Utilities/SessionTemporaryStorage.swift b/Mindbox/Utilities/SessionTemporaryStorage.swift index b936bcac..de06f3fd 100644 --- a/Mindbox/Utilities/SessionTemporaryStorage.swift +++ b/Mindbox/Utilities/SessionTemporaryStorage.swift @@ -10,9 +10,9 @@ import Foundation import UserNotifications final class SessionTemporaryStorage { - + public static let shared = SessionTemporaryStorage() - + var observedCustomOperations: Set = [] var operationsFromSettings: Set = [] var geoRequestCompleted = false @@ -28,15 +28,13 @@ final class SessionTemporaryStorage { } } } - - private init() { - - } - + + private init() {} + var customOperations: Set { return observedCustomOperations.union(operationsFromSettings) } - + func erase() { observedCustomOperations = [] operationsFromSettings = [] diff --git a/Mindbox/Utilities/String+Regex.swift b/Mindbox/Utilities/String+Regex.swift index 5e79f54e..19160669 100644 --- a/Mindbox/Utilities/String+Regex.swift +++ b/Mindbox/Utilities/String+Regex.swift @@ -8,13 +8,15 @@ import Foundation +// swiftlint:disable force_unwrapping + extension String { var operationNameIsValid: Bool { let range = NSRange(location: 0, length: self.utf16.count) let regex = try? NSRegularExpression(pattern: "^[A-Za-z0-9\\-\\.]+$") return regex?.firstMatch(in: self, options: [], range: range) != nil } - + func parseTimeSpanToMillis() throws -> Int64 { let regex = try NSRegularExpression(pattern: "^(-)?((\\d+)\\.)?([01]?\\d|2[0-3]):([0-5]?\\d):([0-5]?\\d)(\\.(\\d{1,7}))?$") let matches = regex.matches(in: self, range: NSRange(self.startIndex..., in: self)) diff --git a/Mindbox/Utilities/UNAuthorizationStatusProviding/UNAuthorizationStatusProvider.swift b/Mindbox/Utilities/UNAuthorizationStatusProviding/UNAuthorizationStatusProvider.swift index 0720770e..cebc6bce 100644 --- a/Mindbox/Utilities/UNAuthorizationStatusProviding/UNAuthorizationStatusProvider.swift +++ b/Mindbox/Utilities/UNAuthorizationStatusProviding/UNAuthorizationStatusProvider.swift @@ -10,7 +10,7 @@ import Foundation import UIKit class UNAuthorizationStatusProvider: UNAuthorizationStatusProviding { - + func getStatus(result: @escaping (Bool) -> Void) { UNUserNotificationCenter.current().getNotificationSettings { settings in SessionTemporaryStorage.shared.pushPermissionStatus = settings.authorizationStatus diff --git a/Mindbox/Utilities/UUIDDebugService/PasteboardUUIDDebugService.swift b/Mindbox/Utilities/UUIDDebugService/PasteboardUUIDDebugService.swift index 6e71f447..20e8f75e 100644 --- a/Mindbox/Utilities/UUIDDebugService/PasteboardUUIDDebugService.swift +++ b/Mindbox/Utilities/UUIDDebugService/PasteboardUUIDDebugService.swift @@ -48,7 +48,8 @@ internal final class PasteboardUUIDDebugService: UUIDDebugService { started = true } - @objc private func didReceiveNotification() { + @objc + private func didReceiveNotification() { let now = currentDateProvider() if now.timeIntervalSince(lastReceivedDate ?? now) < Self.triggerNotificationInterval { @@ -70,5 +71,4 @@ internal final class PasteboardUUIDDebugService: UUIDDebugService { pasteboard.string = id } - } diff --git a/Mindbox/Utilities/UtilitiesFetcher/MBUtilitiesFetcher.swift b/Mindbox/Utilities/UtilitiesFetcher/MBUtilitiesFetcher.swift index 9b0632b4..6173b5a7 100644 --- a/Mindbox/Utilities/UtilitiesFetcher/MBUtilitiesFetcher.swift +++ b/Mindbox/Utilities/UtilitiesFetcher/MBUtilitiesFetcher.swift @@ -16,19 +16,19 @@ import SDKVersionProvider import MindboxLogger class MBUtilitiesFetcher: UtilitiesFetcher { - + private let appBundle: Bundle = { var bundle: Bundle = .main prepareBundle(&bundle) return bundle }() - + private let sdkBundle: Bundle = { var bundle = BundleToken.bundle prepareBundle(&bundle) return bundle }() - + var applicationGroupIdentifier: String { guard let hostApplicationName = hostApplicationName else { fatalError("CFBundleShortVersionString not found for host app") @@ -46,11 +46,9 @@ class MBUtilitiesFetcher: UtilitiesFetcher { } return identifier } - - init() { - - } - + + 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 @@ -60,26 +58,26 @@ class MBUtilitiesFetcher: UtilitiesFetcher { } } } - + var appVerson: String? { - appBundle.object(forInfoDictionaryKey:"CFBundleShortVersionString") as? String + appBundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String } - + var sdkVersion: String? { SDKVersionProvider.sdkVersion } - + var hostApplicationName: String? { appBundle.bundleIdentifier } - + func getDeviceUUID(completion: @escaping (String) -> Void) { if let uuid = IDFAFetcher().fetch() { Logger.common(message: "IDFAFetcher uuid:\(uuid.uuidString)", level: .default, category: .general) completion(uuid.uuidString) } else { Logger.common(message: "IDFAFetcher fail", level: .default, category: .general) - IDFVFetcher().fetch(tryCount: 3) { (uuid) in + IDFVFetcher().fetch(tryCount: 3) { uuid in if let uuid = uuid { Logger.common(message: "IDFVFetcher uuid:\(uuid.uuidString)", level: .default, category: .general) completion(uuid.uuidString) diff --git a/Mindbox/Utilities/UtilitiesFetcher/UtilitiesFetcher.swift b/Mindbox/Utilities/UtilitiesFetcher/UtilitiesFetcher.swift index de910b6c..3e574af7 100644 --- a/Mindbox/Utilities/UtilitiesFetcher/UtilitiesFetcher.swift +++ b/Mindbox/Utilities/UtilitiesFetcher/UtilitiesFetcher.swift @@ -14,9 +14,8 @@ protocol UtilitiesFetcher { var appVerson: String? { get } var sdkVersion: String? { get } var hostApplicationName: String? { get } - + var applicationGroupIdentifier: String { get } - + func getDeviceUUID(completion: @escaping (String) -> Void) - } diff --git a/Mindbox/Validators/ABTestValidator.swift b/Mindbox/Validators/ABTestValidator.swift index 160b6270..b7e0c888 100644 --- a/Mindbox/Validators/ABTestValidator.swift +++ b/Mindbox/Validators/ABTestValidator.swift @@ -10,12 +10,12 @@ import Foundation import MindboxLogger class ABTestValidator: Validator { - + typealias T = ABTest? - + private let sdkVersionValidator: SDKVersionValidator private let variantsValidator: ABTestVariantsValidator - + init(sdkVersionValidator: SDKVersionValidator, variantsValidator: ABTestVariantsValidator) { self.sdkVersionValidator = sdkVersionValidator self.variantsValidator = variantsValidator @@ -26,7 +26,7 @@ class ABTestValidator: Validator { Logger.common(message: "The element in abtests block cannot be null. All abtests will not be used.") return false } - + if item.id.isEmpty { Logger.common(message: "The field 'id' in abtests block cannot be null. All abtests will not be used.") return false @@ -56,13 +56,13 @@ class ABTestValidator: Validator { let sortedVariants = variants.sorted { ($0.modulus?.lower ?? 0) < ($1.modulus?.lower ?? 0) } - + for variant in sortedVariants { guard let modulus = variant.modulus, let upper = modulus.upper else { Logger.common(message: "In abtest \(item.id) 'variants' field contains a variant with a nil modulus. All abtests will not be used.") return false } - + if modulus.lower == start { start = upper } else { @@ -70,12 +70,12 @@ class ABTestValidator: Validator { return false } } - + if !(99...100).contains(start) { Logger.common(message: "In abtest \(item.id) 'variants' field does not have full cover. All abtests will not be used.") return false } - + return true } } diff --git a/Mindbox/Validators/ABTestVariantsValidator.swift b/Mindbox/Validators/ABTestVariantsValidator.swift index 23631a68..c1e031c2 100644 --- a/Mindbox/Validators/ABTestVariantsValidator.swift +++ b/Mindbox/Validators/ABTestVariantsValidator.swift @@ -15,56 +15,56 @@ class ABTestVariantsValidator: Validator { private let typeInApps = "inapps" private let all = "all" private let concrete = "concrete" - + func isValid(item: ABTest.ABTestVariant?) -> Bool { guard let item = item else { Logger.common(message: "Variant item can not be null.") return false } - + guard !item.id.isEmpty else { Logger.common(message: "The 'id' field can not be null or empty.") return false } - + guard let modulus = item.modulus else { Logger.common(message: "The 'modulus' field can not be null.") return false } - + guard let upper = modulus.upper else { Logger.common(message: "The 'upper' field in 'modulus' can not be null.") return false } - + guard modulus.lower >= 0, upper <= 100, modulus.lower < upper else { Logger.common(message: "The 'lower' and 'upper' fields are invalid.") return false } - + guard let objects = item.objects else { Logger.common(message: "The 'objects' field can not be null.") return false } - + guard objects.count == 1 else { Logger.common(message: "The 'objects' field must contain only one item.") return false } - + guard objects.first?.type.rawValue == typeInApps else { Logger.common(message: "The 'objects' field type can be \(typeInApps).") return false } - + guard let kind = objects.first?.kind.rawValue, kind == all || kind == concrete else { Logger.common(message: "The 'kind' field must be \(all) or \(concrete).") return false } - + return true } } diff --git a/Mindbox/Validators/InappFrequencyValidator.swift b/Mindbox/Validators/InappFrequencyValidator.swift index 31e2a243..650f8eb2 100644 --- a/Mindbox/Validators/InappFrequencyValidator.swift +++ b/Mindbox/Validators/InappFrequencyValidator.swift @@ -11,18 +11,18 @@ import MindboxLogger class InappFrequencyValidator: Validator { typealias T = InApp - + let persistenceStorage: PersistenceStorage - + init(persistenceStorage: PersistenceStorage) { self.persistenceStorage = persistenceStorage } - + func isValid(item: InApp) -> Bool { guard let frequency = item.frequency else { return false } - + switch frequency { case .periodic(let periodicFrequency): let validator = PeriodicFrequencyValidator(persistenceStorage: persistenceStorage) @@ -38,11 +38,11 @@ class InappFrequencyValidator: Validator { class OnceFrequencyValidator { let persistenceStorage: PersistenceStorage - + init(persistenceStorage: PersistenceStorage) { self.persistenceStorage = persistenceStorage } - + func isValid(item: OnceFrequency, id: String) -> Bool { let shownInappsDictionary = persistenceStorage.shownInappsDictionary ?? [:] var result = false @@ -60,7 +60,7 @@ class OnceFrequencyValidator { result = true } } - + Logger.common(message: "[Inapp frequency] Current frequency is [once] and kind is [\(item.kind.rawValue)]. Valid = \(result)", level: .debug, category: .inAppMessages) return result @@ -69,15 +69,15 @@ class OnceFrequencyValidator { class PeriodicFrequencyValidator { let persistenceStorage: PersistenceStorage - + init(persistenceStorage: PersistenceStorage) { self.persistenceStorage = persistenceStorage } - + func isValid(item: PeriodicFrequency, id: String) -> Bool { guard item.value > 0 else { Logger.common(message: """ - [Inapp frequency] Current frequency is [periodic], it's unit is [\(item.unit.rawValue)]. + [Inapp frequency] Current frequency is [periodic], it's unit is [\(item.unit.rawValue)]. Value (\(item.value)) is zero or negative. Inapp is not valid. """, level: .info, category: .inAppMessages) @@ -89,16 +89,16 @@ class PeriodicFrequencyValidator { Logger.common(message: "shownInappsDictionary not exists. Inapp is not valid.", level: .error, category: .inAppMessages) return false } - + guard let shownDate = inappsDict[id] else { Logger.common(message: """ [Inapp frequency] Current frequency is [periodic] and unit is [\(item.unit.rawValue)]. - Inapp ID \(id) is never shown before. + Inapp ID \(id) is never shown before. Keeping in-app. """, level: .info, category: .inAppMessages) return true } - + let calendar = Calendar.current let component = item.unit.calendarComponent if let shownDatePlusFrequency = calendar.date(byAdding: component, value: item.value, to: shownDate) { diff --git a/Mindbox/Validators/MindboxPushValidator.swift b/Mindbox/Validators/MindboxPushValidator.swift index 9a942192..4957f387 100644 --- a/Mindbox/Validators/MindboxPushValidator.swift +++ b/Mindbox/Validators/MindboxPushValidator.swift @@ -11,15 +11,15 @@ import UserNotifications import MindboxLogger class MindboxPushValidator: Validator { - + typealias T = [AnyHashable: Any] - - func isValid(item: [AnyHashable : Any]) -> Bool { + + func isValid(item: [AnyHashable: Any]) -> Bool { 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 } - + Logger.common(message: "MindboxPushValidator: Successfully validated Mindbox push model.", level: .info, category: .notification) return true } diff --git a/Mindbox/Validators/SDKVersionValidator.swift b/Mindbox/Validators/SDKVersionValidator.swift index 66c0511a..3e59c2cf 100644 --- a/Mindbox/Validators/SDKVersionValidator.swift +++ b/Mindbox/Validators/SDKVersionValidator.swift @@ -10,19 +10,19 @@ import Foundation class SDKVersionValidator: Validator { typealias T = SdkVersion? - + var sdkVersionNumeric: Int - + init(sdkVersionNumeric: Int) { self.sdkVersionNumeric = sdkVersionNumeric } func isValid(item: SdkVersion?) -> Bool { guard let sdkVersion = item else { return false } - + let minVersionValid = sdkVersion.min.map { $0 <= sdkVersionNumeric } ?? false let maxVersionValid = sdkVersion.max.map { $0 >= sdkVersionNumeric } ?? true - + return minVersionValid && maxVersionValid } } diff --git a/Mindbox/Validators/String+Extensions.swift b/Mindbox/Validators/String+Extensions.swift index 08bc3245..1b48b8c5 100644 --- a/Mindbox/Validators/String+Extensions.swift +++ b/Mindbox/Validators/String+Extensions.swift @@ -18,32 +18,32 @@ extension String { } catch { } } - + if let data = self.data(using: .utf8) { let parser = XMLParser(data: data) if parser.parse() { return false } } - + if let url = URL(string: self), UIApplication.shared.canOpenURL(url) { return false } return true } - + func isHexValid() -> Bool { if !self.hasPrefix("#") { return false } - + let hexValue = String(self.dropFirst()) - + if hexValue.count != 6 { return false } - + let hexCharacterSet = CharacterSet(charactersIn: "0123456789ABCDEFabcdef") return hexValue.unicodeScalars.allSatisfy { hexCharacterSet.contains($0) } } diff --git a/Mindbox/Validators/UDIDValidator.swift b/Mindbox/Validators/UDIDValidator.swift index 785a47d5..470929dc 100644 --- a/Mindbox/Validators/UDIDValidator.swift +++ b/Mindbox/Validators/UDIDValidator.swift @@ -9,14 +9,13 @@ import Foundation struct UDIDValidator { - + let udid: String - + func evaluate() -> Bool { return !udid .replacingOccurrences(of: "0", with: "") .replacingOccurrences(of: "-", with: "") .isEmpty } - } diff --git a/Mindbox/Validators/URLValidator.swift b/Mindbox/Validators/URLValidator.swift index 132fd425..2ea14c72 100644 --- a/Mindbox/Validators/URLValidator.swift +++ b/Mindbox/Validators/URLValidator.swift @@ -8,17 +8,20 @@ import Foundation +// FIXME: Rewrite this struct in the future +// swiftlint:disable line_length force_try + struct URLValidator { - + let url: URL - + let urlPattern = "^(http|https|ftp)\\://([a-zA-Z0-9\\.\\-]+(\\:[a-zA-Z0-9\\.&%\\$\\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\\-]+\\.)*[a-zA-Z0-9\\-]+\\.(com|cloud|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|tech|[a-zA-Z]{2}))(\\:[0-9]+)*(/($|[a-zA-Z0-9\\.\\,\\?\\'\\\\\\+&%\\$#\\=~_\\-]+))*$" - + func evaluate() -> Bool { return matches(string: url.absoluteString, pattern: urlPattern) } - - private func matches(string: String ,pattern: String) -> Bool { + + private func matches(string: String, pattern: String) -> Bool { let regex = try! NSRegularExpression( pattern: pattern, options: [.caseInsensitive]) @@ -27,5 +30,4 @@ struct URLValidator { options: [], range: NSRange(location: 0, length: string.utf16.count)) != nil } - } diff --git a/MindboxLogger/Network/Enums/SDKLogsStatus.swift b/MindboxLogger/Network/Enums/SDKLogsStatus.swift index 3da64646..de4a17bc 100644 --- a/MindboxLogger/Network/Enums/SDKLogsStatus.swift +++ b/MindboxLogger/Network/Enums/SDKLogsStatus.swift @@ -13,7 +13,7 @@ public enum SDKLogsStatus: Equatable { case elderLog(date: String) case latestLog(date: String) case largeSize - + public var value: String { switch self { case .ok: diff --git a/MindboxLogger/Shared/Extensions/Date+Extension.swift b/MindboxLogger/Shared/Extensions/Date+Extension.swift index d1211809..b3d794f5 100644 --- a/MindboxLogger/Shared/Extensions/Date+Extension.swift +++ b/MindboxLogger/Shared/Extensions/Date+Extension.swift @@ -18,13 +18,13 @@ public extension Date { dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" return dateFormatter.string(from: self as Date) } - + static var dateFormatter: DateFormatter { let formatter = DateFormatter() formatter.dateFormat = "hh:mm:ss.SSSS" return formatter } - + func toString(withFormat format: DateFormat) -> String { let dateFormatter = DateFormatter() dateFormatter.dateFormat = format.value diff --git a/MindboxLogger/Shared/Extensions/MBLoggerUtilitiesFetcher.swift b/MindboxLogger/Shared/Extensions/MBLoggerUtilitiesFetcher.swift index 29179372..f793d5e0 100644 --- a/MindboxLogger/Shared/Extensions/MBLoggerUtilitiesFetcher.swift +++ b/MindboxLogger/Shared/Extensions/MBLoggerUtilitiesFetcher.swift @@ -9,13 +9,13 @@ import Foundation class MBLoggerUtilitiesFetcher { - + let appBundle: Bundle = { var bundle: Bundle = .main prepareBundle(&bundle) return bundle }() - + var applicationGroupIdentifier: String { guard let hostApplicationName = hostApplicationName else { fatalError("CFBundleShortVersionString not found for host app") @@ -30,14 +30,12 @@ class MBLoggerUtilitiesFetcher { fatalError(message) #endif } - + return identifier } - - init() { - - } - + + 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 @@ -47,7 +45,7 @@ class MBLoggerUtilitiesFetcher { } } } - + var hostApplicationName: String? { appBundle.bundleIdentifier } diff --git a/MindboxLogger/Shared/Extensions/NSManagedObjectContext+Extension.swift b/MindboxLogger/Shared/Extensions/NSManagedObjectContext+Extension.swift index 51f6725e..05d2a843 100644 --- a/MindboxLogger/Shared/Extensions/NSManagedObjectContext+Extension.swift +++ b/MindboxLogger/Shared/Extensions/NSManagedObjectContext+Extension.swift @@ -9,8 +9,10 @@ import Foundation import CoreData +// swiftlint:disable force_unwrapping + public extension NSManagedObjectContext { - + func executePerformAndWait(_ block: () throws -> Void) rethrows { if #available(iOS 15, *) { try self.performAndWait(block) @@ -18,7 +20,7 @@ public extension NSManagedObjectContext { try mindboxPerformAndWait(block) } } - + func mindboxPerformAndWait(_ block: () throws -> T) rethrows -> T { return try _performAndWaitHelper( fn: performAndWait, execute: block, rescue: { throw $0 } @@ -32,8 +34,7 @@ public extension NSManagedObjectContext { private func _performAndWaitHelper( fn: (() -> Void) -> Void, execute work: () throws -> T, - rescue: ((Error) throws -> (T))) rethrows -> T - { + rescue: ((Error) throws -> (T))) rethrows -> T { var result: T? var error: Error? withoutActuallyEscaping(work) { _work in diff --git a/MindboxLogger/Shared/Extensions/String+Extensions.swift b/MindboxLogger/Shared/Extensions/String+Extensions.swift index 3a6f8929..ecbcb046 100644 --- a/MindboxLogger/Shared/Extensions/String+Extensions.swift +++ b/MindboxLogger/Shared/Extensions/String+Extensions.swift @@ -10,19 +10,18 @@ import Foundation public enum DateFormat: String { case api = "yyyy-MM-dd'T'HH:mm:ss" case utc = "yyyy-MM-dd'T'HH:mm:ss'Z'" - + var value: String { return self.rawValue } } - public extension String { func toDate(withFormat format: DateFormat) -> Date? { let dateFormatterGet = DateFormatter() dateFormatterGet.dateFormat = format.value dateFormatterGet.timeZone = TimeZone(identifier: "UTC") - + return dateFormatterGet.date(from: self) } } diff --git a/MindboxLogger/Shared/Extensions/URL+Extensions.swift b/MindboxLogger/Shared/Extensions/URL+Extensions.swift index 53680930..3912d48b 100644 --- a/MindboxLogger/Shared/Extensions/URL+Extensions.swift +++ b/MindboxLogger/Shared/Extensions/URL+Extensions.swift @@ -8,7 +8,7 @@ import Foundation extension URL { - var attributes: [FileAttributeKey : Any]? { + var attributes: [FileAttributeKey: Any]? { do { return try FileManager.default.attributesOfItem(atPath: path) } catch let error as NSError { @@ -16,7 +16,7 @@ extension URL { } return nil } - + var fileSize: UInt64 { return attributes?[.size] as? UInt64 ?? UInt64(0) } diff --git a/MindboxLogger/Shared/Group/LogCategory.swift b/MindboxLogger/Shared/Group/LogCategory.swift index b22547e1..d5bd9419 100644 --- a/MindboxLogger/Shared/Group/LogCategory.swift +++ b/MindboxLogger/Shared/Group/LogCategory.swift @@ -9,7 +9,7 @@ import Foundation public enum LogCategory: String, CaseIterable { - + case general case network case database @@ -19,7 +19,7 @@ public enum LogCategory: String, CaseIterable { case visit case migration case inAppMessages - + var emoji: String { switch self { case .general: @@ -42,5 +42,4 @@ public enum LogCategory: String, CaseIterable { return "🖼️" } } - } diff --git a/MindboxLogger/Shared/Group/LogLevel.swift b/MindboxLogger/Shared/Group/LogLevel.swift index 558cc5ea..f0f88705 100644 --- a/MindboxLogger/Shared/Group/LogLevel.swift +++ b/MindboxLogger/Shared/Group/LogLevel.swift @@ -19,14 +19,15 @@ import Foundation 4. fault ⚠️ 5. none */ -@objc public enum LogLevel: Int, CaseIterable, Comparable, Equatable { +@objc +public enum LogLevel: Int, CaseIterable, Comparable, Equatable { case debug = 0 case info = 1 case `default` = 2 case error = 3 case fault = 4 case none = 5 - + var emoji: String { switch self { case .none: @@ -43,7 +44,7 @@ import Foundation return "[⚠️]" } } - + public static func < (lhs: LogLevel, rhs: LogLevel) -> Bool { lhs.rawValue < rhs.rawValue } diff --git a/MindboxLogger/Shared/Group/LogMessage.swift b/MindboxLogger/Shared/Group/LogMessage.swift index 8b19401c..370affe7 100644 --- a/MindboxLogger/Shared/Group/LogMessage.swift +++ b/MindboxLogger/Shared/Group/LogMessage.swift @@ -10,7 +10,7 @@ import Foundation public struct LogMessage { public let timestamp: Date public let message: String - + public var description: String { return timestamp.toString(withFormat: .utc) + " | " + message } diff --git a/MindboxLogger/Shared/Group/LogWriter.swift b/MindboxLogger/Shared/Group/LogWriter.swift index 321dda79..348958f6 100644 --- a/MindboxLogger/Shared/Group/LogWriter.swift +++ b/MindboxLogger/Shared/Group/LogWriter.swift @@ -14,7 +14,7 @@ protocol LogWriter { } class OSLogWriter: LogWriter { - + let log: OSLog init(subsystem: String, category: String) { @@ -27,7 +27,7 @@ class OSLogWriter: LogWriter { } fileprivate extension LogLevel { - + var asOSLogType: OSLogType { switch self { case .none: @@ -44,5 +44,4 @@ fileprivate extension LogLevel { return .fault } } - } diff --git a/MindboxLogger/Shared/LoggerRepository/CDLogMessage+CoreDataClass.swift b/MindboxLogger/Shared/LoggerRepository/CDLogMessage+CoreDataClass.swift index 621cc84b..7e9fd403 100644 --- a/MindboxLogger/Shared/LoggerRepository/CDLogMessage+CoreDataClass.swift +++ b/MindboxLogger/Shared/LoggerRepository/CDLogMessage+CoreDataClass.swift @@ -9,9 +9,7 @@ import Foundation import CoreData -public class CDLogMessage: NSManagedObject { - -} +public class CDLogMessage: NSManagedObject {} extension CDLogMessage { @NSManaged public var timestamp: Date diff --git a/MindboxLogger/Shared/LoggerRepository/MBLoggerCoreDataManager.swift b/MindboxLogger/Shared/LoggerRepository/MBLoggerCoreDataManager.swift index 1a846783..8b2a8358 100644 --- a/MindboxLogger/Shared/LoggerRepository/MBLoggerCoreDataManager.swift +++ b/MindboxLogger/Shared/LoggerRepository/MBLoggerCoreDataManager.swift @@ -11,13 +11,13 @@ import CoreData public class MBLoggerCoreDataManager { public static let shared = MBLoggerCoreDataManager() - + private enum Constants { static let model = "CDLogMessage" static let dbSizeLimitKB: Int = 10_000 static let operationLimitBeforeNeedToDelete = 20 } - + private let queue = DispatchQueue(label: "com.Mindbox.loggerManager", qos: .utility) private var persistentStoreDescription: NSPersistentStoreDescription? private var writeCount = 0 { @@ -30,7 +30,7 @@ public class MBLoggerCoreDataManager { lazy var persistentContainer: MBPersistentContainer = { MBPersistentContainer.applicationGroupIdentifier = MBLoggerUtilitiesFetcher().applicationGroupIdentifier - + #if SWIFT_PACKAGE guard let bundleURL = Bundle.module.url(forResource: Constants.model, withExtension: "momd"), let mom = NSManagedObjectModel(contentsOf: bundleURL) else { @@ -49,14 +49,14 @@ public class MBLoggerCoreDataManager { container = MBPersistentContainer(name: Constants.model) } #endif - + let storeURL = FileManager.storeURL(for: MBLoggerUtilitiesFetcher().applicationGroupIdentifier, databaseName: Constants.model) - + let storeDescription = NSPersistentStoreDescription(url: storeURL) storeDescription.setOption(FileProtectionType.none as NSObject, forKey: NSPersistentStoreFileProtectionKey) storeDescription.setValue("DELETE" as NSObject, forPragmaNamed: "journal_mode") // Disabling WAL journal container.persistentStoreDescriptions = [storeDescription] - container.loadPersistentStores { (storeDescription, error) in + container.loadPersistentStores { _, error in if let error = error { fatalError("Failed to load persistent stores: \(error)") } @@ -65,14 +65,13 @@ public class MBLoggerCoreDataManager { return container }() - private lazy var context: NSManagedObjectContext = { let context = persistentContainer.newBackgroundContext() context.automaticallyMergesChangesFromParent = true context.mergePolicy = NSMergePolicy(merge: .mergeByPropertyStoreTrumpMergePolicyType) return context }() - + // MARK: - CRUD Operations public func create(message: String, timestamp: Date, completion: (() -> Void)? = nil) { queue.async { @@ -82,58 +81,56 @@ public class MBLoggerCoreDataManager { if isTimeToDelete && self.getDBFileSize() > Constants.dbSizeLimitKB { try self.delete() } - + try self.context.executePerformAndWait { let entity = CDLogMessage(context: self.context) entity.message = message entity.timestamp = timestamp try self.saveEvent(withContext: self.context) - + completion?() } - } catch { - - } + } catch {} } } - + public func getFirstLog() throws -> LogMessage? { - var fetchedLogMessage: LogMessage? = nil + var fetchedLogMessage: LogMessage? try context.executePerformAndWait { let fetchRequest = NSFetchRequest(entityName: Constants.model) fetchRequest.predicate = NSPredicate(value: true) fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: true)] fetchRequest.fetchLimit = 1 let results = try context.fetch(fetchRequest) - + if let first = results.first { fetchedLogMessage = LogMessage(timestamp: first.timestamp, message: first.message) } } - + return fetchedLogMessage } public func getLastLog() throws -> LogMessage? { - var fetchedLogMessage: LogMessage? = nil + var fetchedLogMessage: LogMessage? try context.executePerformAndWait { let fetchRequest = NSFetchRequest(entityName: Constants.model) fetchRequest.predicate = NSPredicate(value: true) fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)] fetchRequest.fetchLimit = 1 let results = try context.fetch(fetchRequest) - + if let last = results.last { fetchedLogMessage = LogMessage(timestamp: last.timestamp, message: last.message) } } - + return fetchedLogMessage } - + public func fetchPeriod(_ from: Date, _ to: Date) throws -> [LogMessage] { var fetchedLogs: [LogMessage] = [] - + try context.executePerformAndWait { let fetchRequest = NSFetchRequest(entityName: Constants.model) fetchRequest.predicate = NSPredicate(format: "timestamp >= %@ AND timestamp <= %@", @@ -142,10 +139,10 @@ public class MBLoggerCoreDataManager { let logs = try context.fetch(fetchRequest) fetchedLogs = logs.map { LogMessage(timestamp: $0.timestamp, message: $0.message) } } - + return fetchedLogs } - + public func delete() throws { try context.executePerformAndWait { let request = NSFetchRequest(entityName: Constants.model) @@ -163,7 +160,7 @@ public class MBLoggerCoreDataManager { Logger.common(message: "10% logs has been deleted", level: .debug, category: .general) } } - + public func deleteAll() throws { try context.executePerformAndWait { let request = NSFetchRequest(entityName: Constants.model) @@ -181,7 +178,7 @@ private extension MBLoggerCoreDataManager { guard context.hasChanges else { return } try saveContext(context) } - + private func saveContext(_ context: NSManagedObjectContext) throws { do { try context.save() @@ -195,7 +192,7 @@ private extension MBLoggerCoreDataManager { throw error } } - + private func getDBFileSize() -> Int { guard let url = context.persistentStoreCoordinator?.persistentStores.first?.url else { return 0 @@ -204,5 +201,3 @@ private extension MBLoggerCoreDataManager { return Int(size) } } - - diff --git a/MindboxLogger/Shared/LoggerRepository/MBPersistentContainer.swift b/MindboxLogger/Shared/LoggerRepository/MBPersistentContainer.swift index 98dec6c3..ff276780 100644 --- a/MindboxLogger/Shared/LoggerRepository/MBPersistentContainer.swift +++ b/MindboxLogger/Shared/LoggerRepository/MBPersistentContainer.swift @@ -10,14 +10,13 @@ import Foundation import CoreData public class MBPersistentContainer: NSPersistentContainer, @unchecked Sendable { - - public static var applicationGroupIdentifier: String? = nil - - public override class func defaultDirectoryURL() -> URL { + + public static var applicationGroupIdentifier: String? + + override public class func defaultDirectoryURL() -> URL { guard let applicationGroupIdentifier = applicationGroupIdentifier else { return super.defaultDirectoryURL() } return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: applicationGroupIdentifier) ?? super.defaultDirectoryURL() } - } diff --git a/MindboxLogger/Shared/MBLogger.swift b/MindboxLogger/Shared/MBLogger.swift index 73cdd510..ec4ae6bc 100644 --- a/MindboxLogger/Shared/MBLogger.swift +++ b/MindboxLogger/Shared/MBLogger.swift @@ -11,9 +11,9 @@ import os import CoreData public class MBLogger { - + public static let shared = MBLogger() - + /** ### Levels: 0. debug - 🪲 @@ -26,14 +26,14 @@ public class MBLogger { - Note: `.error` by default; `.none` for disable logging */ public var logLevel: LogLevel = .error - + private enum ExecutionMethod { case sync(lock: NSRecursiveLock) case async(queue: DispatchQueue) } - + private let executionMethod: ExecutionMethod - + private init() { #if DEBUG executionMethod = .sync(lock: NSRecursiveLock()) @@ -62,7 +62,7 @@ public class MBLogger { return } let writer = makeWriter(subsystem: subsystem, category: category) - + switch executionMethod { case let .async(queue: queue): queue.async { @@ -73,7 +73,7 @@ public class MBLogger { writer.writeMessage(message, logLevel: level) } } - + /** Method to write log in Xcode debug output as well in Console.app. @@ -114,7 +114,7 @@ private extension MBLogger { private func makeWriter(subsystem: String, category: LogCategory) -> LogWriter { return OSLogWriter(subsystem: subsystem, category: category.rawValue.capitalized) } - + private func writeToCD(message: String, timestamp: Date = Date()) { MBLoggerCoreDataManager.shared.create(message: message, timestamp: timestamp) } diff --git a/MindboxLogger/Shared/MindboxError/LoggerErrorModel.swift b/MindboxLogger/Shared/MindboxError/LoggerErrorModel.swift index fb80837b..c466d400 100644 --- a/MindboxLogger/Shared/MindboxError/LoggerErrorModel.swift +++ b/MindboxLogger/Shared/MindboxError/LoggerErrorModel.swift @@ -16,7 +16,7 @@ public struct LoggerErrorModel { self.statusCode = statusCode self.errorKey = errorKey } - + let errorType: LoggerErrorType let description: String? let status: String? diff --git a/MindboxLogger/Shared/MindboxError/MindboxError.swift b/MindboxLogger/Shared/MindboxError/MindboxError.swift index fd3c1243..8329104d 100644 --- a/MindboxLogger/Shared/MindboxError/MindboxError.swift +++ b/MindboxLogger/Shared/MindboxError/MindboxError.swift @@ -24,7 +24,7 @@ public enum MindboxError: LocalizedError { case connectionError /// Unhandled errors case unknown(Error) - + /// Return a formatted error message public var errorDescription: String? { switch self { @@ -45,7 +45,7 @@ public enum MindboxError: LocalizedError { return "No response received from the server, please check your internet connection." } } - + public var failureReason: String? { switch self { case let .serverError(error): @@ -64,7 +64,7 @@ public enum MindboxError: LocalizedError { return "Connection error" } } - + public var errorKey: String? { switch self { case let .internalError(error): @@ -73,7 +73,7 @@ public enum MindboxError: LocalizedError { return nil } } - + public init(_ error: InternalError) { self = .internalError(error) } @@ -182,7 +182,6 @@ public extension MindboxError { errorMessage: error.localizedDescription).convertToString() } } - } public struct InternalError: CustomStringConvertible { @@ -201,7 +200,7 @@ public struct InternalError: CustomStringConvertible { self.rawError = rawError self.statusCode = statusCode } - + public init( errorKey: ErrorKey, rawError: Error? = nil, @@ -211,7 +210,7 @@ public struct InternalError: CustomStringConvertible { self.rawError = rawError self.statusCode = statusCode } - + public init( errorKey: ErrorKey, reason: String? = nil, @@ -221,12 +220,12 @@ public struct InternalError: CustomStringConvertible { self.reason = reason self.suggestion = suggestion } - + public var description: String { var string: String = "" - + string += "\nError Key: \(errorKey)" - + if let rawError = rawError { if let rawError = rawError as? DecodingError { switch rawError { @@ -247,19 +246,19 @@ public struct InternalError: CustomStringConvertible { string += "\nError description: \(rawError.localizedDescription)" } } - + if let statusCode = statusCode { string += "\nStatus code: \(statusCode)" } - + if let reason = reason { string += "\nReason: \(reason)" } - + if let suggestion = suggestion { string += "\nSuggestion: \(suggestion)" } - + return string } } diff --git a/MindboxLogger/Shared/MindboxError/ProtocolError.swift b/MindboxLogger/Shared/MindboxError/ProtocolError.swift index 5750d7eb..b72fc809 100644 --- a/MindboxLogger/Shared/MindboxError/ProtocolError.swift +++ b/MindboxLogger/Shared/MindboxError/ProtocolError.swift @@ -35,7 +35,7 @@ public struct ProtocolError: Codable, CustomStringConvertible { self.httpStatusCode = httpStatusCode self.errorId = errorId } - + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: Keys.self) httpStatusCode = try container.decode(Int.self, forKey: .httpStatusCode) diff --git a/MindboxLogger/Shared/MindboxLogger.swift b/MindboxLogger/Shared/MindboxLogger.swift index 0e9f48e3..8fe2bdea 100644 --- a/MindboxLogger/Shared/MindboxLogger.swift +++ b/MindboxLogger/Shared/MindboxLogger.swift @@ -7,11 +7,11 @@ // import Foundation - + public class Logger { private typealias Meta = (filename: String, line: Int, funcName: String) private typealias Borders = (start: String, end: String) - + private static let subsystem: String = "cloud.Mindbox" private static func log(message: String, level: LogLevel, @@ -27,7 +27,7 @@ public class Logger { header += timestamp.toString() + " " header += "\n[\(sourceFileName(filePath: meta.filename))]:\(meta.line) \(meta.funcName)" - + MBLogger.shared.log( level: level, message: borders.start + message + borders.end, @@ -36,7 +36,7 @@ public class Logger { subsystem: subsystem ?? "cloud.Mindbox" ) } - + public static func error(_ error: LoggerErrorModel, level: LogLevel = .error, category: LogCategory = .network, @@ -44,76 +44,76 @@ public class Logger { line: Int = #line, funcName: String = #function) { var logMessage: String = "" - logMessage = logMessage + "\n[\(error.errorType.rawValue) error: \(error.description ?? "No description")]" + logMessage += "\n[\(error.errorType.rawValue) error: \(error.description ?? "No description")]" if let status = error.status { - logMessage = logMessage + "\n[status: \(status)]" + logMessage += "\n[status: \(status)]" } - + if let statusCode = error.statusCode { - logMessage = logMessage + "\n[httpStatusCode: \(statusCode)]" + logMessage += "\n[httpStatusCode: \(statusCode)]" } - + if logMessage.isEmpty { return } let message = "LogManager: \n--- Error --- \(String(describing: logMessage)) \n--- End ---\n" - + let meta: Meta = (fileName, line, funcName) let borders: Borders = ("", "\n") log(message: message, level: .debug, category: .network, meta: meta, borders: borders) } - + @available(*, deprecated, message: "Method deprecated. Use error(_ error: LoggerErrorModel:) instead") public static func error(_ error: MindboxError, - level: LogLevel = .error, - category: LogCategory = .network, - fileName: String = #file, - line: Int = #line, - funcName: String = #function + level: LogLevel = .error, + category: LogCategory = .network, + fileName: String = #file, + line: Int = #line, + funcName: String = #function ) { var logMessage: String = "" switch error { case .validationError: - logMessage = logMessage + "\n[validationError: \(error.errorDescription ?? "No description")]" + logMessage += "\n[validationError: \(error.errorDescription ?? "No description")]" case let .protocolError(e): - logMessage = logMessage + "\n[status: \(e.status)]" - logMessage = logMessage + "\n[responseError: \(error.errorDescription ?? "No description")]" - logMessage = logMessage + "\n[httpStatusCode: \(e.httpStatusCode)]" + logMessage += "\n[status: \(e.status)]" + logMessage += "\n[responseError: \(error.errorDescription ?? "No description")]" + logMessage += "\n[httpStatusCode: \(e.httpStatusCode)]" case let .serverError(e): - logMessage = logMessage + "\n\(e.description)" + logMessage += "\n\(e.description)" case let .internalError(e): - logMessage = logMessage + "\n[key: \(e.errorKey)]" + logMessage += "\n[key: \(e.errorKey)]" if let rawError = e.rawError { - logMessage = logMessage + "\n[message: \(rawError.localizedDescription)]" + logMessage += "\n[message: \(rawError.localizedDescription)]" } case let .invalidResponse(e): guard let e = e else { return } - logMessage = logMessage + "\n[response: \(String(describing: e))]" + logMessage += "\n[response: \(String(describing: e))]" case .connectionError: - logMessage = logMessage + "\n[connectionError]" + logMessage += "\n[connectionError]" case let .unknown(e): - logMessage = logMessage + "\n[error: \(e.localizedDescription)]" + logMessage += "\n[error: \(e.localizedDescription)]" } if logMessage.isEmpty { return } let message = "LogManager: \n--- Error --- \(String(describing: logMessage)) \n--- End ---\n" - + let meta: Meta = (fileName, line, funcName) let borders: Borders = ("", "\n") log(message: message, level: .debug, category: .network, meta: meta, borders: borders) } - + public static func network(request: URLRequest, - httpAdditionalHeaders: [AnyHashable: Any]? = nil, - fileName: String = #file, - line: Int = #line, - funcName: String = #function) { - + httpAdditionalHeaders: [AnyHashable: Any]? = nil, + fileName: String = #file, + line: Int = #line, + funcName: String = #function) { + let urlString = request.url?.absoluteString ?? "" let components = NSURLComponents(string: urlString) - let method = request.httpMethod != nil ? "\(request.httpMethod!)" : "" + let method = "\(request.httpMethod ?? "")" let path = "\(components?.path ?? "")" let query = "\(components?.query ?? "")" let host = "\(components?.host ?? "")" @@ -137,19 +137,19 @@ public class Logger { let bodyString = NSString(data: body, encoding: String.Encoding.utf8.rawValue) ?? "Can't render body; not utf8 encoded" requestLog += "\n\(bodyString)\n" } - + let message = requestLog let meta: Meta = (fileName, line, funcName) let borders: Borders = ("\n[---------- OUT ---------->\n", "\n------------------------>]") log(message: message, level: .debug, category: .network, meta: meta, borders: borders) } - + public static func response(data: Data?, - response: URLResponse?, - error: Error?, - fileName: String = #file, - line: Int = #line, - funcName: String = #function) { + response: URLResponse?, + error: Error?, + fileName: String = #file, + line: Int = #line, + funcName: String = #function) { let urlString = response?.url?.absoluteString let components = NSURLComponents(string: urlString ?? "") @@ -171,7 +171,7 @@ public class Logger { // for (key, value) in (response as? HTTPURLResponse)?.allHeaderFields ?? [:] { // responseLog += "\(key): \(value)\n" // } - + if let body = data, let object = try? JSONSerialization.jsonObject(with: body, options: []), let data = try? JSONSerialization.data(withJSONObject: object, options: [.prettyPrinted]), @@ -182,7 +182,7 @@ public class Logger { // if let body = data { // responseLog += "[Body]: \n\(String(data: body, encoding: .utf8) ?? "")\n" // } - + var level: LogLevel = .debug if let error = error { responseLog += "\n[Error]: \(error.localizedDescription)\n" @@ -193,14 +193,14 @@ public class Logger { let borders: Borders = ("\n[<---------- IN ----------\n", "\n<------------------------]") log(message: message, level: level, category: .network, meta: meta, borders: borders) } - + public static func common(message: String, - level: LogLevel = .debug, - category: LogCategory = .general, - subsystem: String? = nil, - fileName: String = #file, - line: Int = #line, - funcName: String = #function) { + level: LogLevel = .debug, + category: LogCategory = .general, + subsystem: String? = nil, + fileName: String = #file, + line: Int = #line, + funcName: String = #function) { let meta: Meta = (fileName, line, funcName) let borders: Borders = ("", "\n") log(message: message, level: level, category: category, meta: meta, borders: borders, subsystem: subsystem) @@ -215,6 +215,6 @@ private extension Logger { /// - Returns: File Name with extension static func sourceFileName(filePath: String) -> String { let components = filePath.components(separatedBy: "/") - return components.isEmpty ? "" : components.last! + return components.last ?? "" } } diff --git a/MindboxNotifications/MindboxNotificationService.swift b/MindboxNotifications/MindboxNotificationService.swift index 7e4b4293..bfd217ff 100644 --- a/MindboxNotifications/MindboxNotificationService.swift +++ b/MindboxNotifications/MindboxNotificationService.swift @@ -13,23 +13,23 @@ import UserNotificationsUI @objcMembers public class MindboxNotificationService: NSObject { - + // MARK: MindboxNotificationServiceProtocol - + public var contentHandler: ((UNNotificationContent) -> Void)? public var bestAttemptContent: UNMutableNotificationContent? // MARK: Internal properties - + var context: NSExtensionContext? var viewController: UIViewController? - + var pushValidator: PushValidator? // MARK: Public initializer - + /// Mindbox proxy for `NotificationsService` and `NotificationViewController` - public override init() { + override public init() { super.init() pushValidator = MindboxPushValidator() } diff --git a/MindboxNotifications/MindboxPushNotification.swift b/MindboxNotifications/MindboxPushNotification.swift index b2251f0c..aed26da5 100644 --- a/MindboxNotifications/MindboxPushNotification.swift +++ b/MindboxNotifications/MindboxPushNotification.swift @@ -17,14 +17,14 @@ public protocol MindboxPushNotificationProtocol { // MARK: - MindboxPushNotificationProtocol extension MindboxNotificationService: MindboxPushNotificationProtocol { - - public func isMindboxPush(userInfo: [AnyHashable : Any]) -> Bool { + + 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? { + + 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/Models/ImageFormat.swift b/MindboxNotifications/Models/ImageFormat.swift index aedefd5b..9195fcef 100644 --- a/MindboxNotifications/Models/ImageFormat.swift +++ b/MindboxNotifications/Models/ImageFormat.swift @@ -27,7 +27,7 @@ enum ImageFormat: String { extension ImageFormat { static func get(from data: Data) -> ImageFormat? { - guard let firstByte = data.first else { + guard let firstByte = data.first else { Logger.common(message: "ImageFormat: Failed to get firstByte", level: .error, category: .notification) return nil } diff --git a/MindboxNotifications/NotificationContent.swift b/MindboxNotifications/NotificationContent.swift index ea423b50..38fb78be 100644 --- a/MindboxNotifications/NotificationContent.swift +++ b/MindboxNotifications/NotificationContent.swift @@ -13,7 +13,7 @@ import UserNotificationsUI import MindboxLogger public protocol MindboxNotificationContentProtocol: MindboxPushNotificationProtocol { - + /// Call this method in `didReceive(_ notification: UNNotification)` of `NotificationViewController` func didReceive( notification: UNNotification, @@ -25,7 +25,7 @@ public protocol MindboxNotificationContentProtocol: MindboxPushNotificationProto // 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 @@ -38,7 +38,7 @@ extension MindboxNotificationService: MindboxNotificationContentProtocol { // 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 { @@ -58,7 +58,8 @@ private extension MindboxNotificationService { 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) + let message = "Failed to create actions. payload: \(payload), context: \(String(describing: context)), payload.withButton?.buttons: \(String(describing: payload.withButton?.buttons))" + Logger.common(message: message, level: .error, category: .notification) return } let actions = buttons.map { button in @@ -92,19 +93,19 @@ private extension MindboxNotificationService { 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), + imageView.heightAnchor.constraint(lessThanOrEqualToConstant: imageViewHeight) ]) } } diff --git a/MindboxNotifications/NotificationService.swift b/MindboxNotifications/NotificationService.swift index 54d001f4..83b34bd5 100644 --- a/MindboxNotifications/NotificationService.swift +++ b/MindboxNotifications/NotificationService.swift @@ -11,19 +11,19 @@ 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. @@ -33,7 +33,7 @@ public protocol MindboxNotificationServiceProtocol: MindboxPushNotificationProto // 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 @@ -44,7 +44,7 @@ extension MindboxNotificationService: MindboxNotificationServiceProtocol { } pushDelivered(request) - + if let imageUrl = parse(request: request)?.withImageURL?.imageUrl, let allowedUrl = imageUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: allowedUrl) { @@ -56,7 +56,7 @@ extension MindboxNotificationService: MindboxNotificationServiceProtocol { proceedFinalStage(bestAttemptContent) } } - + /// Call this method in `serviceExtensionTimeWillExpire()` of `NotificationService` public func serviceExtensionTimeWillExpire() { if let bestAttemptContent = bestAttemptContent { @@ -64,7 +64,7 @@ extension MindboxNotificationService: MindboxNotificationServiceProtocol { 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. @@ -88,13 +88,13 @@ private extension MindboxNotificationService { } 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 { @@ -113,7 +113,7 @@ private extension MindboxNotificationService { return nil } } - + func proceedFinalStage(_ bestAttemptContent: UNMutableNotificationContent) { bestAttemptContent.categoryIdentifier = Constants.categoryIdentifier contentHandler?(bestAttemptContent) diff --git a/MindboxNotifications/PushNotifications/PushNotificationFormatStrategies/NotificationFormatStrategy.swift b/MindboxNotifications/PushNotifications/PushNotificationFormatStrategies/NotificationFormatStrategy.swift index 04ad29ea..7044e252 100644 --- a/MindboxNotifications/PushNotifications/PushNotificationFormatStrategies/NotificationFormatStrategy.swift +++ b/MindboxNotifications/PushNotifications/PushNotificationFormatStrategies/NotificationFormatStrategy.swift @@ -22,12 +22,12 @@ final class LegacyFormatStrategy: NotificationFormatStrategy { 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, @@ -37,7 +37,7 @@ final class LegacyFormatStrategy: NotificationFormatStrategy { } 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), @@ -54,7 +54,7 @@ final class LegacyFormatStrategy: NotificationFormatStrategy { } final class CurrentFormatStrategy: NotificationFormatStrategy { - func handle(userInfo: [AnyHashable : Any]) -> MBPushNotification? { + func handle(userInfo: [AnyHashable: Any]) -> MBPushNotification? { guard let data = try? JSONSerialization.data(withJSONObject: userInfo), let notificationModel = try? JSONDecoder().decode(MBPushNotification.self, from: data), notificationModel.clickUrl != nil, @@ -63,7 +63,7 @@ final class CurrentFormatStrategy: NotificationFormatStrategy { 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 index 3329c047..217c154d 100644 --- a/MindboxNotifications/PushNotifications/PushNotificationFormatStrategies/NotificationStrategyFactory.swift +++ b/MindboxNotifications/PushNotifications/PushNotificationFormatStrategies/NotificationStrategyFactory.swift @@ -15,7 +15,7 @@ final class NotificationStrategyFactory { 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 index 2f42d93d..f0ade11a 100644 --- a/MindboxNotifications/PushNotifications/PushValidator.swift +++ b/MindboxNotifications/PushNotifications/PushValidator.swift @@ -14,12 +14,12 @@ protocol PushValidator { } final class MindboxPushValidator: PushValidator { - func isValid(item: [AnyHashable : Any]) -> Bool { + func isValid(item: [AnyHashable: Any]) -> Bool { 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 } - + 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 index 161301c0..19b83ae8 100644 --- a/MindboxNotifications/SharedInternalMethods.swift +++ b/MindboxNotifications/SharedInternalMethods.swift @@ -26,19 +26,19 @@ extension MindboxNotificationService { 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 diff --git a/MindboxNotifications/Utilities/Constants.swift b/MindboxNotifications/Utilities/Constants.swift index a083fad9..f82bb198 100644 --- a/MindboxNotifications/Utilities/Constants.swift +++ b/MindboxNotifications/Utilities/Constants.swift @@ -9,7 +9,7 @@ import Foundation enum Constants { - + /// For `UNMutableNotificationContent.categoryIdentifier` static var categoryIdentifier = "MindBoxCategoryIdentifier" } diff --git a/MindboxNotificationsTests/MindboxNotificationContentTests.swift b/MindboxNotificationsTests/MindboxNotificationContentTests.swift index 285aaab6..2607e660 100644 --- a/MindboxNotificationsTests/MindboxNotificationContentTests.swift +++ b/MindboxNotificationsTests/MindboxNotificationContentTests.swift @@ -9,6 +9,8 @@ import XCTest @testable import MindboxNotifications +// swiftlint:disable force_unwrapping force_try + @available(iOS 13.0, *) final class MindboxNotificationContentTests: XCTestCase { @@ -16,13 +18,13 @@ final class MindboxNotificationContentTests: XCTestCase { 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": [ @@ -32,7 +34,7 @@ final class MindboxNotificationContentTests: XCTestCase { "content-available": 1, "sound": "default" ] - + let userInfo: [AnyHashable: Any] = [ "clickUrl": "https://mindbox.ru/", "payload": "{\n \"payload\": \"data\"\n}", @@ -52,25 +54,25 @@ final class MindboxNotificationContentTests: XCTestCase { ], "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 @@ -81,23 +83,23 @@ final class MindboxNotificationContentTests: XCTestCase { func testDidReceiveFromMindboxNotificationContentProtocol() { let mockNotification = MockUNNotification(request: mockNotificationRequest) - + XCTAssertFalse(mockNotification.request.content.attachments.isEmpty) - - service.didReceive(notification: mockNotification, + + 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") } @@ -106,7 +108,7 @@ final class MindboxNotificationContentTests: XCTestCase { @available(iOS 12.0, *) fileprivate class MockExtensionContext: NSExtensionContext { var actions: [UNNotificationAction] = [] - + override var notificationActions: [UNNotificationAction] { get { return actions @@ -117,25 +119,24 @@ fileprivate class MockExtensionContext: NSExtensionContext { } } - @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 index 3262b105..4b191b51 100644 --- a/MindboxNotificationsTests/MindboxNotificationServiceTests.swift +++ b/MindboxNotificationsTests/MindboxNotificationServiceTests.swift @@ -9,6 +9,8 @@ import XCTest @testable import MindboxNotifications +// swiftlint:disable force_unwrapping + final class MindboxNotificationServiceTests: XCTestCase { var service: MindboxNotificationServiceProtocol! @@ -17,7 +19,7 @@ final class MindboxNotificationServiceTests: XCTestCase { override func setUp() { super.setUp() service = MindboxNotificationService() - + let aps: [AnyHashable: Any] = [ "mutable-content": 1, "alert": [ @@ -27,7 +29,7 @@ final class MindboxNotificationServiceTests: XCTestCase { "content-available": 1, "sound": "default" ] - + let userInfo: [AnyHashable: Any] = [ "clickUrl": "https://mindbox.ru/", "payload": "{\n \"payload\": \"data\"\n}", @@ -47,14 +49,14 @@ final class MindboxNotificationServiceTests: XCTestCase { ], "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 @@ -64,18 +66,18 @@ final class MindboxNotificationServiceTests: XCTestCase { 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) diff --git a/MindboxNotificationsTests/SharedInternalMethodsTests.swift b/MindboxNotificationsTests/SharedInternalMethodsTests.swift index 220afa16..a420cb7a 100644 --- a/MindboxNotificationsTests/SharedInternalMethodsTests.swift +++ b/MindboxNotificationsTests/SharedInternalMethodsTests.swift @@ -13,11 +13,11 @@ 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": [ @@ -27,7 +27,7 @@ final class SharedInternalMethodsTests: XCTestCase { "content-available": 1, "sound": "default" ] - + let userInfo: [AnyHashable: Any] = [ "clickUrl": "https://mindbox.ru/", "payload": "{\n \"payload\": \"data\"\n}", @@ -47,18 +47,18 @@ final class SharedInternalMethodsTests: XCTestCase { ], "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) @@ -70,19 +70,19 @@ final class SharedInternalMethodsTests: XCTestCase { 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") @@ -109,26 +109,25 @@ final class SharedInternalMethodsTests: XCTestCase { "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") diff --git a/MindboxTests/ABTests/ABTestDeviceMixerTests.swift b/MindboxTests/ABTests/ABTestDeviceMixerTests.swift index c372b843..1da71a0d 100644 --- a/MindboxTests/ABTests/ABTestDeviceMixerTests.swift +++ b/MindboxTests/ABTests/ABTestDeviceMixerTests.swift @@ -9,8 +9,10 @@ import XCTest @testable import Mindbox +// swiftlint:disable force_unwrapping + final class ABTestDeviceMixerTests: XCTestCase { - + var sut: ABTestDeviceMixer! override func setUp() { @@ -22,11 +24,11 @@ final class ABTestDeviceMixerTests: XCTestCase { sut = nil super.tearDown() } - + func testModulusGuidHash() throws { let salt = "BBBC2BA1-0B5B-4C9E-AB0E-95C54775B4F1" let array = try getArray(resourceName: "MixerUUIDS") - + for item in array { let components = item.components(separatedBy: " | ") guard components.count == 2, @@ -49,7 +51,7 @@ final class ABTestDeviceMixerTests: XCTestCase { if let jsonArray = try JSONSerialization.jsonObject(with: data) as? [String] { return jsonArray } - + throw MindboxError.internalError(.init(errorKey: .parsing, reason: "Failed to convert data to JSON array.")) } catch { let errorReason: String = "Error loading resource \(resourceName): \(error.localizedDescription)." diff --git a/MindboxTests/ABTests/ABTestValidatorTests.swift b/MindboxTests/ABTests/ABTestValidatorTests.swift index f7d819eb..3cd5bce1 100644 --- a/MindboxTests/ABTests/ABTestValidatorTests.swift +++ b/MindboxTests/ABTests/ABTestValidatorTests.swift @@ -10,43 +10,43 @@ import XCTest @testable import Mindbox class ABTestValidatorTests: XCTestCase { - + var validator: ABTestValidator! - + struct TestCase { let abTest: ABTest let isValid: Bool } - + var testCases: [TestCase] = [] - + override func setUp() { super.setUp() 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"]) let variant1 = ABTest.ABTestVariant(id: "1", modulus: modulus, objects: [abObject]) - + let abtest = ABTest(id: "123", sdkVersion: SdkVersion(min: 4, max: nil), salt: "salt", variants: [ createVariant(lower: 0, upper: 50, objects: [abObject]), createVariant(lower: 50, upper: 100, objects: [abObject]) ]) - + // TODO: - Divide to few different unit tests. - + testCases = [ // Valid cases TestCase(abTest: abtest, isValid: true), TestCase(abTest: createAbTest(id: "sdfkj-sdfds-213123-dsew", sdkVersion: SdkVersion(min: 4, max: nil), salt: abtest.salt, variants: abtest.variants), isValid: true), TestCase(abTest: createAbTest(id: abtest.id, sdkVersion: SdkVersion(min: 4, max: nil), salt: "sdfsdf-sdfsd", variants: abtest.variants), isValid: true), - + // Invalid cases: No variants provided TestCase(abTest: createAbTest(id: abtest.id, sdkVersion: SdkVersion(min: 4, max: nil), salt: abtest.salt, variants: []), isValid: false), - + // Invalid cases: Only one variant provided TestCase(abTest: createAbTest(id: abtest.id, sdkVersion: SdkVersion(min: 4, max: nil), salt: abtest.salt, variants: [variant1]), isValid: false), - + // Invalid cases: Incorrect modulus values TestCase(abTest: createAbTest(id: abtest.id, sdkVersion: SdkVersion(min: 4, max: nil), salt: abtest.salt, variants: [ createVariant(lower: -1, upper: 50, objects: [abObject]), @@ -56,73 +56,72 @@ class ABTestValidatorTests: XCTestCase { variant1, createVariant(lower: 101, upper: 100, objects: [abObject]) ]), isValid: false), - + // Invalid cases: Modulus values do not span from 0 to 100 TestCase(abTest: createAbTest(id: abtest.id, sdkVersion: SdkVersion(min: 4, max: nil), salt: abtest.salt, variants: [ createVariant(lower: 1, upper: 50, objects: [abObject]), createVariant(lower: 50, upper: 100, objects: [abObject]) ]), isValid: false), - - + // Invalid cases: Incorrect modulus values TestCase(abTest: createAbTest(id: abtest.id, sdkVersion: SdkVersion(min: 4, max: nil), salt: abtest.salt, variants: [ createVariant(lower: 50, upper: 98, objects: [abObject]), variant1 ]), isValid: false), - + TestCase(abTest: createAbTest(id: abtest.id, sdkVersion: SdkVersion(min: 4, max: nil), salt: abtest.salt, variants: [ createVariant(lower: 60, upper: 100, objects: [abObject]), variant1 ]), isValid: false), - + TestCase(abTest: createAbTest(id: abtest.id, sdkVersion: SdkVersion(min: 4, max: nil), salt: abtest.salt, variants: [ createVariant(lower: 40, upper: 100, objects: [abObject]), variant1 ]), isValid: false), - + TestCase(abTest: createAbTest(id: abtest.id, sdkVersion: SdkVersion(min: 4, max: nil), salt: abtest.salt, variants: [ createVariant(lower: 0, upper: 50, objects: [abObject]), createVariant(lower: 50, upper: 50, objects: [abObject]), createVariant(lower: 50, upper: 100, objects: [abObject]) ]), isValid: false), - + TestCase(abTest: createAbTest(id: abtest.id, sdkVersion: SdkVersion(min: 4, max: nil), salt: abtest.salt, variants: [ createVariant(lower: 0, upper: 60, objects: [abObject]), createVariant(lower: 40, upper: 60, objects: [abObject]), createVariant(lower: 60, upper: 100, objects: [abObject]) ]), isValid: false), - + TestCase(abTest: createAbTest(id: abtest.id, sdkVersion: SdkVersion(min: 4, max: nil), salt: abtest.salt, variants: [ createVariant(lower: 0, upper: 60, objects: [abObject]), createVariant(lower: 40, upper: 100, objects: [abObject]), createVariant(lower: 60, upper: 40, objects: [abObject]) ]), isValid: false), - + // Invalid cases: Empty or null values TestCase(abTest: createAbTest(id: abtest.id, sdkVersion: SdkVersion(min: 4, max: nil), salt: nil, variants: abtest.variants), isValid: false), TestCase(abTest: createAbTest(id: abtest.id, sdkVersion: SdkVersion(min: 4, max: nil), salt: "", variants: abtest.variants), isValid: false), - + // Invalid cases: Empty id TestCase(abTest: createAbTest(id: "", sdkVersion: SdkVersion(min: 4, max: nil), salt: abtest.salt, variants: [variant1, variant1]), isValid: false), - + // Invalid cases: Null sdkVersion TestCase(abTest: createAbTest(id: abtest.id, sdkVersion: nil, salt: abtest.salt, variants: [variant1, variant1]), isValid: false) ] } - + override func tearDown() { validator = nil testCases = [] super.tearDown() } - + func testVariantIsValid() { for testCase in testCases { let result = validator.isValid(item: testCase.abTest) XCTAssertEqual(result, testCase.isValid, "Expected \(testCase.isValid) for variant \(String(describing: testCase.abTest)), but got \(result)") } } - + private func createVariant(id: String = "Test", lower: Int = 0, upper: Int = 100, @@ -131,7 +130,7 @@ class ABTestValidatorTests: XCTestCase { modulus: .init(lower: lower, upper: upper), objects: objects) } - + private func createAbTest(id: String, sdkVersion: SdkVersion?, salt: String?, diff --git a/MindboxTests/ABTests/ABTestVariantsValidatorTests.swift b/MindboxTests/ABTests/ABTestVariantsValidatorTests.swift index 0f28bf82..6c0a8d2d 100644 --- a/MindboxTests/ABTests/ABTestVariantsValidatorTests.swift +++ b/MindboxTests/ABTests/ABTestVariantsValidatorTests.swift @@ -11,20 +11,20 @@ import XCTest class ABTestVariantsValidatorTests: XCTestCase { var variantValidator: ABTestVariantsValidator! - + struct TestCase { let variant: ABTest.ABTestVariant? let isValid: Bool } - + var testCases: [TestCase] = [] - + override func setUp() { super.setUp() variantValidator = DI.injectOrFail(ABTestVariantsValidator.self) - + // TODO: - Divide to few different unit tests. - + let object1 = ABTest.ABTestVariant.ABTestObject(type: .inapps, kind: .all, inapps: ["inapp1"]) let object2 = ABTest.ABTestVariant.ABTestObject(type: .inapps, kind: .all, inapps: ["inapp2"]) testCases = [ @@ -38,23 +38,21 @@ class ABTestVariantsValidatorTests: XCTestCase { TestCase(variant: createVariant(objects: [object1, object2]), isValid: false), // more than one object TestCase(variant: nil, isValid: false) // variant is nil ] - - } - + override func tearDown() { variantValidator = nil testCases = [] super.tearDown() } - + func testVariantIsValid() { for testCase in testCases { let result = variantValidator.isValid(item: testCase.variant) XCTAssertEqual(result, testCase.isValid, "Expected \(testCase.isValid) for variant \(String(describing: testCase.variant)), but got \(result)") } } - + private func createVariant(id: String = "Test", lower: Int = 0, upper: Int = 100, diff --git a/MindboxTests/ConfigParsing/ABTests/ABTestsConfigParsingTests.swift b/MindboxTests/ConfigParsing/ABTests/ABTestsConfigParsingTests.swift index e2a96214..087dffb5 100644 --- a/MindboxTests/ConfigParsing/ABTests/ABTestsConfigParsingTests.swift +++ b/MindboxTests/ConfigParsing/ABTests/ABTestsConfigParsingTests.swift @@ -11,12 +11,12 @@ import XCTest fileprivate enum ABTestsConfig: String, Configurable { typealias DecodeType = [ABTest] - + case configWithABTests = "ABTestsConfig" // Correct config - + case abTestsIdConfigError = "ABTestsIdConfigError" // Key is `idTest` instead of `id` case abTestsIdConfigTypeError = "ABTestsIdConfigTypeError" // Type of `id` is Int instead of String - + case abTestsSdkVersionConfigError = "ABTestsSdkVersionConfigError" // Key is `sdkVersionTest` instead of `sdkVersion` case abTestsSdkVersionConfigTypeError = "ABTestsSdkVersionConfigTypeError" // Type of `sdkVersion` is Int instead of SdkVersion } @@ -27,16 +27,16 @@ final class ABTestsConfigParsingTests: XCTestCase { // Correct config let config = try ABTestsConfig.configWithABTests.getConfig() XCTAssertEqual(config.count, 2) - + for abTest in config { - XCTContext.runActivity(named: "Check abTest \(abTest) is in `config`") { test in + XCTContext.runActivity(named: "Check abTest \(abTest) is in `config`") { _ in XCTAssertNotNil(abTest.salt) XCTAssertNotNil(abTest.sdkVersion) XCTAssertNotNil(abTest.variants) } } } - + func test_ABTestsConfig_withIdError_shouldThrowDecodingError() throws { // Key is `idTest` instead of `id` XCTAssertThrowsError(try ABTestsConfig.abTestsIdConfigError.getConfig()) { error in @@ -53,7 +53,7 @@ final class ABTestsConfigParsingTests: XCTestCase { } } } - + func test_ABTestsConfig_withIdTypeError_shouldThrowDecodingError() throws { // Type of `id` is Int instead of String XCTAssertThrowsError(try ABTestsConfig.abTestsIdConfigTypeError.getConfig()) { error in @@ -71,14 +71,14 @@ final class ABTestsConfigParsingTests: XCTestCase { } } } - + func test_ABTestsConfig_withSdkVersionError_shouldSetToNilCorruptedData() throws { // Key is `sdkVersionTest` instead of `sdkVersion` let config = try ABTestsConfig.abTestsSdkVersionConfigError.getConfig() XCTAssertEqual(config.count, 2) - + for abTest in config { - XCTContext.runActivity(named: "Check abTest \(abTest) is in `config`") { test in + XCTContext.runActivity(named: "Check abTest \(abTest) is in `config`") { _ in if abTest.id == "94CD824A-59AA-4937-9E0E-089895A0DB6F" { XCTAssertNil(abTest.sdkVersion) } else { @@ -89,7 +89,7 @@ final class ABTestsConfigParsingTests: XCTestCase { } } } - + func test_ABTestsConfig_withSdkVersionTypeError_shouldThrowDecodingError() throws { // Type of `sdkVersion` is Int instead of SdkVersion XCTAssertThrowsError(try ABTestsConfig.abTestsSdkVersionConfigTypeError.getConfig()) { error in diff --git a/MindboxTests/ConfigParsing/Config/ConfigParsingTests.swift b/MindboxTests/ConfigParsing/Config/ConfigParsingTests.swift index 23fc7611..0333dac9 100644 --- a/MindboxTests/ConfigParsing/Config/ConfigParsingTests.swift +++ b/MindboxTests/ConfigParsing/Config/ConfigParsingTests.swift @@ -9,29 +9,31 @@ import XCTest @testable import Mindbox +// swiftlint:disable force_try + fileprivate enum Config: String, Configurable { typealias DecodeType = ConfigResponse - + case configWithSettingsABTestsMonitoringInapps = "ConfigWithSettingsABTestsMonitoringInapps" // Correct config - + case configSettingsError = "ConfigSettingsError" // Key is `settingsTest` instead of `settings` case configSettingsTypeError = "ConfigSettingsTypeError" // Type of `settings` is Int instead of Settings - + case configMonitoringError = "ConfigMonitoringError" // Key is `monitoringTest` instead of `monitoring` case configMonitoringTypeError = "ConfigMonitoringTypeError" // Type of `monitoring` is Int instead of Monitoring - + case configABTestsError = "ConfigABTestsError" // Key is `abtestsTest` instead of `abtests` case configABTestsTypeError = "ConfigABTestsTypeError" // Type of `abtests` is Int instead of [ABTest] - + case configInAppsError = "ConfigInAppsError" // Key is `inappsTest` instead of `inapps` case configInAppsTypeError = "ConfigInAppsTypeError" // Type of `inapps` is Int instead of FailableDecodableArray - + case configABTestsOneElementError = "ConfigABTestsOneElementError" // Key is `saltTest` instead of `salt` case configABTestsOneElementTypeError = "ConfigABTestsOneElementTypeError" // Type of `variants` is Int instead of [ABTestVariant] } final class ConfigParsingTests: XCTestCase { - + func test_Config_shouldParseSuccessfully() throws { // Correct config let config = try! Config.configWithSettingsABTestsMonitoringInapps.getConfig() @@ -40,7 +42,7 @@ final class ConfigParsingTests: XCTestCase { XCTAssertNotNil(config.settings, "Must NOT be nil") XCTAssertNotNil(config.monitoring, "Must NOT be nil") } - + func test_Config_withSettingsError_shouldSetSettingsToNil() { // Key is `settingsTest` instead of `settings` let config = try! Config.configSettingsError.getConfig() @@ -49,7 +51,7 @@ final class ConfigParsingTests: XCTestCase { XCTAssertNotNil(config.abtests) XCTAssertNotNil(config.monitoring) } - + func test_Config_withSettingsTypeError_shouldSetSettingsToNil() { // Type of `settings` is Int instead of Settings let config = try! Config.configSettingsTypeError.getConfig() @@ -58,7 +60,7 @@ final class ConfigParsingTests: XCTestCase { XCTAssertNotNil(config.abtests) XCTAssertNotNil(config.monitoring) } - + func test_Config_withMonitoringError_shouldSetMonitoringToNil() { // Key is `monitoringTest` instead of `monitoring` let config = try! Config.configMonitoringError.getConfig() @@ -67,7 +69,7 @@ final class ConfigParsingTests: XCTestCase { XCTAssertNotNil(config.inapps) XCTAssertNotNil(config.abtests) } - + func test_Config_withMonitoringTypeError_shouldSetMonitoringToNil() { // Type of `monitoring` is Int instead of Monitoring let config = try! Config.configMonitoringTypeError.getConfig() @@ -76,7 +78,7 @@ final class ConfigParsingTests: XCTestCase { XCTAssertNotNil(config.inapps) XCTAssertNotNil(config.abtests) } - + func test_Config_withABTestsError_shouldSetABTestsToNil() { // Key is `abtestsTest` instead of `abtests` let config = try! Config.configABTestsError.getConfig() @@ -85,7 +87,7 @@ final class ConfigParsingTests: XCTestCase { XCTAssertNotNil(config.settings) XCTAssertNotNil(config.inapps) } - + func test_Config_withABTestsTypeError_shouldSetABTestsToNil() { // Type of `abtests` is Int instead of [ABTest] let config = try! Config.configABTestsTypeError.getConfig() @@ -94,7 +96,7 @@ final class ConfigParsingTests: XCTestCase { XCTAssertNotNil(config.settings) XCTAssertNotNil(config.inapps) } - + func test_Config_withInAppsError_shouldSetInAppsToNil() { // Key is `inappsTest` instead of `inapps` let config = try! Config.configInAppsError.getConfig() @@ -103,7 +105,7 @@ final class ConfigParsingTests: XCTestCase { XCTAssertNotNil(config.monitoring) XCTAssertNotNil(config.settings) } - + func test_Config_withInAppsTypeError_shouldSetInAppsToNil() { // Type of `inapps` is Int instead of FailableDecodableArray let config = try! Config.configInAppsTypeError.getConfig() @@ -112,7 +114,7 @@ final class ConfigParsingTests: XCTestCase { XCTAssertNotNil(config.monitoring) XCTAssertNotNil(config.settings) } - + func test_Config_withABTestsOneElementError_shouldSetABTestsToNil() { // Key is `saltTest` instead of `salt` let config = try! Config.configABTestsOneElementError.getConfig() @@ -121,7 +123,7 @@ final class ConfigParsingTests: XCTestCase { XCTAssertNotNil(config.settings) XCTAssertNotNil(config.inapps) } - + func test_Config_withABTestsOneElementTypeError_shouldSetABTestsToNil() { // Type of `variants` is Int instead of [ABTestVariant] let config = try! Config.configABTestsOneElementTypeError.getConfig() diff --git a/MindboxTests/ConfigParsing/Monitoring/MonitoringConfigParsingTests.swift b/MindboxTests/ConfigParsing/Monitoring/MonitoringConfigParsingTests.swift index c055172f..ee8f00a5 100644 --- a/MindboxTests/ConfigParsing/Monitoring/MonitoringConfigParsingTests.swift +++ b/MindboxTests/ConfigParsing/Monitoring/MonitoringConfigParsingTests.swift @@ -9,32 +9,34 @@ import XCTest @testable import Mindbox +// swiftlint:disable force_try force_unwrapping + fileprivate enum MonitoringConfig: String, Configurable { typealias DecodeType = Monitoring - + case configWithMonitoring = "MonitoringConfig" // Correct config - + case monitoringLogsError = "MonitoringLogsError" // Key is `logsTests` instead of `logs` case monitoringLogsTypeError = "MonitoringLogsTypeError" // Type of `logs` is Int instead of FailableDecodableArray - + case monitoringLogsOneElementError = "MonitoringLogsOneElementError" // Key is `request` instead of `requestId` case monitoringLogsTwoElementsError = "MonitoringLogsTwoElementsError" // Key is `request` instead of `requestId` and key is `device` instead of `deviceUUID` - + case monitoringLogsOneElementTypeError = "MonitoringLogsOneElementTypeError" // Type of `requestId` is Int instead of String case monitoringLogsTwoElementsTypeError = "MonitoringLogsTwoElementsTypeError" // Type of `requestId` is Int instead of String and type of `from` is Object instead `String` case monitoringLogsElementsMixedError = "MonitoringLogsElementsMixedError" // Type of `requestId` is Int instead of String and key is `fromTest` instead of `from` } final class MonitoringConfigParsingTests: XCTestCase { - + func test_MonitoringConfig_shouldParseSuccessfully() { // Correct config let config = try! MonitoringConfig.configWithMonitoring.getConfig() - + XCTAssertEqual(config.logs.elements.count, 2) - + for log in config.logs.elements { - XCTContext.runActivity(named: "Check log \(log) is in `config.logs.elements`") { test in + XCTContext.runActivity(named: "Check log \(log) is in `config.logs.elements`") { _ in XCTAssertNotNil(log.deviceUUID) XCTAssertNotNil(log.requestId) XCTAssertNotNil(log.from) @@ -42,7 +44,7 @@ final class MonitoringConfigParsingTests: XCTestCase { } } } - + func test_MonitoringConfig_withLogsError_shouldSetMonitoringToNil() { // Key is `logsTests` instead of `logs` let config = try? MonitoringConfig.monitoringLogsError.getConfig() @@ -56,16 +58,16 @@ final class MonitoringConfigParsingTests: XCTestCase { XCTAssertNil(config, "Monitoring must be `nil` if the type of `logs` is not a `FailableDecodableArray`") XCTAssertNil(config?.logs, "Logs must be `nil` if the type of `logs` is not a `FailableDecodableArray`") } - + func test_MonitoringConfig_withLogsOneElementError_shouldParseSuccessfullyRemainsElements() { // Type of `requestId` is Int instead of String let config = try? MonitoringConfig.monitoringLogsOneElementError.getConfig() XCTAssertNotNil(config?.logs, "Monitoring must be parsed successfully") - + XCTAssertEqual(config?.logs.elements.count, 1) - + for log in config!.logs.elements { - XCTContext.runActivity(named: "Check log \(log) is in `config.logs.elements`") { test in + XCTContext.runActivity(named: "Check log \(log) is in `config.logs.elements`") { _ in XCTAssertNotNil(log.deviceUUID) XCTAssertNotNil(log.requestId) XCTAssertNotNil(log.from) @@ -73,16 +75,16 @@ final class MonitoringConfigParsingTests: XCTestCase { } } } - + func test_MonitoringConfig_withLogsOneElementTypeError_shouldParseSuccessfullyRemainsElements() { // Type of `requestId` is Int instead of String let config = try? MonitoringConfig.monitoringLogsOneElementTypeError.getConfig() XCTAssertNotNil(config?.logs, "Monitoring must be parsed successfully") - + XCTAssertEqual(config?.logs.elements.count, 1) - + for log in config!.logs.elements { - XCTContext.runActivity(named: "Check log \(log) is in `config.logs.elements`") { test in + XCTContext.runActivity(named: "Check log \(log) is in `config.logs.elements`") { _ in XCTAssertNotNil(log.deviceUUID) XCTAssertNotNil(log.requestId) XCTAssertNotNil(log.from) @@ -90,28 +92,28 @@ final class MonitoringConfigParsingTests: XCTestCase { } } } - + func test_MonitoringConfig_withLogsTwoElementsError_shouldParseSuccessfullyRemainsElements() { // Key is `request` instead `requestId` and key is `device` instead of `deviceUUID` let config = try? MonitoringConfig.monitoringLogsTwoElementsError.getConfig() XCTAssertNotNil(config?.logs, "Monitoring must be parsed successfully") - + XCTAssertEqual(config?.logs.elements.count, 0) } - + func test_MonitoringConfig_withLogsTwoElementsTypeError_shouldParseSuccessfullyRemainsElements() { // Type of `requestId` is Int instead of String and type of `from` is Object instead of `String` let config = try? MonitoringConfig.monitoringLogsTwoElementsTypeError.getConfig() XCTAssertNotNil(config?.logs, "Monitoring must be parsed successfully, but with empty array") - + XCTAssertEqual(config?.logs.elements.count, 0) } - + func test_MonitoringConfig_withLogsTwoElementsMixedError_shouldParseSuccessfullyRemainsElements() { // Type of `requestId` is Int instead of String and key is `fromTest` instead of `from` let config = try? MonitoringConfig.monitoringLogsElementsMixedError.getConfig() XCTAssertNotNil(config?.logs, "Monitoring must be parsed successfully, but with empty array") - + XCTAssertEqual(config?.logs.elements.count, 0) } } diff --git a/MindboxTests/ConfigParsing/Settings/SettingsConfigParsingTests.swift b/MindboxTests/ConfigParsing/Settings/SettingsConfigParsingTests.swift index a3280474..4c37e6a7 100644 --- a/MindboxTests/ConfigParsing/Settings/SettingsConfigParsingTests.swift +++ b/MindboxTests/ConfigParsing/Settings/SettingsConfigParsingTests.swift @@ -9,36 +9,38 @@ import XCTest @testable import Mindbox +// swiftlint:disable force_try line_length + fileprivate enum SettingsConfig: String, Configurable { typealias DecodeType = Settings - + case configWithSettings = "SettingsConfig" // Correct config - + // Operations file names - + case settingsOperationsError = "SettingsOperationsError" // Key is `operationsTests` instead of `operations` case settingsOperationsTypeError = "SettingsOperationsTypeError" // Type of `operations` is Int instead of `SettingsOperations` - + case settingsOperationsViewProductError = "SettingsOperationsViewProductError" // Key is `viewProductTest` instead of `viewProduct` case settingsOperationsViewProductTypeError = "SettingsOperationsViewProductTypeError" // Type of `viewProduct` is Int instead of Operation - + case settingsOperationsViewProductSystemNameError = "SettingsOperationsViewProductSystemNameError" // Key is `systemNameTest` instead of `systemName` case settingsOperationsViewProductSystemNameTypeError = "SettingsOperationsViewProductSystemNameTypeError" // Type of `systemName` is Int instead of String - + case settingsAllOperationsWithErrors = "SettingsAllOperationsWithErrors" // Keys are `viewProductTest`, `viewCategoryTest` and `setCartTest` instead of `viewProduct`, `viewCategory` and `setCart` case settingsAllOperationsWithTypeErrors = "SettingsAllOperationsWithTypeErrors" // Types of `viewProduct`, `viewCategory` and `setCart` are Int instead of String - + case settingsOperationsViewCategoryAndSetCartError = "SettingsOperationsViewCategoryAndSetCartError" // Keys are `viewCategoryTest` and `setCartTest` instead of `viewCategory` and `setCart` case settingsOperationsViewCategoryAndSetCartTypeError = "SettingsOperationsViewCategoryAndSetCartTypeError" // Types of `viewCategory` and `setCart` are Int instead of String case settingsOperationsViewCategoryAndSetCartSystemNameError = "SettingsOperationsViewCategoryAndSetCartSystemNameError" // Keys are `systemNameTest` instead of `systemName` case settingsOperationsViewCategoryAndSetCartSystemNameTypeError = "SettingsOperationsViewCategoryAndSetCartSystemNameTypeError" // Types of `systemName` are Int instead of String case settingsOperationsViewCategoryAndSetCartSystemNameMixedError = "SettingsOperationsViewCategoryAndSetCartSystemNameMixedError" // Key of `viewCategory` is `systemNameTest` instead of `systemName` and type of `setCart`:`systemName` is Int instead of String - + // TTL file names - + case ttlError = "SettingsTtlError" // Key `ttlTest` instead of `ttl` case ttlTypeError = "SettingsTtlTypeError" // Type of `ttl` is Int instead of TimeToLive - + case ttlInappsError = "SettingsTtlInappsError" // Key is `inappsTest` instead of `inapps` case ttlInappsTypeError = "SettingsTtlInappsTypeError" // Type of `ttl` is Int instead of String } @@ -46,7 +48,7 @@ fileprivate enum SettingsConfig: String, Configurable { final class SettingsConfigParsingTests: XCTestCase { // MARK: Settings - + func test_SettingsConfig_shouldParseSuccessfully() { // Correct config let config = try! SettingsConfig.configWithSettings.getConfig() @@ -54,13 +56,13 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNotNil(config.operations?.viewProduct) XCTAssertNotNil(config.operations?.viewCategory) XCTAssertNotNil(config.operations?.setCart) - + XCTAssertNotNil(config.ttl) XCTAssertNotNil(config.ttl?.inapps) } - + // MARK: - Operations - + func test_SettingsConfig_withOperationsError_shouldSetOperationsToNil() { // Key is `operationsTests` instead of `operations` let config = try! SettingsConfig.settingsOperationsError.getConfig() @@ -68,11 +70,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNil(config.operations?.viewProduct) XCTAssertNil(config.operations?.viewCategory) XCTAssertNil(config.operations?.setCart) - + XCTAssertNotNil(config.ttl, "TTL must be successfully parsed") XCTAssertNotNil(config.ttl?.inapps, "TTL must be successfully parsed") } - + func test_SettingsConfig_withOperationsTypeError_shouldSetOperationsToNil() { // Type of `operations` is Int instead of `SettingsOperations` let config = try! SettingsConfig.settingsOperationsTypeError.getConfig() @@ -80,11 +82,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNil(config.operations?.viewProduct) XCTAssertNil(config.operations?.viewCategory) XCTAssertNil(config.operations?.setCart) - + XCTAssertNotNil(config.ttl, "TTL must be successfully parsed") XCTAssertNotNil(config.ttl?.inapps, "TTL must be successfully parsed") } - + func test_SettingsConfig_withOperationsViewProductError_shouldSetViewProductToNil() { // Key is `viewProductTest` instead of `viewProduct` let config = try! SettingsConfig.settingsOperationsViewProductError.getConfig() @@ -92,11 +94,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNil(config.operations?.viewProduct, "ViewProduct must be `nil` if the key `viewProduct` is not found") XCTAssertNotNil(config.operations?.viewCategory) XCTAssertNotNil(config.operations?.setCart) - + XCTAssertNotNil(config.ttl, "TTL must be successfully parsed") XCTAssertNotNil(config.ttl?.inapps, "TTL must be successfully parsed") } - + func test_SettingsConfig_withOperationsViewProductTypeError_shouldSetViewProductToNil() { // Type of `viewProduct` is Int instead of Operation let config = try! SettingsConfig.settingsOperationsViewProductTypeError.getConfig() @@ -104,11 +106,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNil(config.operations?.viewProduct, "ViewProduct must be `nil` if the type of `viewProduct` is not an `Operation`") XCTAssertNotNil(config.operations?.viewCategory) XCTAssertNotNil(config.operations?.setCart) - + XCTAssertNotNil(config.ttl, "TTL must be successfully parsed") XCTAssertNotNil(config.ttl?.inapps, "TTL must be successfully parsed") } - + func test_SettingsConfig_withOperationsViewProductSystemNameError_shouldSetViewProductToNil() { // Key is `systemNameTest` instead of `systemName` let config = try! SettingsConfig.settingsOperationsViewProductSystemNameError.getConfig() @@ -116,11 +118,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNil(config.operations?.viewProduct, "ViewProduct must be `nil` if the key `systemName` is not found") XCTAssertNotNil(config.operations?.viewCategory) XCTAssertNotNil(config.operations?.setCart) - + XCTAssertNotNil(config.ttl, "TTL must be successfully parsed") XCTAssertNotNil(config.ttl?.inapps, "TTL must be successfully parsed") } - + func test_SettingsConfig_withOperationsViewProductSystemNameTypeError_shouldSetViewProductToNil() { // Type of `systemName` is Int instead of String let config = try! SettingsConfig.settingsOperationsViewProductSystemNameTypeError.getConfig() @@ -128,11 +130,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNil(config.operations?.viewProduct, "ViewProduct must be `nil` if the type of `systemName` is not a `String`") XCTAssertNotNil(config.operations?.viewCategory) XCTAssertNotNil(config.operations?.setCart) - + XCTAssertNotNil(config.ttl, "TTL must be successfully parsed") XCTAssertNotNil(config.ttl?.inapps, "TTL must be successfully parsed") } - + func test_SettingsConfig_withAllOperationsWithErrors_shouldSetOperationsToNil() { // Keys are `viewProductTest`, `viewCategoryTest` and `setCartTest` instead of `viewProduct`, `viewCategory` and `setCart` let config = try! SettingsConfig.settingsAllOperationsWithErrors.getConfig() @@ -140,11 +142,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNil(config.operations?.viewProduct) XCTAssertNil(config.operations?.viewCategory) XCTAssertNil(config.operations?.setCart) - + XCTAssertNotNil(config.ttl, "TTL must be successfully parsed") XCTAssertNotNil(config.ttl?.inapps, "TTL must be successfully parsed") } - + func test_SettingsConfig_withAllOperationsWithTypeErrors_shouldSetOperationsToNil() { // Types of `viewProduct`, `viewCategory` and `setCart` are Int instead of String let config = try! SettingsConfig.settingsAllOperationsWithTypeErrors.getConfig() @@ -152,11 +154,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNil(config.operations?.viewProduct) XCTAssertNil(config.operations?.viewCategory) XCTAssertNil(config.operations?.setCart) - + XCTAssertNotNil(config.ttl, "TTL must be successfully parsed") XCTAssertNotNil(config.ttl?.inapps, "TTL must be successfully parsed") } - + func test_SettingsConfig_withOperationsViewCategoryAndSetCartError_shouldSetViewCategoryAndSetCartToNil() { // Keys are `viewCategoryTest` and `setCartTest` instead of `viewCategory` and `setCart` let config = try! SettingsConfig.settingsOperationsViewCategoryAndSetCartError.getConfig() @@ -164,11 +166,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNotNil(config.operations?.viewProduct, "ViewProduct must be successfully parsed") XCTAssertNil(config.operations?.viewCategory, "ViewCategory must be `nil` if the key `viewCategory` is not found") XCTAssertNil(config.operations?.setCart, "setCart must be `nil` if the key `setCart` is not found") - + XCTAssertNotNil(config.ttl, "TTL must be successfully parsed") XCTAssertNotNil(config.ttl?.inapps, "TTL must be successfully parsed") } - + func test_SettingsConfig_withOperationsViewCategoryAndSetCartTypeError_shouldSetViewCategoryAndSetCartToNil() { // Types of `viewCategory` and `setCart` are Int instead of String let config = try! SettingsConfig.settingsOperationsViewCategoryAndSetCartTypeError.getConfig() @@ -176,11 +178,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNotNil(config.operations?.viewProduct, "ViewProduct must be successfully parsed") XCTAssertNil(config.operations?.viewCategory, "ViewCategory must be `nil` if the type of `ViewCategory` is not a `SettingsOperations`") XCTAssertNil(config.operations?.setCart, "setCart must be `nil` if the type `setCart` is not a `SettingsOperations`") - + XCTAssertNotNil(config.ttl, "TTL must be successfully parsed") XCTAssertNotNil(config.ttl?.inapps, "TTL must be successfully parsed") } - + func test_SettingsConfig_withOperationsViewCategoryAndSetCartSystemNameError_shouldSetViewCategoryAndSetCartToNil() { // Keys are `systemNameTest` instead of `systemName` let config = try! SettingsConfig.settingsOperationsViewCategoryAndSetCartSystemNameError.getConfig() @@ -188,11 +190,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNotNil(config.operations?.viewProduct, "ViewProduct must be successfully parsed") XCTAssertNil(config.operations?.viewCategory, "ViewCategory must be `nil` if the key `systemName` is not found") XCTAssertNil(config.operations?.setCart, "setCart must be `nil` if the key `systemName` is not found") - + XCTAssertNotNil(config.ttl, "TTL must be successfully parsed") XCTAssertNotNil(config.ttl?.inapps, "TTL must be successfully parsed") } - + func test_SettingsConfig_withOperationsViewCategoryAndSetCartSystemNameTypeError_shouldSetViewCategoryAndSetCartToNil() { // Types of `systemName` are Int instead of String let config = try! SettingsConfig.settingsOperationsViewCategoryAndSetCartSystemNameTypeError.getConfig() @@ -200,11 +202,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNotNil(config.operations?.viewProduct, "ViewProduct must be successfully parsed") XCTAssertNil(config.operations?.viewCategory, "ViewCategory must be `nil` if the type of `systemName` is not a `String`") XCTAssertNil(config.operations?.setCart, "setCart must be `nil` if the type `systemName` is not a `String`") - + XCTAssertNotNil(config.ttl, "TTL must be successfully parsed") XCTAssertNotNil(config.ttl?.inapps, "TTL must be successfully parsed") } - + func test_SettingsConfig_withOperationsViewCategoryAndSetCartSystemNameMixedError_shouldSetViewCategoryAndSetCartToNil() { // Key of `viewCategory` is `systemNameTest` instead of `systemName` and type of `setCart`:`systemName` is Int instead of String let config = try! SettingsConfig.settingsOperationsViewCategoryAndSetCartSystemNameMixedError.getConfig() @@ -212,13 +214,13 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNotNil(config.operations?.viewProduct, "ViewProduct must be successfully parsed") XCTAssertNil(config.operations?.viewCategory, "ViewCategory must be `nil` if the key `systemName` is not found") XCTAssertNil(config.operations?.setCart, "setCart must be `nil` if the type `systemName` is not a `String`") - + XCTAssertNotNil(config.ttl, "TTL must be successfully parsed") XCTAssertNotNil(config.ttl?.inapps, "TTL must be successfully parsed") } - + // MARK: - TTL - + func test_SettingsConfig_withTtlError_shouldSetTtlToNil() { // Key `ttlTest` instead of `ttl` let config = try! SettingsConfig.ttlError.getConfig() @@ -226,11 +228,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNotNil(config.operations?.viewProduct) XCTAssertNotNil(config.operations?.viewCategory) XCTAssertNotNil(config.operations?.setCart) - + XCTAssertNil(config.ttl, "TTL must be `nil` if the key `ttl` is not found") XCTAssertNil(config.ttl?.inapps, "TTL must be nil") } - + func test_SettingsConfig_withTtlTypeError_shouldSetTtlToNil() { // Type of `ttl` is Int instead of TimeToLive let config = try! SettingsConfig.ttlTypeError.getConfig() @@ -238,11 +240,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNotNil(config.operations?.viewProduct) XCTAssertNotNil(config.operations?.viewCategory) XCTAssertNotNil(config.operations?.setCart) - + XCTAssertNil(config.ttl, "TTL must be `nil` if the type of `inapps` is not a `TimeToLive`") XCTAssertNil(config.ttl?.inapps, "TTL must be nil") } - + func test_SettingsConfig_withTtlInappsError_shouldSetTtlToNil() { // Key is `inappsTest` instead of `inapps` let config = try! SettingsConfig.ttlInappsError.getConfig() @@ -250,11 +252,11 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNotNil(config.operations?.viewProduct) XCTAssertNotNil(config.operations?.viewCategory) XCTAssertNotNil(config.operations?.setCart) - + XCTAssertNil(config.ttl, "TTL must be `nil` if the key `inapps` is not found") XCTAssertNil(config.ttl?.inapps, "TTL must be nil") } - + func test_SettingsConfig_withTtlInappsTypeError_shouldSetTtlToNil() { // Type of `ttl` is Int instead of String let config = try! SettingsConfig.ttlInappsTypeError.getConfig() @@ -262,7 +264,7 @@ final class SettingsConfigParsingTests: XCTestCase { XCTAssertNotNil(config.operations?.viewProduct) XCTAssertNotNil(config.operations?.viewCategory) XCTAssertNotNil(config.operations?.setCart) - + XCTAssertNil(config.ttl, "TTL must be `nil` if the key `inapps` is not a `String`") XCTAssertNil(config.ttl?.inapps, "TTL must be `nil` if the key `inapps` is not a `String`") } diff --git a/MindboxTests/DI/DIMainModuleRegistrationTests.swift b/MindboxTests/DI/DIMainModuleRegistrationTests.swift index 28fc5c90..3e2af74f 100644 --- a/MindboxTests/DI/DIMainModuleRegistrationTests.swift +++ b/MindboxTests/DI/DIMainModuleRegistrationTests.swift @@ -15,7 +15,7 @@ final class DIMainModuleRegistrationTests: XCTestCase { super.setUp() MBInject.mode = .standard } - + func testCoreControllerIsRegistered() { let coreController: CoreController? = DI.inject(CoreController.self) XCTAssertNotNil(coreController) @@ -40,7 +40,7 @@ final class DIMainModuleRegistrationTests: XCTestCase { let manager: InAppCoreManagerProtocol? = DI.inject(InAppCoreManagerProtocol.self) XCTAssertNotNil(manager) } - + func testUUIDDebugServiceIsRegistered() { let service: UUIDDebugService? = DI.inject(UUIDDebugService.self) XCTAssertNotNil(service) @@ -156,98 +156,98 @@ final class DIMainModuleRegistrationTests: XCTestCase { 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) diff --git a/MindboxTests/DI/DITests.swift b/MindboxTests/DI/DITests.swift index 535175ad..099747bf 100644 --- a/MindboxTests/DI/DITests.swift +++ b/MindboxTests/DI/DITests.swift @@ -9,7 +9,6 @@ import XCTest @testable import Mindbox - class TestModeRegistrationTests: XCTestCase { override func setUp() { super.setUp() diff --git a/MindboxTests/DI/Injections/InjectionMocks.swift b/MindboxTests/DI/Injections/InjectionMocks.swift index 0003a262..a614ee1a 100644 --- a/MindboxTests/DI/Injections/InjectionMocks.swift +++ b/MindboxTests/DI/Injections/InjectionMocks.swift @@ -9,38 +9,40 @@ import Foundation @testable import Mindbox +// swiftlint:disable force_try + 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) @@ -51,25 +53,25 @@ extension MBContainer { 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 index c06c6e56..509e6c6e 100644 --- a/MindboxTests/DI/StubContainer.swift +++ b/MindboxTests/DI/StubContainer.swift @@ -21,7 +21,7 @@ enum TestConfiguration { .registerInappPresentation() .registerMocks() } - + MBInject.mode = .test } } diff --git a/MindboxTests/Database/DatabaseLoaderTest.swift b/MindboxTests/Database/DatabaseLoaderTest.swift index 774ddb80..257d412a 100644 --- a/MindboxTests/Database/DatabaseLoaderTest.swift +++ b/MindboxTests/Database/DatabaseLoaderTest.swift @@ -10,23 +10,25 @@ import XCTest import CoreData @testable import Mindbox +// swiftlint:disable force_try force_unwrapping + class DatabaseLoaderTest: XCTestCase { - + var persistentContainer: NSPersistentContainer! var databaseLoader: DataBaseLoader! - + override func setUp() { super.setUp() databaseLoader = DI.injectOrFail(DataBaseLoader.self) persistentContainer = DI.injectOrFail(MBDatabaseRepository.self).persistentContainer } - + override func tearDown() { databaseLoader = nil persistentContainer = nil super.tearDown() } - + func testDestroyDatabase() { let persistentStoreURL = databaseLoader.persistentStoreURL! XCTAssertNotNil(persistentContainer.persistentStoreCoordinator.persistentStore(for: persistentStoreURL)) diff --git a/MindboxTests/Database/DatabaseRepositoryTestCase.swift b/MindboxTests/Database/DatabaseRepositoryTestCase.swift index f3a7b1ff..bd7e314b 100644 --- a/MindboxTests/Database/DatabaseRepositoryTestCase.swift +++ b/MindboxTests/Database/DatabaseRepositoryTestCase.swift @@ -10,38 +10,44 @@ import XCTest import CoreData @testable import Mindbox +// swiftlint:disable force_try + class DatabaseRepositoryTestCase: XCTestCase { - + var databaseRepository: MBDatabaseRepository! var eventGenerator: EventGenerator! - + override func setUp() { super.setUp() databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) eventGenerator = EventGenerator() - + try! databaseRepository.erase() updateDatabaseRepositoryWith(createsDeprecated: false) // (databaseRepository as! MockDatabaseRepository).tempLimit = nil // Put setup code here. This method is called before the invocation of each test method in the class. } - + override func tearDown() { databaseRepository = nil eventGenerator = nil super.tearDown() } - + private func updateDatabaseRepositoryWith(createsDeprecated: Bool) { - (databaseRepository as! MockDatabaseRepository).createsDeprecated = createsDeprecated + guard let mockDatabaseRepository = databaseRepository as? MockDatabaseRepository else { + fatalError("Failed to cast databaseRepository to MockDatabaseRepository") + } + + mockDatabaseRepository.createsDeprecated = createsDeprecated } - + func testCreateDatabaseRepository() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. XCTAssertNotNil(databaseRepository) } - + func testCreateEvent() { let event = eventGenerator.generateEvent() let expectation = self.expectation(description: "create event") @@ -53,7 +59,7 @@ class DatabaseRepositoryTestCase: XCTestCase { } waitForExpectations(timeout: 1, handler: nil) } - + func testReadEvent() { let event = eventGenerator.generateEvent() let expectation = self.expectation(description: "read event") @@ -69,10 +75,10 @@ class DatabaseRepositoryTestCase: XCTestCase { } catch { XCTFail(error.localizedDescription) } - + waitForExpectations(timeout: 4, handler: nil) } - + func testUpdateEvent() { let event = eventGenerator.generateEvent() var initailRetryTimeStamp: Double? @@ -103,7 +109,7 @@ class DatabaseRepositoryTestCase: XCTestCase { } XCTAssertNotEqual(initailRetryTimeStamp, updatedRetryTimeStamp) } - + func testDeleteEvent() { let event = eventGenerator.generateEvent() do { @@ -120,7 +126,7 @@ class DatabaseRepositoryTestCase: XCTestCase { } waitForExpectations(timeout: 1, handler: nil) } - + func testHasEventsAfterCreation() { databaseRepository.onObjectsDidChange = { [self] in do { @@ -132,7 +138,7 @@ class DatabaseRepositoryTestCase: XCTestCase { } testCreateEvent() } - + func testLimitCount() { // try! databaseRepository.erase() //// (databaseRepository as! MockDatabaseRepository).tempLimit = 3 @@ -152,7 +158,7 @@ class DatabaseRepositoryTestCase: XCTestCase { // XCTFail(error.localizedDescription) // } } - + func testLifeTimeLimit() { XCTAssertNotNil(databaseRepository.lifeLimitDate) let event = eventGenerator.generateEvent() @@ -162,7 +168,7 @@ class DatabaseRepositoryTestCase: XCTestCase { } XCTAssertTrue(event.enqueueTimeStamp > monthLimitDate.timeIntervalSince1970) } - + func testRemoveDeprecatedEvents() { let event = eventGenerator.generateEvent() do { @@ -176,7 +182,7 @@ class DatabaseRepositoryTestCase: XCTestCase { XCTFail(error.localizedDescription) } } - + func testDeprecatedEventsCount() { updateDatabaseRepositoryWith(createsDeprecated: true) let count = 5 @@ -200,10 +206,10 @@ class DatabaseRepositoryTestCase: XCTestCase { XCTFail(error.localizedDescription) } } - + waitForExpectations(timeout: 1) } - + func testDeprecatedEventsDelete() { updateDatabaseRepositoryWith(createsDeprecated: true) let count = 5 @@ -226,10 +232,10 @@ class DatabaseRepositoryTestCase: XCTestCase { XCTFail(error.localizedDescription) } } - + waitForExpectations(timeout: 1) } - + func testFetchUnretryEvents() { let count = 5 let events = eventGenerator.generateEvents(count: count) @@ -248,7 +254,7 @@ class DatabaseRepositoryTestCase: XCTestCase { XCTFail(error.localizedDescription) } } - + func testFetchRetryEvents() { let count = 5 let events = eventGenerator.generateEvents(count: count) @@ -286,4 +292,3 @@ class DatabaseRepositoryTestCase: XCTestCase { waitForExpectations(timeout: retryDeadline + 2.0) } } - diff --git a/MindboxTests/Database/MockDataBaseLoader.swift b/MindboxTests/Database/MockDataBaseLoader.swift index 9cd9c36a..10a9f246 100644 --- a/MindboxTests/Database/MockDataBaseLoader.swift +++ b/MindboxTests/Database/MockDataBaseLoader.swift @@ -12,11 +12,10 @@ import CoreData @testable import Mindbox class MockDataBaseLoader: DataBaseLoader { - + init() throws { let inMemoryDescription = NSPersistentStoreDescription() inMemoryDescription.type = NSInMemoryStoreType try super.init(persistentStoreDescriptions: [inMemoryDescription]) } - } diff --git a/MindboxTests/Database/MockDatabaseRepository.swift b/MindboxTests/Database/MockDatabaseRepository.swift index f8eb8e52..50226152 100644 --- a/MindboxTests/Database/MockDatabaseRepository.swift +++ b/MindboxTests/Database/MockDatabaseRepository.swift @@ -10,7 +10,7 @@ import Foundation @testable import Mindbox class MockDatabaseRepository: MBDatabaseRepository { - + var createsDeprecated: Bool = false // var tempLimit: Int? // diff --git a/MindboxTests/EventRepository/EventRepositoryTestCase.swift b/MindboxTests/EventRepository/EventRepositoryTestCase.swift index 9e727cac..d6dbe944 100644 --- a/MindboxTests/EventRepository/EventRepositoryTestCase.swift +++ b/MindboxTests/EventRepository/EventRepositoryTestCase.swift @@ -9,11 +9,13 @@ @testable import Mindbox import XCTest +// swiftlint:disable force_try + class EventRepositoryTestCase: XCTestCase { var coreController: CoreController! var controllerQueue: DispatchQueue! var persistenceStorage: PersistenceStorage! - + override func setUp() { super.setUp() persistenceStorage = DI.injectOrFail(PersistenceStorage.self) @@ -23,14 +25,14 @@ class EventRepositoryTestCase: XCTestCase { let databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) try! databaseRepository.erase() } - + override func tearDown() { - + coreController = nil controllerQueue = nil super.tearDown() } - + func testSendEvent() { let configuration = try! MBConfiguration(plistName: "TestEventConfig") coreController.initialization(configuration: configuration) @@ -46,12 +48,12 @@ class EventRepositoryTestCase: XCTestCase { case .success: expectation.fulfill() case .failure: - XCTFail() + XCTFail("Expectations must be met and result must be success") } } waitForExpectations(timeout: 2, handler: nil) } - + func testSendDecodableEvent() { let configuration = try! MBConfiguration(plistName: "TestEventConfig") coreController.initialization(configuration: configuration) @@ -65,19 +67,19 @@ class EventRepositoryTestCase: XCTestCase { if data.status == "Success" { expectation.fulfill() } else { - XCTFail() + XCTFail("Expectations must be met") } case .failure: - XCTFail() + XCTFail("Expectations must be met and result must be success") } } waitForExpectations(timeout: 2, handler: nil) } - + private struct SuccessCase: Decodable { let status: String } - + private func waitForInitializationFinished() { let expectation = self.expectation(description: "controller initialization") controllerQueue.async { expectation.fulfill() } diff --git a/MindboxTests/Extensions/StringExtensionsTests.swift b/MindboxTests/Extensions/StringExtensionsTests.swift index 070ace20..31f15062 100644 --- a/MindboxTests/Extensions/StringExtensionsTests.swift +++ b/MindboxTests/Extensions/StringExtensionsTests.swift @@ -7,13 +7,11 @@ // import Foundation - - import XCTest @testable import Mindbox final class StringExtensionsTests: XCTestCase { - + override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. } @@ -47,9 +45,9 @@ final class StringExtensionsTests: XCTestCase { (str: "10675199.02:48:05.4775807", result: 922337203685477), (str: "-10675199.02:48:05.4775808", result: -922337203685477) ] - + for (str, result) in testPositiveCases { - XCTContext.runActivity(named: "string(\(str)) parse to \(String(describing: result))") { activity in + XCTContext.runActivity(named: "string(\(str)) parse to \(String(describing: result))") { _ in do { let milliseconds = try String(str).parseTimeSpanToMillis() XCTAssertEqual(milliseconds, Int64(result)) @@ -59,7 +57,7 @@ final class StringExtensionsTests: XCTestCase { } } } - + func test_parseTimeSpanToMillisNegative() throws { let testCases: Array = [ "6", @@ -85,16 +83,15 @@ final class StringExtensionsTests: XCTestCase { "000:00:00", "00:00:000", "+0:0:0", - "12345678901234567890.00:00:00.00", + "12345678901234567890.00:00:00.00" ] - + for str in testCases { - try XCTContext.runActivity(named: "string(\(str)) parse with error") { activity in + try XCTContext.runActivity(named: "string(\(str)) parse with error") { _ in XCTAssertThrowsError(try String(str).parseTimeSpanToMillis()) { error in XCTAssertEqual((error as NSError).domain, "Invalid timeSpan format") } } } - } } diff --git a/MindboxTests/Extensions/XCTestCase+Extensions.swift b/MindboxTests/Extensions/XCTestCase+Extensions.swift index 48f55a80..a1348b71 100644 --- a/MindboxTests/Extensions/XCTestCase+Extensions.swift +++ b/MindboxTests/Extensions/XCTestCase+Extensions.swift @@ -11,7 +11,7 @@ import XCTest @testable import Mindbox extension XCTestCase { - open override func setUp() { + override open func setUp() { super.setUp() TestConfiguration.configure() } diff --git a/MindboxTests/GuaranteedDelivery/GuaranteedDeliveryTestCase.swift b/MindboxTests/GuaranteedDelivery/GuaranteedDeliveryTestCase.swift index 5a8b96c5..67f7f014 100644 --- a/MindboxTests/GuaranteedDelivery/GuaranteedDeliveryTestCase.swift +++ b/MindboxTests/GuaranteedDelivery/GuaranteedDeliveryTestCase.swift @@ -10,8 +10,10 @@ import CoreData @testable import Mindbox import XCTest +// swiftlint:disable force_try + class GuaranteedDeliveryTestCase: XCTestCase { - + var databaseRepository: MBDatabaseRepository! var guaranteedDeliveryManager: GuaranteedDeliveryManager! var persistenceStorage: PersistenceStorage! @@ -21,13 +23,13 @@ class GuaranteedDeliveryTestCase: XCTestCase { override func setUp() { super.setUp() Mindbox.logger.logLevel = .none - + databaseRepository = DI.injectOrFail(MBDatabaseRepository.self) guaranteedDeliveryManager = DI.injectOrFail(GuaranteedDeliveryManager.self) persistenceStorage = DI.injectOrFail(PersistenceStorage.self) eventGenerator = EventGenerator() isDelivering = guaranteedDeliveryManager.state.isDelivering - + let configuration = try! MBConfiguration(plistName: "TestEventConfig") persistenceStorage.configuration = configuration persistenceStorage.configuration?.previousDeviceUUID = configuration.previousDeviceUUID @@ -35,7 +37,7 @@ class GuaranteedDeliveryTestCase: XCTestCase { try! databaseRepository.erase() // Put setup code here. This method is called before the invocation of each test method in the class. } - + override func tearDown() { databaseRepository = nil guaranteedDeliveryManager = nil @@ -69,17 +71,17 @@ class GuaranteedDeliveryTestCase: XCTestCase { var state: NSString { NSString(string: guaranteedDeliveryManager.state.rawValue) } - + func testEventEqualsMockEvent() { let type: Event.Operation = .installed let body = UUID().uuidString - + let event: EventProtocol = Event(type: type, body: body) let mockEvent: EventProtocol = MockEvent(type: type, body: body) - + XCTAssertEqual(!event.transactionId.isEmpty, !mockEvent.transactionId.isEmpty, "Transaction Ids should not be empty") XCTAssertEqual(event.enqueueTimeStamp, mockEvent.enqueueTimeStamp, accuracy: 0.001, "Enqueue timestamps should match with some accuracy") - + XCTAssertEqual(event.serialNumber, mockEvent.serialNumber, "Serial numbers should be equal") XCTAssertEqual(event.body, mockEvent.body, "Bodies should be equal") XCTAssertEqual(event.type, mockEvent.type, "Types should be equal") @@ -168,7 +170,7 @@ class GuaranteedDeliveryTestCase: XCTestCase { XCTFail("New state is not expected type. ErrorCase:\(errorCase) Iterator:\(iterator); Received: \(String(describing: change.newValue))") return } - + if newState == errorCase[iterator] { errorExpectations[iterator].fulfill() } diff --git a/MindboxTests/Helpers/APNSTokenGenerator.swift b/MindboxTests/Helpers/APNSTokenGenerator.swift index bb409267..09399e23 100644 --- a/MindboxTests/Helpers/APNSTokenGenerator.swift +++ b/MindboxTests/Helpers/APNSTokenGenerator.swift @@ -6,24 +6,22 @@ // Copyright © 2021 Mindbox. All rights reserved. // -import Foundation - - import Foundation @testable import Mindbox +// swiftlint:disable force_unwrapping + struct APNSTokenGenerator { - + func generate() -> Data { (1...8) .map { _ in randomString(length: 8) + " " } .reduce("", +) .data(using: .utf8)! } - + private func randomString(length: Int = 10) -> String { let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - return String((0.. Event { Event( type: .installed, body: UUID().uuidString ) } - + func generateEvents(count: Int) -> [Event] { return (1...count).map { _ in return Event( diff --git a/MindboxTests/InApp/Mock/InAppCoreManagerMock.swift b/MindboxTests/InApp/Mock/InAppCoreManagerMock.swift index 39277dd1..477db585 100644 --- a/MindboxTests/InApp/Mock/InAppCoreManagerMock.swift +++ b/MindboxTests/InApp/Mock/InAppCoreManagerMock.swift @@ -10,7 +10,7 @@ import Foundation @testable import Mindbox class InAppCoreManagerMock: InAppCoreManagerProtocol { - var delegate: InAppMessagesDelegate? + weak var delegate: InAppMessagesDelegate? func start() { } diff --git a/MindboxTests/InApp/Mock/InAppImageDownloaderMock.swift b/MindboxTests/InApp/Mock/InAppImageDownloaderMock.swift index 05ac004a..6ab24a27 100644 --- a/MindboxTests/InApp/Mock/InAppImageDownloaderMock.swift +++ b/MindboxTests/InApp/Mock/InAppImageDownloaderMock.swift @@ -13,11 +13,11 @@ class MockImageDownloader: ImageDownloader { var expectedLocalURL: URL? var expectedResponse: HTTPURLResponse? var expectedError: Error? - + func downloadImage(withUrl imageUrl: String, completion: @escaping (URL?, HTTPURLResponse?, Error?) -> Void) { completion(expectedLocalURL, expectedResponse, expectedError) } - + func cancel() { } } diff --git a/MindboxTests/InApp/Tests/ABTesting/ABTests.swift b/MindboxTests/InApp/Tests/ABTesting/ABTests.swift index bb451788..871b62a1 100644 --- a/MindboxTests/InApp/Tests/ABTesting/ABTests.swift +++ b/MindboxTests/InApp/Tests/ABTesting/ABTests.swift @@ -5,7 +5,9 @@ //// Created by vailence on 19.06.2023. //// Copyright © 2023 Mindbox. All rights reserved. //// -// + +// swiftlint:disable comment_spacing + //import Foundation //import XCTest //@testable import Mindbox diff --git a/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppConfigResponseTests.swift b/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppConfigResponseTests.swift index a1a46248..0df47cc5 100644 --- a/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppConfigResponseTests.swift +++ b/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppConfigResponseTests.swift @@ -10,6 +10,8 @@ import Foundation import XCTest @testable import Mindbox +// swiftlint:disable comment_spacing + //class InAppConfigResponseTests: XCTestCase { // // var container = try! TestDependencyProvider() diff --git a/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppConfigurationMapperTests.swift b/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppConfigurationMapperTests.swift index da2cc023..25def470 100644 --- a/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppConfigurationMapperTests.swift +++ b/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppConfigurationMapperTests.swift @@ -32,5 +32,4 @@ final class InAppConfigurationMapperTests: XCTestCase { // Put the code you want to measure the time of here. } } - } diff --git a/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppTargetingRequestsTests.swift b/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppTargetingRequestsTests.swift index a71ddf38..ddb51328 100644 --- a/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppTargetingRequestsTests.swift +++ b/MindboxTests/InApp/Tests/InAppConfigResponseTests/InAppTargetingRequestsTests.swift @@ -9,14 +9,16 @@ import XCTest @testable import Mindbox +// swiftlint:disable force_try force_unwrapping + class InAppTargetingRequestsTests: XCTestCase { private var mockDataFacade: MockInAppConfigurationDataFacade! private var mapper: InAppConfigurationMapperProtocol! private var persistenceStorage: PersistenceStorage! - + private var targetingChecker: InAppTargetingCheckerProtocol! - + override func setUp() { super.setUp() SessionTemporaryStorage.shared.erase() @@ -29,7 +31,7 @@ class InAppTargetingRequestsTests: XCTestCase { mapper = DI.injectOrFail(InAppConfigurationMapperProtocol.self) persistenceStorage = DI.injectOrFail(PersistenceStorage.self) } - + override func tearDown() { mockDataFacade = nil targetingChecker = nil @@ -63,7 +65,7 @@ class InAppTargetingRequestsTests: XCTestCase { self.mapper.sendRemainingInappsTargeting() expectation.fulfill() } - + wait(for: [expectation], timeout: 1) targetingContains("2") targetingContains("1") @@ -71,33 +73,33 @@ class InAppTargetingRequestsTests: XCTestCase { XCTFail("Some error: \(error)") } } - + func test_OneInappGeo_NotShownBefore() { let expectation = XCTestExpectation(description: "Waiting for sendRemainingInappsTargeting to complete") do { let config = try getConfig(name: "7-TargetingRequests") targetingChecker.geoModels = .init(city: 1, region: 2, country: 3) SessionTemporaryStorage.shared.geoRequestCompleted = true - + mapper.mapConfigResponse(nil, config) { _ in self.mapper.sendRemainingInappsTargeting() expectation.fulfill() } - + wait(for: [expectation], timeout: 1) targetingContains("1", expectedToShow: true) } catch { print("Произошла ошибка: \(error)") } } - + func test_OneTrue_OneGeo_NotShownBefore() { let expectation = XCTestExpectation(description: "Waiting for sendRemainingInappsTargeting to complete") do { let config = try getConfig(name: "8-TargetingRequests") targetingChecker.geoModels = .init(city: 1, region: 2, country: 3) SessionTemporaryStorage.shared.geoRequestCompleted = true - + mapper.mapConfigResponse(nil, config) { _ in self.mapper.sendRemainingInappsTargeting() expectation.fulfill() @@ -105,54 +107,53 @@ class InAppTargetingRequestsTests: XCTestCase { wait(for: [expectation], timeout: 1) targetingContains("1", expectedToShow: true) targetingContains("2") - } catch { print("Произошла ошибка: \(error)") } } - + func test_TrueShown_OperationTest_TrueNotShown_Geo_Segment() { let expectationForsendRemainingInappsTargeting = XCTestExpectation(description: "Waiting for first sendRemainingInappsTargeting to complete") let expectationForMapConfigResponse = XCTestExpectation(description: "Waiting for mapConfigResponse to complete") - + do { let config = try getConfig(name: "9-TargetingRequests") targetingChecker.geoModels = .init(city: 1, region: 2, country: 3) SessionTemporaryStorage.shared.geoRequestCompleted = true - + targetingChecker.checkedSegmentations = [.init(segmentation: .init(ids: .init(externalId: "0000000")), segment: nil)] SessionTemporaryStorage.shared.checkSegmentsRequestCompleted = true - + mapper.mapConfigResponse(nil, config) { _ in self.mapper.sendRemainingInappsTargeting() expectationForsendRemainingInappsTargeting.fulfill() } - + wait(for: [expectationForsendRemainingInappsTargeting], timeout: 1) targetingContains("3") targetingContains("1") targetingContains("4") targetingContains("5") - + mockDataFacade.clean() - + let event = ApplicationEvent(name: "test", model: nil) mapper.mapConfigResponse(event, config) { _ in self.mapper.sendRemainingInappsTargeting() expectationForMapConfigResponse.fulfill() } - + wait(for: [expectationForMapConfigResponse], timeout: 1) targetingEqual(["2"]) } catch { print("Произошла ошибка: \(error)") } } - + func test_OneInappTwoOperations1OR2() { let expectationTest = XCTestExpectation(description: "Operation 1") let expectationTest2 = XCTestExpectation(description: "Operation 2") - + do { let config = try getConfig(name: "14-TargetingRequests") let testEvent = ApplicationEvent(name: "1", model: nil) @@ -160,78 +161,77 @@ class InAppTargetingRequestsTests: XCTestCase { self.mapper.sendRemainingInappsTargeting() expectationTest.fulfill() } - + wait(for: [expectationTest], timeout: 1) targetingContains("1", expectedToShow: true) - + mockDataFacade.clean() - + let test2Event = ApplicationEvent(name: "2", model: nil) mapper.mapConfigResponse(test2Event, config) { _ in self.mapper.sendRemainingInappsTargeting() expectationTest2.fulfill() } - + wait(for: [expectationTest2], timeout: 1) targetingEqual(["1"]) } catch { print("Произошла ошибка: \(error)") } } - + func test_TrueShown_OperationTest() { let expectationTrue = XCTestExpectation(description: "True") let expectationTest = XCTestExpectation(description: "Operation test") let expectationTestAgain = XCTestExpectation(description: "Operation test again") - + do { let config = try getConfig(name: "16-17-TargetingRequests") mapper.mapConfigResponse(nil, config) { _ in self.mapper.sendRemainingInappsTargeting() expectationTrue.fulfill() } - + wait(for: [expectationTrue], timeout: 1) targetingContains("1") mockDataFacade.clean() - + let testEvent = ApplicationEvent(name: "test", model: nil) mapper.mapConfigResponse(testEvent, config) { _ in self.mapper.sendRemainingInappsTargeting() expectationTest.fulfill() } - + wait(for: [expectationTest], timeout: 1) targetingEqual(["2"]) - + mockDataFacade.clean() - + let testEventAgain = ApplicationEvent(name: "test", model: nil) mapper.mapConfigResponse(testEventAgain, config) { _ in self.mapper.sendRemainingInappsTargeting() expectationTestAgain.fulfill() } - + wait(for: [expectationTestAgain], timeout: 1) targetingEqual(["2"]) } catch { print("Произошла ошибка: \(error)") } } - + func test_unknownInapp_lowerSDK_trueInapp() { let expectation = XCTestExpectation(description: "Waiting for sendRemainingInappsTargeting to complete") do { let config = try getConfig(name: "27-TargetingRequests") - + mapper.mapConfigResponse(nil, config) { _ in self.mapper.sendRemainingInappsTargeting() expectation.fulfill() } wait(for: [expectation], timeout: 1) targetingEqual(["3"]) - } catch { print("Произошла ошибка: \(error)") } @@ -244,85 +244,83 @@ class InAppTargetingRequestsTests: XCTestCase { do { let config = try getConfig(name: "31-TargetingRequests") - + persistenceStorage.deviceUUID = "40909d27-4bef-4a8d-9164-6bfcf58ecc76" // 1 вариант - + targetingChecker.geoModels = .init(city: 1, region: 2, country: 3) SessionTemporaryStorage.shared.geoRequestCompleted = true - + mapper.mapConfigResponse(nil, config) { _ in self.mapper.sendRemainingInappsTargeting() expectation.fulfill() } wait(for: [expectation], timeout: 3) - + DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: { expectationForArray.fulfill() }) - + wait(for: [expectationForArray], timeout: 3) targetingContains("1") targetingContains("2") targetingContains("3") - + mockDataFacade.clean() - + let testEventAgain = ApplicationEvent(name: "test", model: nil) mapper.mapConfigResponse(testEventAgain, config) { _ in self.mapper.sendRemainingInappsTargeting() expectationTestAgain.fulfill() } - + wait(for: [expectationTestAgain], timeout: 3) targetingEqual(["4"]) - } catch { print("Произошла ошибка: \(error)") } } - + func test_A_fourInappsWithABTests_variant2() { let expectation = XCTestExpectation(description: "Waiting for sendRemainingInappsTargeting to complete") let expectationTestAgain = XCTestExpectation(description: "Operation test again") let expectationForArray = XCTestExpectation(description: "Waiting for sendRemainingInappsTargeting to complete") - + do { let config = try getConfig(name: "31-TargetingRequests") persistenceStorage.deviceUUID = "b4e0f767-fe8f-4825-9772-f1162f2db52d" // 2 вариант targetingChecker.geoModels = .init(city: 1, region: 2, country: 3) SessionTemporaryStorage.shared.geoRequestCompleted = true - + mapper.mapConfigResponse(nil, config) { _ in self.mapper.sendRemainingInappsTargeting() expectation.fulfill() } wait(for: [expectation], timeout: 3) - + DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: { expectationForArray.fulfill() }) - + wait(for: [expectationForArray], timeout: 3) targetingContains("1") targetingContains("2") targetingContains("3") - + mockDataFacade.clean() - + let testEventAgain = ApplicationEvent(name: "test", model: nil) mapper.mapConfigResponse(testEventAgain, config) { _ in self.mapper.sendRemainingInappsTargeting() expectationTestAgain.fulfill() } - + wait(for: [expectationTestAgain], timeout: 3) targetingEqual(["4"]) - } catch { print("Произошла ошибка: \(error)") } } - + func test_fourInappsWithABTests_variant3() { let expectation = XCTestExpectation(description: "Waiting for sendRemainingInappsTargeting to complete") let expectationTestAgain = XCTestExpectation(description: "Operation test again") @@ -330,43 +328,42 @@ class InAppTargetingRequestsTests: XCTestCase { do { let config = try getConfig(name: "31-TargetingRequests") - + persistenceStorage.deviceUUID = "55fbd965-c658-47a8-8786-d72ba79b38a2" // 3 вариант - + targetingChecker.geoModels = .init(city: 1, region: 2, country: 3) SessionTemporaryStorage.shared.geoRequestCompleted = true - + mapper.mapConfigResponse(nil, config) { _ in self.mapper.sendRemainingInappsTargeting() expectation.fulfill() } wait(for: [expectation], timeout: 3) - + DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: { expectationForArray.fulfill() }) - + wait(for: [expectationForArray], timeout: 3) targetingContains("1") targetingContains("2") targetingContains("3") - + mockDataFacade.clean() - + let testEventAgain = ApplicationEvent(name: "test", model: nil) mapper.mapConfigResponse(testEventAgain, config) { _ in self.mapper.sendRemainingInappsTargeting() expectationTestAgain.fulfill() } - + wait(for: [expectationTestAgain], timeout: 2) targetingEqual(["4"]) - } catch { print("Произошла ошибка: \(error)") } } - + private func getConfig(name: String) throws -> ConfigResponse { let bundle = Bundle(for: InAppTargetingRequestsTests.self) let fileURL = bundle.url(forResource: name, withExtension: "json")! @@ -374,7 +371,7 @@ class InAppTargetingRequestsTests: XCTestCase { return try JSONDecoder().decode(ConfigResponse.self, from: data) } - func targetingContains(_ id: String, expectedToShow: Bool = false){ + func targetingContains(_ id: String, expectedToShow: Bool = false) { XCTAssertTrue(mockDataFacade.targetingArray.contains(id), "ID \(id) is expected to be in targeting list") if expectedToShow { XCTAssertTrue(mockDataFacade.showArray.contains(id), "ID \(id) is expected to be shown") diff --git a/MindboxTests/InApp/Tests/InAppConfigResponseTests/InappTTLTests.swift b/MindboxTests/InApp/Tests/InAppConfigResponseTests/InappTTLTests.swift index 3e357b2a..3a7e1238 100644 --- a/MindboxTests/InApp/Tests/InAppConfigResponseTests/InappTTLTests.swift +++ b/MindboxTests/InApp/Tests/InAppConfigResponseTests/InappTTLTests.swift @@ -18,13 +18,13 @@ class InappTTLTests: XCTestCase { persistenceStorage = DI.injectOrFail(PersistenceStorage.self) service = TTLValidationService(persistenceStorage: persistenceStorage) } - + override func tearDown() { persistenceStorage = nil service = nil super.tearDown() } - + func testNeedResetInapps_WithTTL_Exceeds() throws { persistenceStorage.configDownloadDate = Calendar.current.date(byAdding: .hour, value: -2, to: Date()) let settings = Settings(operations: nil, ttl: .init(inapps: "01:00:00")) @@ -32,7 +32,7 @@ class InappTTLTests: XCTestCase { let result = service.needResetInapps(config: config) XCTAssertTrue(result, "Inapps должны быть сброшены, так как время ttl истекло.") } - + func testNeedResetInapps_WithTTL_NotExceeded() throws { persistenceStorage.configDownloadDate = Calendar.current.date(byAdding: .second, value: -1, to: Date()) let settings = Settings(operations: nil, ttl: .init(inapps: "00:00:02")) @@ -40,7 +40,7 @@ class InappTTLTests: XCTestCase { let result = service.needResetInapps(config: config) XCTAssertFalse(result, "Inapps не должны быть сброшены, так как время ttl еще не истекло.") } - + func testNeedResetInapps_WithoutTTL() throws { persistenceStorage.configDownloadDate = Date() let settings = Settings(operations: nil, ttl: nil) @@ -48,7 +48,7 @@ class InappTTLTests: XCTestCase { let result = service.needResetInapps(config: config) XCTAssertFalse(result, "Inapps не должны быть сброшены, так как в конфиге отсутствует TTL.") } - + func testNeedResetInapps_WithTTLHalfHourAgo_NotExceeded() throws { persistenceStorage.configDownloadDate = Calendar.current.date(byAdding: .minute, value: -30, to: Date()) let settings = Settings(operations: nil, ttl: .init(inapps: "01:00:00")) @@ -56,7 +56,7 @@ class InappTTLTests: XCTestCase { let result = service.needResetInapps(config: config) XCTAssertFalse(result, "Inapps не должны быть сброшены, так как время TTL еще не истекло.") } - + func testNeedResetInapps_WithTTLHalfMinutesAgo_NotExceeded() throws { persistenceStorage.configDownloadDate = Calendar.current.date(byAdding: .second, value: -30, to: Date()) let settings = Settings(operations: nil, ttl: .init(inapps: "00:01:00")) @@ -64,7 +64,7 @@ class InappTTLTests: XCTestCase { let result = service.needResetInapps(config: config) XCTAssertFalse(result, "Inapps не должны быть сброшены, так как время TTL еще не истекло.") } - + func testNeedResetInapps_WithTTLOneDayAgo_NotExceeded() throws { persistenceStorage.configDownloadDate = Calendar.current.date(byAdding: .day, value: -1, to: Date()) let settings = Settings(operations: nil, ttl: .init(inapps: "2.00:00:00")) @@ -72,7 +72,7 @@ class InappTTLTests: XCTestCase { let result = service.needResetInapps(config: config) XCTAssertFalse(result, "Inapps не должны быть сброшены, так как время TTL еще не истекло.") } - + func testNeedResetInapps_WithMinusTTL_NotExceeded() throws { persistenceStorage.configDownloadDate = Calendar.current.date(byAdding: .day, value: 1, to: Date()) let settings = Settings(operations: nil, ttl: .init(inapps: "-2.00:00:00")) @@ -80,7 +80,7 @@ class InappTTLTests: XCTestCase { let result = service.needResetInapps(config: config) XCTAssertFalse(result, "Inapps не должны быть сброшены, так как время TTL еще не истекло.") } - + func testNeedResetInapps_WithMinusOneDayTTL_NotExceeded() throws { persistenceStorage.configDownloadDate = Calendar.current.date(byAdding: .day, value: -2, to: Date()) let settings = Settings(operations: nil, ttl: .init(inapps: "-1.00:00:00")) diff --git a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/GeoServiceTests.swift b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/GeoServiceTests.swift index 6ab776d0..17a2d35e 100644 --- a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/GeoServiceTests.swift +++ b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/GeoServiceTests.swift @@ -9,19 +9,21 @@ import XCTest @testable import Mindbox +// swiftlint:disable force_try + final class GeoServiceTests: XCTestCase { - + var sut: GeoServiceProtocol! var networkFetcher: MockNetworkFetcher! var targetingChecker: InAppTargetingCheckerProtocol! - + override func setUp() { super.setUp() networkFetcher = DI.injectOrFail(NetworkFetcher.self) as? MockNetworkFetcher targetingChecker = DI.injectOrFail(InAppTargetingCheckerProtocol.self) sut = DI.injectOrFail(GeoServiceProtocol.self) } - + override func tearDown() { sut = nil networkFetcher = nil @@ -29,41 +31,41 @@ final class GeoServiceTests: XCTestCase { SessionTemporaryStorage.shared.erase() super.tearDown() } - + func test_geo_request_valid() throws { let model = InAppGeoResponse(city: 1, region: 2, country: 3) let responseData = try! JSONEncoder().encode(model) var result: InAppGeoResponse? networkFetcher.data = responseData - + let expectations = expectation(description: "test_geo_request") - + sut.geoRequest { response in result = response expectations.fulfill() } - + waitForExpectations(timeout: 1) - + XCTAssertEqual(result, model) } - + func test_geo_request_geoRequestCompleted() throws { let model = InAppGeoResponse(city: 1, region: 2, country: 3) let responseData = try! JSONEncoder().encode(model) var result: InAppGeoResponse? networkFetcher.data = responseData SessionTemporaryStorage.shared.geoRequestCompleted = true - + let expectations = expectation(description: "test_geo_request") - + sut.geoRequest { response in result = response expectations.fulfill() } - + waitForExpectations(timeout: 1) - + XCTAssertNil(result) } } diff --git a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/InappFilterServiceTests/InappFilterServiceTests.swift b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/InappFilterServiceTests/InappFilterServiceTests.swift index cf143b58..5a5984b7 100644 --- a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/InappFilterServiceTests/InappFilterServiceTests.swift +++ b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/InappFilterServiceTests/InappFilterServiceTests.swift @@ -9,8 +9,10 @@ import XCTest @testable import Mindbox +// swiftlint:disable force_unwrapping + final class InappFilterServiceTests: XCTestCase { - + enum Constants { static let defaultID = "5696ac18-70cb-496f-80c5-a47eb7573df7" static let defaultColor = "#FFFFFF" @@ -19,53 +21,53 @@ final class InappFilterServiceTests: XCTestCase { } var sut: InappFilterProtocol! - + override func setUp() { super.setUp() sut = DI.injectOrFail(InappFilterProtocol.self) } - + override func tearDown() { sut = nil super.tearDown() } - + func test_unknown_type_for_variants() throws { let config = try getConfig(name: "unknownVariantType") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_missingBackgroundSection() throws { let config = try getConfig(name: "missingBackgroundSection") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_emptyLayersSection() throws { let config = try getConfig(name: "emptyLayersSection") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_unknownLayerType() throws { let config = try getConfig(name: "unknownLayerType") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_knownImageUnknownPictureLayerType() throws { let config = try getConfig(name: "knownImageUnknownPictureLayerType") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 2) - + if let variant = inapps.first?.form.variants.first { switch variant { - + case .modal(let model): XCTAssertEqual(model.content.background.layers.count, 1) default: @@ -73,71 +75,71 @@ final class InappFilterServiceTests: XCTestCase { } } } - + func test_unknownActionLayerType() throws { let config = try getConfig(name: "unknownActionLayerType") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_redirectUrlValueNumberInsteadOfString() throws { let config = try getConfig(name: "redirectUrlValueNumberInsteadOfString") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_missingIntentPayloadInActionLayer() throws { let config = try getConfig(name: "missingIntentPayloadInActionLayer") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_missingSourceSection() throws { let config = try getConfig(name: "missingSourceSection") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_emptyVariantsArray() throws { let config = try getConfig(name: "emptyVariantsArray") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_unknownSourceType() throws { let config = try getConfig(name: "unknownSourceType") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_missingValueInSourceLayer() throws { let config = try getConfig(name: "missingValueInSourceLayer") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_missingImageLinkInSourceLayerValue() throws { let config = try getConfig(name: "missingImageLinkInSourceLayerValue") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_missingElementsSection() throws { let config = try getConfig(name: "missingElementsSection") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 2) - + if let variant = inapps.first?.form.variants.first { switch variant { - + case .modal(let model): XCTAssertEqual(model.content.elements?.count, 0) default: @@ -145,12 +147,12 @@ final class InappFilterServiceTests: XCTestCase { } } } - + func test_invalidCloseButtonColor() throws { let config = try getConfig(name: "invalidCloseButtonColor") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 2) - + if let variant = inapps.first?.form.variants.first { switch variant { case .modal(let model): @@ -169,12 +171,12 @@ final class InappFilterServiceTests: XCTestCase { } } } - + func test_missingCloseButtonColorLineWidthSize() throws { let config = try getConfig(name: "missingCloseButtonColorLineWidthSize") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 2) - + if let variant = inapps.first?.form.variants.first { switch variant { case .modal(let model): @@ -196,12 +198,12 @@ final class InappFilterServiceTests: XCTestCase { } } } - + func test_twoCloseButtonsInApp() throws { let config = try getConfig(name: "twoCloseButtonsInApp") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 2) - + if let variant = inapps.first?.form.variants.first { switch variant { case .modal(let model): @@ -209,24 +211,24 @@ final class InappFilterServiceTests: XCTestCase { assertionFailure("elements not exists.") return } - + var counter = 0 elements.forEach { counter += $0.elementType == .closeButton ? 1 : 0 } - + XCTAssertEqual(counter, 2) default: break } } } - + func test_closeButtonWithOpenButton() throws { let config = try getConfig(name: "closeButtonWithOpenButton") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 2) - + if let variant = inapps.first?.form.variants.first { switch variant { case .modal(let model): @@ -234,7 +236,7 @@ final class InappFilterServiceTests: XCTestCase { assertionFailure("elements not exists.") return } - + XCTAssertEqual(elements.count, 1) switch elements.first! { case .closeButton: @@ -247,15 +249,15 @@ final class InappFilterServiceTests: XCTestCase { break } } - + assertionFailure() } - + func test_unknownSizeKind() throws { let config = try getConfig(name: "unknownSizeKind") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 2) - + if let variant = inapps.first?.form.variants.first { switch variant { case .modal(let model): @@ -263,7 +265,7 @@ final class InappFilterServiceTests: XCTestCase { assertionFailure("elements not exists.") return } - + XCTAssertEqual(elements.count, 1) switch elements.first! { case .closeButton(let model): @@ -278,38 +280,38 @@ final class InappFilterServiceTests: XCTestCase { break } } - + assertionFailure() } - + func test_missingMarginFieldInSection() throws { let config = try getConfig(name: "missingMarginFieldInSection") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_negativeCloseButtonSizeValues() throws { let config = try getConfig(name: "negativeCloseButtonSizeValues") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_closeButtonMarginAboveOne() throws { let config = try getConfig(name: "closeButtonMarginAboveOne") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + func test_closeButtonMarginBelowZero() throws { let config = try getConfig(name: "closeButtonMarginBelowZero") let inapps = sut.filter(inapps: config.inapps?.elements, abTests: config.abtests) XCTAssertEqual(inapps.count, 1) XCTAssertEqual(inapps.first?.id, Constants.defaultID) } - + private func getConfig(name: String) throws -> ConfigResponse { let bundle = Bundle(for: InappFilterServiceTests.self) let fileURL = bundle.url(forResource: name, withExtension: "json")! diff --git a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/SegmentationServiceTests.swift b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/SegmentationServiceTests.swift index 2b0153c2..d5ab9ea2 100644 --- a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/SegmentationServiceTests.swift +++ b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/SegmentationServiceTests.swift @@ -10,55 +10,55 @@ import XCTest @testable import Mindbox final class SegmentationServiceTests: XCTestCase { - + var sut: SegmentationService! var targetingChecker: InAppTargetingCheckerProtocol! - + override func setUp() { super.setUp() targetingChecker = DI.injectOrFail(InAppTargetingCheckerProtocol.self) - + sut = DI.injectOrFail(SegmentationServiceProtocol.self) as? SegmentationService - let customerSegmentAPI = CustomerSegmentsAPI { segmentationCheckRequest, completion in + let customerSegmentAPI = CustomerSegmentsAPI { _, 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: { _, completion in completion(.init(status: .success, products: [.init(ids: ["Hello": "World"], segmentations: [.init(ids: .init(externalId: "123"), segment: .init(ids: .init(externalId: "456")))])])) } - + sut.customerSegmentsAPI = customerSegmentAPI } - + override func tearDown() { SessionTemporaryStorage.shared.erase() targetingChecker = nil sut = nil super.tearDown() } - + func test_checkSegmentation_requestCompleted() throws { targetingChecker.checkedSegmentations = [.init(segmentation: .init(ids: .init(externalId: "Completed")), segment: .init(ids: .init(externalId: "Completed")))] - + SessionTemporaryStorage.shared.checkSegmentsRequestCompleted = true - + let expectations = expectation(description: "test_checkSegmentation_requestCompleted") var result: [SegmentationCheckResponse.CustomerSegmentation]? sut.checkSegmentationRequest { segmentations in result = segmentations expectations.fulfill() } - + let expectedModel: [SegmentationCheckResponse.CustomerSegmentation] = [.init(segmentation: .init(ids: .init(externalId: "Completed")), segment: .init(ids: .init(externalId: "Completed")))] - + waitForExpectations(timeout: 1) - + XCTAssertEqual(result, expectedModel) } - + func test_checkSegmentation_segmentsEmpty_returnNil() throws { var result: [SegmentationCheckResponse.CustomerSegmentation]? let expectations = expectation(description: "test_checkSegmentation_segmentsEmpty_returnNil") @@ -66,30 +66,30 @@ final class SegmentationServiceTests: XCTestCase { result = segmentations expectations.fulfill() } - + waitForExpectations(timeout: 1) - + XCTAssertNil(result) } - + func test_checkSegmentation_request_valid() throws { let expectedModel: [SegmentationCheckResponse.CustomerSegmentation] = [.init(segmentation: .init(ids: .init(externalId: "1")), segment: .init(ids: .init(externalId: "2")))] - + var result: [SegmentationCheckResponse.CustomerSegmentation]? targetingChecker.context.segments.append("123") let expectations = expectation(description: "test_checkSegmentation_request_valid") - + sut.checkSegmentationRequest { segmentations in result = segmentations expectations.fulfill() } - + waitForExpectations(timeout: 1) - + XCTAssertEqual(result, expectedModel) } - + func test_checkProductSegmentation_isPresentingInAppMessage() throws { SessionTemporaryStorage.shared.isPresentingInAppMessage = true let expectations = expectation(description: "test_checkProductSegmentation_isPresentingInAppMessage") @@ -98,11 +98,11 @@ final class SegmentationServiceTests: XCTestCase { result = segmentations expectations.fulfill() } - + waitForExpectations(timeout: 1) XCTAssertNil(result) } - + func test_checkProductSegmentation_segmentsEmpty_returnNil() throws { var result: [InAppProductSegmentResponse.CustomerSegmentation]? let expectations = expectation(description: "test_checkProductSegmentation_segmentsEmpty_returnNil") @@ -110,29 +110,29 @@ final class SegmentationServiceTests: XCTestCase { result = segmentations expectations.fulfill() } - + waitForExpectations(timeout: 1) - + XCTAssertNil(result) } - + func test_checkProductSegmentation_request_valid() throws { let expectedModel: [InAppProductSegmentResponse.CustomerSegmentation] = [ .init(ids: .init(externalId: "123"), segment: .init(ids: .init(externalId: "456"))) ] - + var result: [InAppProductSegmentResponse.CustomerSegmentation]? targetingChecker.context.productSegments.append("0000") let expectations = expectation(description: "test_geo_request") - + sut.checkProductSegmentationRequest(products: .init(ids: ["Hello": "World"])) { segmentations in result = segmentations expectations.fulfill() } - + waitForExpectations(timeout: 1) - + XCTAssertEqual(result, expectedModel) } } diff --git a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/VariantImageUrlExtractorServiceTests.swift b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/VariantImageUrlExtractorServiceTests.swift index 7df9aba3..f06f93ee 100644 --- a/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/VariantImageUrlExtractorServiceTests.swift +++ b/MindboxTests/InApp/Tests/InAppConfigurationMapperTests/VariantImageUrlExtractorServiceTests.swift @@ -10,9 +10,9 @@ import XCTest @testable import Mindbox final class VariantImageUrlExtractorServiceTests: XCTestCase { - + var sut: VariantImageUrlExtractorServiceProtocol! - + override func setUp() { super.setUp() sut = VariantImageUrlExtractorService() @@ -22,7 +22,7 @@ final class VariantImageUrlExtractorServiceTests: XCTestCase { sut = nil super.tearDown() } - + func decodeJSON(_ json: String, to type: T.Type) -> T? { guard let data = json.data(using: .utf8) else { return nil } let decoder = JSONDecoder() @@ -34,7 +34,7 @@ final class VariantImageUrlExtractorServiceTests: XCTestCase { return nil } } - + func testExtractUrls_modal_all_valid() { let formVariantJSON = """ { @@ -108,21 +108,21 @@ final class VariantImageUrlExtractorServiceTests: XCTestCase { "$type": "modal" } """ - + guard let formVariant: MindboxFormVariant = decodeJSON(formVariantJSON, to: MindboxFormVariant.self) else { XCTFail("Could not decode MindboxFormVariant from JSON") return } - + let extractedUrls = sut.extractImageURL(from: formVariant) let expectedUrls = [ "https://images.pexels.com/photos/1624496/pexels-photo-1624496.jpeg", - "https://mindbox-pushok.umbrellait.tech:444/?image=mindbox.png&broken=true&error=wait&speed=20", + "https://mindbox-pushok.umbrellait.tech:444/?image=mindbox.png&broken=true&error=wait&speed=20" ] - + XCTAssertEqual(extractedUrls, expectedUrls) } - + func testExtractUrls_snackbar_all_valid() { let formVariantJSON = """ { @@ -196,18 +196,18 @@ final class VariantImageUrlExtractorServiceTests: XCTestCase { "$type": "snackbar" } """ - + guard let formVariant: MindboxFormVariant = decodeJSON(formVariantJSON, to: MindboxFormVariant.self) else { XCTFail("Could not decode MindboxFormVariant from JSON") return } - + let extractedUrls = sut.extractImageURL(from: formVariant) let expectedUrls = [ "https://www.getmailbird.com/setup/assets/imgs/logos/gmail.com.webp", - "https://images.pexels.com/photos/1402787/pexels-photo-1402787.jpeg?auto=compress&cs=tinysrgb&w=6000&h=4000&dpr=2", + "https://images.pexels.com/photos/1402787/pexels-photo-1402787.jpeg?auto=compress&cs=tinysrgb&w=6000&h=4000&dpr=2" ] - + XCTAssertEqual(extractedUrls, expectedUrls) } } diff --git a/MindboxTests/InApp/Tests/InAppResponseModelTests/InAppResponseModelTests.swift b/MindboxTests/InApp/Tests/InAppResponseModelTests/InAppResponseModelTests.swift index 58cfa844..bbd2805c 100644 --- a/MindboxTests/InApp/Tests/InAppResponseModelTests/InAppResponseModelTests.swift +++ b/MindboxTests/InApp/Tests/InAppResponseModelTests/InAppResponseModelTests.swift @@ -9,6 +9,8 @@ import XCTest @testable import Mindbox +// swiftlint:disable force_unwrapping + final class InAppResponseModelTests: XCTestCase { func test_TrueTargeting_valid() { @@ -83,18 +85,18 @@ final class InAppResponseModelTests: XCTestCase { XCTAssertEqual(config.ids.count, 3) XCTAssertEqual(config.ids[0], 1) } - + func test_visit_vargeting_valid() { guard let config: VisitTargeting = getConfig(resourceName: "VisitTargetingModelValid") else { assertionFailure("config is Nil") return } - + XCTAssertNotNil(config) XCTAssertEqual(config.kind, .lte) XCTAssertEqual(config.value, 1) } - + func test_visit_targeting_invalid() { let config: VisitTargeting? = getConfig(resourceName: "SegmentTargetingModelValid") XCTAssertNil(config) diff --git a/MindboxTests/InApp/Tests/InAppTargetingCheckerTests/InAppStub.swift b/MindboxTests/InApp/Tests/InAppTargetingCheckerTests/InAppStub.swift index e9851a4d..d357cee7 100644 --- a/MindboxTests/InApp/Tests/InAppTargetingCheckerTests/InAppStub.swift +++ b/MindboxTests/InApp/Tests/InAppTargetingCheckerTests/InAppStub.swift @@ -13,19 +13,19 @@ class InAppStub { func getTargetingTrueNode() -> Targeting { .true(TrueTargeting()) } - + func getTargetingCity(model: CityTargeting) -> Targeting { .city(model) } - + func getTargetingRegion(model: RegionTargeting) -> Targeting { .region(model) } - + func getTargetingCountry(model: CountryTargeting) -> Targeting { .country(model) } - + func getTargetingSegment(model: SegmentTargeting) -> Targeting { .segment(model) } @@ -33,13 +33,13 @@ class InAppStub { func getTargetingProductSegment(model: ProductSegmentTargeting) -> Targeting { .viewProductSegment(model) } - + func getCheckedSegmentation(segmentationID: String, segmentID: String?) -> SegmentationCheckResponse.CustomerSegmentation { var segment: SegmentationCheckResponse.Segment? if let segmentID = segmentID { segment = SegmentationCheckResponse.Segment(ids: SegmentationCheckResponse.Id(externalId: segmentID)) } - + return .init(segmentation: SegmentationCheckResponse.Segmentation(ids: SegmentationCheckResponse.Id(externalId: segmentationID)), segment: segment) } @@ -53,11 +53,11 @@ class InAppStub { return .init(ids: .init(externalId: segmentationID), segment: segment) } - + func getAnd(model: AndTargeting) -> Targeting { .and(model) } - + func getOr(model: OrTargeting) -> Targeting { .or(model) } diff --git a/MindboxTests/InApp/Tests/InAppTargetingCheckerTests/InAppTargetingCheckerTests.swift b/MindboxTests/InApp/Tests/InAppTargetingCheckerTests/InAppTargetingCheckerTests.swift index 1a26791a..d34b3131 100644 --- a/MindboxTests/InApp/Tests/InAppTargetingCheckerTests/InAppTargetingCheckerTests.swift +++ b/MindboxTests/InApp/Tests/InAppTargetingCheckerTests/InAppTargetingCheckerTests.swift @@ -10,93 +10,93 @@ import XCTest @testable import Mindbox final class InAppTargetingCheckerTests: XCTestCase { - + let inAppStub = InAppStub() let trueTargeting: Targeting = .true(TrueTargeting()) var targetingChecker: InAppTargetingCheckerProtocol! var storage: PersistenceStorage! - + override func setUp() { super.setUp() 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 super.tearDown() } - + // MARK: - TRUE func test_true_always_true() { XCTAssertTrue(targetingChecker.check(targeting: inAppStub.getTargetingTrueNode())) } - + // MARK: - CITY func test_city_targetging_positive_success() { let cityModel = CityTargeting(kind: .positive, ids: [123, 456]) XCTAssertTrue(targetingChecker.check(targeting: inAppStub.getTargetingCity(model: cityModel))) } - + func test_city_targetging_positive_error() { let cityModel = CityTargeting(kind: .positive, ids: [788]) XCTAssertFalse(targetingChecker.check(targeting: inAppStub.getTargetingCity(model: cityModel))) } - + func test_city_targetging_negative_success() { let cityModel = CityTargeting(kind: .negative, ids: [788]) XCTAssertTrue(targetingChecker.check(targeting: inAppStub.getTargetingCity(model: cityModel))) } - + func test_city_targetging_negative_error() { let cityModel = CityTargeting(kind: .negative, ids: [123, 456]) XCTAssertFalse(targetingChecker.check(targeting: inAppStub.getTargetingCity(model: cityModel))) } - + // MARK: - REGION func test_region_targetging_positive_success() { let regionModel = RegionTargeting(kind: .positive, ids: [789, 456]) XCTAssertTrue(targetingChecker.check(targeting: inAppStub.getTargetingRegion(model: regionModel))) } - + func test_region_targetging_positive_error() { let regionModel = RegionTargeting(kind: .positive, ids: [788]) XCTAssertFalse(targetingChecker.check(targeting: inAppStub.getTargetingRegion(model: regionModel))) } - + func test_region_targetging_negative_success() { let regionModel = RegionTargeting(kind: .negative, ids: [788]) XCTAssertTrue(targetingChecker.check(targeting: inAppStub.getTargetingRegion(model: regionModel))) } - + func test_region_targetging_negative_error() { let regionModel = RegionTargeting(kind: .negative, ids: [789, 456]) XCTAssertFalse(targetingChecker.check(targeting: inAppStub.getTargetingRegion(model: regionModel))) } - + // MARK: - COUNTRY func test_country_targetging_positive_success() { let countryModel = CountryTargeting(kind: .positive, ids: [789, 456]) XCTAssertTrue(targetingChecker.check(targeting: inAppStub.getTargetingCountry(model: countryModel))) } - + func test_country_targetging_positive_error() { let countryModel = CountryTargeting(kind: .positive, ids: [788]) XCTAssertFalse(targetingChecker.check(targeting: inAppStub.getTargetingCountry(model: countryModel))) } - + func test_country_targetging_negative_success() { let countryModel = CountryTargeting(kind: .negative, ids: [788]) XCTAssertTrue(targetingChecker.check(targeting: inAppStub.getTargetingCountry(model: countryModel))) } - + func test_country_targetging_negative_error() { let countryModel = CountryTargeting(kind: .negative, ids: [789, 456]) XCTAssertFalse(targetingChecker.check(targeting: inAppStub.getTargetingCountry(model: countryModel))) } - + // MARK: - SEGMENT func test_segment_targeting_positive_success() { let segmentModel = SegmentTargeting(kind: .positive, @@ -107,17 +107,17 @@ final class InAppTargetingCheckerTests: XCTestCase { targetingChecker.checkedSegmentations?.append(inAppStub.getCheckedSegmentation(segmentationID: "123", segmentID: "234")) XCTAssertTrue(targetingChecker.check(targeting: inAppStub.getTargetingSegment(model: segmentModel))) } - + func test_segment_targeting_positive_error() { let segmentModel = SegmentTargeting(kind: .positive, segmentationInternalId: "-", segmentationExternalId: "123", segmentExternalId: "234") - + targetingChecker.checkedSegmentations?.append(inAppStub.getCheckedSegmentation(segmentationID: "123", segmentID: "233")) XCTAssertFalse(targetingChecker.check(targeting: inAppStub.getTargetingSegment(model: segmentModel))) } - + func test_segment_targeting_negative_success() { let segmentModel = SegmentTargeting(kind: .negative, segmentationInternalId: "-", @@ -127,112 +127,112 @@ final class InAppTargetingCheckerTests: XCTestCase { targetingChecker.checkedSegmentations?.append(inAppStub.getCheckedSegmentation(segmentationID: "123", segmentID: nil)) XCTAssertTrue(targetingChecker.check(targeting: inAppStub.getTargetingSegment(model: segmentModel))) } - + func test_segment_targeting_negative_error() { let segmentModel = SegmentTargeting(kind: .negative, segmentationInternalId: "-", segmentationExternalId: "123", segmentExternalId: "234") - + targetingChecker.checkedSegmentations?.append(inAppStub.getCheckedSegmentation(segmentationID: "123", segmentID: "234")) XCTAssertFalse(targetingChecker.check(targeting: inAppStub.getTargetingSegment(model: segmentModel))) } - + func test_AND_targeting_both_true() { let country = CountryTargeting(kind: .positive, ids: [789]) let city = CityTargeting(kind: .positive, ids: [123]) let andTargeting = AndTargeting(nodes: [.country(country), .city(city)]) XCTAssertTrue(targetingChecker.check(targeting: inAppStub.getAnd(model: andTargeting))) } - + func test_AND_targeting_both_false() { let country = CountryTargeting(kind: .positive, ids: [234]) let city = CityTargeting(kind: .positive, ids: [234]) let andTargeting = AndTargeting(nodes: [.country(country), .city(city)]) XCTAssertFalse(targetingChecker.check(targeting: inAppStub.getAnd(model: andTargeting))) } - + func test_AND_targeting_one_true_one_false() { let country = CountryTargeting(kind: .positive, ids: [234]) let city = CityTargeting(kind: .positive, ids: [123]) let andTargeting = AndTargeting(nodes: [.country(country), .city(city)]) XCTAssertFalse(targetingChecker.check(targeting: inAppStub.getAnd(model: andTargeting))) } - + func test_OR_targeting_both_true() { let country = CountryTargeting(kind: .positive, ids: [456]) let city = CityTargeting(kind: .positive, ids: [123]) let andTargeting = OrTargeting(nodes: [.country(country), .city(city)]) XCTAssertTrue(targetingChecker.check(targeting: inAppStub.getOr(model: andTargeting))) } - + func test_OR_targeting_both_false() { let country = CountryTargeting(kind: .positive, ids: [234]) let city = CityTargeting(kind: .positive, ids: [234]) let andTargeting = OrTargeting(nodes: [.country(country), .city(city)]) XCTAssertFalse(targetingChecker.check(targeting: inAppStub.getOr(model: andTargeting))) } - + func test_OR_targeting_one_true_one_false() { let country = CountryTargeting(kind: .positive, ids: [234]) let city = CityTargeting(kind: .positive, ids: [123]) let andTargeting = OrTargeting(nodes: [.country(country), .city(city)]) XCTAssertTrue(targetingChecker.check(targeting: inAppStub.getOr(model: andTargeting))) } - + func test_OR_contain_unknown() { let city = CityTargeting(kind: .positive, ids: [123]) let orTargeting = OrTargeting(nodes: [.unknown, .city(city)]) XCTAssertFalse(targetingChecker.check(targeting: inAppStub.getOr(model: orTargeting))) } - + func test_AND_contain_unknown() { let city = CityTargeting(kind: .positive, ids: [123]) let andTargeting = AndTargeting(nodes: [.unknown, .city(city)]) XCTAssertFalse(targetingChecker.check(targeting: inAppStub.getAnd(model: andTargeting))) } - + func test_unknown_false() { XCTAssertFalse(targetingChecker.check(targeting: .unknown)) } - + // MARK: - VISIT func test_visit_gte_equal_true() { storage.userVisitCount = 1 let visitTargeting = VisitTargeting(kind: .gte, value: 1) XCTAssertTrue(targetingChecker.check(targeting: .visit(visitTargeting))) } - + func test_visit_gte_greater_true() { storage.userVisitCount = 2 let visitTargeting = VisitTargeting(kind: .gte, value: 1) XCTAssertTrue(targetingChecker.check(targeting: .visit(visitTargeting))) } - + func test_visit_gte_false() { storage.userVisitCount = 1 let visitTargeting = VisitTargeting(kind: .gte, value: 2) XCTAssertFalse(targetingChecker.check(targeting: .visit(visitTargeting))) } - + func test_visit_lte_equal_true() { storage.userVisitCount = 1 let visitTargeting = VisitTargeting(kind: .lte, value: 1) XCTAssertTrue(targetingChecker.check(targeting: .visit(visitTargeting))) } - + func test_visit_lte_less_true() { storage.userVisitCount = 1 let visitTargeting = VisitTargeting(kind: .lte, value: 2) XCTAssertTrue(targetingChecker.check(targeting: .visit(visitTargeting))) } - + func test_visit_lte_false() { storage.userVisitCount = 2 let visitTargeting = VisitTargeting(kind: .lte, value: 1) XCTAssertFalse(targetingChecker.check(targeting: .visit(visitTargeting))) } - + func test_visit_equal_true() { storage.userVisitCount = 1 let visitTargeting = VisitTargeting(kind: .equals, value: 1) diff --git a/MindboxTests/InApp/Tests/URLSessionImageDownloaderTests.swift b/MindboxTests/InApp/Tests/URLSessionImageDownloaderTests.swift index de3f16a2..34131b04 100644 --- a/MindboxTests/InApp/Tests/URLSessionImageDownloaderTests.swift +++ b/MindboxTests/InApp/Tests/URLSessionImageDownloaderTests.swift @@ -9,6 +9,8 @@ import XCTest @testable import Mindbox +// swiftlint:disable force_unwrapping + class URLSessionImageDownloaderTests: XCTestCase { var imageDownloader: ImageDownloader! @@ -32,7 +34,7 @@ class URLSessionImageDownloaderTests: XCTestCase { mockDownloader.expectedResponse = HTTPURLResponse(url: URL(string: imageUrl)!, statusCode: 200, httpVersion: nil, headerFields: nil) imageDownloader = mockDownloader - imageDownloader.downloadImage(withUrl: imageUrl) { (localURL, response, error) in + imageDownloader.downloadImage(withUrl: imageUrl) { localURL, response, error in XCTAssertEqual(localURL, mockDownloader.expectedLocalURL, "Local URL should match the expected value") XCTAssertEqual(response?.statusCode, mockDownloader.expectedResponse?.statusCode, "Response status code should match the expected value") XCTAssertNil(error, "Error should be nil") @@ -52,7 +54,7 @@ class URLSessionImageDownloaderTests: XCTestCase { imageDownloader = mockDownloader - imageDownloader.downloadImage(withUrl: imageUrl) { (localURL, response, error) in + imageDownloader.downloadImage(withUrl: imageUrl) { localURL, _, error in XCTAssertNil(localURL, "Local URL should be nil") XCTAssertNotNil(error, "Error should not be nil") XCTAssertEqual((error as NSError?)?.code, NSURLErrorTimedOut, "Error code should be NSURLErrorTimedOut") @@ -63,7 +65,6 @@ class URLSessionImageDownloaderTests: XCTestCase { wait(for: [expectation], timeout: 5.0) } - func testNon200StatusCode() { let imageUrl = "https://example.com/non-existing-image.jpg" let expectation = XCTestExpectation(description: "Image download should fail with non-200 status code") @@ -75,7 +76,7 @@ class URLSessionImageDownloaderTests: XCTestCase { imageDownloader = mockDownloader - imageDownloader.downloadImage(withUrl: imageUrl) { (localURL, response, error) in + imageDownloader.downloadImage(withUrl: imageUrl) { localURL, response, error in XCTAssertNil(localURL, "Local URL should be nil") XCTAssertNotNil(response, "Response should not be nil") XCTAssertEqual(response?.statusCode, non200Response?.statusCode, "Response status code should match the expected value") @@ -102,7 +103,7 @@ class URLSessionImageDownloaderTests: XCTestCase { let corruptedImagePath = imageURL.path FileManager.default.createFile(atPath: corruptedImagePath, contents: Data(), attributes: nil) - imageDownloader.downloadImage(withUrl: imageUrl) { (localURL, response, error) in + imageDownloader.downloadImage(withUrl: imageUrl) { localURL, response, error in XCTAssertNotNil(localURL, "Local URL should not be nil") XCTAssertNil(error, "Error should be nil") XCTAssertEqual(response?.statusCode, 200, "Response status code should be 200") diff --git a/MindboxTests/MBConfigurationTestCase.swift b/MindboxTests/MBConfigurationTestCase.swift index 9e117482..b3f88629 100644 --- a/MindboxTests/MBConfigurationTestCase.swift +++ b/MindboxTests/MBConfigurationTestCase.swift @@ -10,10 +10,10 @@ import XCTest @testable import Mindbox class MBConfigurationTestCase: XCTestCase { - //Invalid + // Invalid let emptyDomainFile = "TestConfig_Invalid_1" let emptyEndpointFile = "TestConfig_Invalid_2" - //Valid + // Valid let emptyUUIDFile = "TestConfig_Invalid_3" let emptyIDDomainFile = "TestConfig_Invalid_4" @@ -27,7 +27,7 @@ class MBConfigurationTestCase: XCTestCase { try [ emptyUUIDFile, emptyIDDomainFile - ].forEach { (file) in + ].forEach { file in XCTAssertNoThrow(try MBConfiguration(plistName: file), "") } } @@ -35,9 +35,9 @@ class MBConfigurationTestCase: XCTestCase { func test_MBConfiguration_should_throw() throws { try [ emptyDomainFile, - emptyEndpointFile, - ].forEach { (file) in - XCTAssertThrowsError(try MBConfiguration(plistName: file), "") { (error) in + emptyEndpointFile + ].forEach { file in + XCTAssertThrowsError(try MBConfiguration(plistName: file), "") { error in if let localizedError = error as? LocalizedError { XCTAssertNotNil(localizedError.errorDescription) XCTAssertNotNil(localizedError.failureReason) diff --git a/MindboxTests/MigrationsTests/MigrationManagerTests.swift b/MindboxTests/MigrationsTests/MigrationManagerTests.swift index 5a385e99..c3eb530a 100644 --- a/MindboxTests/MigrationsTests/MigrationManagerTests.swift +++ b/MindboxTests/MigrationsTests/MigrationManagerTests.swift @@ -10,10 +10,10 @@ 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) @@ -26,24 +26,24 @@ final class MigrationManagerTests: XCTestCase { "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() } - + @available(*, deprecated, message: "Suppress `deprecated` shownInAppsIds warning") func testGeneralProductionMigrations() { migrationManager = MigrationManager(persistenceStorage: persistenceStorageMock) @@ -51,18 +51,18 @@ final class MigrationManagerTests: XCTestCase { migrationManager.migrate() XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == Constants.Migration.sdkVersionCode) XCTAssertNotNil(persistenceStorageMock.configDownloadDate, "Must NOT `softReset()` `persistenceStorage`") - + XCTAssertNotNil(persistenceStorageMock.shownInappsDictionary, "shownInAppDictionary must NOT be nil after MigrationShownInAppIds") XCTAssertNil(persistenceStorageMock.shownInAppsIds, "shownInAppsIds must be nil after MigrationShownInAppIds") } - + func testPerformTestMigrationsButFirstInstallationAndSkipMigrations() { let testMigrations: [MigrationProtocol] = [ TestBaseMigration_1() ] - + persistenceStorageMock.installationDate = nil - + migrationManager = MigrationManager(persistenceStorage: persistenceStorageMock, migrations: testMigrations, sdkVersionCode: 1) XCTAssertTrue(persistenceStorageMock.versionCodeForMigration == 0) @@ -75,86 +75,86 @@ final class MigrationManagerTests: XCTestCase { // 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(), + 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(), + 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") @@ -166,56 +166,56 @@ extension MigrationManagerTests { // 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") @@ -227,25 +227,23 @@ extension MigrationManagerTests { // 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` + 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/ShownInAppsIdsMigrationTests.swift b/MindboxTests/MigrationsTests/ShownInAppsIdsMigrationTests.swift index 52ddf72b..a3b19c5c 100644 --- a/MindboxTests/MigrationsTests/ShownInAppsIdsMigrationTests.swift +++ b/MindboxTests/MigrationsTests/ShownInAppsIdsMigrationTests.swift @@ -10,43 +10,45 @@ import XCTest @testable import Mindbox @testable import MindboxLogger +// swiftlint:disable force_try force_unwrapping + final class ShownInAppsIdsMigrationTests: XCTestCase { private var shownInAppsIdsMigration: MigrationProtocol! private var migrationManager: MigrationManagerProtocol! private var persistenceStorageMock: PersistenceStorage! - + private var mbLoggerCDManager: MBLoggerCoreDataManager! - + private let shownInAppsIdsBeforeMigration: [String] = [ "36920d7e-3c42-4194-9a11-b0b5c550460c", "37bed734-aa34-4c10-918b-873f67505d46" ] - + @available(*, deprecated, message: "Suppress `deprecated` shownInAppsIds warning") override func setUp() { super.setUp() shownInAppsIdsMigration = MigrationShownInAppsIds() - + mbLoggerCDManager = MBLoggerCoreDataManager() - + persistenceStorageMock = DI.injectOrFail(PersistenceStorage.self) persistenceStorageMock.deviceUUID = "00000000-0000-0000-0000-000000000000" persistenceStorageMock.installationDate = Date() persistenceStorageMock.shownInappsDictionary = nil persistenceStorageMock.shownInAppsIds = shownInAppsIdsBeforeMigration - + let testMigrations: [MigrationProtocol] = [ shownInAppsIdsMigration ] - + migrationManager = MigrationManager( persistenceStorage: persistenceStorageMock, migrations: testMigrations, sdkVersionCode: 0 ) } - + override func tearDown() { shownInAppsIdsMigration = nil mbLoggerCDManager = nil @@ -54,104 +56,103 @@ final class ShownInAppsIdsMigrationTests: XCTestCase { persistenceStorageMock = nil super.tearDown() } - + @available(*, deprecated, message: "Suppress `deprecated` shownInAppsIds warning") func test_ShownInAppsIdsMigration_withIsNeededTrue_shouldPerfromSuccessfully() throws { try mbLoggerCDManager.deleteAll() - + let migrationExpectation = XCTestExpectation(description: "Migration completed") migrationManager.migrate() DispatchQueue.main.asyncAfter(deadline: .now() + 1) { XCTAssertNotNil(self.persistenceStorageMock.shownInappsDictionary, "shownInAppDictionary must NOT be nil after MigrationShownInAppIds") XCTAssertNil(self.persistenceStorageMock.shownInAppsIds, "shownInAppsIds must be nil after MigrationShownInAppIds") XCTAssertEqual(self.shownInAppsIdsBeforeMigration.count, self.persistenceStorageMock.shownInappsDictionary?.count, "Count must be equal") - + for shownInAppsIdBeforeMigration in self.shownInAppsIdsBeforeMigration { - XCTContext.runActivity(named: "Check shownInAppsId \(shownInAppsIdBeforeMigration) is in shownInappsDictionary") { test in + XCTContext.runActivity(named: "Check shownInAppsId \(shownInAppsIdBeforeMigration) is in shownInappsDictionary") { _ in let contains = self.persistenceStorageMock.shownInappsDictionary?.keys.contains(shownInAppsIdBeforeMigration) ?? false XCTAssertTrue(contains, "The shownInAppsId \(shownInAppsIdBeforeMigration) should be in shownInappsDictionary") } } - + migrationExpectation.fulfill() } - + wait(for: [migrationExpectation], timeout: 5) - + let lastLog = try mbLoggerCDManager.getLastLog() let expectedLogMessage = "[Migrations] Migrations have been successful\n" XCTAssertEqual(lastLog?.message, expectedLogMessage) } - + @available(*, deprecated, message: "Suppress `deprecated` shownInAppsIds warning") func test_ShownInAppsIdsMigration_withIsNeededFalse_shouldHaveBeenSkipped() throws { try mbLoggerCDManager.deleteAll() - + let migrationExpectation = XCTestExpectation(description: "Migration completed") - + let testMigrations: [MigrationProtocol] = [ shownInAppsIdsMigration ] - + let shownInappsDictionary: [String: Date] = [ "36920d7e-3c42-4194-9a11-b0b5c550460c": Date(), "37bed734-aa34-4c10-918b-873f67505d46": Date() ] - + persistenceStorageMock.shownInappsDictionary = shownInappsDictionary persistenceStorageMock.shownInAppsIds = nil - + migrationManager = MigrationManager(persistenceStorage: persistenceStorageMock, migrations: testMigrations, sdkVersionCode: 0) - + migrationManager.migrate() - + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { XCTAssertNotNil(self.persistenceStorageMock.shownInappsDictionary, "shownInAppDictionary must NOT be nil") XCTAssertNil(self.persistenceStorageMock.shownInAppsIds, "shownInAppsIds must be nil") XCTAssertEqual(shownInappsDictionary, self.persistenceStorageMock.shownInappsDictionary, "Must be equal") - + let defaultSetDateAfterMigration = Date(timeIntervalSince1970: 0) for (_, value) in self.persistenceStorageMock.shownInappsDictionary! { XCTAssertNotEqual(value, defaultSetDateAfterMigration) } - + migrationExpectation.fulfill() } - + wait(for: [migrationExpectation], timeout: 5) - + let lastLog = try mbLoggerCDManager.getLastLog() let expectedLogMessage = "[Migrations] Migrations have been skipped\n" XCTAssertEqual(lastLog?.message, expectedLogMessage) } - + func test_ShownInAppsIdsMigration_withDoubleCall_shouldBePerformedOnlyTheFirstTime() throws { try mbLoggerCDManager.deleteAll() - + migrationManager.migrate() let migrationExpectation = XCTestExpectation(description: "Migration completed") DispatchQueue.main.asyncAfter(deadline: .now() + 1) { migrationExpectation.fulfill() } - + wait(for: [migrationExpectation], timeout: 5) - + var lastLog = try! mbLoggerCDManager.getLastLog()?.message var expectedLogMessage = "[Migrations] Migrations have been successful\n" XCTAssertEqual(lastLog, expectedLogMessage) - + migrationManager.migrate() - + let migrationExpectationTwo = XCTestExpectation(description: "Migration 2 completed") DispatchQueue.main.asyncAfter(deadline: .now() + 1) { migrationExpectationTwo.fulfill() } - + wait(for: [migrationExpectationTwo], timeout: 5) lastLog = try! mbLoggerCDManager.getLastLog()?.message expectedLogMessage = "[Migrations] Migrations have been skipped\n" XCTAssertEqual(lastLog, expectedLogMessage) } } - diff --git a/MindboxTests/MigrationsTests/TestsMigrations/TestBaseMigrations.swift b/MindboxTests/MigrationsTests/TestsMigrations/TestBaseMigrations.swift index c8e704d5..9534f1e5 100644 --- a/MindboxTests/MigrationsTests/TestsMigrations/TestBaseMigrations.swift +++ b/MindboxTests/MigrationsTests/TestsMigrations/TestBaseMigrations.swift @@ -13,15 +13,15 @@ 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 } @@ -31,15 +31,15 @@ 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 } @@ -49,15 +49,15 @@ 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 } @@ -67,15 +67,15 @@ 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/TestsMigrations/TestProtocolMigrations.swift b/MindboxTests/MigrationsTests/TestsMigrations/TestProtocolMigrations.swift index d3a6fba4..5fd5bf6b 100644 --- a/MindboxTests/MigrationsTests/TestsMigrations/TestProtocolMigrations.swift +++ b/MindboxTests/MigrationsTests/TestsMigrations/TestProtocolMigrations.swift @@ -9,74 +9,76 @@ import Foundation @testable import Mindbox +// swiftlint:disable force_unwrapping + 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/MBLoggerCoreDataManagerTests.swift b/MindboxTests/MindboxLogger/MBLoggerCoreDataManagerTests.swift index f3f085c0..68b68596 100644 --- a/MindboxTests/MindboxLogger/MBLoggerCoreDataManagerTests.swift +++ b/MindboxTests/MindboxLogger/MBLoggerCoreDataManagerTests.swift @@ -10,6 +10,8 @@ import XCTest import MindboxLogger @testable import Mindbox +// swiftlint:disable force_unwrapping + final class MBLoggerCoreDataManagerTests: XCTestCase { var manager: MBLoggerCoreDataManager! @@ -22,7 +24,7 @@ final class MBLoggerCoreDataManagerTests: XCTestCase { manager = nil super.tearDown() } - + func test_measure_create() throws { try manager.deleteAll() measure { @@ -35,7 +37,7 @@ final class MBLoggerCoreDataManagerTests: XCTestCase { wait(for: [fetchExpectation], timeout: 2.0) } } - + func test_measure_create_10_000() throws { try manager.deleteAll() measure { @@ -49,23 +51,23 @@ final class MBLoggerCoreDataManagerTests: XCTestCase { fetchExpectation.fulfill() } } - + wait(for: [fetchExpectation], timeout: 100.0) } } func testCreate() throws { try manager.deleteAll() - + let fetchExpectation = XCTestExpectation(description: "Fetch created log") let message = "Test message" let timestamp = Date() manager.create(message: message, timestamp: timestamp) { fetchExpectation.fulfill() } - + wait(for: [fetchExpectation], timeout: 3.0) - + let fetchResult = try self.manager.fetchPeriod(timestamp, timestamp) XCTAssertEqual(fetchResult.count, 1, "Должно быть извлечено 1 сообщение") XCTAssertEqual(fetchResult[0].message, message, "Сообщение должно совпадать") @@ -74,14 +76,14 @@ final class MBLoggerCoreDataManagerTests: XCTestCase { func testFetchFirstLog() throws { try manager.deleteAll() - + let message1 = "Test message 1" let message2 = "Test message 2" let message3 = "Test message 3" let timestamp1 = Date().addingTimeInterval(-60) let timestamp2 = Date().addingTimeInterval(-30) let timestamp3 = Date() - + let fetchExpectation = XCTestExpectation(description: "Fetch created log") fetchExpectation.expectedFulfillmentCount = 3 @@ -94,9 +96,9 @@ final class MBLoggerCoreDataManagerTests: XCTestCase { manager.create(message: message3, timestamp: timestamp3) { fetchExpectation.fulfill() } - + wait(for: [fetchExpectation], timeout: 5.0) - + let fetchResult = try self.manager.getFirstLog() XCTAssertNotNil(fetchResult) XCTAssertEqual(fetchResult!.message, message1) @@ -105,7 +107,7 @@ final class MBLoggerCoreDataManagerTests: XCTestCase { func testFetchLastLog() throws { try manager.deleteAll() - + let message1 = "Test message 1" let message2 = "Test message 2" let message3 = "Test message 3" @@ -115,33 +117,31 @@ final class MBLoggerCoreDataManagerTests: XCTestCase { manager.create(message: message1, timestamp: timestamp1) manager.create(message: message2, timestamp: timestamp2) manager.create(message: message3, timestamp: timestamp3) - + let fetchExpectation = XCTestExpectation(description: "Fetch last log") - + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { do { let fetchResult = try self.manager.getLastLog() XCTAssertEqual(fetchResult!.message, message3) XCTAssertEqual(fetchResult!.timestamp, timestamp3) fetchExpectation.fulfill() - } catch { - - } + } catch {} } - + wait(for: [fetchExpectation], timeout: 5.0) } func testFetchPeriod() throws { try manager.deleteAll() - + let message1 = "Test message 1" let message2 = "Test message 2" let message3 = "Test message 3" let timestamp1 = Date().addingTimeInterval(-60) let timestamp2 = Date().addingTimeInterval(-30) let timestamp3 = Date() - + let fetchExpectation = XCTestExpectation(description: "Fetch created log") fetchExpectation.expectedFulfillmentCount = 3 @@ -154,9 +154,9 @@ final class MBLoggerCoreDataManagerTests: XCTestCase { manager.create(message: message3, timestamp: timestamp3) { fetchExpectation.fulfill() } - + wait(for: [fetchExpectation], timeout: 5.0) - + let fetchResult = try self.manager.fetchPeriod(timestamp1, timestamp2) XCTAssertEqual(fetchResult.count, 2) XCTAssertEqual(fetchResult[0].message, message1) @@ -167,7 +167,7 @@ final class MBLoggerCoreDataManagerTests: XCTestCase { func testDelete_10_percents() throws { try manager.deleteAll() - + let message = "testDelete_10_percents" let calendar = Calendar.current @@ -181,24 +181,24 @@ final class MBLoggerCoreDataManagerTests: XCTestCase { dateComponents.timeZone = TimeZone(abbreviation: "UTC") let specificDate = calendar.date(from: dateComponents)! - + let cycleCount = 10 let fetchExpectation = XCTestExpectation(description: "Fetch logs for period") fetchExpectation.expectedFulfillmentCount = cycleCount - + for _ in 0..) -> Void) { guard let body = BodyDecoder(decodable: event.body)?.body else { return } - + lastBody = body requests.append(body) return } - - func send(type: T.Type, event: Event, completion: @escaping (Result) -> Void) where T : Decodable { + + func send(type: T.Type, event: Event, completion: @escaping (Result) -> Void) where T: Decodable { return } } diff --git a/MindboxTests/MindboxLoggerTests/OSLogWritterTests/OSLogWritterTests.swift b/MindboxTests/MindboxLoggerTests/OSLogWritterTests/OSLogWritterTests.swift index c865d735..9ac93824 100644 --- a/MindboxTests/MindboxLoggerTests/OSLogWritterTests/OSLogWritterTests.swift +++ b/MindboxTests/MindboxLoggerTests/OSLogWritterTests/OSLogWritterTests.swift @@ -7,6 +7,9 @@ // import XCTest + +// swiftlint:disable comment_spacing + // //final class OSLogWritterTests: XCTestCase { // diff --git a/MindboxTests/MindboxTests.swift b/MindboxTests/MindboxTests.swift index 0ebfe6b3..f4eba3c3 100644 --- a/MindboxTests/MindboxTests.swift +++ b/MindboxTests/MindboxTests.swift @@ -9,6 +9,8 @@ import XCTest @testable import Mindbox +// swiftlint:disable force_try + class MindboxTests: XCTestCase { var mindBoxDidInstalledFlag: Bool = false var apnsTokenDidUpdatedFlag: Bool = false @@ -138,7 +140,7 @@ class MindboxTests: XCTestCase { firstApnsToken = token } - let tokenData = "740f4707 bebcf74f 9b7c25d4 8e335894 5f6aa01d a5ddb387 462c7eaf 61bb78ad".data(using: .utf8)! + let tokenData = Data("740f4707 bebcf74f 9b7c25d4 8e335894 5f6aa01d a5ddb387 462c7eaf 61bb78ad".utf8) let tokenString = tokenData.map { String(format: "%02.2hhx", $0) }.joined() Mindbox.shared.apnsTokenUpdate(deviceToken: tokenData) @@ -160,7 +162,7 @@ class MindboxTests: XCTestCase { Mindbox.shared.getAPNSToken { _ in firstCountApnsToken += 1 } - let tokenData = "740f4707 bebcf74f 9b7c25d4 8e335894 5f6aa01d a5ddb387 462c7eaf 61bb78ad".data(using: .utf8)! + let tokenData = Data("740f4707 bebcf74f 9b7c25d4 8e335894 5f6aa01d a5ddb387 462c7eaf 61bb78ad".utf8) Mindbox.shared.apnsTokenUpdate(deviceToken: tokenData) waitForInitializationFinished() diff --git a/MindboxTests/Mock/MockEvent.swift b/MindboxTests/Mock/MockEvent.swift index 0efe4bf9..78da985f 100644 --- a/MindboxTests/Mock/MockEvent.swift +++ b/MindboxTests/Mock/MockEvent.swift @@ -11,7 +11,7 @@ import Foundation struct MockEvent: EventProtocol { let transactionId: String - + var dateTimeOffset: Int64 { guard isRetry else { return 0 @@ -20,17 +20,17 @@ struct MockEvent: EventProtocol { let ms = (Date().timeIntervalSince(enqueueDate) * 1000).rounded() return Int64(ms) } - + let enqueueTimeStamp: Double - + let serialNumber: String? - + let type: Event.Operation - + let isRetry: Bool - + let body: String - + init(type: Event.Operation, body: String) { self.transactionId = UUID().uuidString self.enqueueTimeStamp = Date().timeIntervalSince1970 @@ -39,7 +39,7 @@ struct MockEvent: EventProtocol { self.serialNumber = nil self.isRetry = false } - + init?(_ event: CDEvent) { guard let transactionId = event.transactionId else { return nil diff --git a/MindboxTests/Mock/MockFailureNetworkFetcher.swift b/MindboxTests/Mock/MockFailureNetworkFetcher.swift index 45f2c640..e70c6a69 100644 --- a/MindboxTests/Mock/MockFailureNetworkFetcher.swift +++ b/MindboxTests/Mock/MockFailureNetworkFetcher.swift @@ -9,28 +9,29 @@ import Foundation @testable import Mindbox +// swiftlint:disable force_try force_unwrapping + class MockFailureNetworkFetcher: NetworkFetcher { - + init() { } private var routesCount: Int = 0 - + var failableRouteIndex = Int.random(in: 0...9) - + private func errorForRoute() -> MindboxError? { if routesCount >= 9 { let error: MindboxError = .internalError(.init( errorKey: .parsing, rawError: nil )) - + return error } - + routesCount += 1 return nil - } func request(route: Route, completion: @escaping ((Result) -> Void)) { @@ -41,7 +42,7 @@ class MockFailureNetworkFetcher: NetworkFetcher { } } - func request(type: T.Type, route: Route, needBaseResponse: Bool, completion: @escaping ((Result) -> Void)) where T : Decodable { + func request(type: T.Type, route: Route, needBaseResponse: Bool, completion: @escaping ((Result) -> Void)) where T: Decodable { if let error = errorForRoute() { completion(.failure(error)) } else { @@ -53,7 +54,7 @@ class MockFailureNetworkFetcher: NetworkFetcher { let decoded = try JSONDecoder().decode(type, from: data) completion(Result.success(decoded)) } catch let decodeError { - let error: MindboxError = MindboxError(.init(errorKey: .parsing, rawError: decodeError)) + let error = MindboxError(.init(errorKey: .parsing, rawError: decodeError)) completion(Result.failure(error)) } } diff --git a/MindboxTests/Mock/MockInAppConfigurationDataFacade.swift b/MindboxTests/Mock/MockInAppConfigurationDataFacade.swift index b7ed99ce..0abeae98 100644 --- a/MindboxTests/Mock/MockInAppConfigurationDataFacade.swift +++ b/MindboxTests/Mock/MockInAppConfigurationDataFacade.swift @@ -6,20 +6,21 @@ // Copyright © 2024 Mindbox. All rights reserved. // -import Foundation import UIKit @testable import Mindbox +// swiftlint:disable force_unwrapping + class MockInAppConfigurationDataFacade: InAppConfigurationDataFacadeProtocol { - + let segmentationService: SegmentationServiceProtocol var targetingChecker: InAppTargetingCheckerProtocol let imageService: ImageDownloadServiceProtocol let tracker: InappTargetingTrackProtocol - + public var showArray: [String] = [] public var targetingArray: [String] = [] - + init(segmentationService: SegmentationServiceProtocol, targetingChecker: InAppTargetingCheckerProtocol, imageService: ImageDownloadServiceProtocol, @@ -29,11 +30,11 @@ class MockInAppConfigurationDataFacade: InAppConfigurationDataFacadeProtocol { self.imageService = imageService self.tracker = tracker } - + func fetchDependencies(model: InappOperationJSONModel?, _ completion: @escaping () -> Void) { completion() } - + func setObservedOperation() { SessionTemporaryStorage.shared.observedCustomOperations = Set(targetingChecker.context.operationsName) } @@ -46,17 +47,17 @@ class MockInAppConfigurationDataFacade: InAppConfigurationDataFacadeProtocol { completion(.success(UIImage())) } } - + func trackTargeting(id: String?) { if let id = id { if showArray.isEmpty { showArray.append(id) } - + targetingArray.append(id) } } - + func clean() { targetingArray = [] } diff --git a/MindboxTests/Mock/MockNetworkFetcher.swift b/MindboxTests/Mock/MockNetworkFetcher.swift index d52059f9..5251e82d 100644 --- a/MindboxTests/Mock/MockNetworkFetcher.swift +++ b/MindboxTests/Mock/MockNetworkFetcher.swift @@ -9,10 +9,12 @@ import Foundation @testable import Mindbox +// swiftlint:disable force_try force_unwrapping + class MockNetworkFetcher: NetworkFetcher { var data: Data? var error: MindboxError? - + init() { let bundle = Bundle(for: MockNetworkFetcher.self) let path = bundle.path(forResource: "SuccessResponse", ofType: "json")! @@ -29,7 +31,7 @@ class MockNetworkFetcher: NetworkFetcher { } } - func request(type: T.Type, route: Route, needBaseResponse: Bool, completion: @escaping ((Result) -> Void)) where T : Decodable { + func request(type: T.Type, route: Route, needBaseResponse: Bool, completion: @escaping ((Result) -> Void)) where T: Decodable { if let error = error { completion(.failure(error)) } else if let data = data { @@ -37,7 +39,7 @@ class MockNetworkFetcher: NetworkFetcher { let decoded = try JSONDecoder().decode(type, from: data) completion(.success(decoded)) } catch let decodeError { - let error: MindboxError = MindboxError(.init(errorKey: .parsing, rawError: decodeError, statusCode: nil)) + let error = MindboxError(.init(errorKey: .parsing, rawError: decodeError, statusCode: nil)) completion(.failure(error)) } } diff --git a/MindboxTests/Mock/MockPersistenceStorage.swift b/MindboxTests/Mock/MockPersistenceStorage.swift index d6879e29..5866526c 100644 --- a/MindboxTests/Mock/MockPersistenceStorage.swift +++ b/MindboxTests/Mock/MockPersistenceStorage.swift @@ -10,14 +10,11 @@ import Foundation @testable import Mindbox class MockPersistenceStorage: PersistenceStorage { - + var onDidChange: (() -> Void)? - - - init() { - } - + init() {} + var deviceUUID: String? { didSet { configuration?.previousDeviceUUID = deviceUUID @@ -46,13 +43,13 @@ class MockPersistenceStorage: PersistenceStorage { onDidChange?() } } - + var deprecatedEventsRemoveDate: Date? { didSet { onDidChange?() } } - + var configuration: MBConfiguration? { didSet { onDidChange?() @@ -64,13 +61,13 @@ class MockPersistenceStorage: PersistenceStorage { onDidChange?() } } - + var isNotificationsEnabled: Bool? { didSet { onDidChange?() } } - + var installationDate: Date? { didSet { onDidChange?() @@ -78,33 +75,33 @@ class MockPersistenceStorage: PersistenceStorage { } var shownInAppsIds: [String]? - - var shownInappsDictionary: [String : Date]? - + + var shownInappsDictionary: [String: Date]? + var handledlogRequestIds: [String]? - + 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 { + didSet { onDidChange?() } } - + func reset() { installationDate = nil deviceUUID = nil @@ -116,7 +113,7 @@ class MockPersistenceStorage: PersistenceStorage { isNotificationsEnabled = nil resetBackgroundExecutions() } - + func softReset() { configDownloadDate = nil shownInappsDictionary = nil @@ -124,23 +121,20 @@ class MockPersistenceStorage: PersistenceStorage { userVisitCount = 0 resetBackgroundExecutions() } - + func setBackgroundExecution(_ value: BackgroudExecution) { backgroundExecutions.append(value) } - + func resetBackgroundExecutions() { backgroundExecutions = [] } - - func storeToFileBackgroundExecution() { - - } - + + func storeToFileBackgroundExecution() {} + var needUpdateInfoOnce: Bool? { didSet { onDidChange?() } } - } diff --git a/MindboxTests/Mock/MockSessionManager.swift b/MindboxTests/Mock/MockSessionManager.swift index 5ad61fc2..9d1e9368 100644 --- a/MindboxTests/Mock/MockSessionManager.swift +++ b/MindboxTests/Mock/MockSessionManager.swift @@ -13,11 +13,10 @@ class MockSessionManager: SessionManager { public var _isActiveNow: Bool = false var isActiveNow: Bool { return _isActiveNow } - - var sessionHandler: ((Bool) -> Void)? = { isActive in } - + + var sessionHandler: ((Bool) -> Void)? = { _ in } + func trackDirect() {} - - func trackForeground() { } + func trackForeground() { } } diff --git a/MindboxTests/Mock/MockUNAuthorizationStatusProvider.swift b/MindboxTests/Mock/MockUNAuthorizationStatusProvider.swift index ad26db53..b1db458e 100644 --- a/MindboxTests/Mock/MockUNAuthorizationStatusProvider.swift +++ b/MindboxTests/Mock/MockUNAuthorizationStatusProvider.swift @@ -11,16 +11,14 @@ import UIKit @testable import Mindbox class MockUNAuthorizationStatusProvider: UNAuthorizationStatusProviding { - + func getStatus(result: @escaping (Bool) -> Void) { result(status.rawValue == UNAuthorizationStatus.authorized.rawValue) } - - + private let status: UNAuthorizationStatus init(status: UNAuthorizationStatus) { self.status = status } - } diff --git a/MindboxTests/PushNotificationTests/MindboxPushValidatorTests.swift b/MindboxTests/PushNotificationTests/MindboxPushValidatorTests.swift index 9aaa563f..32e48f41 100644 --- a/MindboxTests/PushNotificationTests/MindboxPushValidatorTests.swift +++ b/MindboxTests/PushNotificationTests/MindboxPushValidatorTests.swift @@ -10,19 +10,19 @@ import XCTest @testable import Mindbox 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": [ @@ -46,15 +46,15 @@ final class MindboxPushValidatorTests: XCTestCase { ], "uniqueKey": "guid-for-message" ] - + XCTAssertTrue(validator.isValid(item: validNotification), "Validator should return true for a valid notification") } - + func test_no_body_return_false() { let invalidNotification: [AnyHashable: Any] = [ "aps": [ "alert": [ - "title": "Sample Message Title", + "title": "Sample Message Title" ], "sound": "default", "mutable-content": 1, @@ -72,10 +72,10 @@ final class MindboxPushValidatorTests: XCTestCase { ], "uniqueKey": "guid-for-message" ] - + XCTAssertFalse(validator.isValid(item: invalidNotification), "Validator should return false for an invalid notification") } - + func test_no_clickUrl_return_false() { let invalidNotification: [AnyHashable: Any] = [ "aps": [ @@ -98,7 +98,7 @@ final class MindboxPushValidatorTests: XCTestCase { ], "uniqueKey": "guid-for-message" ] - + XCTAssertFalse(validator.isValid(item: invalidNotification), "Validator should return false for an invalid notification") } } diff --git a/MindboxTests/PushNotificationTests/NotificationFormatTests.swift b/MindboxTests/PushNotificationTests/NotificationFormatTests.swift index 06d77f97..817889cd 100644 --- a/MindboxTests/PushNotificationTests/NotificationFormatTests.swift +++ b/MindboxTests/PushNotificationTests/NotificationFormatTests.swift @@ -40,12 +40,12 @@ final class NotificationFormatTests: XCTestCase { XCTFail("MBPushNotification is nil") return } - + XCTAssertEqual(model.clickUrl, "https://example.com/click") XCTAssertEqual(model.aps?.alert?.body, "Текст уведомления") XCTAssertEqual(model.uniqueKey, "uniqueNotificationKey") } - + func test_legacy_format_no_clickURL_return_nil() { let userInfo: [AnyHashable: Any] = [ "aps": [ @@ -69,7 +69,6 @@ final class NotificationFormatTests: XCTestCase { ] ] - XCTAssertNil(NotificationFormatter.formatNotification(userInfo)) } @@ -96,10 +95,9 @@ final class NotificationFormatTests: XCTestCase { ] ] - XCTAssertNil(NotificationFormatter.formatNotification(userInfo)) } - + // MARK: - Current format func test_current_format_return_model() { let userInfo: [AnyHashable: Any] = [ @@ -124,12 +122,12 @@ final class NotificationFormatTests: XCTestCase { ], "uniqueKey": "uniqueNotificationKey" ] - + guard let model = NotificationFormatter.formatNotification(userInfo) else { XCTFail("MBPushNotification is nil") return } - + XCTAssertEqual(model.clickUrl, "https://example.com/click") XCTAssertEqual(model.aps?.alert?.body, "Текст уведомления") XCTAssertEqual(model.uniqueKey, "uniqueNotificationKey") diff --git a/MindboxTests/UserVisitManagerTests/UserVisitManagerTests.swift b/MindboxTests/UserVisitManagerTests/UserVisitManagerTests.swift index 8181e959..f9005407 100644 --- a/MindboxTests/UserVisitManagerTests/UserVisitManagerTests.swift +++ b/MindboxTests/UserVisitManagerTests/UserVisitManagerTests.swift @@ -10,18 +10,18 @@ import XCTest @testable import Mindbox final class UserVisitManagerTests: XCTestCase { - + private var persistenceStorageMock: PersistenceStorage! private var userVisitManager: UserVisitManagerProtocol! private var sessionManagerMock: MockSessionManager! - + override func setUp() { super.setUp() persistenceStorageMock = DI.injectOrFail(PersistenceStorage.self) userVisitManager = DI.injectOrFail(UserVisitManagerProtocol.self) persistenceStorageMock.deviceUUID = "00000000-0000-0000-0000-000000000000" } - + override func tearDown() { persistenceStorageMock.reset() persistenceStorageMock = nil @@ -29,21 +29,21 @@ final class UserVisitManagerTests: XCTestCase { sessionManagerMock = nil super.tearDown() } - + func test_save_first_user_visit_for_first_initialization() throws { userVisitManager.saveUserVisit() XCTAssertEqual(persistenceStorageMock.userVisitCount, 1) } - + func test_save_user_visit_for_difference_session() throws { persistenceStorageMock.userVisitCount = 1 - + userVisitManager.saveUserVisit() XCTAssertEqual(persistenceStorageMock.userVisitCount, 2) - + userVisitManager = DI.injectOrFail(UserVisitManagerProtocol.self) userVisitManager.saveUserVisit() - + XCTAssertEqual(persistenceStorageMock.userVisitCount, 3) } @@ -51,35 +51,35 @@ final class UserVisitManagerTests: XCTestCase { persistenceStorageMock.userVisitCount = 10 persistenceStorageMock.installationDate = Date() SessionTemporaryStorage.shared.isInstalledFromPersistenceStorageBeforeInitSDK = persistenceStorageMock.isInstalled - + userVisitManager.saveUserVisit() - + XCTAssertEqual(persistenceStorageMock.userVisitCount, 11) } - + func test_save_first_user_visit_for_not_first_initialization() throws { persistenceStorageMock.installationDate = Date() SessionTemporaryStorage.shared.isInstalledFromPersistenceStorageBeforeInitSDK = persistenceStorageMock.isInstalled - + userVisitManager.saveUserVisit() - + XCTAssertEqual(persistenceStorageMock.userVisitCount, 2) } - + func test_save_not_first_user_visit_for_not_first_initialization() throws { persistenceStorageMock.userVisitCount = 10 - + userVisitManager.saveUserVisit() - + XCTAssertEqual(persistenceStorageMock.userVisitCount, 11) } - + func test_save_user_visit_twice() throws { persistenceStorageMock.userVisitCount = 10 - + userVisitManager.saveUserVisit() userVisitManager.saveUserVisit() - + XCTAssertEqual(persistenceStorageMock.userVisitCount, 11) } } diff --git a/MindboxTests/Utilities/UUIDDebugService/MockDateProvider.swift b/MindboxTests/Utilities/UUIDDebugService/MockDateProvider.swift index 64ac606d..b0562151 100644 --- a/MindboxTests/Utilities/UUIDDebugService/MockDateProvider.swift +++ b/MindboxTests/Utilities/UUIDDebugService/MockDateProvider.swift @@ -10,5 +10,4 @@ import Foundation final class MockDateProvider { var date: Date! - } diff --git a/MindboxTests/Utilities/UUIDDebugService/MockUUIDDebugService.swift b/MindboxTests/Utilities/UUIDDebugService/MockUUIDDebugService.swift index 1a179474..6182de4b 100644 --- a/MindboxTests/Utilities/UUIDDebugService/MockUUIDDebugService.swift +++ b/MindboxTests/Utilities/UUIDDebugService/MockUUIDDebugService.swift @@ -21,5 +21,4 @@ final class MockUUIDDebugService: UUIDDebugService { invokedStartParameters = (uuid, ()) invokedStartParametersList.append((uuid, ())) } - } diff --git a/MindboxTests/Validators/InappFrequencyTests.swift b/MindboxTests/Validators/InappFrequencyTests.swift index 0e17d56d..26d8101e 100644 --- a/MindboxTests/Validators/InappFrequencyTests.swift +++ b/MindboxTests/Validators/InappFrequencyTests.swift @@ -9,6 +9,8 @@ import XCTest @testable import Mindbox +// swiftlint:disable force_unwrapping + class InappFrequencyTests: XCTestCase { var validator: InappFrequencyValidator! @@ -20,20 +22,20 @@ class InappFrequencyTests: XCTestCase { persistenceStorage.shownInappsDictionary = [:] validator = InappFrequencyValidator(persistenceStorage: persistenceStorage) } - + override func tearDown() { validator = nil persistenceStorage = nil super.tearDown() } - + func test_once_lifetime_firstTime_shown() throws { let onceFrequency = OnceFrequency(kind: .lifetime) let inappFrequency: InappFrequency = .once(onceFrequency) let inapp = getInapp(frequency: inappFrequency) XCTAssertTrue(validator.isValid(item: inapp)) } - + func test_once_lifetime_shownBefore() throws { persistenceStorage.shownInappsDictionary = ["1": Date(timeIntervalSince1970: 0)] let onceFrequency = OnceFrequency(kind: .lifetime) @@ -41,14 +43,14 @@ class InappFrequencyTests: XCTestCase { let inapp = getInapp(frequency: inappFrequency) XCTAssertFalse(validator.isValid(item: inapp)) } - + func test_once_session_firstTime() throws { let onceFrequency = OnceFrequency(kind: .session) let inappFrequency: InappFrequency = .once(onceFrequency) let inapp = getInapp(frequency: inappFrequency) XCTAssertTrue(validator.isValid(item: inapp)) } - + func test_once_session_shownBefore() throws { persistenceStorage.shownInappsDictionary = ["1": Date(timeIntervalSince1970: 0)] let onceFrequency = OnceFrequency(kind: .session) @@ -56,14 +58,14 @@ class InappFrequencyTests: XCTestCase { let inapp = getInapp(frequency: inappFrequency) XCTAssertTrue(validator.isValid(item: inapp)) } - + func test_periodic_seconds_firstTime() throws { let periodicFrequency = PeriodicFrequency(unit: .days, value: 1) let inappFrequency: InappFrequency = .periodic(periodicFrequency) let inapp = getInapp(frequency: inappFrequency) XCTAssertTrue(validator.isValid(item: inapp)) } - + func test_periodic_days_alreadyShown_two_days_ago() throws { let calendar = Calendar.current let shownDate = calendar.date(byAdding: .day, value: -2, to: Date())! @@ -73,7 +75,7 @@ class InappFrequencyTests: XCTestCase { let inapp = getInapp(frequency: inappFrequency) XCTAssertTrue(validator.isValid(item: inapp)) } - + func test_periodic_days_alreadyShown_today() throws { let calendar = Calendar.current let shownDate = calendar.date(byAdding: .day, value: 0, to: Date())! @@ -83,7 +85,7 @@ class InappFrequencyTests: XCTestCase { let inapp = getInapp(frequency: inappFrequency) XCTAssertFalse(validator.isValid(item: inapp)) } - + func test_periodic_days_alreadyShown_in_future() throws { let calendar = Calendar.current let shownDate = calendar.date(byAdding: .day, value: 2, to: Date())! @@ -93,7 +95,7 @@ class InappFrequencyTests: XCTestCase { let inapp = getInapp(frequency: inappFrequency) XCTAssertFalse(validator.isValid(item: inapp)) } - + func test_periodic_hours_alreadyShown_two_days_ago() throws { let calendar = Calendar.current let shownDate = calendar.date(byAdding: .hour, value: -2, to: Date())! @@ -103,7 +105,7 @@ class InappFrequencyTests: XCTestCase { let inapp = getInapp(frequency: inappFrequency) XCTAssertTrue(validator.isValid(item: inapp)) } - + func test_periodic_hours_alreadyShown_today() throws { let calendar = Calendar.current let shownDate = calendar.date(byAdding: .hour, value: 0, to: Date())! @@ -113,7 +115,7 @@ class InappFrequencyTests: XCTestCase { let inapp = getInapp(frequency: inappFrequency) XCTAssertFalse(validator.isValid(item: inapp)) } - + func test_periodic_hours_alreadyShown_in_future() throws { let calendar = Calendar.current let shownDate = calendar.date(byAdding: .hour, value: 2, to: Date())! @@ -123,7 +125,7 @@ class InappFrequencyTests: XCTestCase { let inapp = getInapp(frequency: inappFrequency) XCTAssertFalse(validator.isValid(item: inapp)) } - + func test_periodic_minutes_alreadyShown_two_days_ago() throws { let calendar = Calendar.current let shownDate = calendar.date(byAdding: .minute, value: -2, to: Date())! @@ -133,7 +135,7 @@ class InappFrequencyTests: XCTestCase { let inapp = getInapp(frequency: inappFrequency) XCTAssertTrue(validator.isValid(item: inapp)) } - + func test_periodic_minutes_alreadyShown_today() throws { let calendar = Calendar.current let shownDate = calendar.date(byAdding: .minute, value: 0, to: Date())! @@ -143,7 +145,7 @@ class InappFrequencyTests: XCTestCase { let inapp = getInapp(frequency: inappFrequency) XCTAssertFalse(validator.isValid(item: inapp)) } - + func test_periodic_minutes_alreadyShown_in_future() throws { let calendar = Calendar.current let shownDate = calendar.date(byAdding: .minute, value: 2, to: Date())! @@ -153,7 +155,7 @@ class InappFrequencyTests: XCTestCase { let inapp = getInapp(frequency: inappFrequency) XCTAssertFalse(validator.isValid(item: inapp)) } - + func test_periodic_seconds_alreadyShown_two_days_ago() throws { let calendar = Calendar.current let shownDate = calendar.date(byAdding: .second, value: -2, to: Date())! @@ -163,7 +165,7 @@ class InappFrequencyTests: XCTestCase { let inapp = getInapp(frequency: inappFrequency) XCTAssertTrue(validator.isValid(item: inapp)) } - + func test_periodic_seconds_alreadyShown_today() throws { let calendar = Calendar.current let shownDate = calendar.date(byAdding: .second, value: 0, to: Date())! @@ -173,7 +175,7 @@ class InappFrequencyTests: XCTestCase { let inapp = getInapp(frequency: inappFrequency) XCTAssertFalse(validator.isValid(item: inapp)) } - + func test_periodic_seconds_alreadyShown_in_future() throws { let calendar = Calendar.current let shownDate = calendar.date(byAdding: .second, value: 2, to: Date())! @@ -183,14 +185,14 @@ class InappFrequencyTests: XCTestCase { let inapp = getInapp(frequency: inappFrequency) XCTAssertFalse(validator.isValid(item: inapp)) } - + func test_frequency_is_zero() throws { let periodicFrequency = PeriodicFrequency(unit: .seconds, value: 0) let inappFrequency: InappFrequency = .periodic(periodicFrequency) let inapp = getInapp(frequency: inappFrequency) XCTAssertFalse(validator.isValid(item: inapp)) } - + private func getInapp(frequency: InappFrequency) -> InApp { return InApp(id: "1", sdkVersion: SdkVersion(min: 9, max: nil), @@ -198,7 +200,7 @@ class InappFrequencyTests: XCTestCase { frequency: frequency, form: InAppForm(variants: [.unknown])) } - + private func getConfig(name: String) throws -> ConfigResponse { let bundle = Bundle(for: InAppTargetingRequestsTests.self) let fileURL = bundle.url(forResource: name, withExtension: "json")! diff --git a/MindboxTests/Validators/SDKVersionValidatorTests.swift b/MindboxTests/Validators/SDKVersionValidatorTests.swift index 4fa4139a..52b36c3c 100644 --- a/MindboxTests/Validators/SDKVersionValidatorTests.swift +++ b/MindboxTests/Validators/SDKVersionValidatorTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import Mindbox class SDKVersionValidatorTests: XCTestCase { - + var validator: SDKVersionValidator! override func setUp() { @@ -18,32 +18,32 @@ class SDKVersionValidatorTests: XCTestCase { validator = DI.injectOrFail(SDKVersionValidator.self) validator.sdkVersionNumeric = 6 } - + override func tearDown() { validator = nil super.tearDown() } - + func test_whenMinVersionIsTooHigh_returnsFalse() { let sdkVersion = SdkVersion(min: 10, max: nil) XCTAssertFalse(validator.isValid(item: sdkVersion)) } - + func testValidator_whenMaxVersionIsTooLow_returnsFalse() { let sdkVersion = SdkVersion(min: nil, max: 5) XCTAssertFalse(validator.isValid(item: sdkVersion)) } - + func testValidator_whenVersionIsWithinBounds_returnsTrue() { let sdkVersion = SdkVersion(min: 5, max: 7) XCTAssertTrue(validator.isValid(item: sdkVersion)) } - + func testValidator_whenMinAndMaxAreNil_returnsTrue() { let sdkVersion = SdkVersion(min: nil, max: nil) XCTAssertFalse(validator.isValid(item: sdkVersion)) } - + func testValidator_whenVersionIsNull_returnsFalse() { XCTAssertFalse(validator.isValid(item: nil)) } diff --git a/MindboxTests/Validators/ValidatorsTestCase.swift b/MindboxTests/Validators/ValidatorsTestCase.swift index 458a8aca..429b7306 100644 --- a/MindboxTests/Validators/ValidatorsTestCase.swift +++ b/MindboxTests/Validators/ValidatorsTestCase.swift @@ -9,30 +9,32 @@ import XCTest @testable import Mindbox +// swiftlint:disable line_length + class ValidatorsTestCase: XCTestCase { - + func testURLValidator() { [ "https://www.google.com/search?rlz=1C5CHFA_enRU848RU848&ei=GMYTYIKCK9SSwPAP8cWjiAM&q=umbrella+it&oq=umbrella+it&gs_lcp=CgZwc3ktYWIQAzIICAAQxwEQrwEyCAgAEMcBEK8BMgIIADICCAAyAggAMggIABDHARCvATICCAA6BQgAELEDOggIABCxAxCDAToICAAQxwEQowI6BggAEAoQAToOCAAQxwEQrwEQChABECo6BAgAEAo6BAgAEB46CgguELEDEEMQkwI6BAgAEEM6BwguELEDEEM6CggAEMcBEK8BEAo6BwgAELEDEAo6CwgAELEDEMcBEKMCOgUILhCxAzoOCAAQsQMQgwEQxwEQowI6CggAEAoQARBDECo6BwgAELEDEENQolhYx7IBYM61AWgJcAB4AIABcIgBqA2SAQQxNi40mAEAoAEBqgEHZ3dzLXdperABAMABAQ&sclient=psy-ab&ved=0ahUKEwiC7tnL28DuAhVUCRAIHfHiCDEQ4dUDCA0&uact=5", - + "http://www.google.com" ] .compactMap({ URL(string: $0) }) .forEach { XCTAssertTrue(URLValidator(url: $0).evaluate()) } - + [ "", "https://www google com/", - "www.google.com", + "www.google.com" ] .compactMap { URL(string: $0) } .forEach { XCTAssertFalse(URLValidator(url: $0).evaluate()) } } - + func testUDIDValidator() { XCTAssertFalse(UDIDValidator(udid: "00000000-0000-0000-0000-000000000000").evaluate()) XCTAssertFalse(UDIDValidator(udid: "00000000-0000-0000-0000").evaluate()) diff --git a/MindboxTests/Versioning/VersioningTestCase.swift b/MindboxTests/Versioning/VersioningTestCase.swift index 730c72a2..b3091bc8 100644 --- a/MindboxTests/Versioning/VersioningTestCase.swift +++ b/MindboxTests/Versioning/VersioningTestCase.swift @@ -9,6 +9,8 @@ @testable import Mindbox import XCTest +// swiftlint:disable force_try + class VersioningTestCase: XCTestCase { private var queues: [DispatchQueue] = []