From 52e0f4b7bb71cc2e04bcb32ffa36f10e86e9feee Mon Sep 17 00:00:00 2001 From: akhiljain1907 Date: Tue, 24 Sep 2024 23:07:49 +0530 Subject: [PATCH 01/13] added trackDisplayedProposition public api to support display event fired for multiple propositions moved generateInteractionXDM and trackWithData method from Offers class to public Optimize extension added relevant OptimizePuiblicApiTests to validate code functioning for multiple propositions --- AEPOptimize.xcodeproj/project.pbxproj | 8 + Sources/AEPOptimize/Offer+Tracking.swift | 86 ++------- Sources/AEPOptimize/Optimize+Tracking.swift | 91 +++++++++ .../UnitTests/OfferTrackingTests.swift | 24 +-- .../UnitTests/OptimizePropositionTests.swift | 124 +----------- .../OptimizePropositionTrackingTests.swift | 4 +- .../UnitTests/OptimizePublicAPITests.swift | 176 ++++++++++++++++++ .../Propositions+OptimizeTests.swift | 136 ++++++++++++++ 8 files changed, 443 insertions(+), 206 deletions(-) create mode 100644 Sources/AEPOptimize/Optimize+Tracking.swift create mode 100644 Tests/AEPOptimizeTests/UnitTests/Propositions+OptimizeTests.swift diff --git a/AEPOptimize.xcodeproj/project.pbxproj b/AEPOptimize.xcodeproj/project.pbxproj index 5c12c0f..254a741 100644 --- a/AEPOptimize.xcodeproj/project.pbxproj +++ b/AEPOptimize.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ 78AF6DBB284FD9AA0022EB24 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 78AF6DB9284FD9AA0022EB24 /* MainInterface.storyboard */; }; 78AF6DBF284FD9AA0022EB24 /* AEPOptimizeDemoAppExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 78AF6DB5284FD9AA0022EB24 /* AEPOptimizeDemoAppExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 79AF0BBA3B4B3F0D2B46C847 /* Pods_AEPOptimize.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89FA9F1584C29AFB176C28B9 /* Pods_AEPOptimize.framework */; }; + B65FBE642CA28CD300EC3440 /* Optimize+Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65FBE632CA28CD300EC3440 /* Optimize+Tracking.swift */; }; + B65FBE662CA2E37500EC3440 /* Propositions+OptimizeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65FBE652CA2E37500EC3440 /* Propositions+OptimizeTests.swift */; }; B6B401C52C8052210082B3FE /* OptimizeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B401C42C8052210082B3FE /* OptimizeError.swift */; }; C8076C4D265EE786006BEC5D /* TargetProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8076C3F265EE768006BEC5D /* TargetProduct.swift */; }; C8076C52265EE789006BEC5D /* TargetOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8076C46265EE776006BEC5D /* TargetOrder.swift */; }; @@ -188,6 +190,8 @@ AB699BC71E1531AB6A02B9AD /* Pods-shared-AEPOptimizeDemoAppExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shared-AEPOptimizeDemoAppExtension.debug.xcconfig"; path = "Target Support Files/Pods-shared-AEPOptimizeDemoAppExtension/Pods-shared-AEPOptimizeDemoAppExtension.debug.xcconfig"; sourceTree = ""; }; AB8BAF0E948D2E2747418C4E /* Pods-FunctionalTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FunctionalTests.debug.xcconfig"; path = "Target Support Files/Pods-FunctionalTests/Pods-FunctionalTests.debug.xcconfig"; sourceTree = ""; }; B588A06BD3941420A1C1CBC5 /* Pods-AEPOptimizeDemoObjC.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AEPOptimizeDemoObjC.debug.xcconfig"; path = "Target Support Files/Pods-AEPOptimizeDemoObjC/Pods-AEPOptimizeDemoObjC.debug.xcconfig"; sourceTree = ""; }; + B65FBE632CA28CD300EC3440 /* Optimize+Tracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optimize+Tracking.swift"; sourceTree = ""; }; + B65FBE652CA2E37500EC3440 /* Propositions+OptimizeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Propositions+OptimizeTests.swift"; sourceTree = ""; }; B6B401C42C8052210082B3FE /* OptimizeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizeError.swift; sourceTree = ""; }; BEA1EA2DAE5E666C7DEEA655 /* Pods-UnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.debug.xcconfig"; path = "Target Support Files/Pods-UnitTests/Pods-UnitTests.debug.xcconfig"; sourceTree = ""; }; C2AB724370848D9EA2ABD4AA /* Pods-AEPOptimizeDemoAppExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AEPOptimizeDemoAppExtension.release.xcconfig"; path = "Target Support Files/Pods-AEPOptimizeDemoAppExtension/Pods-AEPOptimizeDemoAppExtension.release.xcconfig"; sourceTree = ""; }; @@ -516,6 +520,7 @@ isa = PBXGroup; children = ( C88C5F79261D06A5003AE3DE /* Optimize+PublicAPI.swift */, + B65FBE632CA28CD300EC3440 /* Optimize+Tracking.swift */, C88C5F1126152DAB003AE3DE /* DecisionScope.swift */, C88C5EDC2614F640003AE3DE /* Offer.swift */, C8DE6B692684181A0076623F /* Offer+Tracking.swift */, @@ -600,6 +605,7 @@ C8CA16DA2624BDA5005B09A0 /* String+OptimizeTests.swift */, C8CA16DB2624BDA5005B09A0 /* StringInterpolation+OptimizeTests.swift */, C8227C7627DAD19C005A7703 /* KeyedDecodingContainer+OptimizeTests.swift */, + B65FBE652CA2E37500EC3440 /* Propositions+OptimizeTests.swift */, ); name = Utils; sourceTree = ""; @@ -1219,6 +1225,7 @@ buildActionMask = 2147483647; files = ( C88C5EDD2614F640003AE3DE /* Offer.swift in Sources */, + B65FBE642CA28CD300EC3440 /* Optimize+Tracking.swift in Sources */, C8DE6BEF2686848B0076623F /* Proposition+Tracking.swift in Sources */, C8227C7527DA5BD6005A7703 /* KeyedDecodingContainer+Optimize.swift in Sources */, C88C5F3326185EF8003AE3DE /* StringInterpolation+Optimize.swift in Sources */, @@ -1246,6 +1253,7 @@ C88C5F0A261523A4003AE3DE /* OfferTests.swift in Sources */, C8CA16DE2624BDA6005B09A0 /* String+OptimizeTests.swift in Sources */, C88C5FE3261D8FBF003AE3DE /* MockExtension.swift in Sources */, + B65FBE662CA2E37500EC3440 /* Propositions+OptimizeTests.swift in Sources */, C88C5F1F2615916D003AE3DE /* DecisionScopeTests.swift in Sources */, C88C5F56261C3F6A003AE3DE /* OptimizePropositionTests.swift in Sources */, C88C5F272615A057003AE3DE /* OfferTypeTests.swift in Sources */, diff --git a/Sources/AEPOptimize/Offer+Tracking.swift b/Sources/AEPOptimize/Offer+Tracking.swift index 0873bc3..2a48a37 100644 --- a/Sources/AEPOptimize/Offer+Tracking.swift +++ b/Sources/AEPOptimize/Offer+Tracking.swift @@ -27,7 +27,12 @@ public extension Offer { /// - Returns A dictionary containing XDM data for the propositon interactions. /// - SeeAlso: `interactionXdm(for:)` func generateDisplayInteractionXdm() -> [String: Any]? { - generateInteractionXdm(for: OptimizeConstants.JsonValues.EE_EVENT_TYPE_PROPOSITION_DISPLAY) + guard let proposition = proposition else { + Log.debug(label: OptimizeConstants.LOG_TAG, + "Cannot send display proposition interaction event for option \(id), proposition reference is not available.") + return nil + } + return Optimize.generateInteractionXdm(for: [proposition], for: OptimizeConstants.JsonValues.EE_EVENT_TYPE_PROPOSITION_DISPLAY) } /// Creates a dictionary containing XDM formatted data for `Experience Event - Proposition Interactions` field group from the given proposition option. @@ -39,88 +44,25 @@ public extension Offer { /// - Returns A dictionary containing XDM data for the propositon interactions. /// - SeeAlso: `interactionXdm(for:)` func generateTapInteractionXdm() -> [String: Any]? { - generateInteractionXdm(for: OptimizeConstants.JsonValues.EE_EVENT_TYPE_PROPOSITION_INTERACT) + guard let proposition = proposition else { + Log.debug(label: OptimizeConstants.LOG_TAG, + "Cannot send tap proposition interaction event for option \(id), proposition reference is not available.") + return nil + } + return Optimize.generateInteractionXdm(for: [proposition], for: OptimizeConstants.JsonValues.EE_EVENT_TYPE_PROPOSITION_INTERACT) } /// Dispatches an event for the Edge extension to send an Experience Event to the Edge network with the display interaction data for the given proposition item. /// /// - SeeAlso: `trackWithData(_:)` func displayed() { - trackWithData(generateDisplayInteractionXdm()) + Optimize.trackWithData(generateDisplayInteractionXdm()) } /// Dispatches an event for the Edge extension to send an Experience Event to the Edge network with the tap interaction data for the given proposition item. /// /// - SeeAlso: `trackWithData(_:)` func tapped() { - trackWithData(generateTapInteractionXdm()) - } - - /// Creates a dictionary containing XDM formatted data for `Experience Event - Proposition Interactions` field group from the given proposition option and for the provided event type. - /// - /// If the proposition reference within the option is released and no longer valid, the method returns `nil`. - /// - /// - Parameter eventType: The Experience Event event type for the proposition interaction. - /// - Returns A dictionary containing XDM data for the propositon interactions. - private func generateInteractionXdm(for eventType: String) -> [String: Any]? { - var propositionDetailsData: [String: Any] = [:] - guard let proposition = proposition else { - Log.debug(label: OptimizeConstants.LOG_TAG, - "Cannot send proposition interaction event (\(eventType)) for option \(id), proposition reference is not available.") - return nil - } - - propositionDetailsData = [ - OptimizeConstants.JsonKeys.DECISIONING_PROPOSITIONS_ID: proposition.id, - OptimizeConstants.JsonKeys.DECISIONING_PROPOSITIONS_SCOPE: proposition.scope, - OptimizeConstants.JsonKeys.DECISIONING_PROPOSITIONS_SCOPEDETAILS: proposition.scopeDetails, - OptimizeConstants.JsonKeys.DECISIONING_PROPOSITIONS_ITEMS: [ - [ - OptimizeConstants.JsonKeys.DECISIONING_PROPOSITIONS_ITEMS_ID: id - ] - ] - ] - - var propositionEventType: [String: Any] = [:] - let propEventType = (eventType == OptimizeConstants.JsonValues.EE_EVENT_TYPE_PROPOSITION_DISPLAY) ? - OptimizeConstants.JsonKeys.PROPOSITION_EVENT_TYPE_DISPLAY : - OptimizeConstants.JsonKeys.PROPOSITION_EVENT_TYPE_INTERACT - propositionEventType[propEventType] = 1 - - let xdmData: [String: Any] = [ - OptimizeConstants.JsonKeys.EXPERIENCE_EVENT_TYPE: eventType, - OptimizeConstants.JsonKeys.EXPERIENCE: [ - OptimizeConstants.JsonKeys.EXPERIENCE_DECISIONING: [ - OptimizeConstants.JsonKeys.DECISIONING_PROPOSITION_EVENT_TYPE: propositionEventType, - OptimizeConstants.JsonKeys.DECISIONING_PROPOSITIONS: [propositionDetailsData] - ] - ] - ] - return xdmData - } - - /// Dispatches the track propositions request event with type `EventType.optimize` and source `EventSource.requestContent` and given proposition interactions data. - /// - /// No event is dispatched if the input xdm data is `nil`. - /// - /// - Parameter xdmData: A dictionary containing XDM data for the propositon interactions. - private func trackWithData(_ xdmData: [String: Any]?) { - guard let xdmData = xdmData else { - Log.debug(label: OptimizeConstants.LOG_TAG, - "Cannot send track propositions request event, the provided xdmData is nil.") - return - } - - let eventData: [String: Any] = [ - OptimizeConstants.EventDataKeys.REQUEST_TYPE: OptimizeConstants.EventDataValues.REQUEST_TYPE_TRACK, - OptimizeConstants.EventDataKeys.PROPOSITION_INTERACTIONS: xdmData - ] - - let event = Event(name: OptimizeConstants.EventNames.TRACK_PROPOSITIONS_REQUEST, - type: EventType.optimize, - source: EventSource.requestContent, - data: eventData) - - MobileCore.dispatch(event: event) + Optimize.trackWithData(generateTapInteractionXdm()) } } diff --git a/Sources/AEPOptimize/Optimize+Tracking.swift b/Sources/AEPOptimize/Optimize+Tracking.swift new file mode 100644 index 0000000..a1418e6 --- /dev/null +++ b/Sources/AEPOptimize/Optimize+Tracking.swift @@ -0,0 +1,91 @@ +// Delete this line +/* +Copyright 2024 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ +import AEPCore +import AEPServices +import Foundation + +@objc +public extension Optimize { + /// This API dispatches an event for the Edge extension to send an Experience Event to the Edge network with the display interaction data for list of propositions passed. + /// + /// - Parameter propositions: An array of optimize propositions. + static func trackDisplayedPropositions(for propositions: [OptimizeProposition]) { + let xdm = generateInteractionXdm(for: propositions, for: OptimizeConstants.JsonValues.EE_EVENT_TYPE_PROPOSITION_DISPLAY) + trackWithData(xdm) + } + + /// Creates a dictionary containing XDM formatted data for `Experience Event - Proposition Interactions` field group from the given list of propositions and for the provided event type. + /// + /// - Parameter propositions: An array of optimize propositions. + /// - Parameter eventType: The Experience Event event type for the proposition interaction. + /// - Returns A dictionary containing XDM data for the propositon interactions. + static func generateInteractionXdm(for propositions: [OptimizeProposition], for eventType: String) -> [String: Any]? { + var propositionDetailsData: [[String: Any]] = [] + + for proposition in propositions { + let propositionMap: [String: Any] = [ + OptimizeConstants.JsonKeys.DECISIONING_PROPOSITIONS_ID: proposition.id, + OptimizeConstants.JsonKeys.DECISIONING_PROPOSITIONS_SCOPE: proposition.scope, + OptimizeConstants.JsonKeys.DECISIONING_PROPOSITIONS_SCOPEDETAILS: proposition.scopeDetails, + OptimizeConstants.JsonKeys.DECISIONING_PROPOSITIONS_ITEMS: [ + [ + OptimizeConstants.JsonKeys.DECISIONING_PROPOSITIONS_ITEMS_ID: proposition.offers[0].id + ] + ] + ] + propositionDetailsData.append(propositionMap) + } + + var propositionEventType: [String: Any] = [:] + let propEventType = (eventType == OptimizeConstants.JsonValues.EE_EVENT_TYPE_PROPOSITION_DISPLAY) ? + OptimizeConstants.JsonKeys.PROPOSITION_EVENT_TYPE_DISPLAY : + OptimizeConstants.JsonKeys.PROPOSITION_EVENT_TYPE_INTERACT + propositionEventType[propEventType] = 1 + + let xdmData: [String: Any] = [ + OptimizeConstants.JsonKeys.EXPERIENCE_EVENT_TYPE: eventType, + OptimizeConstants.JsonKeys.EXPERIENCE: [ + OptimizeConstants.JsonKeys.EXPERIENCE_DECISIONING: [ + OptimizeConstants.JsonKeys.DECISIONING_PROPOSITION_EVENT_TYPE: propositionEventType, + OptimizeConstants.JsonKeys.DECISIONING_PROPOSITIONS: propositionDetailsData + ] + ] + ] + return xdmData + } + + /// Dispatches the track propositions request event with type `EventType.optimize` and source `EventSource.requestContent` and given proposition interactions data. + /// + /// No event is dispatched if the input xdm data is `nil`. + /// + /// - Parameter xdmData: A dictionary containing XDM data for the propositon interactions. + static func trackWithData(_ xdmData: [String: Any]?) { + guard let xdmData = xdmData else { + Log.debug(label: OptimizeConstants.LOG_TAG, + "Cannot send track propositions request event, the provided xdmData is nil.") + return + } + + let eventData: [String: Any] = [ + OptimizeConstants.EventDataKeys.REQUEST_TYPE: OptimizeConstants.EventDataValues.REQUEST_TYPE_TRACK, + OptimizeConstants.EventDataKeys.PROPOSITION_INTERACTIONS: xdmData + ] + + let event = Event(name: OptimizeConstants.EventNames.TRACK_PROPOSITIONS_REQUEST, + type: EventType.optimize, + source: EventSource.requestContent, + data: eventData) + + MobileCore.dispatch(event: event) + } +} diff --git a/Tests/AEPOptimizeTests/UnitTests/OfferTrackingTests.swift b/Tests/AEPOptimizeTests/UnitTests/OfferTrackingTests.swift index 1b11414..f95d5dc 100644 --- a/Tests/AEPOptimizeTests/UnitTests/OfferTrackingTests.swift +++ b/Tests/AEPOptimizeTests/UnitTests/OfferTrackingTests.swift @@ -31,7 +31,7 @@ extension OptimizePropositionTests { func testGenerateDisplayInteractionXdm_validProposition() throws { guard - let propositionData = PROPOSITION_VALID.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") @@ -80,7 +80,7 @@ extension OptimizePropositionTests { func testGenerateDisplayInteractionXdm_validPropositionFromTarget() throws { guard - let propositionData = PROPOSITION_VALID_TARGET.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID_TARGET.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") @@ -136,7 +136,7 @@ extension OptimizePropositionTests { func testGenerateDisplayInteractionXdm_nilPropositionReference() throws { guard - let propositionData = PROPOSITION_VALID.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") @@ -161,7 +161,7 @@ extension OptimizePropositionTests { func testGenerateTapInteractionXdm_validProposition() throws { guard - let propositionData = PROPOSITION_VALID.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") @@ -208,7 +208,7 @@ extension OptimizePropositionTests { func testGenerateTapInteractionXdm_validPropositionFromTarget() throws { guard - let propositionData = PROPOSITION_VALID_TARGET.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID_TARGET.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") @@ -264,7 +264,7 @@ extension OptimizePropositionTests { func testGenerateTapInteractionXdm_nilPropositionReference() throws { guard - let propositionData = PROPOSITION_VALID.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") @@ -353,7 +353,7 @@ extension OptimizePropositionTests { } guard - let propositionData = PROPOSITION_VALID.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") @@ -452,7 +452,7 @@ extension OptimizePropositionTests { } guard - let propositionData = PROPOSITION_VALID_TARGET.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID_TARGET.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") @@ -481,7 +481,7 @@ extension OptimizePropositionTests { } guard - let propositionData = PROPOSITION_VALID.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") @@ -566,7 +566,7 @@ extension OptimizePropositionTests { } guard - let propositionData = PROPOSITION_VALID.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") @@ -666,7 +666,7 @@ extension OptimizePropositionTests { } guard - let propositionData = PROPOSITION_VALID_TARGET.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID_TARGET.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") @@ -695,7 +695,7 @@ extension OptimizePropositionTests { } guard - let propositionData = PROPOSITION_VALID.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") diff --git a/Tests/AEPOptimizeTests/UnitTests/OptimizePropositionTests.swift b/Tests/AEPOptimizeTests/UnitTests/OptimizePropositionTests.swift index 76595fb..94a83ec 100644 --- a/Tests/AEPOptimizeTests/UnitTests/OptimizePropositionTests.swift +++ b/Tests/AEPOptimizeTests/UnitTests/OptimizePropositionTests.swift @@ -15,126 +15,10 @@ import XCTest class OptimizePropositionTests: XCTestCase { - let PROPOSITION_VALID = -""" -{\ - "id": "de03ac85-802a-4331-a905-a57053164d35",\ - "items": [{\ - "id": "xcore:personalized-offer:1111111111111111",\ - "etag": "10",\ - "schema": "https://ns.adobe.com/experience/offer-management/content-component-html",\ - "data": {\ - "id": "xcore:personalized-offer:1111111111111111",\ - "format": "text/html",\ - "content": "

This is a HTML content

"\ - }\ - }],\ - "placement": {\ - "etag": "1",\ - "id": "xcore:offer-placement:1111111111111111"\ - },\ - "activity": {\ - "etag": "8",\ - "id": "xcore:offer-activity:1111111111111111"\ - },\ - "scope": "eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="\ -} -""" - - let PROPOSITION_VALID_WITH_LANGUAGE_AND_CHARACTERISTICS = - """ - {\ - "id": "de03ac85-802a-4331-a905-a57053164d35",\ - "items": [{\ - "id": "xcore:personalized-offer:1111111111111111",\ - "etag": "10",\ - "schema": "https://ns.adobe.com/experience/offer-management/content-component-html",\ - "data": {\ - "id": "xcore:personalized-offer:1111111111111111",\ - "format": "text/html",\ - "content": "

This is a HTML content

",\ - "language": [\ - "en-us"\ - ],\ - "characteristics": {\ - "mobile": "true"\ - }\ - }\ - }],\ - "placement": {\ - "etag": "1",\ - "id": "xcore:offer-placement:1111111111111111"\ - },\ - "activity": {\ - "etag": "8",\ - "id": "xcore:offer-activity:1111111111111111"\ - },\ - "scope": "eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="\ - } - """ - - let PROPOSITION_VALID_TARGET = -""" -{\ - "id": "AT:eyJhY3Rpdml0eUlkIjoiMTI1NTg5IiwiZXhwZXJpZW5jZUlkIjoiMCJ9",\ - "items": [{\ - "id": "246315",\ - "schema": "https://ns.adobe.com/personalization/json-content-item",\ - "data": {\ - "id": "246315",\ - "format": "application/json", - "content": {\ - "device": "mobile"\ - }\ - }\ - }],\ - "scope": "myMbox",\ - "scopeDetails": {\ - "decisionProvider": "TGT",\ - "activity": {\ - "id": "125589"\ - },\ - "experience": {\ - "id": "0"\ - },\ - "strategies": [\ - {\ - "algorithmID": "0",\ - "trafficType": "0"\ - }\ - ]\ - }\ -} -""" - - let PROPOSITION_INVALID = -""" -{\ - "items": [{\ - "id": "xcore:personalized-offer:1111111111111111",\ - "etag": "10",\ - "schema": "https://ns.adobe.com/experience/offer-management/content-component-html",\ - "data": {\ - "id": "xcore:personalized-offer:1111111111111111",\ - "format": "text/html",\ - "content": "

This is a HTML content

"\ - }\ - }],\ - "placement": {\ - "etag": "1",\ - "id": "xcore:offer-placement:1111111111111111"\ - },\ - "activity": {\ - "etag": "8",\ - "id": "xcore:offer-activity:1111111111111111"\ - },\ - "scope": "eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="\ -} -""" func testProposition_valid() throws { // Decode guard - let propositionData = PROPOSITION_VALID.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") @@ -166,7 +50,7 @@ class OptimizePropositionTests: XCTestCase { func testProposition_validFromTarget() throws { // Decode guard - let propositionData = PROPOSITION_VALID_TARGET.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID_TARGET.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") @@ -208,7 +92,7 @@ class OptimizePropositionTests: XCTestCase { } func testProposition_invalid() throws { - guard let propositionData = PROPOSITION_INVALID.data(using: .utf8) else { + guard let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_INVALID.data(using: .utf8) else { XCTFail("Proposition json data should be valid.") return } @@ -217,7 +101,7 @@ class OptimizePropositionTests: XCTestCase { } func testInitFromData() throws { - guard let propositionData = PROPOSITION_VALID_WITH_LANGUAGE_AND_CHARACTERISTICS.data(using: .utf8) else { + guard let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID_WITH_LANGUAGE_AND_CHARACTERISTICS.data(using: .utf8) else { XCTFail("Proposition json data should be valid.") return } diff --git a/Tests/AEPOptimizeTests/UnitTests/OptimizePropositionTrackingTests.swift b/Tests/AEPOptimizeTests/UnitTests/OptimizePropositionTrackingTests.swift index 6502836..aebd56e 100644 --- a/Tests/AEPOptimizeTests/UnitTests/OptimizePropositionTrackingTests.swift +++ b/Tests/AEPOptimizeTests/UnitTests/OptimizePropositionTrackingTests.swift @@ -17,7 +17,7 @@ extension OptimizePropositionTests { func testGenerateReferenceXdm_validProposition() throws { guard - let propositionData = PROPOSITION_VALID.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") @@ -39,7 +39,7 @@ extension OptimizePropositionTests { func testGenerateReferenceXdm_validPropositionFromTarget() throws { guard - let propositionData = PROPOSITION_VALID_TARGET.data(using: .utf8), + let propositionData = Propositions_OptimizeTests.shared.PROPOSITION_VALID_TARGET.data(using: .utf8), let proposition = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData) else { XCTFail("Proposition should be valid.") diff --git a/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift b/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift index ea7e87c..2674ecf 100644 --- a/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift +++ b/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift @@ -517,6 +517,182 @@ class OptimizePublicAPITests: XCTestCase { // verify wait(for: [expectation], timeout: 1) } + + func testTrackDisplayedPropositions() { + let expectation = XCTestExpectation(description: "trackDisplayedPropositions should dispatch an event with expected data.") + expectation.assertForOverFulfill = true + + let testScopeDetails: [String: Any] = [ + "decisionProvider": "TGT", + "activity": [ + "id": "125589" + ], + "experience": [ + "id": "0" + ], + "strategies": [ + [ + "algorithmID": "0", + "trafficType": "0" + ] + ] + ] + + let testEventData: [String: Any] = [ + "requesttype": "trackpropositions", + "propositioninteractions": [ + "eventType": "decisioning.propositionDisplay", + "decisioning": [ + "propositions": [ + [ + "id": "de03ac85-802a-4331-a905-a57053164d35", + "scope": "eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==", + "scopeDetails": [:], + "items": [ + [ + "id": "xcore:personalized-offer:1111111111111111" + ] + ] + ], + [ + "id": "AT:eyJhY3Rpdml0eUlkIjoiMTI1NTg5IiwiZXhwZXJpZW5jZUlkIjoiMCJ9", + "scope": "eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==", + "scopeDetails": testScopeDetails, + "items": [ + [ + "id": "xcore:personalized-offer:1111111111111111" + ] + ] + ] + ] + ] + ] + ] + + let testEvent = Event(name: "Optimize Track Propositions Request", + type: "com.adobe.eventType.optimize", + source: "com.adobe.eventSource.requestContent", + data: testEventData) + + EventHub.shared.getExtensionContainer(MockExtension.self)?.registerListener(type: testEvent.type, + source: testEvent.source) { event in + XCTAssertEqual(event.name, testEvent.name) + XCTAssertNotNil(event.data) + XCTAssertEqual("trackpropositions", event.data?["requesttype"] as? String) + + let propositioninteractions = event.data?["propositioninteractions"] as? [String: Any] + XCTAssertEqual("decisioning.propositionDisplay", propositioninteractions?["eventType"] as? String) + + let experience = propositioninteractions?["_experience"] as? [String: Any] + let decisioning = experience?["decisioning"] as? [String: Any] + let propositionEventType = decisioning?["propositionEventType"] as? [String: Any] + XCTAssertEqual(1, propositionEventType?["display"] as? Int) + let propositionDetailsArray = decisioning?["propositions"] as? [[String: Any]] + + guard let propositionDetailsData1 = propositionDetailsArray?[0] else { + XCTFail("Propositions array should contain proposition details data.") + return + } + XCTAssertEqual("de03ac85-802a-4331-a905-a57053164d35", propositionDetailsData1["id"] as? String) + XCTAssertEqual("eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==", propositionDetailsData1["scope"] as? String) + + // To fix, once https://jira.corp.adobe.com/browse/CSMO-12405 is resolved. + let scopeDetails = propositionDetailsData1["scopeDetails"] as? [String: Any] ?? [:] + XCTAssertTrue(scopeDetails.isEmpty) + + let items = propositionDetailsData1["items"] as? [[String: Any]] + XCTAssertEqual(1, items?.count) + + let item = items?[0] + XCTAssertEqual("xcore:personalized-offer:1111111111111111", item?["id"] as? String) + + guard let propositionDetailsData2 = propositionDetailsArray?[1] else { + XCTFail("Propositions array should contain proposition details data.") + return + } + XCTAssertEqual("AT:eyJhY3Rpdml0eUlkIjoiMTI1NTg5IiwiZXhwZXJpZW5jZUlkIjoiMCJ9", propositionDetailsData2["id"] as? String) + XCTAssertEqual("myMbox", propositionDetailsData2["scope"] as? String) + + let scopeDetails2 = propositionDetailsData2["scopeDetails"] as? [String: Any] ?? [:] + XCTAssertTrue(testScopeDetails == scopeDetails2) + + let items2 = propositionDetailsData2["items"] as? [[String: Any]] + XCTAssertEqual(1, items2?.count) + + let item2 = items2?[0] + XCTAssertEqual("246315", item2?["id"] as? String) + + expectation.fulfill() + } + + guard + let propositionData1 = Propositions_OptimizeTests.shared.PROPOSITION_VALID.data(using: .utf8), + let proposition1 = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData1), + let propositionData2 = Propositions_OptimizeTests.shared.PROPOSITION_VALID_TARGET.data(using: .utf8), + let proposition2 = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData2) + else { + XCTFail("Propositions should be valid.") + return + } + + Optimize.trackDisplayedPropositions(for: [proposition1, proposition2]) + wait(for: [expectation], timeout: 2) + + } + + func testGenerateInteractionXdm_multiplePropositions() throws { + guard + let propositionData1 = Propositions_OptimizeTests.shared.PROPOSITION_VALID.data(using: .utf8), + let proposition1 = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData1), + let propositionData2 = Propositions_OptimizeTests.shared.PROPOSITION_VALID_TARGET.data(using: .utf8), + let proposition2 = try? JSONDecoder().decode(OptimizeProposition.self, from: propositionData2) + else { + XCTFail("Proposition should be valid.") + return + } + + guard let propositionInteractionXdm = Optimize.generateInteractionXdm(for: [proposition1, proposition2], + for: OptimizeConstants.JsonValues.EE_EVENT_TYPE_PROPOSITION_DISPLAY) else { + XCTFail("Generated proposition interaction XDM should be valid.") + return + } + + let eventType = try XCTUnwrap(propositionInteractionXdm["eventType"] as? String) + XCTAssertEqual("decisioning.propositionDisplay", eventType) + + let experience = try XCTUnwrap(propositionInteractionXdm["_experience"] as? [String: Any]) + let decisioning = try XCTUnwrap(experience["decisioning"] as? [String: Any]) + let propositionEventType = try XCTUnwrap(decisioning["propositionEventType"] as? [String: Any]) + XCTAssertEqual(1, propositionEventType["display"] as? Int) + let propositionInteractionDetailsArray = try XCTUnwrap(decisioning["propositions"] as? [[String: Any]]) + XCTAssertEqual(2, propositionInteractionDetailsArray.count) + + let propositionInteractionDetails1 = try XCTUnwrap(propositionInteractionDetailsArray[0]) + XCTAssertEqual(proposition1.id, propositionInteractionDetails1["id"] as? String) + XCTAssertEqual(proposition1.scope, propositionInteractionDetails1["scope"] as? String) + + let scopeDetails = propositionInteractionDetails1["scopeDetails"] as? [String: Any] ?? [:] + XCTAssertTrue(scopeDetails == [:]) + + let items = try XCTUnwrap(propositionInteractionDetails1["items"] as? [[String: Any]]) + XCTAssertEqual(1, items.count) + + let item = items[0] + XCTAssertEqual("xcore:personalized-offer:1111111111111111", item["id"] as? String) + + let propositionInteractionDetails2 = try XCTUnwrap(propositionInteractionDetailsArray[1]) + XCTAssertEqual(proposition2.id, propositionInteractionDetails2["id"] as? String) + XCTAssertEqual(proposition2.scope, propositionInteractionDetails2["scope"] as? String) + + let scopeDetails2 = propositionInteractionDetails2["scopeDetails"] as? [String: Any] ?? [:] + XCTAssertTrue(proposition2.scopeDetails == scopeDetails2) + + let items2 = try XCTUnwrap(propositionInteractionDetails2["items"] as? [[String: Any]]) + XCTAssertEqual(1, items2.count) + + let item2 = items2[0] + XCTAssertEqual("246315", item2["id"] as? String) + } func testClearCachedPropositions() { // setup diff --git a/Tests/AEPOptimizeTests/UnitTests/Propositions+OptimizeTests.swift b/Tests/AEPOptimizeTests/UnitTests/Propositions+OptimizeTests.swift new file mode 100644 index 0000000..b7b14b1 --- /dev/null +++ b/Tests/AEPOptimizeTests/UnitTests/Propositions+OptimizeTests.swift @@ -0,0 +1,136 @@ +// Delete this line +/* + Copyright 2024 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + + +import Foundation + +class Propositions_OptimizeTests { + static let shared = Propositions_OptimizeTests() + + let PROPOSITION_VALID = + """ + {\ + "id": "de03ac85-802a-4331-a905-a57053164d35",\ + "items": [{\ + "id": "xcore:personalized-offer:1111111111111111",\ + "etag": "10",\ + "schema": "https://ns.adobe.com/experience/offer-management/content-component-html",\ + "data": {\ + "id": "xcore:personalized-offer:1111111111111111",\ + "format": "text/html",\ + "content": "

This is a HTML content

"\ + }\ + }],\ + "placement": {\ + "etag": "1",\ + "id": "xcore:offer-placement:1111111111111111"\ + },\ + "activity": {\ + "etag": "8",\ + "id": "xcore:offer-activity:1111111111111111"\ + },\ + "scope": "eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="\ + } + """ + + let PROPOSITION_VALID_WITH_LANGUAGE_AND_CHARACTERISTICS = + """ + {\ + "id": "de03ac85-802a-4331-a905-a57053164d35",\ + "items": [{\ + "id": "xcore:personalized-offer:1111111111111111",\ + "etag": "10",\ + "schema": "https://ns.adobe.com/experience/offer-management/content-component-html",\ + "data": {\ + "id": "xcore:personalized-offer:1111111111111111",\ + "format": "text/html",\ + "content": "

This is a HTML content

",\ + "language": [\ + "en-us"\ + ],\ + "characteristics": {\ + "mobile": "true"\ + }\ + }\ + }],\ + "placement": {\ + "etag": "1",\ + "id": "xcore:offer-placement:1111111111111111"\ + },\ + "activity": {\ + "etag": "8",\ + "id": "xcore:offer-activity:1111111111111111"\ + },\ + "scope": "eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="\ + } + """ + + let PROPOSITION_VALID_TARGET = + """ + {\ + "id": "AT:eyJhY3Rpdml0eUlkIjoiMTI1NTg5IiwiZXhwZXJpZW5jZUlkIjoiMCJ9",\ + "items": [{\ + "id": "246315",\ + "schema": "https://ns.adobe.com/personalization/json-content-item",\ + "data": {\ + "id": "246315",\ + "format": "application/json", + "content": {\ + "device": "mobile"\ + }\ + }\ + }],\ + "scope": "myMbox",\ + "scopeDetails": {\ + "decisionProvider": "TGT",\ + "activity": {\ + "id": "125589"\ + },\ + "experience": {\ + "id": "0"\ + },\ + "strategies": [\ + {\ + "algorithmID": "0",\ + "trafficType": "0"\ + }\ + ]\ + }\ + } + """ + + let PROPOSITION_INVALID = + """ + {\ + "items": [{\ + "id": "xcore:personalized-offer:1111111111111111",\ + "etag": "10",\ + "schema": "https://ns.adobe.com/experience/offer-management/content-component-html",\ + "data": {\ + "id": "xcore:personalized-offer:1111111111111111",\ + "format": "text/html",\ + "content": "

This is a HTML content

"\ + }\ + }],\ + "placement": {\ + "etag": "1",\ + "id": "xcore:offer-placement:1111111111111111"\ + },\ + "activity": {\ + "etag": "8",\ + "id": "xcore:offer-activity:1111111111111111"\ + },\ + "scope": "eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="\ + } + """ +} From 97adcf005564f6db82905278e5584c2fedf62833 Mon Sep 17 00:00:00 2001 From: akhiljain1907 Date: Thu, 29 Aug 2024 17:49:19 +0530 Subject: [PATCH 02/13] Synced with 5.0.2 branch | updated test cases. --- Sources/AEPOptimize/Optimize+PublicAPI.swift | 3 ++- Sources/AEPOptimize/Optimize.swift | 3 ++- Sources/AEPOptimize/OptimizeError.swift | 20 ++++++++--------- .../OptimizeFunctionalTests.swift | 22 +++++++++++-------- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/Sources/AEPOptimize/Optimize+PublicAPI.swift b/Sources/AEPOptimize/Optimize+PublicAPI.swift index 1b3c9b7..f649d43 100644 --- a/Sources/AEPOptimize/Optimize+PublicAPI.swift +++ b/Sources/AEPOptimize/Optimize+PublicAPI.swift @@ -61,7 +61,8 @@ public extension Optimize { status: 408, title: "Request Timeout", detail: "Update proposition request resulted in a timeout.", - aepError: AEPError.callbackTimeout) + aepError: AEPError.callbackTimeout + ) completion?(nil, timeoutError) return } diff --git a/Sources/AEPOptimize/Optimize.swift b/Sources/AEPOptimize/Optimize.swift index 4a3e7a5..a465046 100644 --- a/Sources/AEPOptimize/Optimize.swift +++ b/Sources/AEPOptimize/Optimize.swift @@ -255,7 +255,8 @@ public class Optimize: NSObject, Extension { status: 408, title: "Request Timeout", detail: "Update proposition request resulted in a timeout.", - aepError: AEPError.callbackTimeout) + aepError: AEPError.callbackTimeout + ) self.dispatch(event: event.createErrorResponseEvent(timeoutError)) self.eventsQueue.start() return diff --git a/Sources/AEPOptimize/OptimizeError.swift b/Sources/AEPOptimize/OptimizeError.swift index 4e65c16..abc311d 100644 --- a/Sources/AEPOptimize/OptimizeError.swift +++ b/Sources/AEPOptimize/OptimizeError.swift @@ -1,15 +1,15 @@ // Delete this line /* -Copyright 2024 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ + Copyright 2024 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ import AEPCore import Foundation diff --git a/Tests/AEPOptimizeTests/FunctionalTests/OptimizeFunctionalTests.swift b/Tests/AEPOptimizeTests/FunctionalTests/OptimizeFunctionalTests.swift index d7fe346..51d3611 100644 --- a/Tests/AEPOptimizeTests/FunctionalTests/OptimizeFunctionalTests.swift +++ b/Tests/AEPOptimizeTests/FunctionalTests/OptimizeFunctionalTests.swift @@ -1021,13 +1021,15 @@ class OptimizeFunctionalTests: XCTestCase { let expectation = XCTestExpectation(description: "Get propositions request should now dispatch response event after update completion.") mockRuntime.onEventDispatch = { event in - expectation.fulfill() + if event.responseID == getEvent.id { + expectation.fulfill() + } } - wait(for: [expectation], timeout: 10) - XCTAssertEqual(mockRuntime.dispatchedEvents.count, 1) + wait(for: [expectation], timeout: 12) + XCTAssertEqual(mockRuntime.dispatchedEvents.count, 2) - let dispatchedEvent = mockRuntime.dispatchedEvents.first + let dispatchedEvent = mockRuntime.secondEvent XCTAssertEqual(dispatchedEvent?.type, "com.adobe.eventType.optimize") XCTAssertEqual(dispatchedEvent?.source, "com.adobe.eventSource.responseContent") @@ -1119,7 +1121,9 @@ class OptimizeFunctionalTests: XCTestCase { let expectationGet = XCTestExpectation(description: "Get event should be queued.") mockRuntime.onEventDispatch = { event in - expectationGet.fulfill() + if event.responseID == getEvent.id { + expectationGet.fulfill() + } } /// Dispatch the get event Immediately. @@ -1130,13 +1134,13 @@ class OptimizeFunctionalTests: XCTestCase { self?.mockRuntime.simulateComingEvents(optimizeContentComplete) }) - wait(for: [expectationGet], timeout: 10) + wait(for: [expectationGet], timeout: 12) /// Verify that the get proposition event was queued & is the last event to be executed. - XCTAssertEqual(mockRuntime.firstEvent?.type, "com.adobe.eventType.optimize") - XCTAssertEqual(mockRuntime.firstEvent?.source, "com.adobe.eventSource.responseContent") + XCTAssertEqual(mockRuntime.secondEvent?.type, "com.adobe.eventType.optimize") + XCTAssertEqual(mockRuntime.secondEvent?.source, "com.adobe.eventSource.responseContent") - guard let propositionsDictionary: [DecisionScope: OptimizeProposition] = mockRuntime.firstEvent?.getTypedData(for: "propositions") else { + guard let propositionsDictionary: [DecisionScope: OptimizeProposition] = mockRuntime.secondEvent?.getTypedData(for: "propositions") else { XCTFail("Propositions dictionary should be valid.") return } From 2cf399755992fe5b809486ca9eef4943a627f875 Mon Sep 17 00:00:00 2001 From: akhiljain1907 Date: Thu, 29 Aug 2024 17:51:29 +0530 Subject: [PATCH 03/13] minor formatting change --- Sources/AEPOptimize/Optimize.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AEPOptimize/Optimize.swift b/Sources/AEPOptimize/Optimize.swift index a465046..225b9bc 100644 --- a/Sources/AEPOptimize/Optimize.swift +++ b/Sources/AEPOptimize/Optimize.swift @@ -3,7 +3,7 @@ This file is licensed to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the License for the specific language From 54f37f8a285d55c66055e741f1b16d935ed8873f Mon Sep 17 00:00:00 2001 From: akhiljain1907 Date: Thu, 29 Aug 2024 17:53:58 +0530 Subject: [PATCH 04/13] minor code formatting --- Sources/AEPOptimize/Optimize.swift | 2 +- Sources/AEPOptimize/OptimizeError.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/AEPOptimize/Optimize.swift b/Sources/AEPOptimize/Optimize.swift index 225b9bc..a465046 100644 --- a/Sources/AEPOptimize/Optimize.swift +++ b/Sources/AEPOptimize/Optimize.swift @@ -3,7 +3,7 @@ This file is licensed to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the License for the specific language diff --git a/Sources/AEPOptimize/OptimizeError.swift b/Sources/AEPOptimize/OptimizeError.swift index abc311d..8905cef 100644 --- a/Sources/AEPOptimize/OptimizeError.swift +++ b/Sources/AEPOptimize/OptimizeError.swift @@ -4,7 +4,7 @@ This file is licensed to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the License for the specific language From 4600bf83f11d27928d2a18eea417551721352208 Mon Sep 17 00:00:00 2001 From: akhiljain1907 Date: Thu, 29 Aug 2024 19:12:23 +0530 Subject: [PATCH 05/13] added test cases for updateProposition api callback and verified for timeout --- Sources/AEPOptimize/Optimize+PublicAPI.swift | 1 + Sources/AEPOptimize/OptimizeError.swift | 10 ++-- .../OptimizeIntegrationTests.swift | 60 +++++++++++++++++++ .../UnitTests/OptimizePublicAPITests.swift | 20 +++++++ 4 files changed, 86 insertions(+), 5 deletions(-) diff --git a/Sources/AEPOptimize/Optimize+PublicAPI.swift b/Sources/AEPOptimize/Optimize+PublicAPI.swift index f649d43..31a0229 100644 --- a/Sources/AEPOptimize/Optimize+PublicAPI.swift +++ b/Sources/AEPOptimize/Optimize+PublicAPI.swift @@ -32,6 +32,7 @@ public extension Optimize { guard !flattenedDecisionScopes.isEmpty else { Log.warning(label: OptimizeConstants.LOG_TAG, "Cannot update propositions, provided decision scopes array is empty or has invalid items.") + completion?(nil,nil) return } diff --git a/Sources/AEPOptimize/OptimizeError.swift b/Sources/AEPOptimize/OptimizeError.swift index 8905cef..8122f4d 100644 --- a/Sources/AEPOptimize/OptimizeError.swift +++ b/Sources/AEPOptimize/OptimizeError.swift @@ -17,11 +17,11 @@ import Foundation /// AEPOptimizeError class used to create AEPOptimizeError from error details received from Experience Edge. @objc(AEPOptimizeError) public class AEPOptimizeError: NSObject { - let type: String? - let status: Int? - let title: String? - let detail: String? - var aepError = AEPError.none + public let type: String? + public let status: Int? + public let title: String? + public let detail: String? + public var aepError = AEPError.none public init(type: String?, status: Int?, title: String?, detail: String?, aepError: AEPError? = nil) { self.type = type diff --git a/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift b/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift index e2c5d4f..9d73e25 100644 --- a/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift +++ b/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift @@ -54,6 +54,66 @@ class OptimizeIntegrationTests: XCTestCase { } wait(for: [initExpectation], timeout: 1) } + + func testUpdatePropositions_timeoutError() { + // setup + let timeoutResponse = HTTPURLResponse(url: URL(string: "https://edge.adobedc.net/ee/v1/interact?configId=configId&requestId=requestId")!, statusCode: 408, httpVersion: nil, headerFields: nil) + let responseString = """ + {\ + "requestId":"FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF",\ + "handle":[],\ + "errors":[\ + {\ + "type":"EXEG-0201-503",\ + "status":408,\ + "title":"Request timed out. Please try again."\ + }\ + ]\ + } + """ + + // mock edge response + let requestExpectation = XCTestExpectation(description: "Request for mock service response.") + let mockNetworkService = TestableNetworkService() + ServiceProvider.shared.networkService = mockNetworkService + mockNetworkService.mock { request in + if request.url.absoluteString.contains("edge.adobedc.net/ee/v1/interact?configId=configId") { + requestExpectation.fulfill() + return (data: responseString.data(using: .utf8), response: timeoutResponse, error: nil) + } + return (data: nil, response: timeoutResponse, error: nil) + } + + // init extensions + initExtensionsAndWait() + + // update configuration + MobileCore.updateConfigurationWith(configDict: [ + "experienceCloud.org": "orgid", + "experienceCloud.server": "test.com", + "global.privacy": "optedin", + "edge.configId": "configId"]) + + let decisionScope = DecisionScope(activityId: "xcore:offer-activity:1111111111111111", + placementId: "xcore:offer-placement:1111111111111111") + + let exp = expectation(description: "The Update Proposition should result in a time out") + + // test + Optimize.updatePropositions(for: [decisionScope], withXdm: nil) {scope, error in + // verify + XCTAssertNil(scope) + XCTAssertNotNil(error) + XCTAssertTrue(error?.status == 408) + XCTAssertTrue(error?.aepError == .callbackTimeout) + XCTAssertTrue(error?.title == "Request Timeout") + XCTAssertTrue(error?.detail == "Update proposition request resulted in a timeout.") + exp.fulfill() + } + + wait(for: [exp, requestExpectation], timeout: 12) + } + func testUpdatePropositions_validEdgeRequest() { // setup diff --git a/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift b/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift index 2674ecf..acb1aa0 100644 --- a/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift +++ b/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift @@ -72,6 +72,26 @@ class OptimizePublicAPITests: XCTestCase { // verify wait(for: [expectation], timeout: 1) } + + func testUpdatePropositions_updateTimeout() { + // setup + let expectation = XCTestExpectation(description: "The Update proposition request should time out.") + expectation.assertForOverFulfill = true + + let decisionScope = DecisionScope(name: "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==") + + // test + Optimize.updatePropositions(for: [decisionScope], withXdm: nil) { scope, error in + XCTAssert(error?.status == 408) + XCTAssert(error?.title == "Request Timeout") + XCTAssert(error?.detail == "Update proposition request resulted in a timeout.") + XCTAssert(error?.aepError == .callbackTimeout) + expectation.fulfill() + } + + // verify + wait(for: [expectation], timeout: 11) + } func testUpdatePropositions_validDecisionScopeWithXdmAndData() { // setup From 6022eb513d8fe567d08d03dcf70114664c85c498 Mon Sep 17 00:00:00 2001 From: akhiljain1907 Date: Fri, 30 Aug 2024 13:14:09 +0530 Subject: [PATCH 06/13] minor change and formatting --- Sources/AEPOptimize/Optimize+PublicAPI.swift | 2 +- .../IntegrationTests/OptimizeIntegrationTests.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/AEPOptimize/Optimize+PublicAPI.swift b/Sources/AEPOptimize/Optimize+PublicAPI.swift index 31a0229..9a3cdb1 100644 --- a/Sources/AEPOptimize/Optimize+PublicAPI.swift +++ b/Sources/AEPOptimize/Optimize+PublicAPI.swift @@ -32,7 +32,7 @@ public extension Optimize { guard !flattenedDecisionScopes.isEmpty else { Log.warning(label: OptimizeConstants.LOG_TAG, "Cannot update propositions, provided decision scopes array is empty or has invalid items.") - completion?(nil,nil) + completion?(nil, nil) return } diff --git a/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift b/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift index 9d73e25..7b1a79c 100644 --- a/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift +++ b/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift @@ -64,13 +64,13 @@ class OptimizeIntegrationTests: XCTestCase { "handle":[],\ "errors":[\ {\ - "type":"EXEG-0201-503",\ + "type":"EXEG-0201-408",\ "status":408,\ "title":"Request timed out. Please try again."\ }\ ]\ } - """ + """ // mock edge response let requestExpectation = XCTestExpectation(description: "Request for mock service response.") From 6392765f844816f1cc1b0e9c36022d376c1573c5 Mon Sep 17 00:00:00 2001 From: akhiljain1907 Date: Mon, 2 Sep 2024 16:03:00 +0530 Subject: [PATCH 07/13] mapped aepError values in OptimizeError class on basis of status and changed default value of aepError to unexpected --- Sources/AEPOptimize/OptimizeError.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/AEPOptimize/OptimizeError.swift b/Sources/AEPOptimize/OptimizeError.swift index 8122f4d..312f0a2 100644 --- a/Sources/AEPOptimize/OptimizeError.swift +++ b/Sources/AEPOptimize/OptimizeError.swift @@ -21,7 +21,7 @@ public class AEPOptimizeError: NSObject { public let status: Int? public let title: String? public let detail: String? - public var aepError = AEPError.none + public var aepError = AEPError.unexpected public init(type: String?, status: Int?, title: String?, detail: String?, aepError: AEPError? = nil) { self.type = type @@ -31,7 +31,13 @@ public class AEPOptimizeError: NSObject { if let aepError { self.aepError = aepError } else { - if status == 400 { + if status == 408 { + self.aepError = .callbackTimeout + } else if status == 400 || status == 403 || status == 404 { + self.aepError = .invalidRequest + } else if status == 429 || status == 500 || status == 503 { + self.aepError = .serverError + } else if status == 502 || status == 504 { self.aepError = .networkError } } From 87bd3d17f95c45d105eb0664c9b4c1a6efa5963f Mon Sep 17 00:00:00 2001 From: akhiljain1907 Date: Mon, 9 Sep 2024 20:13:25 +0530 Subject: [PATCH 08/13] updated updateProposition api to return [DecisionScope: OptimizeProposition]? instead of [DecisionScope]? in callback and code review changes --- Sources/AEPOptimize/Optimize+PublicAPI.swift | 10 +++++----- Sources/AEPOptimize/Optimize.swift | 8 ++++---- Sources/AEPOptimize/OptimizeConstants.swift | 11 +++++++++++ Sources/AEPOptimize/OptimizeError.swift | 2 +- .../FunctionalTests/OptimizeFunctionalTests.swift | 14 ++++++++++++++ .../OptimizeIntegrationTests.swift | 11 ++++++----- .../UnitTests/OptimizePublicAPITests.swift | 9 +++++---- 7 files changed, 46 insertions(+), 19 deletions(-) diff --git a/Sources/AEPOptimize/Optimize+PublicAPI.swift b/Sources/AEPOptimize/Optimize+PublicAPI.swift index 9a3cdb1..124cb75 100644 --- a/Sources/AEPOptimize/Optimize+PublicAPI.swift +++ b/Sources/AEPOptimize/Optimize+PublicAPI.swift @@ -24,7 +24,7 @@ public extension Optimize { /// - Parameter data: Additional free-form data to be sent in the personalization request. /// - Parameter completion: Optional completion handler invoked with list of successful decision scopes and errors, if any @objc(updatePropositions:withXdm:andData:completion:) - static func updatePropositions(for decisionScopes: [DecisionScope], withXdm xdm: [String: Any]?, andData data: [String: Any]? = nil, _ completion: (([DecisionScope]?, AEPOptimizeError?) -> Void)? = nil) { + static func updatePropositions(for decisionScopes: [DecisionScope], withXdm xdm: [String: Any]?, andData data: [String: Any]? = nil, _ completion: (([DecisionScope: OptimizeProposition]?, AEPOptimizeError?) -> Void)? = nil) { let flattenedDecisionScopes = decisionScopes .filter { $0.isValid } .compactMap { $0.asDictionary() } @@ -59,15 +59,15 @@ public extension Optimize { guard let responseEvent = responseEvent else { let timeoutError = AEPOptimizeError( type: nil, - status: 408, - title: "Request Timeout", - detail: "Update proposition request resulted in a timeout.", + status: OptimizeConstants.ErrorData.Timeout.STATUS, + title: OptimizeConstants.ErrorData.Timeout.TITLE, + detail: OptimizeConstants.ErrorData.Timeout.DETAIL, aepError: AEPError.callbackTimeout ) completion?(nil, timeoutError) return } - let result = responseEvent.data?[OptimizeConstants.EventDataKeys.DECISION_SCOPES] as? [DecisionScope] + let result = responseEvent.data?[OptimizeConstants.EventDataKeys.PROPOSITIONS] as? [DecisionScope: OptimizeProposition] let error = responseEvent.data?[OptimizeConstants.EventDataKeys.RESPONSE_ERROR] as? AEPOptimizeError completion?(result, error) } diff --git a/Sources/AEPOptimize/Optimize.swift b/Sources/AEPOptimize/Optimize.swift index a465046..f69a6c6 100644 --- a/Sources/AEPOptimize/Optimize.swift +++ b/Sources/AEPOptimize/Optimize.swift @@ -252,9 +252,9 @@ public class Optimize: NSObject, Extension { self.propositionsInProgress.removeAll() let timeoutError = AEPOptimizeError( type: nil, - status: 408, - title: "Request Timeout", - detail: "Update proposition request resulted in a timeout.", + status: OptimizeConstants.ErrorData.Timeout.STATUS, + title: OptimizeConstants.ErrorData.Timeout.TITLE, + detail: OptimizeConstants.ErrorData.Timeout.DETAIL, aepError: AEPError.callbackTimeout ) self.dispatch(event: event.createErrorResponseEvent(timeoutError)) @@ -268,7 +268,7 @@ public class Optimize: NSObject, Extension { type: EventType.optimize, source: EventSource.responseContent, data: [ - OptimizeConstants.EventDataKeys.DECISION_SCOPES: Array(self.propositionsInProgress.keys) + OptimizeConstants.EventDataKeys.PROPOSITIONS: self.propositionsInProgress ] ) self.dispatch(event: responseEventToSend) diff --git a/Sources/AEPOptimize/OptimizeConstants.swift b/Sources/AEPOptimize/OptimizeConstants.swift index 0968d2a..52b98a0 100644 --- a/Sources/AEPOptimize/OptimizeConstants.swift +++ b/Sources/AEPOptimize/OptimizeConstants.swift @@ -69,7 +69,10 @@ enum OptimizeConstants { static let PAYLOAD = "payload" enum ErrorKeys { static let TYPE = "type" + static let STATUS = "status" + static let TITLE = "title" static let DETAIL = "detail" + static let REPORT = "report" } } @@ -119,4 +122,12 @@ enum OptimizeConstants { static let SCHEMA_OFFER_IMAGE = "https://ns.adobe.com/experience/offer-management/content-component-imagelink" static let SCHEMA_OFFER_TEXT = "https://ns.adobe.com/experience/offer-management/content-component-text" } + + enum ErrorData { + enum Timeout { + static let STATUS = 408 + static let TITLE = "Request Timeout" + static let DETAIL = "Update/Get proposition request resulted in a timeout." + } + } } diff --git a/Sources/AEPOptimize/OptimizeError.swift b/Sources/AEPOptimize/OptimizeError.swift index 312f0a2..a669808 100644 --- a/Sources/AEPOptimize/OptimizeError.swift +++ b/Sources/AEPOptimize/OptimizeError.swift @@ -16,7 +16,7 @@ import Foundation /// AEPOptimizeError class used to create AEPOptimizeError from error details received from Experience Edge. @objc(AEPOptimizeError) -public class AEPOptimizeError: NSObject { +public class AEPOptimizeError: NSObject, Error { public let type: String? public let status: Int? public let title: String? diff --git a/Tests/AEPOptimizeTests/FunctionalTests/OptimizeFunctionalTests.swift b/Tests/AEPOptimizeTests/FunctionalTests/OptimizeFunctionalTests.swift index 51d3611..7bd4690 100644 --- a/Tests/AEPOptimizeTests/FunctionalTests/OptimizeFunctionalTests.swift +++ b/Tests/AEPOptimizeTests/FunctionalTests/OptimizeFunctionalTests.swift @@ -1029,6 +1029,13 @@ class OptimizeFunctionalTests: XCTestCase { wait(for: [expectation], timeout: 12) XCTAssertEqual(mockRuntime.dispatchedEvents.count, 2) + // first event will be a timeout error response event as in functional test we don't recieve response from edge + let firstEvent = mockRuntime.firstEvent + XCTAssertEqual(firstEvent?.type, "com.adobe.eventType.optimize") + XCTAssertEqual(firstEvent?.source, "com.adobe.eventSource.responseContent") + XCTAssertNotNil(firstEvent?.data?[OptimizeConstants.EventDataKeys.RESPONSE_ERROR]) + XCTAssertNil(firstEvent?.data?[OptimizeConstants.EventDataKeys.DECISION_SCOPES]) + let dispatchedEvent = mockRuntime.secondEvent XCTAssertEqual(dispatchedEvent?.type, "com.adobe.eventType.optimize") XCTAssertEqual(dispatchedEvent?.source, "com.adobe.eventSource.responseContent") @@ -1136,6 +1143,13 @@ class OptimizeFunctionalTests: XCTestCase { wait(for: [expectationGet], timeout: 12) + // first event will be a timeout error response event as in functional test we don't recieve response from edge + let firstEvent = mockRuntime.firstEvent + XCTAssertEqual(firstEvent?.type, "com.adobe.eventType.optimize") + XCTAssertEqual(firstEvent?.source, "com.adobe.eventSource.responseContent") + XCTAssertNotNil(firstEvent?.data?[OptimizeConstants.EventDataKeys.RESPONSE_ERROR]) + XCTAssertNil(firstEvent?.data?[OptimizeConstants.EventDataKeys.DECISION_SCOPES]) + /// Verify that the get proposition event was queued & is the last event to be executed. XCTAssertEqual(mockRuntime.secondEvent?.type, "com.adobe.eventType.optimize") XCTAssertEqual(mockRuntime.secondEvent?.source, "com.adobe.eventSource.responseContent") diff --git a/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift b/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift index 7b1a79c..560a2a8 100644 --- a/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift +++ b/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift @@ -100,14 +100,14 @@ class OptimizeIntegrationTests: XCTestCase { let exp = expectation(description: "The Update Proposition should result in a time out") // test - Optimize.updatePropositions(for: [decisionScope], withXdm: nil) {scope, error in + Optimize.updatePropositions(for: [decisionScope], withXdm: nil) {propositions, error in // verify - XCTAssertNil(scope) + XCTAssertNil(propositions) XCTAssertNotNil(error) XCTAssertTrue(error?.status == 408) XCTAssertTrue(error?.aepError == .callbackTimeout) XCTAssertTrue(error?.title == "Request Timeout") - XCTAssertTrue(error?.detail == "Update proposition request resulted in a timeout.") + XCTAssertTrue(error?.detail == "Update/Get proposition request resulted in a timeout.") exp.fulfill() } @@ -165,8 +165,9 @@ class OptimizeIntegrationTests: XCTestCase { placementId: "xcore:offer-placement:1111111111111111") // update propositions - Optimize.updatePropositions(for: [decisionScope], withXdm: nil){ (scope,error) in - XCTAssertNotNil(scope) + Optimize.updatePropositions(for: [decisionScope], withXdm: nil){ (propositions,error) in + XCTAssertNotNil(propositions) + XCTAssertNil(error) requestExpectation.fulfill() } diff --git a/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift b/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift index acb1aa0..6a2da43 100644 --- a/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift +++ b/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift @@ -81,10 +81,11 @@ class OptimizePublicAPITests: XCTestCase { let decisionScope = DecisionScope(name: "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==") // test - Optimize.updatePropositions(for: [decisionScope], withXdm: nil) { scope, error in - XCTAssert(error?.status == 408) - XCTAssert(error?.title == "Request Timeout") - XCTAssert(error?.detail == "Update proposition request resulted in a timeout.") + Optimize.updatePropositions(for: [decisionScope], withXdm: nil) { propositions, error in + XCTAssert(error?.status == OptimizeConstants.ErrorData.Timeout.STATUS) + XCTAssert(error?.aepError == .callbackTimeout) + XCTAssert(error?.title == OptimizeConstants.ErrorData.Timeout.TITLE) + XCTAssert(error?.detail == OptimizeConstants.ErrorData.Timeout.DETAIL) XCTAssert(error?.aepError == .callbackTimeout) expectation.fulfill() } From 3345a879b282265127ccc971d62fe5af1a04c209 Mon Sep 17 00:00:00 2001 From: akhiljain1907 Date: Tue, 10 Sep 2024 22:43:42 +0530 Subject: [PATCH 09/13] removed createErrorResponseEvent(_ error: AEPError) as now we will have AEPOptimizeError as argument and minor changes --- Sources/AEPOptimize/Event+Optimize.swift | 12 ------------ Sources/AEPOptimize/Optimize+PublicAPI.swift | 17 ++++++++++++----- Sources/AEPOptimize/Optimize.swift | 18 ++++++++++++++++-- Sources/AEPOptimize/OptimizeConstants.swift | 6 ++++++ .../OptimizeFunctionalTests.swift | 3 ++- .../OptimizeIntegrationTests.swift | 12 ++++++++---- .../UnitTests/Event+OptimizeTests.swift | 15 +++++++++++++-- .../UnitTests/OptimizePublicAPITests.swift | 14 +++++++++----- 8 files changed, 66 insertions(+), 31 deletions(-) diff --git a/Sources/AEPOptimize/Event+Optimize.swift b/Sources/AEPOptimize/Event+Optimize.swift index 0d7a492..901870f 100644 --- a/Sources/AEPOptimize/Event+Optimize.swift +++ b/Sources/AEPOptimize/Event+Optimize.swift @@ -57,18 +57,6 @@ extension Event { return try? JSONDecoder().decode(T.self, from: jsonData) } - /// Creates a response event with specified AEPError type added in the Event data. - /// - Parameter error: type of AEPError - /// - Returns: error response Event - func createErrorResponseEvent(_ error: AEPError) -> Event { - createResponseEvent(name: OptimizeConstants.EventNames.OPTIMIZE_RESPONSE, - type: EventType.optimize, - source: EventSource.responseContent, - data: [ - OptimizeConstants.EventDataKeys.RESPONSE_ERROR: error - ]) - } - /// Creates a response event with specified AEPOptimizeError type added in the Event data. /// - Parameter error: type of AEPOptimizeError /// - Returns: error response Event diff --git a/Sources/AEPOptimize/Optimize+PublicAPI.swift b/Sources/AEPOptimize/Optimize+PublicAPI.swift index 124cb75..f6b69e1 100644 --- a/Sources/AEPOptimize/Optimize+PublicAPI.swift +++ b/Sources/AEPOptimize/Optimize+PublicAPI.swift @@ -22,9 +22,9 @@ public extension Optimize { /// - Parameter decisionScopes: An array of decision scopes. /// - Parameter xdm: Additional XDM-formatted data to be sent in the personalization request. /// - Parameter data: Additional free-form data to be sent in the personalization request. - /// - Parameter completion: Optional completion handler invoked with list of successful decision scopes and errors, if any + /// - Parameter completion: Optional completion handler invoked with map of successful decision scopes to propositions and errors, if any @objc(updatePropositions:withXdm:andData:completion:) - static func updatePropositions(for decisionScopes: [DecisionScope], withXdm xdm: [String: Any]?, andData data: [String: Any]? = nil, _ completion: (([DecisionScope: OptimizeProposition]?, AEPOptimizeError?) -> Void)? = nil) { + static func updatePropositions(for decisionScopes: [DecisionScope], withXdm xdm: [String: Any]?, andData data: [String: Any]? = nil, _ completion: (([DecisionScope: OptimizeProposition]?, Error?) -> Void)? = nil) { let flattenedDecisionScopes = decisionScopes .filter { $0.isValid } .compactMap { $0.asDictionary() } @@ -32,7 +32,14 @@ public extension Optimize { guard !flattenedDecisionScopes.isEmpty else { Log.warning(label: OptimizeConstants.LOG_TAG, "Cannot update propositions, provided decision scopes array is empty or has invalid items.") - completion?(nil, nil) + let aepOptimizeError = AEPOptimizeError( + type: nil, + status: OptimizeConstants.ErrorData.InvalidRequest.STATUS, + title: OptimizeConstants.ErrorData.InvalidRequest.TITLE, + detail: OptimizeConstants.ErrorData.InvalidRequest.DETAIL, + aepError: AEPError.invalidRequest + ) + completion?(nil, aepOptimizeError) return } @@ -109,8 +116,8 @@ public extension Optimize { return } - if let error = responseEvent.data?[OptimizeConstants.EventDataKeys.RESPONSE_ERROR] as? AEPError { - completion(nil, error) + if let error = responseEvent.data?[OptimizeConstants.EventDataKeys.RESPONSE_ERROR] as? AEPOptimizeError { + completion(nil, error.aepError) return } diff --git a/Sources/AEPOptimize/Optimize.swift b/Sources/AEPOptimize/Optimize.swift index f69a6c6..062c59b 100644 --- a/Sources/AEPOptimize/Optimize.swift +++ b/Sources/AEPOptimize/Optimize.swift @@ -140,7 +140,14 @@ public class Optimize: NSObject, Extension { !eventDecisionScopes.isEmpty else { Log.debug(label: OptimizeConstants.LOG_TAG, "Decision scopes, in event data, is either not present or empty.") - dispatch(event: event.createErrorResponseEvent(AEPError.invalidRequest)) + let aepOptimizeError = AEPOptimizeError( + type: nil, + status: OptimizeConstants.ErrorData.InvalidRequest.STATUS, + title: OptimizeConstants.ErrorData.InvalidRequest.TITLE, + detail: OptimizeConstants.ErrorData.InvalidRequest.DETAIL, + aepError: AEPError.invalidRequest + ) + dispatch(event: event.createErrorResponseEvent(aepOptimizeError)) return } /// Fetch propositions and check if all of the decision scopes are present in the cache @@ -409,7 +416,14 @@ public class Optimize: NSObject, Extension { !decisionScopes.isEmpty else { Log.debug(label: OptimizeConstants.LOG_TAG, "Decision scopes, in event data, is either not present or empty.") - dispatch(event: event.createErrorResponseEvent(AEPError.invalidRequest)) + let aepOptimizeError = AEPOptimizeError( + type: nil, + status: OptimizeConstants.ErrorData.InvalidRequest.STATUS, + title: OptimizeConstants.ErrorData.InvalidRequest.TITLE, + detail: OptimizeConstants.ErrorData.InvalidRequest.DETAIL, + aepError: AEPError.invalidRequest + ) + dispatch(event: event.createErrorResponseEvent(aepOptimizeError)) return } diff --git a/Sources/AEPOptimize/OptimizeConstants.swift b/Sources/AEPOptimize/OptimizeConstants.swift index 52b98a0..b013a41 100644 --- a/Sources/AEPOptimize/OptimizeConstants.swift +++ b/Sources/AEPOptimize/OptimizeConstants.swift @@ -129,5 +129,11 @@ enum OptimizeConstants { static let TITLE = "Request Timeout" static let DETAIL = "Update/Get proposition request resulted in a timeout." } + + enum InvalidRequest { + static let STATUS = 400 + static let TITLE = "Invalid Request" + static let DETAIL = "Decision scopes, in event data, is either not present or empty." + } } } diff --git a/Tests/AEPOptimizeTests/FunctionalTests/OptimizeFunctionalTests.swift b/Tests/AEPOptimizeTests/FunctionalTests/OptimizeFunctionalTests.swift index 7bd4690..f94724c 100644 --- a/Tests/AEPOptimizeTests/FunctionalTests/OptimizeFunctionalTests.swift +++ b/Tests/AEPOptimizeTests/FunctionalTests/OptimizeFunctionalTests.swift @@ -1413,9 +1413,10 @@ class OptimizeFunctionalTests: XCTestCase { XCTAssertEqual(1, mockRuntime.dispatchedEvents.count) let dispatchedEvent = mockRuntime.dispatchedEvents.first + let errorData = dispatchedEvent?.data?["responseerror"] as? AEPOptimizeError XCTAssertEqual("com.adobe.eventType.optimize", dispatchedEvent?.type) XCTAssertEqual("com.adobe.eventSource.responseContent", dispatchedEvent?.source) - XCTAssertEqual(AEPError.invalidRequest, dispatchedEvent?.data?["responseerror"] as! AEPError) + XCTAssertEqual(AEPError.invalidRequest, errorData?.aepError) XCTAssertNil(dispatchedEvent?.data?["propositions"]) } diff --git a/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift b/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift index 560a2a8..8301823 100644 --- a/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift +++ b/Tests/AEPOptimizeTests/IntegrationTests/OptimizeIntegrationTests.swift @@ -102,12 +102,16 @@ class OptimizeIntegrationTests: XCTestCase { // test Optimize.updatePropositions(for: [decisionScope], withXdm: nil) {propositions, error in // verify + guard let error = error as? AEPOptimizeError else { + XCTFail("Type mismatch in error received for Update Propositions") + return + } XCTAssertNil(propositions) XCTAssertNotNil(error) - XCTAssertTrue(error?.status == 408) - XCTAssertTrue(error?.aepError == .callbackTimeout) - XCTAssertTrue(error?.title == "Request Timeout") - XCTAssertTrue(error?.detail == "Update/Get proposition request resulted in a timeout.") + XCTAssertTrue(error.status == 408) + XCTAssertTrue(error.aepError == .callbackTimeout) + XCTAssertTrue(error.title == "Request Timeout") + XCTAssertTrue(error.detail == "Update/Get proposition request resulted in a timeout.") exp.fulfill() } diff --git a/Tests/AEPOptimizeTests/UnitTests/Event+OptimizeTests.swift b/Tests/AEPOptimizeTests/UnitTests/Event+OptimizeTests.swift index 5f4a8de..2d3adcf 100644 --- a/Tests/AEPOptimizeTests/UnitTests/Event+OptimizeTests.swift +++ b/Tests/AEPOptimizeTests/UnitTests/Event+OptimizeTests.swift @@ -117,13 +117,24 @@ class Event_OptimizeTests: XCTestCase { source: "com.adobe.eventSource.requestContent", data: nil) - let errorResponseEvent = testEvent.createErrorResponseEvent(AEPError.invalidRequest) + let aepOptimizeError = AEPOptimizeError( + type: nil, + status: OptimizeConstants.ErrorData.InvalidRequest.STATUS, + title: OptimizeConstants.ErrorData.InvalidRequest.TITLE, + detail: OptimizeConstants.ErrorData.InvalidRequest.DETAIL, + aepError: AEPError.invalidRequest + ) + let errorResponseEvent = testEvent.createErrorResponseEvent(aepOptimizeError) + + let errorData = errorResponseEvent.data?["responseerror"] as? AEPOptimizeError XCTAssertEqual("Optimize Response", errorResponseEvent.name) XCTAssertEqual("com.adobe.eventType.optimize", errorResponseEvent.type) XCTAssertEqual("com.adobe.eventSource.responseContent", errorResponseEvent.source) XCTAssertNotNil(errorResponseEvent.data) XCTAssertEqual(1, errorResponseEvent.data?.count) - XCTAssertEqual(AEPError.invalidRequest, errorResponseEvent.data?["responseerror"] as! AEPError) + XCTAssertEqual(400, errorData?.status) + XCTAssertEqual("Invalid Request", errorData?.title) + XCTAssertEqual(AEPError.invalidRequest, errorData?.aepError) } } diff --git a/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift b/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift index 6a2da43..135b20b 100644 --- a/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift +++ b/Tests/AEPOptimizeTests/UnitTests/OptimizePublicAPITests.swift @@ -82,11 +82,15 @@ class OptimizePublicAPITests: XCTestCase { // test Optimize.updatePropositions(for: [decisionScope], withXdm: nil) { propositions, error in - XCTAssert(error?.status == OptimizeConstants.ErrorData.Timeout.STATUS) - XCTAssert(error?.aepError == .callbackTimeout) - XCTAssert(error?.title == OptimizeConstants.ErrorData.Timeout.TITLE) - XCTAssert(error?.detail == OptimizeConstants.ErrorData.Timeout.DETAIL) - XCTAssert(error?.aepError == .callbackTimeout) + guard let error = error as? AEPOptimizeError else { + XCTFail("Type mismatch in error received for Update Propositions") + return + } + XCTAssert(error.status == OptimizeConstants.ErrorData.Timeout.STATUS) + XCTAssert(error.aepError == .callbackTimeout) + XCTAssert(error.title == OptimizeConstants.ErrorData.Timeout.TITLE) + XCTAssert(error.detail == OptimizeConstants.ErrorData.Timeout.DETAIL) + XCTAssert(error.aepError == .callbackTimeout) expectation.fulfill() } From c7dd5d6f9b75fba073a68535d2604341a394cb89 Mon Sep 17 00:00:00 2001 From: akhiljain1907 Date: Mon, 16 Sep 2024 14:48:53 +0530 Subject: [PATCH 10/13] added updateProposition api without callback for backward compatibility and code optimization --- Sources/AEPOptimize/Optimize+PublicAPI.swift | 27 +++++++------- Sources/AEPOptimize/Optimize.swift | 24 ++----------- Sources/AEPOptimize/OptimizeConstants.swift | 13 +++++++ Sources/AEPOptimize/OptimizeError.swift | 36 ++++++++++++++++--- .../UnitTests/Event+OptimizeTests.swift | 8 +---- 5 files changed, 62 insertions(+), 46 deletions(-) diff --git a/Sources/AEPOptimize/Optimize+PublicAPI.swift b/Sources/AEPOptimize/Optimize+PublicAPI.swift index f6b69e1..eaf4c1d 100644 --- a/Sources/AEPOptimize/Optimize+PublicAPI.swift +++ b/Sources/AEPOptimize/Optimize+PublicAPI.swift @@ -16,6 +16,17 @@ import Foundation @objc public extension Optimize { + /// This API dispatches an Event for the Edge network extension to fetch decision propositions for the provided decision scopes from the decisioning Services enabled behind Experience Edge. + /// + /// The returned decision propositions are cached in memory in the Optimize SDK extension and can be retrieved using `getPropositions(for:_:)` API. + /// - Parameter decisionScopes: An array of decision scopes. + /// - Parameter xdm: Additional XDM-formatted data to be sent in the personalization request. + /// - Parameter data: Additional free-form data to be sent in the personalization request. + @objc(updatePropositions:withXdm:andData:) + static func updatePropositions(for decisionScopes: [DecisionScope], withXdm xdm: [String: Any]?, andData data: [String: Any]? = nil) { + updatePropositions(for: decisionScopes, withXdm: xdm, andData: data, nil) + } + /// This API dispatches an Event for the Edge network extension to fetch decision propositions for the provided decision scopes from the decisioning Services enabled behind Experience Edge. /// /// The returned decision propositions are cached in memory in the Optimize SDK extension and can be retrieved using `getPropositions(for:_:)` API. @@ -32,13 +43,7 @@ public extension Optimize { guard !flattenedDecisionScopes.isEmpty else { Log.warning(label: OptimizeConstants.LOG_TAG, "Cannot update propositions, provided decision scopes array is empty or has invalid items.") - let aepOptimizeError = AEPOptimizeError( - type: nil, - status: OptimizeConstants.ErrorData.InvalidRequest.STATUS, - title: OptimizeConstants.ErrorData.InvalidRequest.TITLE, - detail: OptimizeConstants.ErrorData.InvalidRequest.DETAIL, - aepError: AEPError.invalidRequest - ) + let aepOptimizeError = AEPOptimizeError.createAEPOptimizInvalidRequestError() completion?(nil, aepOptimizeError) return } @@ -64,13 +69,7 @@ public extension Optimize { data: eventData) MobileCore.dispatch(event: event, timeout: 10) { responseEvent in guard let responseEvent = responseEvent else { - let timeoutError = AEPOptimizeError( - type: nil, - status: OptimizeConstants.ErrorData.Timeout.STATUS, - title: OptimizeConstants.ErrorData.Timeout.TITLE, - detail: OptimizeConstants.ErrorData.Timeout.DETAIL, - aepError: AEPError.callbackTimeout - ) + let timeoutError = AEPOptimizeError.createAEPOptimizeTimeoutError() completion?(nil, timeoutError) return } diff --git a/Sources/AEPOptimize/Optimize.swift b/Sources/AEPOptimize/Optimize.swift index 062c59b..2d0129e 100644 --- a/Sources/AEPOptimize/Optimize.swift +++ b/Sources/AEPOptimize/Optimize.swift @@ -140,13 +140,7 @@ public class Optimize: NSObject, Extension { !eventDecisionScopes.isEmpty else { Log.debug(label: OptimizeConstants.LOG_TAG, "Decision scopes, in event data, is either not present or empty.") - let aepOptimizeError = AEPOptimizeError( - type: nil, - status: OptimizeConstants.ErrorData.InvalidRequest.STATUS, - title: OptimizeConstants.ErrorData.InvalidRequest.TITLE, - detail: OptimizeConstants.ErrorData.InvalidRequest.DETAIL, - aepError: AEPError.invalidRequest - ) + let aepOptimizeError = AEPOptimizeError.createAEPOptimizInvalidRequestError() dispatch(event: event.createErrorResponseEvent(aepOptimizeError)) return } @@ -257,13 +251,7 @@ public class Optimize: NSObject, Extension { // response event failed or timed out, remove this event's ID from the requested event IDs dictionary, dispatch an error response event and kick-off queue. self.updateRequestEventIdsInProgress.removeValue(forKey: edgeEvent.id.uuidString) self.propositionsInProgress.removeAll() - let timeoutError = AEPOptimizeError( - type: nil, - status: OptimizeConstants.ErrorData.Timeout.STATUS, - title: OptimizeConstants.ErrorData.Timeout.TITLE, - detail: OptimizeConstants.ErrorData.Timeout.DETAIL, - aepError: AEPError.callbackTimeout - ) + let timeoutError = AEPOptimizeError.createAEPOptimizeTimeoutError() self.dispatch(event: event.createErrorResponseEvent(timeoutError)) self.eventsQueue.start() return @@ -416,13 +404,7 @@ public class Optimize: NSObject, Extension { !decisionScopes.isEmpty else { Log.debug(label: OptimizeConstants.LOG_TAG, "Decision scopes, in event data, is either not present or empty.") - let aepOptimizeError = AEPOptimizeError( - type: nil, - status: OptimizeConstants.ErrorData.InvalidRequest.STATUS, - title: OptimizeConstants.ErrorData.InvalidRequest.TITLE, - detail: OptimizeConstants.ErrorData.InvalidRequest.DETAIL, - aepError: AEPError.invalidRequest - ) + let aepOptimizeError = AEPOptimizeError.createAEPOptimizInvalidRequestError() dispatch(event: event.createErrorResponseEvent(aepOptimizeError)) return } diff --git a/Sources/AEPOptimize/OptimizeConstants.swift b/Sources/AEPOptimize/OptimizeConstants.swift index b013a41..f532179 100644 --- a/Sources/AEPOptimize/OptimizeConstants.swift +++ b/Sources/AEPOptimize/OptimizeConstants.swift @@ -136,4 +136,17 @@ enum OptimizeConstants { static let DETAIL = "Decision scopes, in event data, is either not present or empty." } } + + enum HTTPResponseCodes: Int { + case success = 200 + case noContent = 204 + case multiStatus = 207 + case invalidRequest = 400 + case clientTimeout = 408 + case tooManyRequests = 429 + case internalServerError = 500 + case badGateway = 502 + case serviceUnavailable = 503 + case gatewayTimeout = 504 + } } diff --git a/Sources/AEPOptimize/OptimizeError.swift b/Sources/AEPOptimize/OptimizeError.swift index a669808..510d83d 100644 --- a/Sources/AEPOptimize/OptimizeError.swift +++ b/Sources/AEPOptimize/OptimizeError.swift @@ -17,6 +17,7 @@ import Foundation /// AEPOptimizeError class used to create AEPOptimizeError from error details received from Experience Edge. @objc(AEPOptimizeError) public class AEPOptimizeError: NSObject, Error { + typealias HTTPResponseCodes = OptimizeConstants.HTTPResponseCodes public let type: String? public let status: Int? public let title: String? @@ -31,15 +32,42 @@ public class AEPOptimizeError: NSObject, Error { if let aepError { self.aepError = aepError } else { - if status == 408 { + // map edge error response to AEPError on the basis of status (if received) + guard let status else { + return + } + if status == HTTPResponseCodes.clientTimeout.rawValue { self.aepError = .callbackTimeout - } else if status == 400 || status == 403 || status == 404 { + } else if (400...499).contains(status) && status != HTTPResponseCodes.tooManyRequests.rawValue { self.aepError = .invalidRequest - } else if status == 429 || status == 500 || status == 503 { + } else if status == HTTPResponseCodes.tooManyRequests.rawValue, + status == HTTPResponseCodes.internalServerError.rawValue, + status == HTTPResponseCodes.serviceUnavailable.rawValue { self.aepError = .serverError - } else if status == 502 || status == 504 { + } else if status == HTTPResponseCodes.badGateway.rawValue, + status == HTTPResponseCodes.gatewayTimeout.rawValue { self.aepError = .networkError } } } + + static func createAEPOptimizeTimeoutError() -> AEPOptimizeError { + return AEPOptimizeError( + type: nil, + status: OptimizeConstants.ErrorData.Timeout.STATUS, + title: OptimizeConstants.ErrorData.Timeout.TITLE, + detail: OptimizeConstants.ErrorData.Timeout.DETAIL, + aepError: AEPError.callbackTimeout + ) + } + + static func createAEPOptimizInvalidRequestError() -> AEPOptimizeError { + return AEPOptimizeError( + type: nil, + status: OptimizeConstants.ErrorData.InvalidRequest.STATUS, + title: OptimizeConstants.ErrorData.InvalidRequest.TITLE, + detail: OptimizeConstants.ErrorData.InvalidRequest.DETAIL, + aepError: AEPError.invalidRequest + ) + } } diff --git a/Tests/AEPOptimizeTests/UnitTests/Event+OptimizeTests.swift b/Tests/AEPOptimizeTests/UnitTests/Event+OptimizeTests.swift index 2d3adcf..0363c30 100644 --- a/Tests/AEPOptimizeTests/UnitTests/Event+OptimizeTests.swift +++ b/Tests/AEPOptimizeTests/UnitTests/Event+OptimizeTests.swift @@ -117,13 +117,7 @@ class Event_OptimizeTests: XCTestCase { source: "com.adobe.eventSource.requestContent", data: nil) - let aepOptimizeError = AEPOptimizeError( - type: nil, - status: OptimizeConstants.ErrorData.InvalidRequest.STATUS, - title: OptimizeConstants.ErrorData.InvalidRequest.TITLE, - detail: OptimizeConstants.ErrorData.InvalidRequest.DETAIL, - aepError: AEPError.invalidRequest - ) + let aepOptimizeError = AEPOptimizeError.createAEPOptimizInvalidRequestError() let errorResponseEvent = testEvent.createErrorResponseEvent(aepOptimizeError) From 13293fc063aede99a510ec36da36fc3f7b9e2f2a Mon Sep 17 00:00:00 2001 From: akhiljain1907 Date: Mon, 16 Sep 2024 15:38:45 +0530 Subject: [PATCH 11/13] minor formatting change for code optimization --- Sources/AEPOptimize/OptimizeError.swift | 26 ++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Sources/AEPOptimize/OptimizeError.swift b/Sources/AEPOptimize/OptimizeError.swift index 510d83d..93e02b8 100644 --- a/Sources/AEPOptimize/OptimizeError.swift +++ b/Sources/AEPOptimize/OptimizeError.swift @@ -24,6 +24,17 @@ public class AEPOptimizeError: NSObject, Error { public let detail: String? public var aepError = AEPError.unexpected + private let serverErrors = [ + HTTPResponseCodes.tooManyRequests.rawValue, + HTTPResponseCodes.internalServerError.rawValue, + HTTPResponseCodes.serviceUnavailable.rawValue + ] + + private let networkError = [ + HTTPResponseCodes.badGateway.rawValue, + HTTPResponseCodes.gatewayTimeout.rawValue + ] + public init(type: String?, status: Int?, title: String?, detail: String?, aepError: AEPError? = nil) { self.type = type self.status = status @@ -38,21 +49,18 @@ public class AEPOptimizeError: NSObject, Error { } if status == HTTPResponseCodes.clientTimeout.rawValue { self.aepError = .callbackTimeout - } else if (400...499).contains(status) && status != HTTPResponseCodes.tooManyRequests.rawValue { - self.aepError = .invalidRequest - } else if status == HTTPResponseCodes.tooManyRequests.rawValue, - status == HTTPResponseCodes.internalServerError.rawValue, - status == HTTPResponseCodes.serviceUnavailable.rawValue { + } else if serverErrors.contains(status) { self.aepError = .serverError - } else if status == HTTPResponseCodes.badGateway.rawValue, - status == HTTPResponseCodes.gatewayTimeout.rawValue { + } else if networkError.contains(status) { self.aepError = .networkError + } else if (400...499).contains(status) { + self.aepError = .invalidRequest } } } static func createAEPOptimizeTimeoutError() -> AEPOptimizeError { - return AEPOptimizeError( + AEPOptimizeError( type: nil, status: OptimizeConstants.ErrorData.Timeout.STATUS, title: OptimizeConstants.ErrorData.Timeout.TITLE, @@ -62,7 +70,7 @@ public class AEPOptimizeError: NSObject, Error { } static func createAEPOptimizInvalidRequestError() -> AEPOptimizeError { - return AEPOptimizeError( + AEPOptimizeError( type: nil, status: OptimizeConstants.ErrorData.InvalidRequest.STATUS, title: OptimizeConstants.ErrorData.InvalidRequest.TITLE, From ebd0f3211d23b541eb4b31331a670a422a3c8d45 Mon Sep 17 00:00:00 2001 From: akhiljain1907 Date: Mon, 16 Sep 2024 15:50:00 +0530 Subject: [PATCH 12/13] Minor formatting --- Sources/AEPOptimize/OptimizeError.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AEPOptimize/OptimizeError.swift b/Sources/AEPOptimize/OptimizeError.swift index 93e02b8..5ba39af 100644 --- a/Sources/AEPOptimize/OptimizeError.swift +++ b/Sources/AEPOptimize/OptimizeError.swift @@ -53,7 +53,7 @@ public class AEPOptimizeError: NSObject, Error { self.aepError = .serverError } else if networkError.contains(status) { self.aepError = .networkError - } else if (400...499).contains(status) { + } else if (400 ... 499).contains(status) { self.aepError = .invalidRequest } } From 58e84f2a8b76fc5d452b1969150927d5cf654cf1 Mon Sep 17 00:00:00 2001 From: akhiljain1907 Date: Wed, 25 Sep 2024 16:22:15 +0530 Subject: [PATCH 13/13] formatting fix --- Sources/AEPOptimize/Optimize+Tracking.swift | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Sources/AEPOptimize/Optimize+Tracking.swift b/Sources/AEPOptimize/Optimize+Tracking.swift index a1418e6..23f43a4 100644 --- a/Sources/AEPOptimize/Optimize+Tracking.swift +++ b/Sources/AEPOptimize/Optimize+Tracking.swift @@ -1,15 +1,14 @@ -// Delete this line /* -Copyright 2024 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 + Copyright 2024 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ import AEPCore import AEPServices import Foundation