diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index f22bcf8f6..bc64ab490 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -17,7 +17,7 @@ on: description: release env: - VERSION: 3.10.2 + VERSION: 3.10.3 jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a33d37ec..0ac463e4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Optimizely Swift SDK Changelog +## 3.10.3 +December 5, 2023 + +### Bug Fixes +* Remove redundant post request body in upload task. ([#521](https://github.com/optimizely/swift-sdk/pull/521/)). + +### Enhancement +* Add privacy manifest file ([#522](https://github.com/optimizely/swift-sdk/pull/522/)). +* Handle duplicate keys in experiment ([#523](https://github.com/optimizely/swift-sdk/pull/523/)). + ## 3.10.2 March 14, 2023 diff --git a/OptimizelySwiftSDK.xcodeproj/project.pbxproj b/OptimizelySwiftSDK.xcodeproj/project.pbxproj index ceb2e999a..2697b9739 100644 --- a/OptimizelySwiftSDK.xcodeproj/project.pbxproj +++ b/OptimizelySwiftSDK.xcodeproj/project.pbxproj @@ -2457,6 +2457,7 @@ children = ( 6E75167A22C520D400B2B157 /* Optimizely.h */, 6E75167B22C520D400B2B157 /* Info.plist */, + 987F11D92AF3F56F0083D3F9 /* PrivacyInfo.xcprivacy */, ); path = "Supporting Files"; sourceTree = ""; diff --git a/README.md b/README.md index bc1270ab9..f76aa9013 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Add the dependency on the Optimizely Swift SDK with Swift Package Manager in `Xc #### CocoaPods 1. Add the following lines to the _Podfile_:
 ```use_frameworks!```
-```pod 'OptimizelySwiftSDK', '~> 3.10.2'```
+```pod 'OptimizelySwiftSDK', '~> 3.10.3'```
 
2. Run the following command:
``` pod install ```
@@ -45,7 +45,7 @@ Add the dependency on the Optimizely Swift SDK with Swift Package Manager in `Xc Further installation instructions for Cocoapods: https://guides.cocoapods.org/using/getting-started.html #### Carthage -1. Add the following lines to the _Cartfile_:
```github "optimizely/swift-sdk" ~> 3.10.2```
+1. Add the following lines to the _Cartfile_:
```github "optimizely/swift-sdk" ~> 3.10.3```
2. Run the following command:
```carthage update```
diff --git a/Scripts/run_unit_tests.sh b/Scripts/run_unit_tests.sh index 4541b6548..dd99210a2 100755 --- a/Scripts/run_unit_tests.sh +++ b/Scripts/run_unit_tests.sh @@ -7,7 +7,7 @@ # # unit tests for PR only -if [[ "$BRANCH" == "master" ]] -then +# if [[ "$BRANCH" == "master" ]] +# then xcodebuild test -derivedDataPath $COVERAGE_DIR -workspace OptimizelySwiftSDK.xcworkspace -scheme $SCHEME -configuration Release CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -sdk $TEST_SDK -destination "platform=$PLATFORM,OS=$OS,name=$NAME" ONLY_ACTIVE_ARCH=YES | tee buildoutput | xcpretty && test ${PIPESTATUS[0]} -eq 0 -fi +# fi diff --git a/Sources/Customization/DefaultEventDispatcher.swift b/Sources/Customization/DefaultEventDispatcher.swift index 68e93feae..0311f9d09 100644 --- a/Sources/Customization/DefaultEventDispatcher.swift +++ b/Sources/Customization/DefaultEventDispatcher.swift @@ -183,7 +183,6 @@ open class DefaultEventDispatcher: BackgroundingCallbacks, OPTEventDispatcher { var request = URLRequest(url: event.url) request.httpMethod = "POST" - request.httpBody = event.body request.addValue("application/json", forHTTPHeaderField: "Content-Type") // send notification BEFORE sending event to the server diff --git a/Sources/Optimizely/OptimizelyClient.swift b/Sources/Optimizely/OptimizelyClient.swift index a88fd9de0..0c32c7eb0 100644 --- a/Sources/Optimizely/OptimizelyClient.swift +++ b/Sources/Optimizely/OptimizelyClient.swift @@ -745,7 +745,7 @@ open class OptimizelyClient: NSObject { public func getOptimizelyConfig() throws -> OptimizelyConfig { guard let config = self.config else { throw OptimizelyError.sdkNotReady } - return OptimizelyConfigImp(projectConfig: config) + return OptimizelyConfigImp(projectConfig: config, logger: logger) } } diff --git a/Sources/Optimizely/OptimizelyConfig.swift b/Sources/Optimizely/OptimizelyConfig.swift index af590d31b..e04d3002c 100644 --- a/Sources/Optimizely/OptimizelyConfig.swift +++ b/Sources/Optimizely/OptimizelyConfig.swift @@ -95,8 +95,8 @@ struct OptimizelyConfigImp: OptimizelyConfig { var attributes: [OptimizelyAttribute] = [] var audiences: [OptimizelyAudience] = [] var events: [OptimizelyEvent] = [] - - init(projectConfig: ProjectConfig) { + + init(projectConfig: ProjectConfig, logger: OPTLogger = DefaultLogger()) { guard let project = projectConfig.project else { return } self.environmentKey = project.environmentKey ?? "" @@ -139,7 +139,7 @@ struct OptimizelyConfigImp: OptimizelyConfig { return updatedRollout } - self.experimentsMap = makeExperimentsMap(project: project, experiments: updatedExperiments) + self.experimentsMap = makeExperimentsMap(project: project, experiments: updatedExperiments, logger: logger) self.featuresMap = makeFeaturesMap(project: project, experiments: updatedExperiments, rollouts: updatedRollouts) } } @@ -148,9 +148,12 @@ struct OptimizelyConfigImp: OptimizelyConfig { extension OptimizelyConfigImp { - func makeExperimentsMap(project: Project, experiments: [Experiment]) -> [String: Experiment] { + func makeExperimentsMap(project: Project, experiments: [Experiment], logger: OPTLogger) -> [String: Experiment] { var map = [String: Experiment]() experiments.forEach { + if map.keys.contains($0.key) { + logger.w("Duplicate experiment keys found in datafile: \($0.key)") + } map[$0.key] = $0 } return map diff --git a/Sources/Supporting Files/PrivacyInfo.xcprivacy b/Sources/Supporting Files/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..f6b5e1a82 --- /dev/null +++ b/Sources/Supporting Files/PrivacyInfo.xcprivacy @@ -0,0 +1,17 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPITypeReasons + + To store configuration and event data temporarily + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + + + diff --git a/Tests/OptimizelyTests-APIs/OptimizelyClientTests_OptimizelyConfig.swift b/Tests/OptimizelyTests-APIs/OptimizelyClientTests_OptimizelyConfig.swift index 24880abfa..48891924d 100644 --- a/Tests/OptimizelyTests-APIs/OptimizelyClientTests_OptimizelyConfig.swift +++ b/Tests/OptimizelyTests-APIs/OptimizelyClientTests_OptimizelyConfig.swift @@ -263,6 +263,64 @@ class OptimizelyClientTests_OptimizelyConfig: XCTestCase { let result = try? self.optimizely.getOptimizelyConfig() XCTAssertNil(result) } + + func testOptimizelyConfigWithDuplicateKeys() { + let exp0: [String : Any] = [ + "id": "10001", + "key": "duplicate_key", + "status": "Running", + "layerId": "22222", + "variations": [], + "trafficAllocation": [], + "audienceIds": ["33333"], + "audienceConditions": [], + "forcedVariations": ["12345": "1234567890"] + ] + + let exp1: [String : Any] = [ + "id": "10005", + "key": "duplicate_key", + "status": "Running", + "layerId": "22222", + "variations": [], + "trafficAllocation": [], + "audienceIds": ["33333"], + "audienceConditions": [], + "forcedVariations": ["12345": "1234567890"] + ] + + var projectData: [String: Any] = [ + "version": "4", + "projectId": "11111", + "experiments": [], + "audiences": [], + "groups": [], + "attributes": [], + "accountId": "1234567890", + "events": [], + "revision": "5", + "anonymizeIP": true, + "rollouts": [], + "typedAudiences": [], + "integrations": [], + "featureFlags": [], + "botFiltering": false, + "sendFlagDecisions": true + ] + + projectData["experiments"] = [exp0, exp1] + let model: Project = try! OTUtils.model(from: projectData) + let projectConfig = ProjectConfig() + projectConfig.project = model + + let logger = TestLogger() + let optiConfigImpl = OptimizelyConfigImp(projectConfig: projectConfig, logger: logger) + let optimizelyExpMap: [String: OptimizelyExperiment] = optiConfigImpl.experimentsMap + XCTAssertEqual(logger.getMessages(.warning), ["Duplicate experiment keys found in datafile: duplicate_key"]) + + XCTAssertEqual(optimizelyExpMap.count, 1) + XCTAssertEqual(optimizelyExpMap["duplicate_key"]?.id, "10005") + } } @@ -371,3 +429,41 @@ extension OptimizelyEvent { } } +// MARK: - Mock Loggers + +fileprivate class TestLogger: OPTLogger { + private static var _logLevel: OptimizelyLogLevel? + public static var logLevel: OptimizelyLogLevel { + get { + return _logLevel ?? .info + } + set (newLevel) { + _logLevel = newLevel + } + } + + required public init() { + clearMessages() + } + + func log(level: OptimizelyLogLevel, message: String) { + logMessages[level.rawValue].append(message) + } + + // Utils + + var logMessages = [[String]]() + + var logCount: Int { + return logMessages.reduce(0) { $0 + $1.count } + } + + func getMessages(_ level: OptimizelyLogLevel) -> [String] { + return logMessages[level.rawValue] + } + + func clearMessages() { + logMessages = [[String]](repeating: [], count: OptimizelyLogLevel.debug.rawValue + 1) + } + +}