From e4750879377f3729095acabf60000cb3eda19922 Mon Sep 17 00:00:00 2001 From: Kevin Lind <40409666+kevinlind@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:24:33 -0700 Subject: [PATCH 1/9] Set parent ID to Edge response events (resolves #327) (#361) * Code changes for chaining parent event to network response events * Add functional tests for event chaining * Update method comments, rename parameter to 'parentRequestEvent' * Rename variable 'requestParentEvent' to 'parentRequestEvent' --- Podfile.lock | 2 +- .../NetworkResponseHandler.swift | 75 ++++---- .../FunctionalTests/Edge+PublicAPITests.swift | 56 ++++++ ...etworkResponseHandlerFunctionalTests.swift | 168 ++++++++++++++++-- .../NetworkResponseHandlerTests.swift | 6 +- 5 files changed, 250 insertions(+), 57 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index 338ed96c..5c0296de 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -52,4 +52,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: a88b954489e72716ffa14542a2ff5d27dfbda663 -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 diff --git a/Sources/EdgeNetworkHandlers/NetworkResponseHandler.swift b/Sources/EdgeNetworkHandlers/NetworkResponseHandler.swift index 4d6ca253..70e29241 100644 --- a/Sources/EdgeNetworkHandlers/NetworkResponseHandler.swift +++ b/Sources/EdgeNetworkHandlers/NetworkResponseHandler.swift @@ -24,7 +24,7 @@ class NetworkResponseHandler { private var updateLocationHint: (String, _ ttlSeconds: TimeInterval) -> Void // the order of the request events matter for matching them with the response events - private var sentEventsWaitingResponse = ThreadSafeDictionary() + private var sentEventsWaitingResponse = ThreadSafeDictionary() /// Date of the last generic identity reset request event, for more info see `shouldIgnoreStorePayload` private var lastResetDate = Atomic(Date(timeIntervalSince1970: 0)) @@ -48,9 +48,7 @@ class NetworkResponseHandler { Log.warning(label: self.LOG_TAG, "Name collision for requestId \(requestId), events list is overwritten.") } - let uuids = batchedEvents.map { $0.id.uuidString } - let timestamps = batchedEvents.map { $0.timestamp } - self.sentEventsWaitingResponse[requestId] = zip(uuids, timestamps).map { ($0, $1) } + self.sentEventsWaitingResponse[requestId] = batchedEvents } } @@ -69,15 +67,15 @@ class NetworkResponseHandler { func removeWaitingEvents(requestId: String) -> [String]? { guard !requestId.isEmpty else { return nil } - return sentEventsWaitingResponse.removeValue(forKey: requestId)?.map({$0.uuid}) + return sentEventsWaitingResponse.removeValue(forKey: requestId)?.map({$0.id.uuidString}) } /// Returns the list of unique event ids associated with the provided requestId or empty if not found. /// - Parameter requestId: batch request id - /// - Returns: the list of unique event ids associated with the requestId or nil if none found - func getWaitingEvents(requestId: String) -> [String]? { + /// - Returns: the list of events associated with the requestId or nil if none found + func getWaitingEvents(requestId: String) -> [Event]? { guard !requestId.isEmpty else { return nil } - return sentEventsWaitingResponse[requestId]?.map({$0.uuid}) + return sentEventsWaitingResponse[requestId] } /// Sets the last reset date @@ -150,7 +148,7 @@ class NetworkResponseHandler { Log.trace(label: LOG_TAG, "processEventHandles - Processing \(unwrappedEventHandles.count) event handle(s) for request id: \(requestId)") for eventHandle in unwrappedEventHandles { - let requestEventId = extractRequestEventId(forEventIndex: eventHandle.eventIndex, requestId: requestId) + let requestEvent = extractRequestEvent(forEventIndex: eventHandle.eventIndex, requestId: requestId) if ignoreStorePayloads { Log.debug(label: LOG_TAG, "Identities were reset recently, ignoring state:store payload for request with id: \(requestId)") } else { @@ -166,44 +164,43 @@ class NetworkResponseHandler { guard let eventHandleAsDictionary = eventHandle.asDictionary() else { continue } dispatchResponseEvent(handleAsDictionary: eventHandleAsDictionary, requestId: requestId, - requestEventId: requestEventId, + parentRequestEvent: requestEvent, eventSource: eventHandle.type) - CompletionHandlersManager.shared.eventHandleReceived(forRequestEventId: requestEventId, eventHandle) + CompletionHandlersManager.shared.eventHandleReceived(forRequestEventId: requestEvent?.id.uuidString, eventHandle) } } - /// Extracts the request event identifiers paired with this event handle/error handle based on the index. If no matches are found or the event handle index is missing, - /// this method returns nil + /// Extracts the request event paired with this event handle/error handle based on the index. If no match is found or the event handle index is missing, this method returns nil. /// /// - Parameters: /// - forEventIndex: the `EdgeEventHandle`/ `EdgeEventError` event index /// - requestId: edge request id used to fetch the waiting events associated with it (if any) - /// - Returns: the request event unique identifier for which this event handle was received, nil if not found - private func extractRequestEventId(forEventIndex: Int?, requestId: String) -> String? { - guard let requestEventIdsList = getWaitingEvents(requestId: requestId) else { return nil } + /// - Returns: the request event for which this event handle was received, nil if not found + private func extractRequestEvent(forEventIndex: Int?, requestId: String) -> Event? { + guard let requestEventList = getWaitingEvents(requestId: requestId) else { return nil } // Note: ExEdge does not return eventIndex when there is only one event in the request. // The event handles and errors are associated to that request event, so defaulting to 0 here. let index = forEventIndex ?? 0 - guard index >= 0, index < requestEventIdsList.count else { + guard index >= 0, index < requestEventList.count else { return nil } - return requestEventIdsList[index] + return requestEventList[index] } /// Dispatches a response event with the provided event handle as `[String: Any]`, including the request event id and request identifier /// - Parameters: /// - handleAsDictionary: represents an `EdgeEventHandle` parsed as [String:Any] /// - requestId: the edge request identifier associated with this response - /// - requestEventId: the request event identifier for which this response event handle was received + /// - parentRequestEvent: the parent request event for which this response event handle was received /// - eventSource type of the `EdgeEventHandle` - private func dispatchResponseEvent(handleAsDictionary: [String: Any], requestId: String, requestEventId: String?, eventSource: String?) { + private func dispatchResponseEvent(handleAsDictionary: [String: Any], requestId: String, parentRequestEvent: Event?, eventSource: String?) { guard !handleAsDictionary.isEmpty else { return } - // set eventRequestId and edge requestId on the response event and dispatch data - let eventData = addEventAndRequestIdToDictionary(handleAsDictionary, requestId: requestId, requestEventId: requestEventId) - dispatchResponseEventWithData(eventData, requestId: requestId, isErrorResponseEvent: false, eventSource: eventSource) + // set eventRequestId and edge parent request ID on the response event and dispatch data + let eventData = addEventAndRequestIdToDictionary(handleAsDictionary, requestId: requestId, requestEventId: parentRequestEvent?.id.uuidString) + dispatchResponseEventWithData(eventData, parentRequestEvent: parentRequestEvent, isErrorResponseEvent: false, eventSource: eventSource) } /// Iterates over the provided `errorsArray` and dispatches a new error event to the Event Hub. @@ -224,13 +221,13 @@ class NetworkResponseHandler { if let errorAsDictionary = error.asDictionary() { logErrorMessage(errorAsDictionary, isError: true, requestId: requestId) - let requestEventId = extractRequestEventId(forEventIndex: error.eventIndex, requestId: requestId) + let requestEvent = extractRequestEvent(forEventIndex: error.eventIndex, requestId: requestId) // set eventRequestId and Edge requestId on the response event and dispatch data let eventData = addEventAndRequestIdToDictionary(errorAsDictionary, requestId: requestId, - requestEventId: requestEventId) + requestEventId: requestEvent?.id.uuidString) guard !eventData.isEmpty else { continue } - dispatchResponseEventWithData(eventData, requestId: requestId, isErrorResponseEvent: true, eventSource: nil) + dispatchResponseEventWithData(eventData, parentRequestEvent: requestEvent, isErrorResponseEvent: true, eventSource: nil) } } } @@ -253,13 +250,13 @@ class NetworkResponseHandler { if let warningsAsDictionary = warning.asDictionary() { logErrorMessage(warningsAsDictionary, isError: false, requestId: requestId) - let requestEventId = extractRequestEventId(forEventIndex: warning.eventIndex, requestId: requestId) + let requestEvent = extractRequestEvent(forEventIndex: warning.eventIndex, requestId: requestId) // set eventRequestId and Edge requestId on the response event and dispatch data let eventData = addEventAndRequestIdToDictionary(warningsAsDictionary, requestId: requestId, - requestEventId: requestEventId) + requestEventId: requestEvent?.id.uuidString) guard !eventData.isEmpty else { return } - dispatchResponseEventWithData(eventData, requestId: requestId, isErrorResponseEvent: true, eventSource: nil) + dispatchResponseEventWithData(eventData, parentRequestEvent: requestEvent, isErrorResponseEvent: true, eventSource: nil) } } } @@ -267,22 +264,32 @@ class NetworkResponseHandler { /// Dispatched a new event with the provided `eventData` as responseContent or as errorResponseContent based on the `isErrorResponseEvent` setting /// - Parameters: /// - eventData: Event data to be dispatched, should not be empty - /// - requestId: The request identifier associated with this response event, used for logging + /// - parentRequestEvent: The request parent event associated with this response event /// - isErrorResponseEvent: indicates if this should be dispatched as an error or regular response content event /// - eventSource: an optional `String` to be used as the event source. /// If `eventSource` is nil either Constants.EventSource.ERROR_RESPONSE_CONTENT or Constants.EventSource.RESPONSE_CONTENT will be used for the event source depending on `isErrorResponseEvent` - private func dispatchResponseEventWithData(_ eventData: [String: Any], requestId: String, isErrorResponseEvent: Bool, eventSource: String?) { + private func dispatchResponseEventWithData(_ eventData: [String: Any], parentRequestEvent: Event?, isErrorResponseEvent: Bool, eventSource: String?) { guard !eventData.isEmpty else { return } var source = isErrorResponseEvent ? EdgeConstants.EventSource.ERROR_RESPONSE_CONTENT : EventSource.responseContent if let eventSource = eventSource, !eventSource.isEmpty { source = eventSource } - let responseEvent = Event(name: isErrorResponseEvent ? - EdgeConstants.EventName.ERROR_RESPONSE_CONTENT : EdgeConstants.EventName.RESPONSE_CONTENT, + let eventName = isErrorResponseEvent ? EdgeConstants.EventName.ERROR_RESPONSE_CONTENT : EdgeConstants.EventName.RESPONSE_CONTENT + let responseEvent: Event + + if let parentRequestEvent = parentRequestEvent { + responseEvent = parentRequestEvent.createChainedEvent(name: eventName, + type: EventType.edge, + source: source, + data: eventData) + } else { + Log.debug(label: LOG_TAG, "dispatchResponseEventWithData - Parent Event is nil, dispatching response event without chained parent.") + responseEvent = Event(name: eventName, type: EventType.edge, source: source, data: eventData) + } MobileCore.dispatch(event: responseEvent) } @@ -365,7 +372,7 @@ class NetworkResponseHandler { /// - Returns: true if we should ignore store payload responses for `requestId` private func shouldIgnoreStorePayload(requestId: String) -> Bool { if let firstEvent = sentEventsWaitingResponse[requestId]?.first { - return firstEvent.date < lastResetDate.value + return firstEvent.timestamp < lastResetDate.value } return false diff --git a/Tests/FunctionalTests/Edge+PublicAPITests.swift b/Tests/FunctionalTests/Edge+PublicAPITests.swift index 29558ecc..1d09abff 100644 --- a/Tests/FunctionalTests/Edge+PublicAPITests.swift +++ b/Tests/FunctionalTests/Edge+PublicAPITests.swift @@ -137,4 +137,60 @@ class EdgePublicAPITests: FunctionalTestBase { // verify wait(for: [expectation], timeout: 1) } + + func testGetLocationHint_responseEventChainedToParentId() { + Edge.setLocationHint(FunctionalTestConst.OR2_LOC) + let expectation = XCTestExpectation(description: "Request Location Hint") + expectation.assertForOverFulfill = true + Edge.getLocationHint({ _, _ in + expectation.fulfill() + }) + + // verify + wait(for: [expectation], timeout: 1) + + let dispatchedRequests = getDispatchedEventsWith(type: EventType.edge, source: EventSource.requestIdentity) + XCTAssertEqual(1, dispatchedRequests.count) + + let dispatchedResponses = getDispatchedEventsWith(type: EventType.edge, source: EventSource.responseIdentity) + XCTAssertEqual(1, dispatchedResponses.count) + + XCTAssertEqual(dispatchedRequests[0].id, dispatchedResponses[0].parentID) + } + + func testSendEvent_responseEventsChainedToParentId() { + + // Response data with 1 handle, 1 error, and 1 warning response, all at event index 0 + let responseData: Data? = "\u{0000}{\"handle\":[{\"type\":\"state:store\",\"payload\":[{\"key\":\"s_ecid\",\"value\":\"MCMID|29068398647607325310376254630528178721\",\"maxAge\":15552000}]}],\"errors\":[{\"status\":2003,\"type\":\"personalization\",\"title\":\"Failed to process personalization event\"}],\"warnings\":[{\"type\":\"https://ns.adobe.com/aep/errors/EXEG-0204-200\",\"status\":98,\"title\":\"Some Informative stuff here\",\"report\":{\"cause\":{\"message\":\"Some Informative stuff here\",\"code\":202}}}]}\n".data(using: .utf8) + let responseConnection: HttpConnection = HttpConnection(data: responseData, + response: HTTPURLResponse(url: URL(string: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)!, + statusCode: 200, + httpVersion: nil, + headerFields: nil), + error: nil) + + setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) + + let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"]) + + setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 2) + + Edge.sendEvent(experienceEvent: experienceEvent) + assertNetworkRequestsCount() + + let dispatchedRequests = getDispatchedEventsWith(type: EventType.edge, source: EventSource.requestContent) + XCTAssertEqual(1, dispatchedRequests.count) + + let dispatchedHandleResponses = getDispatchedEventsWith(type: EventType.edge, source: "state:store") + XCTAssertEqual(1, dispatchedHandleResponses.count) + + let dispatchedErrorResponses = getDispatchedEventsWith(type: EventType.edge, source: EventSource.errorResponseContent) + XCTAssertEqual(2, dispatchedErrorResponses.count) + + XCTAssertEqual(dispatchedRequests[0].id, dispatchedHandleResponses[0].parentID) + XCTAssertEqual(dispatchedRequests[0].id, dispatchedErrorResponses[0].parentID) + XCTAssertEqual(dispatchedRequests[0].id, dispatchedErrorResponses[1].parentID) + } } diff --git a/Tests/FunctionalTests/NetworkResponseHandlerFunctionalTests.swift b/Tests/FunctionalTests/NetworkResponseHandlerFunctionalTests.swift index 9020e9d5..fe849431 100644 --- a/Tests/FunctionalTests/NetworkResponseHandlerFunctionalTests.swift +++ b/Tests/FunctionalTests/NetworkResponseHandlerFunctionalTests.swift @@ -80,6 +80,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }\n" + " ]\n" + " }" + networkResponseHandler.addWaitingEvents(requestId: "123", batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnError(jsonError: jsonError, requestId: "123") let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) XCTAssertEqual(1, dispatchEvents.count) @@ -90,11 +91,13 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } let flattenReceivedData: [String: Any] = flattenDictionary(dict: receivedData) - XCTAssertEqual(4, flattenReceivedData.count) + XCTAssertEqual(5, flattenReceivedData.count) XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0201-503", flattenReceivedData["type"] as? String) XCTAssertEqual(500, flattenReceivedData["status"] as? Int) XCTAssertEqual("Failed due to unrecoverable system error: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at path $.commerce.purchases", flattenReceivedData["title"] as? String) XCTAssertEqual("123", flattenReceivedData["requestId"] as? String) + XCTAssertEqual(event1.id.uuidString, flattenReceivedData["requestEventId"] as? String) + XCTAssertEqual(event1.id, dispatchEvents[0].parentID) // Parent ID chained to default event index 0 } func testProcessResponseOnError_WhenValidEventIndex_dispatchesPairedEvent() { @@ -108,7 +111,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " \"status\": 100,\n" + " \"type\": \"personalization\",\n" + " \"title\": \"Button color not found\",\n" + - " \"eventIndex\": 0\n" + + " \"eventIndex\": 1\n" + " }\n" + " ]\n" + " }" @@ -128,7 +131,8 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual(100, flattenReceivedData["status"] as? Int) XCTAssertEqual("Button color not found", flattenReceivedData["title"] as? String) XCTAssertEqual(requestId, flattenReceivedData["requestId"] as? String) - XCTAssertEqual(event1.id.uuidString, flattenReceivedData["requestEventId"] as? String) + XCTAssertEqual(event2.id.uuidString, flattenReceivedData["requestEventId"] as? String) + XCTAssertEqual(event2.id, dispatchEvents[0].parentID) // Parent ID chained to event with index 1 } func testProcessResponseOnError_WhenUnknownEventIndex_doesNotCrash() { @@ -162,6 +166,8 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual(100, flattenReceivedData["status"] as? Int) XCTAssertEqual("Button color not found", flattenReceivedData["title"] as? String) XCTAssertEqual(requestId, flattenReceivedData["requestId"] as? String) + + XCTAssertNil(dispatchEvents[0].parentID) // Parent ID not chained as no event at index 10 } func testProcessResponseOnError_WhenUnknownRequestId_doesNotCrash() { setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) @@ -194,6 +200,8 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual(100, flattenReceivedData["status"] as? Int) XCTAssertEqual("Button color not found", flattenReceivedData["title"] as? String) XCTAssertEqual("567", flattenReceivedData["requestId"] as? String) + + XCTAssertNil(dispatchEvents[0].parentID) // Parent ID not chained as request ID is unknown (does not match any waiting event list) } func testProcessResponseOnError_WhenTwoEventJsonError_dispatchesTwoEvents() { setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 2) @@ -215,6 +223,8 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }\n" + " ]\n" + " }" + + networkResponseHandler.addWaitingEvents(requestId: requestId, batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnError(jsonError: jsonError, requestId: requestId) let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) XCTAssertEqual(2, dispatchEvents.count) @@ -224,22 +234,26 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { return } let flattenReceivedData1: [String: Any] = flattenDictionary(dict: receivedData1) - XCTAssertEqual(4, flattenReceivedData1.count) + XCTAssertEqual(5, flattenReceivedData1.count) XCTAssertEqual(0, flattenReceivedData1["status"] as? Int) XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0201-503", flattenReceivedData1["type"] as? String) XCTAssertEqual("Failed due to unrecoverable system error: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at path $.commerce.purchases", flattenReceivedData1["title"] as? String) XCTAssertEqual(requestId, flattenReceivedData1["requestId"] as? String) + XCTAssertEqual(event1.id.uuidString, flattenReceivedData1["requestEventId"] as? String) + XCTAssertEqual(event1.id, dispatchEvents[0].parentID) // Event chained to event1 as default event index is 0 guard let receivedData2 = dispatchEvents[1].data else { XCTFail("Invalid event data for event 2") return } let flattenReceivedData2: [String: Any] = flattenDictionary(dict: receivedData2) - XCTAssertEqual(4, flattenReceivedData2.count) + XCTAssertEqual(5, flattenReceivedData2.count) XCTAssertEqual(2003, flattenReceivedData2["status"] as? Int) XCTAssertEqual("personalization", flattenReceivedData2["type"] as? String) XCTAssertEqual("Failed to process personalization event", flattenReceivedData2["title"] as? String) XCTAssertEqual(requestId, flattenReceivedData2["requestId"] as? String) + XCTAssertEqual(event1.id.uuidString, flattenReceivedData2["requestEventId"] as? String) + XCTAssertEqual(event1.id, dispatchEvents[1].parentID) // Event chained to event1 as default event index is 0 } // MARK: processResponseOnSuccess @@ -565,7 +579,9 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }],\n" + " \"errors\": []\n" + " }" - networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: "123") + + networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event1, event2]) + networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: "d81c93e5-7558-4996-a93c-489d550748b8") var dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: "state:store") dispatchEvents += getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: "identity:persist") @@ -576,24 +592,28 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { return } let flattenReceivedData1: [String: Any] = flattenDictionary(dict: receivedData1) - XCTAssertEqual(5, flattenReceivedData1.count) + XCTAssertEqual(6, flattenReceivedData1.count) XCTAssertEqual("state:store", flattenReceivedData1["type"] as? String) - XCTAssertEqual("123", flattenReceivedData1["requestId"] as? String) + XCTAssertEqual("d81c93e5-7558-4996-a93c-489d550748b8", flattenReceivedData1["requestId"] as? String) XCTAssertEqual("s_ecid", flattenReceivedData1["payload[0].key"] as? String) XCTAssertEqual("MCMID|29068398647607325310376254630528178721", flattenReceivedData1["payload[0].value"] as? String) XCTAssertEqual(15552000, flattenReceivedData1["payload[0].maxAge"] as? Int) - + XCTAssertEqual(event1.id.uuidString, flattenReceivedData1["requestEventId"] as? String) + XCTAssertEqual(event1.id, dispatchEvents[0].parentID) // Event chained to event1 as default event index is 0 + // verify event 2 guard let receivedData2 = dispatchEvents[1].data else { XCTFail("Invalid event data for event 2") return } let flattenReceivedData2: [String: Any] = flattenDictionary(dict: receivedData2) - XCTAssertEqual(4, flattenReceivedData2.count) + XCTAssertEqual(5, flattenReceivedData2.count) XCTAssertEqual("identity:persist", flattenReceivedData2["type"] as? String) - XCTAssertEqual("123", flattenReceivedData2["requestId"] as? String) + XCTAssertEqual("d81c93e5-7558-4996-a93c-489d550748b8", flattenReceivedData2["requestId"] as? String) XCTAssertEqual("29068398647607325310376254630528178721", flattenReceivedData2["payload[0].id"] as? String) XCTAssertEqual("ECID", flattenReceivedData2["payload[0].namespace.code"] as? String) + XCTAssertEqual(event1.id.uuidString, flattenReceivedData2["requestEventId"] as? String) + XCTAssertEqual(event1.id, dispatchEvents[1].parentID) // Event chained to event1 as default event index is 0 } func testProcessResponseOnSuccess_WhenEventHandleWithEventIndex_dispatchesEventWithRequestEventId() { @@ -643,6 +663,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual(15552000, flattenReceivedData1["payload[0].maxAge"] as? Int) XCTAssertEqual("123", flattenReceivedData1["requestId"] as? String) XCTAssertEqual(event1.id.uuidString, flattenReceivedData1["requestEventId"] as? String) + XCTAssertEqual(event1.id, dispatchEvents[0].parentID) // Event chained to event1 as default event index is 0 // verify event 2 guard let receivedData2 = dispatchEvents[1].data else { @@ -655,6 +676,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual("123612123812381", flattenReceivedData2["payload[0].id"] as? String) XCTAssertEqual("123", flattenReceivedData2["requestId"] as? String) XCTAssertEqual(event2.id.uuidString, flattenReceivedData2["requestEventId"] as? String) + XCTAssertEqual(event2.id, dispatchEvents[1].parentID) // Event chained to event2 as event index is 1 } func testProcessResponseOnSuccess_WhenEventHandleWithUnknownEventIndex_dispatchesUnpairedEvent() { @@ -689,6 +711,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual("pairedeventexample", flattenReceivedData1["type"] as? String) XCTAssertEqual("123", flattenReceivedData1["requestId"] as? String) XCTAssertEqual("123612123812381", flattenReceivedData1["payload[0].id"] as? String) + XCTAssertNil(dispatchEvents[0].parentID) // Parent ID nil as event index does not match any waiting event } func testProcessResponseOnSuccess_WhenUnknownRequestId_doesNotCrash() { @@ -726,6 +749,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual("pairedeventexample", flattenReceivedData1["type"] as? String) XCTAssertEqual("123", flattenReceivedData1["requestId"] as? String) XCTAssertEqual("123612123812381", flattenReceivedData1["payload[0].id"] as? String) + XCTAssertNil(dispatchEvents[0].parentID) // Parent ID nil as request ID does not match any waiting events } // MARK: processResponseOnSuccess with mixed event handles, errors, warnings @@ -743,6 +767,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " \"handle\": [" + " {\n" + " \"type\": \"state:store\",\n" + + " \"eventIndex\": 1, \n" + " \"payload\": [\n" + " {\n" + " \"key\": \"s_ecid\",\n" + @@ -754,11 +779,13 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " {\n" + " \"status\": 2003,\n" + " \"type\": \"personalization\",\n" + - " \"title\": \"Failed to process personalization event\"\n" + + " \"title\": \"Failed to process personalization event\",\n" + + " \"eventIndex\": 1 \n" + " }\n" + " ]\n" + " }" + networkResponseHandler.addWaitingEvents(requestId: requestId, batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: requestId) let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: "state:store") @@ -768,12 +795,14 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { return } let flattenReceivedData1: [String: Any] = flattenDictionary(dict: receivedData1) - XCTAssertEqual(5, flattenReceivedData1.count) + XCTAssertEqual(6, flattenReceivedData1.count) XCTAssertEqual("state:store", flattenReceivedData1["type"] as? String) XCTAssertEqual("123", flattenReceivedData1["requestId"] as? String) XCTAssertEqual("s_ecid", flattenReceivedData1["payload[0].key"] as? String) XCTAssertEqual("MCMID|29068398647607325310376254630528178721", flattenReceivedData1["payload[0].value"] as? String) XCTAssertEqual(15552000, flattenReceivedData1["payload[0].maxAge"] as? Int) + XCTAssertEqual(event2.id.uuidString, flattenReceivedData1["requestEventId"] as? String) + XCTAssertEqual(event2.id, dispatchEvents[0].parentID) // Event chained to event2 as event index is 1 let dispatchErrorEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) XCTAssertEqual(1, dispatchErrorEvents.count) @@ -783,11 +812,13 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } let flattenReceivedData2: [String: Any] = flattenDictionary(dict: receivedData2) - XCTAssertEqual(4, flattenReceivedData2.count) + XCTAssertEqual(5, flattenReceivedData2.count) XCTAssertEqual("personalization", flattenReceivedData2["type"] as? String) XCTAssertEqual(2003, flattenReceivedData2["status"] as? Int) XCTAssertEqual("Failed to process personalization event", flattenReceivedData2["title"] as? String) XCTAssertEqual("123", flattenReceivedData2["requestId"] as? String) + XCTAssertEqual(event2.id.uuidString, flattenReceivedData2["requestEventId"] as? String) + XCTAssertEqual(event2.id, dispatchErrorEvents[0].parentID) // Event chained to event2 as event index is 1 } func testProcessResponseOnSuccess_WhenErrorAndWarning_dispatchesTwoEvents() { @@ -800,7 +831,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " {\n" + " \"status\": 2003,\n" + " \"title\": \"Failed to process personalization event\",\n" + - " \"eventIndex\": 2 \n" + + " \"eventIndex\": 1 \n" + " }\n" + " ],\n" + " \"warnings\": [" + @@ -808,7 +839,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " \"type\": \"https://ns.adobe.com/aep/errors/EXEG-0204-200\",\n" + " \"status\": 98,\n" + " \"title\": \"Some Informative stuff here\",\n" + - " \"eventIndex\": 10, \n" + + " \"eventIndex\": 0, \n" + " \"report\": {" + " \"cause\": {" + " \"message\": \"Some Informative stuff here\",\n" + @@ -819,6 +850,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " ]\n" + " }" + networkResponseHandler.addWaitingEvents(requestId: requestId, batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: requestId) let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) @@ -828,23 +860,121 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { return } let flattenReceivedData1: [String: Any] = flattenDictionary(dict: receivedData1) - XCTAssertEqual(3, flattenReceivedData1.count) + XCTAssertEqual(4, flattenReceivedData1.count) XCTAssertEqual(2003, flattenReceivedData1["status"] as? Int) XCTAssertEqual("Failed to process personalization event", flattenReceivedData1["title"] as? String) XCTAssertEqual("123", flattenReceivedData1["requestId"] as? String) - + XCTAssertEqual(event2.id.uuidString, flattenReceivedData1["requestEventId"] as? String) + XCTAssertEqual(event2.id, dispatchEvents[0].parentID) // Event chained to event2 as event index is 1 + guard let receivedData2 = dispatchEvents[1].data else { XCTFail("Invalid event data for event 2") return } let flattenReceivedData2: [String: Any] = flattenDictionary(dict: receivedData2) - XCTAssertEqual(6, flattenReceivedData2.count) + XCTAssertEqual(7, flattenReceivedData2.count) XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0204-200", flattenReceivedData2["type"] as? String) XCTAssertEqual(98, flattenReceivedData2["status"] as? Int) XCTAssertEqual("Some Informative stuff here", flattenReceivedData2["title"] as? String) XCTAssertEqual("Some Informative stuff here", flattenReceivedData2["report.cause.message"] as? String) XCTAssertEqual(202, flattenReceivedData2["report.cause.code"] as? Int) XCTAssertEqual("123", flattenReceivedData2["requestId"] as? String) + XCTAssertEqual(event1.id.uuidString, flattenReceivedData2["requestEventId"] as? String) + XCTAssertEqual(event1.id, dispatchEvents[1].parentID) // Event chained to event1 as event index is 0 + } + + func testProcessResponseOnSuccess_WhenEventHandleAndErrorAndWarning_dispatchesThreeEvents() { + setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, + source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, + expectedCount: 1) + setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, + source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, + expectedCount: 2) + let requestId = "123" + let jsonResponse = "{\n" + + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + + " \"handle\": [" + + " {\n" + + " \"type\": \"state:store\",\n" + + " \"payload\": [\n" + + " {\n" + + " \"key\": \"s_ecid\",\n" + + " \"value\": \"MCMID|29068398647607325310376254630528178721\",\n" + + " \"maxAge\": 15552000\n" + + " }\n" + + " ]}],\n" + + " \"errors\": [" + + " {\n" + + " \"status\": 2003,\n" + + " \"type\": \"personalization\",\n" + + " \"title\": \"Failed to process personalization event\"\n" + + " }\n" + + " ],\n" + + " \"warnings\": [" + + " {\n" + + " \"type\": \"https://ns.adobe.com/aep/errors/EXEG-0204-200\",\n" + + " \"status\": 98,\n" + + " \"title\": \"Some Informative stuff here\",\n" + + " \"report\": {" + + " \"cause\": {" + + " \"message\": \"Some Informative stuff here\",\n" + + " \"code\": 202\n" + + " }" + + " }" + + " }\n" + + " ]\n" + + " }" + + networkResponseHandler.addWaitingEvents(requestId: requestId, batchedEvents: [event1]) + networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: requestId) + + let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: "state:store") + XCTAssertEqual(1, dispatchEvents.count) + guard let receivedData1 = dispatchEvents[0].data else { + XCTFail("Invalid event data for dispatched handle") + return + } + let flattenReceivedData1: [String: Any] = flattenDictionary(dict: receivedData1) + XCTAssertEqual(6, flattenReceivedData1.count) + XCTAssertEqual("state:store", flattenReceivedData1["type"] as? String) + XCTAssertEqual("123", flattenReceivedData1["requestId"] as? String) + XCTAssertEqual("s_ecid", flattenReceivedData1["payload[0].key"] as? String) + XCTAssertEqual("MCMID|29068398647607325310376254630528178721", flattenReceivedData1["payload[0].value"] as? String) + XCTAssertEqual(15552000, flattenReceivedData1["payload[0].maxAge"] as? Int) + XCTAssertEqual(event1.id.uuidString, flattenReceivedData1["requestEventId"] as? String) + XCTAssertEqual(event1.id, dispatchEvents[0].parentID) // Event chained to event1 as event index defaults to 0 + + let dispatchErrorEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + XCTAssertEqual(2, dispatchErrorEvents.count) + guard let receivedData2 = dispatchErrorEvents[0].data else { + XCTFail("Invalid event data for dispatched error") + return + } + + let flattenReceivedData2: [String: Any] = flattenDictionary(dict: receivedData2) + XCTAssertEqual(5, flattenReceivedData2.count) + XCTAssertEqual("personalization", flattenReceivedData2["type"] as? String) + XCTAssertEqual(2003, flattenReceivedData2["status"] as? Int) + XCTAssertEqual("Failed to process personalization event", flattenReceivedData2["title"] as? String) + XCTAssertEqual("123", flattenReceivedData2["requestId"] as? String) + XCTAssertEqual(event1.id.uuidString, flattenReceivedData1["requestEventId"] as? String) + XCTAssertEqual(event1.id, dispatchErrorEvents[0].parentID) // Event chained to event1 as event index defaults to 0 + + guard let receivedData3 = dispatchErrorEvents[1].data else { + XCTFail("Invalid event data for dispatched warning") + return + } + + let flattenReceivedData3: [String: Any] = flattenDictionary(dict: receivedData3) + XCTAssertEqual(7, flattenReceivedData3.count) + XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0204-200", flattenReceivedData3["type"] as? String) + XCTAssertEqual(98, flattenReceivedData3["status"] as? Int) + XCTAssertEqual("Some Informative stuff here", flattenReceivedData3["title"] as? String) + XCTAssertEqual("Some Informative stuff here", flattenReceivedData3["report.cause.message"] as? String) + XCTAssertEqual(202, flattenReceivedData3["report.cause.code"] as? Int) + XCTAssertEqual("123", flattenReceivedData3["requestId"] as? String) + XCTAssertEqual(event1.id.uuidString, flattenReceivedData1["requestEventId"] as? String) + XCTAssertEqual(event1.id, dispatchErrorEvents[1].parentID) // Event chained to event1 as event index defaults to 0 } // MARK: locationHint:result diff --git a/Tests/UnitTests/NetworkResponseHandlerTests.swift b/Tests/UnitTests/NetworkResponseHandlerTests.swift index 4578b46e..bed72a92 100644 --- a/Tests/UnitTests/NetworkResponseHandlerTests.swift +++ b/Tests/UnitTests/NetworkResponseHandlerTests.swift @@ -36,8 +36,8 @@ class NetworkResponseHandlerTests: XCTestCase { return } - XCTAssertEqual(event1.id.uuidString, result[0]) - XCTAssertEqual(event2.id.uuidString, result[1]) + XCTAssertEqual(event1.id.uuidString, result[0].id.uuidString) + XCTAssertEqual(event2.id.uuidString, result[1].id.uuidString) } func testAddWaitingEvents_skips_whenEmptyRequestId() { @@ -68,7 +68,7 @@ class NetworkResponseHandlerTests: XCTestCase { return } - XCTAssertEqual(event3.id.uuidString, result[0]) + XCTAssertEqual(event3.id.uuidString, result[0].id.uuidString) } func testRemoveWaitingEvents_removesByRequestId() { From b2eabc052633df6b5264ee2103f2fcc4b97f2cae Mon Sep 17 00:00:00 2001 From: Kevin Lind <40409666+kevinlind@users.noreply.github.com> Date: Thu, 13 Jul 2023 17:22:49 -0700 Subject: [PATCH 2/9] Get 'eventIndex' from 'report' object for interact response errors and warnings (#363) * Look for eventIndex in report object of error and warning in network response * Do not encode eventIndex in EdgeEventError or EdgeEventWarning objects --- .../EdgeNetworkHandlers/EdgeEventError.swift | 45 ++++++++-- .../EdgeEventWarning.swift | 22 +++-- .../NetworkResponseHandler.swift | 4 +- .../AEPEdgeFunctionalTests.swift | 8 +- ...etworkResponseHandlerFunctionalTests.swift | 38 +++++---- Tests/UnitTests/EdgeEventErrorTests.swift | 62 ++++++++++++-- Tests/UnitTests/EdgeEventWarningTests.swift | 84 ++++++++++++++----- Tests/UnitTests/EdgeHitProcessorTests.swift | 2 +- 8 files changed, 200 insertions(+), 65 deletions(-) diff --git a/Sources/EdgeNetworkHandlers/EdgeEventError.swift b/Sources/EdgeNetworkHandlers/EdgeEventError.swift index 85c5b42b..955d1e07 100644 --- a/Sources/EdgeNetworkHandlers/EdgeEventError.swift +++ b/Sources/EdgeNetworkHandlers/EdgeEventError.swift @@ -26,18 +26,14 @@ struct EdgeEventError: Codable, Equatable { /// Namespaced error code let type: String? - /// Encodes the event to which this error is attached as the index in the events array in EdgeRequest - let eventIndex: Int? - /// A report for the error containing additional information let report: EdgeErrorReport? - init(title: String?, detail: String?, status: Int?, type: String?, eventIndex: Int?, report: EdgeErrorReport?) { + init(title: String?, detail: String?, status: Int?, type: String?, report: EdgeErrorReport?) { self.title = title self.detail = detail self.status = status self.type = type - self.eventIndex = eventIndex self.report = report } @@ -46,7 +42,6 @@ struct EdgeEventError: Codable, Equatable { self.detail = detail self.status = nil self.type = nil - self.eventIndex = nil self.report = nil } @@ -57,22 +52,24 @@ struct EdgeEventError: Codable, Equatable { case status case type case report - case eventIndex } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - // skip eventIndex when encoding if let unwrapped = title { try container.encodeIfPresent(unwrapped, forKey: .title) } if let unwrapped = detail { try container.encodeIfPresent(unwrapped, forKey: .detail) } if let unwrapped = status { try container.encodeIfPresent(unwrapped, forKey: .status) } if let unwrapped = type { try container.encodeIfPresent(unwrapped, forKey: .type) } - if let unwrapped = report { try container.encodeIfPresent(unwrapped, forKey: .report) } + if let unwrapped = report, unwrapped.shouldEncode() { try container.encodeIfPresent(unwrapped, forKey: .report) } } } // MARK: - EdgeErrorReport struct EdgeErrorReport: Codable, Equatable { + + /// Encodes the event to which this error is attached as the index in the events array in EdgeRequest + let eventIndex: Int? + // An array of errors represented as strings let errors: [String]? @@ -81,4 +78,34 @@ struct EdgeErrorReport: Codable, Equatable { /// The organization ID let orgId: String? + + init(eventIndex: Int?, errors: [String]?, requestId: String?, orgId: String?) { + self.eventIndex = eventIndex + self.errors = errors + self.requestId = requestId + self.orgId = orgId + } + + // Encode this report if it contains `errors`, `requestId`, or `orgId`. + public func shouldEncode() -> Bool { + return errors != nil + || requestId != nil + || orgId != nil + } + + // MARK: - Codable + enum CodingKeys: String, CodingKey { + case eventIndex + case errors + case requestId + case orgId + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + // skip eventIndex when encoding + if let unwrapped = errors { try container.encodeIfPresent(unwrapped, forKey: .errors)} + if let unwrapped = requestId { try container.encodeIfPresent(unwrapped, forKey: .requestId)} + if let unwrapped = orgId { try container.encodeIfPresent(unwrapped, forKey: .orgId)} + } } diff --git a/Sources/EdgeNetworkHandlers/EdgeEventWarning.swift b/Sources/EdgeNetworkHandlers/EdgeEventWarning.swift index 0735cb33..d489cb67 100644 --- a/Sources/EdgeNetworkHandlers/EdgeEventWarning.swift +++ b/Sources/EdgeNetworkHandlers/EdgeEventWarning.swift @@ -29,25 +29,20 @@ struct EdgeEventWarning: Codable { /// The warning report let report: EdgeEventWarningReport? - /// Encodes the event to which this warning is attached as the index in the events array in EdgeRequest - let eventIndex: Int? - // MARK: - Codable enum CodingKeys: String, CodingKey { case type case status case title case report - case eventIndex } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - // skip eventIndex when encoding if let unwrapped = title { try container.encodeIfPresent(unwrapped, forKey: .title) } if let unwrapped = status { try container.encodeIfPresent(unwrapped, forKey: .status) } if let unwrapped = type { try container.encodeIfPresent(unwrapped, forKey: .type) } - if let unwrapped = report { try container.encodeIfPresent(unwrapped, forKey: .report) } + if let unwrapped = report, unwrapped.cause != nil { try container.encodeIfPresent(unwrapped, forKey: .report) } } } @@ -56,8 +51,23 @@ struct EdgeEventWarning: Codable { /// A map of additional properties that aid in debugging such as the request ID or the org ID. In some cases, it might contain data specific to the error at hand, such as a list of validation errors. struct EdgeEventWarningReport: Codable { + /// Encodes the event to which this warning is attached as the index in the events array in EdgeRequest + let eventIndex: Int? + /// The cause for the `EdgeEventWarning` let cause: EdgeEventWarningCause? + + // MARK: - Codable + enum CodingKeys: String, CodingKey { + case eventIndex + case cause + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + // skip eventIndex when encoding + if let unwrapped = cause { try container.encodeIfPresent(unwrapped, forKey: .cause)} + } } // MARK: - EdgeEventWarningCause diff --git a/Sources/EdgeNetworkHandlers/NetworkResponseHandler.swift b/Sources/EdgeNetworkHandlers/NetworkResponseHandler.swift index 70e29241..340fa1ee 100644 --- a/Sources/EdgeNetworkHandlers/NetworkResponseHandler.swift +++ b/Sources/EdgeNetworkHandlers/NetworkResponseHandler.swift @@ -221,7 +221,7 @@ class NetworkResponseHandler { if let errorAsDictionary = error.asDictionary() { logErrorMessage(errorAsDictionary, isError: true, requestId: requestId) - let requestEvent = extractRequestEvent(forEventIndex: error.eventIndex, requestId: requestId) + let requestEvent = extractRequestEvent(forEventIndex: error.report?.eventIndex, requestId: requestId) // set eventRequestId and Edge requestId on the response event and dispatch data let eventData = addEventAndRequestIdToDictionary(errorAsDictionary, requestId: requestId, @@ -250,7 +250,7 @@ class NetworkResponseHandler { if let warningsAsDictionary = warning.asDictionary() { logErrorMessage(warningsAsDictionary, isError: false, requestId: requestId) - let requestEvent = extractRequestEvent(forEventIndex: warning.eventIndex, requestId: requestId) + let requestEvent = extractRequestEvent(forEventIndex: warning.report?.eventIndex, requestId: requestId) // set eventRequestId and Edge requestId on the response event and dispatch data let eventData = addEventAndRequestIdToDictionary(warningsAsDictionary, requestId: requestId, diff --git a/Tests/FunctionalTests/AEPEdgeFunctionalTests.swift b/Tests/FunctionalTests/AEPEdgeFunctionalTests.swift index 32a5ef46..0fa83ff4 100644 --- a/Tests/FunctionalTests/AEPEdgeFunctionalTests.swift +++ b/Tests/FunctionalTests/AEPEdgeFunctionalTests.swift @@ -692,7 +692,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) - let responseBody = "\u{0000}{\"requestId\": \"0ee43289-4a4e-469a-bf5c-1d8186919a26\",\"handle\": [],\"warnings\": [{\"eventIndex\": 0,\"status\": 0,\"title\": \"Failed due to unrecoverable system error\"}]}\n" + let responseBody = "\u{0000}{\"requestId\": \"0ee43289-4a4e-469a-bf5c-1d8186919a26\",\"handle\": [],\"warnings\": [{\"status\": 0,\"title\": \"Failed due to unrecoverable system error\",\"report\":{\"eventIndex\":0}}]}\n" let httpConnection: HttpConnection = HttpConnection(data: responseBody.data(using: .utf8), response: HTTPURLResponse(url: exEdgeInteractProdUrl, statusCode: 200, @@ -730,7 +730,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { // MARK: test persisted hits func testSendEvent_withXDMData_sendsExEdgeNetworkRequest_afterPersisting() { - let error = EdgeEventError(title: nil, detail: "X service is temporarily unable to serve this request. Please try again later.", status: 503, type: "test-type", eventIndex: nil, report: nil) + let error = EdgeEventError(title: nil, detail: "X service is temporarily unable to serve this request. Please try again later.", status: 503, type: "test-type", report: nil) let edgeResponse = EdgeResponse(requestId: "test-req-id", handle: nil, errors: [error], warnings: nil) let responseData = try? JSONEncoder().encode(edgeResponse) @@ -768,7 +768,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { } func testSendEvent_withXDMData_sendsExEdgeNetworkRequest_afterPersistingMultipleHits() { - let error = EdgeEventError(title: nil, detail: "X service is temporarily unable to serve this request. Please try again later.", status: 503, type: nil, eventIndex: nil, report: nil) + let error = EdgeEventError(title: nil, detail: "X service is temporarily unable to serve this request. Please try again later.", status: 503, type: nil, report: nil) let edgeResponse = EdgeResponse(requestId: "test-req-id", handle: nil, errors: [error], warnings: nil) let responseData = try? JSONEncoder().encode(edgeResponse) @@ -810,7 +810,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) // swiftlint:disable line_length let response = """ - {"requestId":"72eaa048-207e-4dde-bf16-0cb2b21336d5","handle":[],"errors":[{"type":"https://ns.adobe.com/aep/errors/EXEG-0201-504","status":504,"title":"The 'com.adobe.experience.platform.ode' service is temporarily unable to serve this request. Please try again later.","eventIndex":0}],"warnings":[{"type":"https://ns.adobe.com/aep/errors/EXEG-0204-200","status":200,"title":"A warning occurred while calling the 'com.adobe.audiencemanager' service for this request.","eventIndex":0,"report":{"cause":{"message":"Cannot read related customer for device id: ...","code":202}}}]} + {"requestId":"72eaa048-207e-4dde-bf16-0cb2b21336d5","handle":[],"errors":[{"type":"https://ns.adobe.com/aep/errors/EXEG-0201-504","status":504,"title":"The 'com.adobe.experience.platform.ode' service is temporarily unable to serve this request. Please try again later.","report":{"eventIndex":0}}],"warnings":[{"type":"https://ns.adobe.com/aep/errors/EXEG-0204-200","status":200,"title":"A warning occurred while calling the 'com.adobe.audiencemanager' service for this request.","report":{"eventIndex":0,"cause":{"message":"Cannot read related customer for device id: ...","code":202}}}]} """ // swiftlint:enable line_length let responseConnection: HttpConnection = HttpConnection(data: response.data(using: .utf8), diff --git a/Tests/FunctionalTests/NetworkResponseHandlerFunctionalTests.swift b/Tests/FunctionalTests/NetworkResponseHandlerFunctionalTests.swift index fe849431..a0db52d0 100644 --- a/Tests/FunctionalTests/NetworkResponseHandlerFunctionalTests.swift +++ b/Tests/FunctionalTests/NetworkResponseHandlerFunctionalTests.swift @@ -111,7 +111,9 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " \"status\": 100,\n" + " \"type\": \"personalization\",\n" + " \"title\": \"Button color not found\",\n" + - " \"eventIndex\": 1\n" + + " \"report\": {\n" + + " \"eventIndex\": 1\n" + + " }\n" + " }\n" + " ]\n" + " }" @@ -146,7 +148,9 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " \"status\": 100,\n" + " \"type\": \"personalization\",\n" + " \"title\": \"Button color not found\",\n" + + " \"report\": {\n" + " \"eventIndex\": 10\n" + + " }\n" + " }\n" + " ]\n" + " }" @@ -166,7 +170,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual(100, flattenReceivedData["status"] as? Int) XCTAssertEqual("Button color not found", flattenReceivedData["title"] as? String) XCTAssertEqual(requestId, flattenReceivedData["requestId"] as? String) - + XCTAssertNil(dispatchEvents[0].parentID) // Parent ID not chained as no event at index 10 } func testProcessResponseOnError_WhenUnknownRequestId_doesNotCrash() { @@ -180,7 +184,9 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " \"status\": 100,\n" + " \"type\": \"personalization\",\n" + " \"title\": \"Button color not found\",\n" + + " \"report\": {\n" + " \"eventIndex\": 0\n" + + " }\n" + " }\n" + " ]\n" + " }" @@ -200,7 +206,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual(100, flattenReceivedData["status"] as? Int) XCTAssertEqual("Button color not found", flattenReceivedData["title"] as? String) XCTAssertEqual("567", flattenReceivedData["requestId"] as? String) - + XCTAssertNil(dispatchEvents[0].parentID) // Parent ID not chained as request ID is unknown (does not match any waiting event list) } func testProcessResponseOnError_WhenTwoEventJsonError_dispatchesTwoEvents() { @@ -223,7 +229,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }\n" + " ]\n" + " }" - + networkResponseHandler.addWaitingEvents(requestId: requestId, batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnError(jsonError: jsonError, requestId: requestId) let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) @@ -579,7 +585,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }],\n" + " \"errors\": []\n" + " }" - + networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: "d81c93e5-7558-4996-a93c-489d550748b8") @@ -600,7 +606,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual(15552000, flattenReceivedData1["payload[0].maxAge"] as? Int) XCTAssertEqual(event1.id.uuidString, flattenReceivedData1["requestEventId"] as? String) XCTAssertEqual(event1.id, dispatchEvents[0].parentID) // Event chained to event1 as default event index is 0 - + // verify event 2 guard let receivedData2 = dispatchEvents[1].data else { XCTFail("Invalid event data for event 2") @@ -780,7 +786,9 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " \"status\": 2003,\n" + " \"type\": \"personalization\",\n" + " \"title\": \"Failed to process personalization event\",\n" + - " \"eventIndex\": 1 \n" + + " \"report\": {\n" + + " \"eventIndex\": 1 \n" + + " }\n" + " }\n" + " ]\n" + " }" @@ -831,7 +839,9 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " {\n" + " \"status\": 2003,\n" + " \"title\": \"Failed to process personalization event\",\n" + - " \"eventIndex\": 1 \n" + + " \"report\": {\n" + + " \"eventIndex\": 1 \n" + + " }\n" + " }\n" + " ],\n" + " \"warnings\": [" + @@ -839,8 +849,8 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " \"type\": \"https://ns.adobe.com/aep/errors/EXEG-0204-200\",\n" + " \"status\": 98,\n" + " \"title\": \"Some Informative stuff here\",\n" + - " \"eventIndex\": 0, \n" + " \"report\": {" + + " \"eventIndex\": 0, \n" + " \"cause\": {" + " \"message\": \"Some Informative stuff here\",\n" + " \"code\": 202\n" + @@ -866,7 +876,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual("123", flattenReceivedData1["requestId"] as? String) XCTAssertEqual(event2.id.uuidString, flattenReceivedData1["requestEventId"] as? String) XCTAssertEqual(event2.id, dispatchEvents[0].parentID) // Event chained to event2 as event index is 1 - + guard let receivedData2 = dispatchEvents[1].data else { XCTFail("Invalid event data for event 2") return @@ -882,7 +892,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual(event1.id.uuidString, flattenReceivedData2["requestEventId"] as? String) XCTAssertEqual(event1.id, dispatchEvents[1].parentID) // Event chained to event1 as event index is 0 } - + func testProcessResponseOnSuccess_WhenEventHandleAndErrorAndWarning_dispatchesThreeEvents() { setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, @@ -943,7 +953,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual(15552000, flattenReceivedData1["payload[0].maxAge"] as? Int) XCTAssertEqual(event1.id.uuidString, flattenReceivedData1["requestEventId"] as? String) XCTAssertEqual(event1.id, dispatchEvents[0].parentID) // Event chained to event1 as event index defaults to 0 - + let dispatchErrorEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) XCTAssertEqual(2, dispatchErrorEvents.count) guard let receivedData2 = dispatchErrorEvents[0].data else { @@ -959,12 +969,12 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual("123", flattenReceivedData2["requestId"] as? String) XCTAssertEqual(event1.id.uuidString, flattenReceivedData1["requestEventId"] as? String) XCTAssertEqual(event1.id, dispatchErrorEvents[0].parentID) // Event chained to event1 as event index defaults to 0 - + guard let receivedData3 = dispatchErrorEvents[1].data else { XCTFail("Invalid event data for dispatched warning") return } - + let flattenReceivedData3: [String: Any] = flattenDictionary(dict: receivedData3) XCTAssertEqual(7, flattenReceivedData3.count) XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0204-200", flattenReceivedData3["type"] as? String) diff --git a/Tests/UnitTests/EdgeEventErrorTests.swift b/Tests/UnitTests/EdgeEventErrorTests.swift index 2fd357a5..d60643e7 100644 --- a/Tests/UnitTests/EdgeEventErrorTests.swift +++ b/Tests/UnitTests/EdgeEventErrorTests.swift @@ -24,10 +24,12 @@ class EdgeEventErrorTests: XCTestCase { // setup let jsonData = """ { - "eventIndex": 1, "type": "https://ns.adobe.com/aep/errors/EXEG-0201-503", "status": 503, - "title": "test title" + "title": "test title", + "report": { + "eventIndex": 1 + } } """.data(using: .utf8) @@ -35,7 +37,7 @@ class EdgeEventErrorTests: XCTestCase { let error = try? JSONDecoder().decode(EdgeEventError.self, from: jsonData ?? Data()) // verify - XCTAssertEqual(1, error?.eventIndex) + XCTAssertEqual(1, error?.report?.eventIndex) XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0201-503", error?.type) XCTAssertEqual(503, error?.status) XCTAssertEqual("test title", error?.title) @@ -46,16 +48,20 @@ class EdgeEventErrorTests: XCTestCase { let jsonData = """ [ { - "eventIndex": 1, "type": "https://ns.adobe.com/aep/errors/EXEG-0201-503", "status": 503, - "title": "test title" + "title": "test title", + "report": { + "eventIndex": 1 + } }, { - "eventIndex": 2, "type": "https://ns.adobe.com/aep/errors/EXEG-0201-504", "status": 504, - "title": "test title 2" + "title": "test title 2", + "report": { + "eventIndex": 2 + } } ] """.data(using: .utf8) @@ -64,12 +70,12 @@ class EdgeEventErrorTests: XCTestCase { let errors = try? JSONDecoder().decode([EdgeEventError].self, from: jsonData ?? Data()) // verify - XCTAssertEqual(1, errors?.first?.eventIndex) + XCTAssertEqual(1, errors?.first?.report?.eventIndex) XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0201-503", errors?.first?.type) XCTAssertEqual(503, errors?.first?.status) XCTAssertEqual("test title", errors?.first?.title) - XCTAssertEqual(2, errors?.last?.eventIndex) + XCTAssertEqual(2, errors?.last?.report?.eventIndex) XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0201-504", errors?.last?.type) XCTAssertEqual(504, errors?.last?.status) XCTAssertEqual("test title 2", errors?.last?.title) @@ -131,4 +137,42 @@ class EdgeEventErrorTests: XCTestCase { XCTAssertEqual("Invalid request (report attached). Please check your input and try again.", error?.detail) } + func testCanEncode_eventError_allParams() { + let report = EdgeErrorReport(eventIndex: 1, errors: ["error1", "error2"], requestId: "1234", orgId: "abcd") + let error = EdgeEventError(title: "Test Error", detail: "details", status: 200, type: "error", report: report) + + let encoded = error.asDictionary() + + XCTAssertNotNil(encoded) + XCTAssertEqual(5, encoded?.count) + XCTAssertEqual("Test Error", encoded?["title"] as? String) + XCTAssertEqual("details", encoded?["detail"] as? String) + XCTAssertEqual(200, encoded?["status"] as? Int) + XCTAssertEqual("error", encoded?["type"] as? String) + + let encodedReport = encoded?["report"] as? [String: Any] + XCTAssertNotNil(encodedReport) + XCTAssertEqual(3, encodedReport?.count) // eventIndex is no encoded + XCTAssertEqual(["error1", "error2"], encodedReport?["errors"] as? [String]) + XCTAssertEqual("1234", encodedReport?["requestId"] as? String) + XCTAssertEqual("abcd", encodedReport?["orgId"] as? String) + } + + func testCanEncode_eventError_emptyReportNotEncoded() { + let report = EdgeErrorReport(eventIndex: 1, errors: nil, requestId: nil, orgId: nil) + let error = EdgeEventError(title: "Test Error", detail: "details", status: 200, type: "error", report: report) + + XCTAssertFalse(report.shouldEncode()) // EdgeErrorReport is not encoded if it only contains eventIndex + + let encoded = error.asDictionary() + + XCTAssertNotNil(encoded) + XCTAssertEqual(4, encoded?.count) + XCTAssertEqual("Test Error", encoded?["title"] as? String) + XCTAssertEqual("details", encoded?["detail"] as? String) + XCTAssertEqual(200, encoded?["status"] as? Int) + XCTAssertEqual("error", encoded?["type"] as? String) + XCTAssertNil(encoded?["report"]) + } + } diff --git a/Tests/UnitTests/EdgeEventWarningTests.swift b/Tests/UnitTests/EdgeEventWarningTests.swift index 91b87e59..98cd6505 100644 --- a/Tests/UnitTests/EdgeEventWarningTests.swift +++ b/Tests/UnitTests/EdgeEventWarningTests.swift @@ -24,11 +24,11 @@ class EdgeEventWarningTests: XCTestCase { // setup let jsonData = """ { - "eventIndex": 1, "type": "https://ns.adobe.com/aep/errors/EXEG-0204-200", "status": 200, "title": "test title", "report": { + "eventIndex": 1, "cause": { "message": "Cannot read related customer for device id: ...", "code": 202 @@ -41,7 +41,7 @@ class EdgeEventWarningTests: XCTestCase { let warning = try? JSONDecoder().decode(EdgeEventWarning.self, from: jsonData ?? Data()) // verify - XCTAssertEqual(1, warning?.eventIndex) + XCTAssertEqual(1, warning?.report?.eventIndex) XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0204-200", warning?.type) XCTAssertEqual(200, warning?.status) XCTAssertEqual("test title", warning?.title) @@ -53,12 +53,12 @@ class EdgeEventWarningTests: XCTestCase { // setup let jsonData = """ { - "eventIndex": 1, "type": "https://ns.adobe.com/aep/errors/EXEG-0204-200", "status": 200, "title": "test title", "extraKey": "extraValue", "report": { + "eventIndex": 1, "cause": { "message": "Cannot read related customer for device id: ...", "code": 202, @@ -72,7 +72,7 @@ class EdgeEventWarningTests: XCTestCase { let warning = try? JSONDecoder().decode(EdgeEventWarning.self, from: jsonData ?? Data()) // verify - XCTAssertEqual(1, warning?.eventIndex) + XCTAssertEqual(1, warning?.report?.eventIndex) XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0204-200", warning?.type) XCTAssertEqual(200, warning?.status) XCTAssertEqual("test title", warning?.title) @@ -85,11 +85,11 @@ class EdgeEventWarningTests: XCTestCase { let jsonData = """ [ { - "eventIndex": 1, "type": "https://ns.adobe.com/aep/errors/EXEG-0204-200", "status": 200, "title": "test title", "report": { + "eventIndex": 1, "cause": { "message": "Cannot read related customer for device id: ...", "code": 202 @@ -97,11 +97,11 @@ class EdgeEventWarningTests: XCTestCase { } }, { - "eventIndex": 2, "type": "https://ns.adobe.com/aep/errors/EXEG-0204-202", "status": 202, "title": "test title 2", "report": { + "eventIndex": 2, "cause": { "message": "Cannot read related customer for device id: ...", "code": 202 @@ -115,14 +115,14 @@ class EdgeEventWarningTests: XCTestCase { let warnings = try? JSONDecoder().decode([EdgeEventWarning].self, from: jsonData ?? Data()) // verify - XCTAssertEqual(1, warnings?.first?.eventIndex) + XCTAssertEqual(1, warnings?.first?.report?.eventIndex) XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0204-200", warnings?.first?.type) XCTAssertEqual(200, warnings?.first?.status) XCTAssertEqual("test title", warnings?.first?.title) XCTAssertEqual("Cannot read related customer for device id: ...", warnings?.first?.report?.cause?.message) XCTAssertEqual(202, warnings?.first?.report?.cause?.code) - XCTAssertEqual(2, warnings?.last?.eventIndex) + XCTAssertEqual(2, warnings?.last?.report?.eventIndex) XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0204-202", warnings?.last?.type) XCTAssertEqual(202, warnings?.last?.status) XCTAssertEqual("test title 2", warnings?.last?.title) @@ -134,10 +134,12 @@ class EdgeEventWarningTests: XCTestCase { // setup let jsonData = """ { - "eventIndex": 1, "type": "https://ns.adobe.com/aep/errors/EXEG-0204-200", "status": 200, - "title": "test title" + "title": "test title", + "report": { + "eventIndex": 1 + } } """.data(using: .utf8) @@ -145,11 +147,11 @@ class EdgeEventWarningTests: XCTestCase { let warning = try? JSONDecoder().decode(EdgeEventWarning.self, from: jsonData ?? Data()) // verify - XCTAssertEqual(1, warning?.eventIndex) + XCTAssertNotNil(warning?.report) + XCTAssertEqual(1, warning?.report?.eventIndex) XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0204-200", warning?.type) XCTAssertEqual(200, warning?.status) XCTAssertEqual("test title", warning?.title) - XCTAssertNil(warning?.report) } func testCanDecode_eventWarning_missingParams_multipleWarnings() { @@ -157,16 +159,20 @@ class EdgeEventWarningTests: XCTestCase { let jsonData = """ [ { - "eventIndex": 1, "type": "https://ns.adobe.com/aep/errors/EXEG-0204-200", "status": 200, - "title": "test title" + "title": "test title", + "report": { + "eventIndex": 1 + } }, { - "eventIndex": 2, "type": "https://ns.adobe.com/aep/errors/EXEG-0204-202", "status": 202, - "title": "test title 2" + "title": "test title 2", + "report": { + "eventIndex": 2 + } } ] """.data(using: .utf8) @@ -175,17 +181,55 @@ class EdgeEventWarningTests: XCTestCase { let warnings = try? JSONDecoder().decode([EdgeEventWarning].self, from: jsonData ?? Data()) // verify - XCTAssertEqual(1, warnings?.first?.eventIndex) + XCTAssertNotNil(warnings?.first?.report) + XCTAssertEqual(1, warnings?.first?.report?.eventIndex) XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0204-200", warnings?.first?.type) XCTAssertEqual(200, warnings?.first?.status) XCTAssertEqual("test title", warnings?.first?.title) - XCTAssertNil(warnings?.first?.report) - XCTAssertEqual(2, warnings?.last?.eventIndex) + XCTAssertNotNil(warnings?.last?.report) + XCTAssertEqual(2, warnings?.last?.report?.eventIndex) XCTAssertEqual("https://ns.adobe.com/aep/errors/EXEG-0204-202", warnings?.last?.type) XCTAssertEqual(202, warnings?.last?.status) XCTAssertEqual("test title 2", warnings?.last?.title) - XCTAssertNil(warnings?.last?.report) + } + + func testCanEncode_eventWarning_allParams() { + let cause = EdgeEventWarningCause(message: "message", code: 5) + let report = EdgeEventWarningReport(eventIndex: 1, cause: cause) + let warning = EdgeEventWarning(type: "warning", status: 200, title: "test", report: report) + + let encoded = warning.asDictionary() + + XCTAssertNotNil(encoded) + XCTAssertEqual(4, encoded?.count) + XCTAssertEqual("warning", encoded?["type"] as? String) + XCTAssertEqual(200, encoded?["status"] as? Int) + XCTAssertEqual("test", encoded?["title"] as? String) + + let encodedReport = encoded?["report"] as? [String: Any] + XCTAssertNotNil(encodedReport) + XCTAssertEqual(1, encodedReport?.count) // eventIndex is not encoded + + let encodedCause = encodedReport?["cause"] as? [String: Any] + XCTAssertNotNil(encodedCause) + XCTAssertEqual(2, encodedCause?.count) + XCTAssertEqual("message", encodedCause?["message"] as? String) + XCTAssertEqual(5, encodedCause?["code"] as? Int) + } + + func testCanEncode_eventWarning_doesNotEncodeEmptyReport() { + let report = EdgeEventWarningReport(eventIndex: 1, cause: nil) + let warning = EdgeEventWarning(type: "warning", status: 200, title: "test", report: report) + + let encoded = warning.asDictionary() + + XCTAssertNotNil(encoded) + XCTAssertEqual(3, encoded?.count) + XCTAssertEqual("warning", encoded?["type"] as? String) + XCTAssertEqual(200, encoded?["status"] as? Int) + XCTAssertEqual("test", encoded?["title"] as? String) + XCTAssertNil(encoded?["report"]) // EdgeEventWarningReport is not encoded if it doesn't contain a "cause" } } diff --git a/Tests/UnitTests/EdgeHitProcessorTests.swift b/Tests/UnitTests/EdgeHitProcessorTests.swift index ec0d6b43..f1449323 100644 --- a/Tests/UnitTests/EdgeHitProcessorTests.swift +++ b/Tests/UnitTests/EdgeHitProcessorTests.swift @@ -243,7 +243,7 @@ class EdgeHitProcessorTests: XCTestCase { let retryValues = [("60", 60.0), ("InvalidHeader", 5.0), ("", 5.0), ("1", 1.0)] for (code, retryValueTuple) in zip(recoverableNetworkErrorCodes, retryValues) { - let error = EdgeEventError(title: "test-title", detail: nil, status: code, type: "test-type", eventIndex: 0, report: nil) + let error = EdgeEventError(title: "test-title", detail: nil, status: code, type: "test-type", report: EdgeErrorReport(eventIndex: 0, errors: nil, requestId: nil, orgId: nil)) let edgeResponse = EdgeResponse(requestId: "test-req-id", handle: nil, errors: [error], warnings: nil) let responseData = try? JSONEncoder().encode(edgeResponse) From a2d61de5af212f0b31bb420796e7856222941eb9 Mon Sep 17 00:00:00 2001 From: kevinlind Date: Fri, 14 Jul 2023 17:42:56 +0000 Subject: [PATCH 3/9] Updating version to 4.1.0. --- AEPEdge.podspec | 2 +- AEPEdge.xcodeproj/project.pbxproj | 4 ++-- Sources/EdgeConstants.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AEPEdge.podspec b/AEPEdge.podspec index 305a20c0..8e6b09f6 100644 --- a/AEPEdge.podspec +++ b/AEPEdge.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "AEPEdge" - s.version = "4.0.0" + s.version = "4.1.0" s.summary = "Experience Platform Edge extension for Adobe Experience Platform Mobile SDK. Written and maintained by Adobe." s.description = <<-DESC diff --git a/AEPEdge.xcodeproj/project.pbxproj b/AEPEdge.xcodeproj/project.pbxproj index 2acba6c6..0a91f93f 100644 --- a/AEPEdge.xcodeproj/project.pbxproj +++ b/AEPEdge.xcodeproj/project.pbxproj @@ -1379,7 +1379,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 4.0.0; + MARKETING_VERSION = 4.1.0; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = com.adobe.aep.edge; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -1415,7 +1415,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 4.0.0; + MARKETING_VERSION = 4.1.0; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = com.adobe.aep.edge; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; diff --git a/Sources/EdgeConstants.swift b/Sources/EdgeConstants.swift index f1a8dbd3..46d10ccd 100644 --- a/Sources/EdgeConstants.swift +++ b/Sources/EdgeConstants.swift @@ -15,7 +15,7 @@ import Foundation enum EdgeConstants { static let EXTENSION_NAME = "com.adobe.edge" - static let EXTENSION_VERSION = "4.0.0" + static let EXTENSION_VERSION = "4.1.0" static let FRIENDLY_NAME = "AEPEdge" static let LOG_TAG = FRIENDLY_NAME From f114e1454f101baed10510e6ec253a2db43b8edf Mon Sep 17 00:00:00 2001 From: Tim Kim <95260439+timkimadobe@users.noreply.github.com> Date: Mon, 17 Jul 2023 14:54:52 -0700 Subject: [PATCH 4/9] [CI] Run unit and functional tests separately in CI (#368) * Split unit and functional test steps Update CircleCI script accordingly * Reorder unit and functional tests * Reorder unit and functional in cleanup step * Test reordering libraries * Split Makefile rules test-ios and test-tvos to run unit and functional tests separately * Run functional tests even when unit tests fail in CI * Move test reports under 'build/reports' folder --------- Co-authored-by: Kevin Lind --- .circleci/config.yml | 40 +++++++++++++----- AEPEdge.xcodeproj/project.pbxproj | 4 +- Makefile | 70 +++++++++++++------------------ 3 files changed, 61 insertions(+), 53 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f346f711..9eeb489d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -105,15 +105,25 @@ jobs: - prestart_ios_simulator - run: - name: Run iOS Tests - command: make test-ios + name: Run iOS Unit Tests + command: make unit-test-ios # Code coverage upload using Codecov # See options explanation here: https://docs.codecov.com/docs/codecov-uploader - codecov/upload: - flags: ios-tests - upload_name: Coverage Report for iOS Tests - xtra_args: -c -v --xc --xp iosresults.xcresult + flags: ios-unit-tests + upload_name: Coverage report for iOS unit tests + xtra_args: -c -v --xc --xp build/reports/iosUnitResults.xcresult + + - run: + name: Run iOS Functional Tests + command: make functional-test-ios + when: always # run even if unit tests fail + + - codecov/upload: + flags: ios-functional-tests + upload_name: Coverage report for iOS functional tests + xtra_args: -c -v --xc --xp build/reports/iosFunctionalResults.xcresult test-tvos: macos: @@ -127,16 +137,26 @@ jobs: - prestart_tvos_simulator - run: - name: Run tvOS Tests - command: make test-tvos + name: Run tvOS Unit Tests + command: make unit-test-tvos # Code coverage upload using Codecov # See options explanation here: https://docs.codecov.com/docs/codecov-uploader - codecov/upload: - flags: tvos-tests - upload_name: Coverage Report for tvOS Tests - xtra_args: -c -v --xc --xp tvosresults.xcresult + flags: tvos-unit-tests + upload_name: Coverage report for tvOS unit tests + xtra_args: -c -v --xc --xp build/reports/tvosUnitResults.xcresult + - run: + name: Run tvOS Functional Tests + command: make functional-test-tvos + when: always # run even if unit tests fail + + - codecov/upload: + flags: tvos-functional-tests + upload_name: Coverage report for tvOS functional tests + xtra_args: -c -v --xc --xp build/reports/tvosFunctionalResults.xcresult + build_xcframework_and_app: macos: xcode: 14.1.0 # Specify the Xcode version to use diff --git a/AEPEdge.xcodeproj/project.pbxproj b/AEPEdge.xcodeproj/project.pbxproj index 0a91f93f..679afc23 100644 --- a/AEPEdge.xcodeproj/project.pbxproj +++ b/AEPEdge.xcodeproj/project.pbxproj @@ -364,8 +364,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D4ABABAE251A7D95008076BF /* AEPEdge.framework in Frameworks */, 783A5F4E903F9B6EABA50D23 /* Pods_UnitTests.framework in Frameworks */, + D4ABABAE251A7D95008076BF /* AEPEdge.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -373,8 +373,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D4ABABAB251A7D41008076BF /* AEPEdge.framework in Frameworks */, 423B5033D43ADB49049383FB /* Pods_FunctionalTests.framework in Frameworks */, + D4ABABAB251A7D41008076BF /* AEPEdge.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Makefile b/Makefile index c58d5c44..5c19f945 100644 --- a/Makefile +++ b/Makefile @@ -22,12 +22,6 @@ setup: clean: rm -rf build -clean-ios-test-files: - rm -rf iosresults.xcresult - -clean-tvos-test-files: - rm -rf tvosresults.xcresult - pod-install: pod install --repo-update @@ -91,41 +85,35 @@ build-app: setup @echo "######################################################################" xcodebuild clean build -workspace $(PROJECT_NAME).xcworkspace -scheme $(TEST_APP_TVOS_SCHEME) -destination 'generic/platform=tvOS Simulator' -test: test-ios test-tvos - -test-ios: clean-ios-test-files - @echo "######################################################################" - @echo "### Testing iOS" - @echo "######################################################################" - @echo "List of available shared Schemes in xcworkspace" - xcodebuild -workspace $(PROJECT_NAME).xcworkspace -list - final_scheme=""; \ - if xcodebuild -workspace $(PROJECT_NAME).xcworkspace -list | grep -q "($(PROJECT_NAME) project)"; \ - then \ - final_scheme="$(EXTENSION_NAME) ($(PROJECT_NAME) project)" ; \ - echo $$final_scheme ; \ - else \ - final_scheme="$(EXTENSION_NAME)" ; \ - echo $$final_scheme ; \ - fi; \ - xcodebuild test -workspace $(PROJECT_NAME).xcworkspace -scheme "$$final_scheme" -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath build/out -resultBundlePath iosresults.xcresult -enableCodeCoverage YES ADB_SKIP_LINT=YES - -test-tvos: clean-tvos-test-files - @echo "######################################################################" - @echo "### Testing tvOS" - @echo "######################################################################" - @echo "List of available shared Schemes in xcworkspace" - xcodebuild -workspace $(PROJECT_NAME).xcworkspace -list - final_scheme=""; \ - if xcodebuild -workspace $(PROJECT_NAME).xcworkspace -list | grep -q "($(PROJECT_NAME) project)"; \ - then \ - final_scheme="$(EXTENSION_NAME) ($(PROJECT_NAME) project)" ; \ - echo $$final_scheme ; \ - else \ - final_scheme="$(EXTENSION_NAME)" ; \ - echo $$final_scheme ; \ - fi; \ - xcodebuild test -workspace $(PROJECT_NAME).xcworkspace -scheme "$$final_scheme" -destination 'platform=tvOS Simulator,name=Apple TV' -derivedDataPath build/out -resultBundlePath tvosresults.xcresult -enableCodeCoverage YES ADB_SKIP_LINT=YES +test: unit-test-ios functional-test-ios unit-test-tvos functional-test-tvos + +unit-test-ios: + @echo "######################################################################" + @echo "### Unit Testing iOS" + @echo "######################################################################" + rm -rf build/reports/iosUnitResults.xcresult + xcodebuild test -workspace $(PROJECT_NAME).xcworkspace -scheme "UnitTests" -destination "platform=iOS Simulator,name=iPhone 14" -derivedDataPath build/out -resultBundlePath build/reports/iosUnitResults.xcresult -enableCodeCoverage YES ADB_SKIP_LINT=YES + +functional-test-ios: + @echo "######################################################################" + @echo "### Functional Testing iOS" + @echo "######################################################################" + rm -rf build/reports/iosFunctionalResults.xcresult + xcodebuild test -workspace $(PROJECT_NAME).xcworkspace -scheme "FunctionalTests" -destination "platform=iOS Simulator,name=iPhone 14" -derivedDataPath build/out -resultBundlePath build/reports/iosFunctionalResults.xcresult -enableCodeCoverage YES ADB_SKIP_LINT=YES + +unit-test-tvos: + @echo "######################################################################" + @echo "### Unit Testing tvOS" + @echo "######################################################################" + rm -rf build/reports/tvosUnitResults.xcresult + xcodebuild test -workspace $(PROJECT_NAME).xcworkspace -scheme "UnitTests" -destination 'platform=tvOS Simulator,name=Apple TV' -derivedDataPath build/out -resultBundlePath build/reports/tvosUnitResults.xcresult -enableCodeCoverage YES ADB_SKIP_LINT=YES + +functional-test-tvos: + @echo "######################################################################" + @echo "### Functional Testing tvOS" + @echo "######################################################################" + rm -rf build/reports/tvosFunctionalResults.xcresult + xcodebuild test -workspace $(PROJECT_NAME).xcworkspace -scheme "FunctionalTests" -destination 'platform=tvOS Simulator,name=Apple TV' -derivedDataPath build/out -resultBundlePath build/reports/tvosFunctionalResults.xcresult -enableCodeCoverage YES ADB_SKIP_LINT=YES install-githook: git config core.hooksPath .githooks From b43e2c84177944ef94c6ce50e9b81b064cd77eaf Mon Sep 17 00:00:00 2001 From: Kevin Lind Date: Mon, 17 Jul 2023 15:00:25 -0700 Subject: [PATCH 5/9] Sleep after EventHub.shared.start in API unit tests --- Tests/UnitTests/EdgePublicAPITests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/UnitTests/EdgePublicAPITests.swift b/Tests/UnitTests/EdgePublicAPITests.swift index cba35430..a80aedd5 100644 --- a/Tests/UnitTests/EdgePublicAPITests.swift +++ b/Tests/UnitTests/EdgePublicAPITests.swift @@ -19,6 +19,7 @@ class EdgePublicAPITests: XCTestCase { override func setUp() { continueAfterFailure = false EventHub.shared.start() + usleep(250000) // sleep 0.25 seconds to allow EventHub to start } override func tearDown() { From 2bf8b01714f4d077621cd3b06fff58a860a11ca9 Mon Sep 17 00:00:00 2001 From: Kevin Lind <40409666+kevinlind@users.noreply.github.com> Date: Tue, 18 Jul 2023 15:43:02 -0700 Subject: [PATCH 6/9] Wait for sendEvent to return before continuing functional test. (#370) --- Tests/FunctionalTests/AEPEdgeFunctionalTests.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Tests/FunctionalTests/AEPEdgeFunctionalTests.swift b/Tests/FunctionalTests/AEPEdgeFunctionalTests.swift index 0fa83ff4..1eb653a5 100644 --- a/Tests/FunctionalTests/AEPEdgeFunctionalTests.swift +++ b/Tests/FunctionalTests/AEPEdgeFunctionalTests.swift @@ -1120,8 +1120,13 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { // Send two requests let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"]) - Edge.sendEvent(experienceEvent: experienceEvent) - sleep(2) + let expectation = XCTestExpectation(description: "Send Event completion closure") + Edge.sendEvent(experienceEvent: experienceEvent) {_ in + expectation.fulfill() + } + wait(for: [expectation], timeout: 2) + + usleep(1500000) // sleep test thread to expire received location hint Edge.sendEvent(experienceEvent: experienceEvent) // verify From 4794f82a3bd62963f7b7ccfcad2dfa27402e9b68 Mon Sep 17 00:00:00 2001 From: Kevin Lind <40409666+kevinlind@users.noreply.github.com> Date: Thu, 20 Jul 2023 13:54:49 -0700 Subject: [PATCH 7/9] Use 'large' resource class when running tests in CI (#371) * Use 'large' resource class when running tests in CI * Install Rosetta on M1 images --- .circleci/config.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9eeb489d..a18991c6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -79,6 +79,15 @@ commands: version: "16.1" device: "Apple TV" + install_rosetta_for_m1: + steps: + - run: + name: Install Rosetta for M1 + command: | + if [ "${CIRCLE_JOB}" == "test-ios" ] || [ "${CIRCLE_JOB}" == "test-tvos" ]; then + softwareupdate --install-rosetta --agree-to-license + fi + jobs: validate-code: macos: @@ -96,8 +105,11 @@ jobs: test-ios: macos: xcode: 14.1.0 # Specify the Xcode version to use + resource_class: macos.m1.large.gen1 # Use large resource for iOS tests steps: + - install_rosetta_for_m1 # needed for CodeCov uploader + - checkout - install_dependencies @@ -128,8 +140,11 @@ jobs: test-tvos: macos: xcode: 14.1.0 # Specify the Xcode version to use + resource_class: macos.m1.large.gen1 # Use large resource for tvOS tests steps: + - install_rosetta_for_m1 # needed for CodeCov uploader + - checkout - install_dependencies From fb725befe220f0ca52d266aa784d5b8dad409968 Mon Sep 17 00:00:00 2001 From: Kevin Lind Date: Thu, 20 Jul 2023 19:43:49 -0700 Subject: [PATCH 8/9] Pod Update Podfile for AEPEdge 4.1.0 --- Podfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index 5c0296de..d89caf11 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -5,7 +5,7 @@ PODS: - AEPCore (4.0.0): - AEPRulesEngine (>= 4.0.0) - AEPServices (>= 4.0.0) - - AEPEdge (4.0.0): + - AEPEdge (4.1.0): - AEPCore (>= 4.0.0) - AEPEdgeIdentity (>= 4.0.0) - AEPEdgeConsent (4.0.0): @@ -43,7 +43,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: AEPAssurance: 4fa3138ddd7308c1f9923570f4d2b0b8526a916f AEPCore: dd7cd69696c768c610e6adc0307032985a381c7e - AEPEdge: ffea1ada1e81c9cb239aac694efa5c8635b50c1f + AEPEdge: 684a60362d17349fab5da48d6e4f965849ed9ad2 AEPEdgeConsent: 54c1b6a30a3d646e3d4bc4bae1713755422b471e AEPEdgeIdentity: c2396b9119abd6eb530ea11efc58ec019b163bd4 AEPRulesEngine: 458450a34922823286ead045a0c2bd8c27e224c6 From a0068858ba190f58e1799aefbef102ac13a7d99c Mon Sep 17 00:00:00 2001 From: Tim Kim <95260439+timkimadobe@users.noreply.github.com> Date: Fri, 21 Jul 2023 11:24:47 -0700 Subject: [PATCH 9/9] Feature/upstream integration tests (#373) * Edge Network (Konductor) integration test with GitHub Action workflow (#321) * initial e2e test * update pods for e2e test target * update workflow config settings * implement pods cache to prevent high macos usage time * Fix method typo in functional test helper * test for locationHint * Update integration test case * Remove specific hint value * update action to include build cache step * update buildcache to upload logs for debug * fix flag typo * trying different cache system * try with command line settings * update to include key * fix var bug in makefile command * remove playground * Remove build cache testing * passing env vars to test target working * Update e2e flow action, yaml, and test scheme for konductor env * Fix action command to updated name * Updating action text and defaults * Update make command for e2e test to improve documentation add conditional check warning output for KONDUCTOR_ENV * update test names * update job name * Update env var extraction logic * update test to use edge location hint * update action to have location hint options * fix github action var name * Update makefile env vars and include edge location hint * update makefile integration test docs test colon in test run echo update env var extraction documentation in test file * update test output to use raw string values * test using shell if else for job run id * fix spacing syntax * Update to user proper Edge Network name * Update to EDGE_ENVIRONMENT * Small doc updates * Update documentation to correct links * Update dropdown description * Add quiet flag to xcodebuild command for integration test * Rename integration test make command Reorder position to under tvOS test * Update name to UpstreamIntegrationTests Test empty string option for dropdown * Update action script to use updated make command * Split enums into separate file, add env var extraction inits * run pod install after new target name * Remove FunctionalTestBase inheritance and unused compile sources * Add validation for network request portion * Rename and simplify IntegrationTestNetworkService to only required methods and properties * Update to use raw multiline string instead of resource files * Revert podfile lock version upgrade * Update action job name to align with makefile name * Update target name in makefile * Remove extra newlines Remove marketing version from integration test debug target build settings * Remove implementation specific documentation * Test spacing in makefile * Test remove silent option * test moving back to the bottom * test updating command name * test newline * test name change again * Revert all test name changes - the workflow error was not specifying the correct branch to run off of * Add job failure help text * Add doc comment to on failure step * Revert changes in functional test utils * Split test enums into separate files with shared util method * Clean up comments and update test method * Update EdgeEnvironment enum to have .prod default value Update usage site in test setup * Update to include xcresult visualizer tool * Update with example test failure case * Update env file ID to use helper method Move NetworkTestingDelegate to network file * Omit coverage from test results to save characters * Fix clang flag warning from cocoapods * Remove Xcode report tool step from this PR * Update result bundle path to avoid name conflicts with other tests * Add testing documentation comment hint * Flexible JSON comparison system (#332) * Implement JSON comparison system * Update to use AEPServices AnyCodable * Move assertion helpers to AnyCodableUtils Update assertion methods to accept file and line args for inline errors on test failure Update key path logic to pretty print * Create flexible validation system, with exact match pathing * Exact match wildcard example * Update general wildcard logic to apply to all elements Rename arguments and usage sites * Complete revamp of flexible json comparison system * Convert AnyCodable test assertion helpers to protocol with default implementations Update test classes to adhere to new protocol and update usages Update flexible assertion to use single method with parameter for default mode Clean up code and update documentation for helper methods Fix bug with escaped keys and add unit test case * Apply swift lint * Extract AnyCodable array extension into separate file * Remove unused test setup methods * Remove redundant test cases covered by AnyCodable unit tests * Switch from protocol to XCTestCase extension Update usages Update documentation text * Update keyPathAsString signature and usages * Simplify implementation of AnyCodable array comparison * Remove unused EventSpec * Update filenames Remove Bool return from assertEqual methods Refactor to allow single public API for assertEqual * Update flexible comparison APIs * Add additional test cases for AnyCodable unit tests * Apply swift lint formatting * Update to use positive case * Update method order Update method signature styling * Update actual condition * Update flexible text setup to propagate file and line Upgrade getCapturedRegexGroups to a test failure Upgrade alternate path index errors to TEST ERROR, add file and line * Fix incorrect find and replace * Update regex capture to handle special cases Add test cases that validate special cases * Extract regex logic into shared function * Update shared testing utilities for integration tests (#334) * Move shared test files * Refactor FunctionalTestBase and FunctionalTestNetworkService to allow for dual mode This allows for sharing common test utilities between functional and integration tests * Renaming all usages of FunctionalTestBase to updated name * Rename mock mode bool * Update method docs and cleanup code comments * Update initializer param Create separate data structs for mocked and real network responses Update connectAsync logic structure * Move XCTestCase+AnyCodableAsserts to shared test utils Update method docs for TestNetworkService Update mock bool in TestBase Move AnyCodable helper methods to shared test utils * Update TestConstants name and usages * Consolidate networkResponses into single data struct With mocked behavior controlled by mockNetworkService flag * Update FunctionalTestConst usages * Temporarily moving TestNetworkService for review * Update set get logic for network responses Consolidate awaitRequest logic Simplify NetworkRequest construction Use force unwrap for test constructions * Move NetworkRequest extension to FTNS * Integration tests - TestNetworkService mock and server API split (#337) * Move FTNS to shared test utils * Rename FTNS to TNS * Initial split of mock and server test network service implementations Before removal of network logic from TestBase * Migrate TestBase network APIs to TestNetworkService Move mock or server specific APIs to respective child classes * Update log source name Update reset test expectations logic * Remove resetting network service logic from test base * Rename delayed network response param and var Remove outdated method docs for return value * Update setup logic Update reset test expectations usage * Update network service to static immutable var Update usages to Self * Remove todo as task is wont do * Update mock response API param name * Update integration test to use static network service Also add helper comments to the purpose of the different setup methods * Rename Server to Real TNS Update base class inheritance for both mock and real TNS Update base TNS to be a shared network request helper Update usages within mock and real TNS to use new shared helper as instance var Add passthrough APIs for both mock and real TNS to access helper methods as needed Refactor static networkService in test class to instance to remove need for Self prefix Update associated setUp logic Add mock prefix to networkService in functional test for clarity * Rename mock and real network service classes * Clean up implementation Update doc comment Add implementation note for isEqual * Move shared network service logic to helper Update usage sites Remove unused import * Remove unused imports * Move NetworkRequest flatten body method into NetworkRequest extension Update usages * Update access level * Update doc class names * Remove unneeded commented code * Move testbase debug flag to per case setup Update doc comment * Refactor CompletionHandlerFunctionalTests to use MockNetworkService * Refactor EdgeConsentTests to use MockNetworkService * Add networkService reset * Add NetworkService reset * Refactor EdgePublicAPITests to use MockNetworkService * Refactor AEPEdgePathOverwriteTests Add test flow doc comments * Refactor IdentityStateFunctionalTests Move debug flag set to after setup * Refactor NoConfigFunctionalTests Update doc comment for what method is used to determine equality * Refactor SampleFunctionalTests * Apply lint autocorrect to PR files * Remove unneeded commented code * Integration test cases (#346) * Implementation notes * WIP invalid datastream test * Add test cases Complex XDM, complex data Preset location hint expected error dataset ID expected error invalid location hint * Update setExpectation API to accept only NetworkRequest Update docs Update usages of updated API Update networkService API usages in integration test class * Clean up code comments * Fix for unpassed params * Update class docs Add test note on how JSON comparison system works * Apply swift lint autocorrect * Rename vars and add additional assert on response count Add changes lost in rebase * Updated flexible JSON comparison notes Refactored assertEdgeResonseHandle API and usages Refactored location hint test case to use single variable for location hint value * Update network service class docs * Update first test case to have two event validation * Remove unused API and refactor used API to call helper directly * Update assertEdgeResponseHandle signature and usages Create new helper methods to construct interact URLs with location hint * Add strict count assertion for all responses Update matchedResponses name to be uniform across test suite * Update assertEdgeResponseError method to use getDispatchedEventsWith directly Revert refactor of assertEdgeResponseHandle and usages Add expectedCount argument to both APIs and update assertion logic to check all events Add org ID related exact match assertions and exact match paths (non-wildcard) Add exact match requirement for error type URL * Remove unused API (actually) * Update test cases to use direct assertions * Refactor assert*Match APIs to non-nil expected AnyCodable * Update assertExpectedEvents API to allow for custom timeout period * Update to use conditional check on location hint if initial value is set Simplify 2x event handle test case Add longer timeout for event expectations * Update test case setup Remove outdated API comment * Remove unused helper API * Refactor functional tests from dev branch to use new testing utilities * Use longer extension registration timeout * Add per test case teardown network service reset * Test extending teardown sleep duration * Add higher timeout value for startup event validation * Add sleep to allow more buffer for listener registration * Extend setup timeout to 10s * Restore original teardown sleep time * Remove sleep from test case * Change order of operations for test teardown * Test unregistering instrumented extension in teardown process * Add sleep to startup process to allow event hub setup time * Add mock network service teardown to all functional tests * Trigger CI workflow * Revert changes in TestBase teardown * Integration testing workflow update (#349) * Add new integration test job * Update device for integration to 8 * Update integration test job name * Remove extra space * Remove code coverage upload from integration job * Test job conditional in circleci workflow * Add non conditional step * Update triggering branches * Update initiating branch name condition to staging * test fetching branch name from fork * remove spaces from command * Test extract both base and head branch names * Add semicolons * Update extraction commands * Only fetch base branch name Make integration setup steps non conditional * Update job trigger conditions * Update simulator for integration test to 14 * Update integration test workflow to use conditional at job level * Update integration test job Xcode version * Fix conditional in integration test job * Move checkout step to non-conditional level * Test current branch name also triggers integration test workflow * Test branch name 2 * Remove test branch name * Add main branch to check * Update deployment version for integration target to 11 * Make integration test make command result output consistent with other tests Add removal of existing old results part of the job like other tests * Refactor to remove branch name conditionals from job level and use workflow job branch filter * Test workflow job filter when set on current branch * Update filter criteria * Remove test branch filter * Reorder jobs so conditional ones come after mandatory * Update integration test target in podfile --------- Co-authored-by: Emilia Dobrin <33132425+emdobrin@users.noreply.github.com> --- .circleci/config.yml | 43 +- .github/workflows/update_versions.yml | 2 +- .../workflows/upstream-integration-test.yml | 78 ++ AEPEdge.xcodeproj/project.pbxproj | 350 ++++++- .../UpstreamIntegrationTests.xcscheme | 73 ++ Makefile | 26 + Podfile | 7 + Podfile.lock | 2 +- .../AEPEdgeFunctionalTests.swift | 390 ++++---- .../CompletionHandlerFunctionalTests.swift | 57 +- Tests/FunctionalTests/Edge+ConsentTests.swift | 118 +-- .../FunctionalTests/Edge+PublicAPITests.swift | 80 +- .../EdgePathOverwriteTests.swift | 78 +- .../IdentityStateFunctionalTests.swift | 53 +- ...etworkResponseHandlerFunctionalTests.swift | 142 +-- .../NoConfigFunctionalTests.swift | 37 +- .../SampleFunctionalTests.swift | 49 +- .../util/FakeIdentityExtension.swift | 2 +- .../util/FunctionalTestNetworkService.swift | 137 --- .../util => TestUtils}/CountDownLatch.swift | 0 .../FileManager+Testable.swift | 0 .../InstrumentedExtension.swift | 18 +- Tests/TestUtils/MockNetworkService.swift | 101 ++ Tests/TestUtils/NetworkRequestHelper.swift | 206 ++++ Tests/TestUtils/RealNetworkService.swift | 54 ++ .../TestBase.swift} | 166 +--- .../TestConstants.swift} | 4 +- .../XCTestCase+AnyCodableAsserts.swift | 818 ++++++++++++++++ .../AnyCodableAssertsTests.swift | 890 ++++++++++++++++++ .../UpstreamIntegrationTests.swift | 627 ++++++++++++ .../util/AnyCodable+Array.swift | 44 + .../util/EdgeEnvironment.swift | 33 + .../util/EdgeLocationHint.swift | 40 + .../util/EnumUtils.swift | 32 + 34 files changed, 3952 insertions(+), 805 deletions(-) create mode 100644 .github/workflows/upstream-integration-test.yml create mode 100644 AEPEdge.xcodeproj/xcshareddata/xcschemes/UpstreamIntegrationTests.xcscheme delete mode 100644 Tests/FunctionalTests/util/FunctionalTestNetworkService.swift rename Tests/{FunctionalTests/util => TestUtils}/CountDownLatch.swift (100%) rename Tests/{FunctionalTests/util => TestUtils}/FileManager+Testable.swift (100%) rename Tests/{FunctionalTests/util => TestUtils}/InstrumentedExtension.swift (82%) create mode 100644 Tests/TestUtils/MockNetworkService.swift create mode 100644 Tests/TestUtils/NetworkRequestHelper.swift create mode 100644 Tests/TestUtils/RealNetworkService.swift rename Tests/{FunctionalTests/util/FunctionalTestBase.swift => TestUtils/TestBase.swift} (52%) rename Tests/{FunctionalTests/util/FunctionalTestConst.swift => TestUtils/TestConstants.swift} (96%) create mode 100644 Tests/TestUtils/XCTestCase+AnyCodableAsserts.swift create mode 100644 Tests/UpstreamIntegrationTests/AnyCodableAssertsTests.swift create mode 100644 Tests/UpstreamIntegrationTests/UpstreamIntegrationTests.swift create mode 100644 Tests/UpstreamIntegrationTests/util/AnyCodable+Array.swift create mode 100644 Tests/UpstreamIntegrationTests/util/EdgeEnvironment.swift create mode 100644 Tests/UpstreamIntegrationTests/util/EdgeLocationHint.swift create mode 100644 Tests/UpstreamIntegrationTests/util/EnumUtils.swift diff --git a/.circleci/config.yml b/.circleci/config.yml index a18991c6..bb27f52b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ orbs: codecov: codecov/codecov@3.2.5 macos: circleci/macos@2 -# Workflows orchestrate a set of jobs to be run; +# Workflows orchestrate a set of jobs to be run workflows: build-test: jobs: @@ -18,9 +18,23 @@ workflows: - test-tvos: requires: - validate-code + # The following jobs are conditional on branch name + - test-ios-integration: + requires: + - validate-code + filters: + branches: + only: + - main + - staging - build_xcframework_and_app: requires: - validate-code + filters: + branches: + only: + - main + - staging commands: install_dependencies: @@ -137,6 +151,21 @@ jobs: upload_name: Coverage report for iOS functional tests xtra_args: -c -v --xc --xp build/reports/iosFunctionalResults.xcresult + test-ios-integration: + macos: + xcode: 14.1.0 # Specify the Xcode version to use + + steps: + - checkout + + - install_dependencies + + - prestart_ios_simulator + + - run: + name: Run iOS integration tests + command: make test-integration-upstream + test-tvos: macos: xcode: 14.1.0 # Specify the Xcode version to use @@ -178,18 +207,12 @@ jobs: steps: - checkout - # verify XCFramework archive builds + # Verify XCFramework archive builds - run: name: Build XCFramework - command: | - if [ "${CIRCLE_BRANCH}" == "main" ]; then - make archive - fi + command: make archive # verify test app builds - run: name: Build Test App - command: | - if [ "${CIRCLE_BRANCH}" == "main" ]; then - make build-app - fi + command: make build-app diff --git a/.github/workflows/update_versions.yml b/.github/workflows/update_versions.yml index 8efb8706..4bd7a9ab 100644 --- a/.github/workflows/update_versions.yml +++ b/.github/workflows/update_versions.yml @@ -9,7 +9,7 @@ on: # Inputs the workflow accepts. inputs: version: - description: 'New version to use for the extension. Example: 1.5.2' + description: 'New version to use for the extension. Example: 1.5.2' required: true branch: diff --git a/.github/workflows/upstream-integration-test.yml b/.github/workflows/upstream-integration-test.yml new file mode 100644 index 00000000..406fe014 --- /dev/null +++ b/.github/workflows/upstream-integration-test.yml @@ -0,0 +1,78 @@ +# Action to execute upstream integration tests - Edge Network (Konductor) +name: Integration Tests + +# Controls when the action will run. Workflow runs when manually triggered using the UI +# or API. +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to use when running integration tests' + required: false + default: 'main' + id: + description: 'Identifier for the run (optional)' + required: false + environment: + type: choice + description: 'Edge Network environment to test' + required: true + default: 'prod' + options: + - prod + - pre-prod + - int + edge-location-hint: + type: choice + description: 'Edge location hint to set before each test (optional)' + required: false + default: '' + options: + - '' # Interpreted in the test code as no preset location hint; any non-valid location hint string is interpreted this way + - 'or2' + - 'va6' + - 'irl1' + - 'ind1' + - 'jpn3' + - 'sgp3' + - 'aus3' + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + test-integration-upstream: + # The type of runner that the job will run on + runs-on: macos-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + + - name: Job run identifier ${{ github.event.inputs.id }} + run: | + if [ -z "${{ github.event.inputs.id }}" ]; then \ + echo No job run identifier was set. + else + echo 'Job run identifier is:' ${{ inputs.id }} + fi; + + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.event.inputs.branch }} + + - name: Cache Cocoapods + uses: actions/cache@v3 + with: + path: Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + + # Runs a single command using the runners shell + - name: Execute Edge Network integration tests + run: make test-integration-upstream EDGE_ENVIRONMENT=${{ github.event.inputs.environment }} EDGE_LOCATION_HINT=${{ github.event.inputs.edge-location-hint }} + + # Potential workflow solutions on job failure + - name: On failure + if: ${{ failure() }} + run: | + echo 'Job used branch: ' ${{ github.event.inputs.branch }}. Please make sure this is the branch to run off of. diff --git a/AEPEdge.xcodeproj/project.pbxproj b/AEPEdge.xcodeproj/project.pbxproj index 679afc23..ce801256 100644 --- a/AEPEdge.xcodeproj/project.pbxproj +++ b/AEPEdge.xcodeproj/project.pbxproj @@ -42,11 +42,36 @@ 2EDD05C6286EAA7000229CB2 /* AEPEdge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D444995F2519506B0093B364 /* AEPEdge.framework */; }; 2EDD05CA286EAA7000229CB2 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D4D5B5852432977F00CAB6E4 /* Preview Assets.xcassets */; }; 2EDD05CB286EAA7000229CB2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D4D5B5822432977F00CAB6E4 /* Assets.xcassets */; }; - 2EDFBCF02899B7CD00D22B25 /* EdgePathOverwriteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EDFBCEF2899B7CD00D22B25 /* EdgePathOverwriteTests.swift */; }; 423B5033D43ADB49049383FB /* Pods_FunctionalTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B13510405D14B13EFD1B58A /* Pods_FunctionalTests.framework */; }; + 4CBEE5CA29D637170084BC50 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF947FA1244587520057A6CC /* TestUtils.swift */; }; + 4CBEE5CB29D637170084BC50 /* EventHub+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49A628E250AE4F500B7C4A3 /* EventHub+Test.swift */; }; + 4CBEE5CC29D637170084BC50 /* UserDefaults+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49A6293250AEEF000B7C4A3 /* UserDefaults+Test.swift */; }; + 4CBEE5CD29D637170084BC50 /* CountDownLatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44BBB74248DC54F00775DAC /* CountDownLatch.swift */; }; + 4CBEE5D829D637170084BC50 /* AEPEdge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D444995F2519506B0093B364 /* AEPEdge.framework */; }; + 4CBEE5E629D637E90084BC50 /* UpstreamIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE5E229D637E90084BC50 /* UpstreamIntegrationTests.swift */; }; + 4CBEE5EB29D63B180084BC50 /* EnumUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE5EA29D63B180084BC50 /* EnumUtils.swift */; }; + 4CBEE5EC29D648F90084BC50 /* FileManager+Testable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21888AF22555F9FC005677ED /* FileManager+Testable.swift */; }; + 4CBEE5EF29DB952E0084BC50 /* EdgeLocationHint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE5EE29DB952E0084BC50 /* EdgeLocationHint.swift */; }; + 4CBEE5F129DB953C0084BC50 /* EdgeEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE5F029DB953C0084BC50 /* EdgeEnvironment.swift */; }; + 4CBEE5FA29FB19600084BC50 /* AnyCodable+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE5F929FB19600084BC50 /* AnyCodable+Array.swift */; }; + 4CBEE5FB29FB47170084BC50 /* TestBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46553FC246C9D1700A150E2 /* TestBase.swift */; }; + 4CBEE5FC29FB47270084BC50 /* InstrumentedExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4655401246CD0B000A150E2 /* InstrumentedExtension.swift */; }; + 4CBEE5FD29FB47270084BC50 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A473C52485938D00D31710 /* TestConstants.swift */; }; + 4CBEE6002A02E2A90084BC50 /* XCTestCase+AnyCodableAsserts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE5FF2A02E2A90084BC50 /* XCTestCase+AnyCodableAsserts.swift */; }; + 4CBEE6022A02E2B70084BC50 /* AnyCodableAssertsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE6012A02E2B70084BC50 /* AnyCodableAssertsTests.swift */; }; + 4CBEE6032A0B47B00084BC50 /* NetworkRequestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4EED36B2489ACE200D4815C /* NetworkRequestHelper.swift */; }; + 4CBEE6052A0C484E0084BC50 /* MockNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE6042A0C484E0084BC50 /* MockNetworkService.swift */; }; + 4CBEE6072A0C4B610084BC50 /* RealNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE6062A0C4B610084BC50 /* RealNetworkService.swift */; }; + 4CBEE6152A156FB20084BC50 /* CompletionHandlerFunctionalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE60D2A156FB10084BC50 /* CompletionHandlerFunctionalTests.swift */; }; + 4CBEE6192A1571DC0084BC50 /* Edge+ConsentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE6182A1571DC0084BC50 /* Edge+ConsentTests.swift */; }; + 4CBEE61B2A1572E50084BC50 /* Edge+PublicAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE61A2A1572E50084BC50 /* Edge+PublicAPITests.swift */; }; + 4CBEE61D2A1574CE0084BC50 /* EdgePathOverwriteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE61C2A1574CE0084BC50 /* EdgePathOverwriteTests.swift */; }; + 4CBEE61F2A15764B0084BC50 /* IdentityStateFunctionalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE61E2A15764B0084BC50 /* IdentityStateFunctionalTests.swift */; }; + 4CBEE6212A1577660084BC50 /* NetworkResponseHandlerFunctionalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE6202A1577660084BC50 /* NetworkResponseHandlerFunctionalTests.swift */; }; + 4CBEE6232A1577E40084BC50 /* NoConfigFunctionalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE6222A1577E40084BC50 /* NoConfigFunctionalTests.swift */; }; + 4CBEE6252A157CB40084BC50 /* SampleFunctionalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBEE6242A157CB40084BC50 /* SampleFunctionalTests.swift */; }; 783A5F4E903F9B6EABA50D23 /* Pods_UnitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DA0155731FA82D43DA47B93 /* Pods_UnitTests.framework */; }; BF024B7924C6238C002131E9 /* FakeIdentityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF024B7824C6238C002131E9 /* FakeIdentityExtension.swift */; }; - BF0C0935246120B200892B5D /* NetworkResponseHandlerFunctionalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D5B53524325AB700CAB6E4 /* NetworkResponseHandlerFunctionalTests.swift */; }; BF0C09482465E42100892B5D /* TestableEdge.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C09472465E42100892B5D /* TestableEdge.swift */; }; BF0F924F27476E6C00378913 /* ImplementationDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0F924E27476E6C00378913 /* ImplementationDetails.swift */; }; BF0F92522756A57800378913 /* ImplementationDetailsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0F92502756A3EF00378913 /* ImplementationDetailsTests.swift */; }; @@ -62,7 +87,6 @@ BF947FA0244569190057A6CC /* EdgeRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF947F9F244569190057A6CC /* EdgeRequestTests.swift */; }; BF947FA4244613FB0057A6CC /* RequestMetadataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF947FA3244613FB0057A6CC /* RequestMetadataTests.swift */; }; BFEEB5AB28D4E35D00B668B8 /* EdgePublicAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D45F2E8125EE1987000AC350 /* EdgePublicAPITests.swift */; }; - BFEEB5AD28D4E3E900B668B8 /* Edge+PublicAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEEB5AC28D4E3E900B668B8 /* Edge+PublicAPITests.swift */; }; D4000F35245A53FB0052C536 /* EdgeNetworkServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4000F34245A53FB0052C536 /* EdgeNetworkServiceTests.swift */; }; D4145F4025D1D3200019EA4C /* EdgeHitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4145F3F25D1D3200019EA4C /* EdgeHitTests.swift */; }; D4145FB525D5D0430019EA4C /* Event+Edge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4145FB425D5D0430019EA4C /* Event+Edge.swift */; }; @@ -75,7 +99,6 @@ D447C82725C3EF0500FE12E5 /* EdgeConsentPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = D447C82625C3EF0500FE12E5 /* EdgeConsentPayload.swift */; }; D44BBB75248DC54F00775DAC /* CountDownLatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44BBB74248DC54F00775DAC /* CountDownLatch.swift */; }; D44BBB982492F5C900775DAC /* UnitTestAssertions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44BBB972492F5C900775DAC /* UnitTestAssertions.swift */; }; - D45F2E5525E9E06C000AC350 /* Edge+ConsentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D45F2E5425E9E06C000AC350 /* Edge+ConsentTests.swift */; }; D45F2E6C25ED8EB1000AC350 /* TestableExtensionRuntime.swift in Sources */ = {isa = PBXBuildFile; fileRef = D45F2E6B25ED8EB1000AC350 /* TestableExtensionRuntime.swift */; }; D45F2E7425EDD2EF000AC350 /* MockDataQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D45F2E7325EDD2EF000AC350 /* MockDataQueue.swift */; }; D45F2E7825EDE0D6000AC350 /* MockHitProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D45F2E7725EDE0D6000AC350 /* MockHitProcessor.swift */; }; @@ -84,17 +107,14 @@ D45F2E8925EE214A000AC350 /* EdgeExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D45F2E6F25ED8EE3000AC350 /* EdgeExtensionTests.swift */; }; D45F2E8E25EE2166000AC350 /* TestableExtensionRuntime.swift in Sources */ = {isa = PBXBuildFile; fileRef = D45F2E6B25ED8EB1000AC350 /* TestableExtensionRuntime.swift */; }; D46553F7246A384900A150E2 /* NetworkResponseHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46553F6246A384900A150E2 /* NetworkResponseHandlerTests.swift */; }; - D4655400246CBF5E00A150E2 /* FunctionalTestBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46553FC246C9D1700A150E2 /* FunctionalTestBase.swift */; }; + D4655400246CBF5E00A150E2 /* TestBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46553FC246C9D1700A150E2 /* TestBase.swift */; }; D4655402246CD0B000A150E2 /* InstrumentedExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4655401246CD0B000A150E2 /* InstrumentedExtension.swift */; }; D48CBD2224A17EEA00A24BD8 /* TestXDMSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48CBD2124A17EEA00A24BD8 /* TestXDMSchema.swift */; }; D49A628F250AE4F500B7C4A3 /* EventHub+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49A628E250AE4F500B7C4A3 /* EventHub+Test.swift */; }; D49A6290250AE4F500B7C4A3 /* EventHub+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49A628E250AE4F500B7C4A3 /* EventHub+Test.swift */; }; D49A6294250AEEF000B7C4A3 /* UserDefaults+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49A6293250AEEF000B7C4A3 /* UserDefaults+Test.swift */; }; D49A6295250AEEF000B7C4A3 /* UserDefaults+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49A6293250AEEF000B7C4A3 /* UserDefaults+Test.swift */; }; - D49A6298250AF39C00B7C4A3 /* NoConfigFunctionalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C092324608EEC00892B5D /* NoConfigFunctionalTests.swift */; }; - D49A6299250AF42000B7C4A3 /* IdentityStateFunctionalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF024B7E24C62BED002131E9 /* IdentityStateFunctionalTests.swift */; }; - D49A62DC250C311100B7C4A3 /* SampleFunctionalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49A62DB250C311100B7C4A3 /* SampleFunctionalTests.swift */; }; - D4A473C7248595CA00D31710 /* FunctionalTestConst.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A473C52485938D00D31710 /* FunctionalTestConst.swift */; }; + D4A473C7248595CA00D31710 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A473C52485938D00D31710 /* TestConstants.swift */; }; D4A473CB2485A8F300D31710 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF947FA1244587520057A6CC /* TestUtils.swift */; }; D4A473CD2485AA3D00D31710 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF947FA1244587520057A6CC /* TestUtils.swift */; }; D4A473D02485B86D00D31710 /* AEPEdgeFunctionalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A473CF2485B86D00D31710 /* AEPEdgeFunctionalTests.swift */; }; @@ -129,19 +149,26 @@ D4D5B5832432977F00CAB6E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D4D5B5822432977F00CAB6E4 /* Assets.xcassets */; }; D4D5B5862432977F00CAB6E4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D4D5B5852432977F00CAB6E4 /* Preview Assets.xcassets */; }; D4D5B5892432977F00CAB6E4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D4D5B5872432977F00CAB6E4 /* LaunchScreen.storyboard */; }; - D4DE9B6D2592B8F900EDCD50 /* CompletionHandlerFunctionalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DE9B6B2592B8F900EDCD50 /* CompletionHandlerFunctionalTests.swift */; }; D4EC0D9725E6CEA00026EBAA /* EdgeHitQueueing+Consent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4EC0D9625E6CEA00026EBAA /* EdgeHitQueueing+Consent.swift */; }; D4EC0DBB25E73FF10026EBAA /* EdgeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4EC0DBA25E73FF10026EBAA /* EdgeState.swift */; }; - D4EED36C2489ACE200D4815C /* FunctionalTestNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4EED36B2489ACE200D4815C /* FunctionalTestNetworkService.swift */; }; + D4EED36C2489ACE200D4815C /* NetworkRequestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4EED36B2489ACE200D4815C /* NetworkRequestHelper.swift */; }; D4F74FE72447A8B800379258 /* MockNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4F74FE62447A8B800379258 /* MockNetworking.swift */; }; D4F74FE92447A92800379258 /* MockURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4F74FE82447A92800379258 /* MockURLSession.swift */; }; D4F74FEB2447A94900379258 /* MockTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4F74FEA2447A94900379258 /* MockTask.swift */; }; E97FFA8059E1B17324D8FFC6 /* Pods_AEPEdge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2E916ED29158164BF50EBF1 /* Pods_AEPEdge.framework */; }; F14D1687F07BBBB83A0A2C8E /* Pods_TestAppiOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 316EF062B0B622235F6BB384 /* Pods_TestAppiOS.framework */; }; F906B76A0F7C19B9067533AC /* Pods_TestApptvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F85212C814AA412355526B9F /* Pods_TestApptvOS.framework */; }; + FCD6B046907107F7D83E41EF /* Pods_UpstreamIntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 773BC882A27A812B96D9C711 /* Pods_UpstreamIntegrationTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 4CBEE5C329D637170084BC50 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D4D5B4DF242EBB1600CAB6E4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D444995E2519506B0093B364; + remoteInfo = AEPEdge; + }; D42F36A5251BEA7E00490C0D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D4D5B4DF242EBB1600CAB6E4 /* Project object */; @@ -227,18 +254,37 @@ 21FFBD6B2563321600B48A8F /* EdgeEventErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeEventErrorTests.swift; sourceTree = ""; }; 240E3C9824EF357100D44993 /* MockDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockDataStore.swift; sourceTree = ""; }; 2EDD05D2286EAA7000229CB2 /* TestApptvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestApptvOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 2EDFBCEF2899B7CD00D22B25 /* EdgePathOverwriteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgePathOverwriteTests.swift; sourceTree = ""; }; 316EF062B0B622235F6BB384 /* Pods_TestAppiOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TestAppiOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4CBEE5E029D637170084BC50 /* UpstreamIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UpstreamIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4CBEE5E229D637E90084BC50 /* UpstreamIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpstreamIntegrationTests.swift; sourceTree = ""; }; + 4CBEE5EA29D63B180084BC50 /* EnumUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnumUtils.swift; sourceTree = ""; }; + 4CBEE5EE29DB952E0084BC50 /* EdgeLocationHint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeLocationHint.swift; sourceTree = ""; }; + 4CBEE5F029DB953C0084BC50 /* EdgeEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeEnvironment.swift; sourceTree = ""; }; + 4CBEE5F929FB19600084BC50 /* AnyCodable+Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AnyCodable+Array.swift"; sourceTree = ""; }; + 4CBEE5FF2A02E2A90084BC50 /* XCTestCase+AnyCodableAsserts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTestCase+AnyCodableAsserts.swift"; sourceTree = ""; }; + 4CBEE6012A02E2B70084BC50 /* AnyCodableAssertsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyCodableAssertsTests.swift; sourceTree = ""; }; + 4CBEE6042A0C484E0084BC50 /* MockNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNetworkService.swift; sourceTree = ""; }; + 4CBEE6062A0C4B610084BC50 /* RealNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealNetworkService.swift; sourceTree = ""; }; + 4CBEE60D2A156FB10084BC50 /* CompletionHandlerFunctionalTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompletionHandlerFunctionalTests.swift; sourceTree = ""; }; + 4CBEE6182A1571DC0084BC50 /* Edge+ConsentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Edge+ConsentTests.swift"; sourceTree = ""; }; + 4CBEE61A2A1572E50084BC50 /* Edge+PublicAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Edge+PublicAPITests.swift"; sourceTree = ""; }; + 4CBEE61C2A1574CE0084BC50 /* EdgePathOverwriteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EdgePathOverwriteTests.swift; sourceTree = ""; }; + 4CBEE61E2A15764B0084BC50 /* IdentityStateFunctionalTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityStateFunctionalTests.swift; sourceTree = ""; }; + 4CBEE6202A1577660084BC50 /* NetworkResponseHandlerFunctionalTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkResponseHandlerFunctionalTests.swift; sourceTree = ""; }; + 4CBEE6222A1577E40084BC50 /* NoConfigFunctionalTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoConfigFunctionalTests.swift; sourceTree = ""; }; + 4CBEE6242A157CB40084BC50 /* SampleFunctionalTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleFunctionalTests.swift; sourceTree = ""; }; 5DA0155731FA82D43DA47B93 /* Pods_UnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_UnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5E8ABC044CA69B7B3B6DA2B8 /* Pods-UpstreamIntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UpstreamIntegrationTests.debug.xcconfig"; path = "Target Support Files/Pods-UpstreamIntegrationTests/Pods-UpstreamIntegrationTests.debug.xcconfig"; sourceTree = ""; }; 7276A55D4B28344E8ED9D27B /* Pods-TestAppiOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TestAppiOS.debug.xcconfig"; path = "Target Support Files/Pods-TestAppiOS/Pods-TestAppiOS.debug.xcconfig"; sourceTree = ""; }; + 773BC882A27A812B96D9C711 /* Pods_UpstreamIntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_UpstreamIntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7D2276E22F20223193235527 /* Pods-AEPEdge.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AEPEdge.release.xcconfig"; path = "Target Support Files/Pods-AEPEdge/Pods-AEPEdge.release.xcconfig"; sourceTree = ""; }; + 85E2DC7CBF2801B140FC24A9 /* Pods-KonductorIntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KonductorIntegrationTests.debug.xcconfig"; path = "Target Support Files/Pods-KonductorIntegrationTests/Pods-KonductorIntegrationTests.debug.xcconfig"; sourceTree = ""; }; 9CF4FC9E0341518F7ED94AED /* Pods-FunctionalTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FunctionalTests.release.xcconfig"; path = "Target Support Files/Pods-FunctionalTests/Pods-FunctionalTests.release.xcconfig"; sourceTree = ""; }; A03EED428A1ABF9130F3BECA /* Pods-TestApptvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TestApptvOS.release.xcconfig"; path = "Target Support Files/Pods-TestApptvOS/Pods-TestApptvOS.release.xcconfig"; sourceTree = ""; }; + AF07B0197B2EA52E67BC99A6 /* Pods-KonductorIntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KonductorIntegrationTests.release.xcconfig"; path = "Target Support Files/Pods-KonductorIntegrationTests/Pods-KonductorIntegrationTests.release.xcconfig"; sourceTree = ""; }; B265C6B876BCBE382B6B8671 /* Pods-UnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.release.xcconfig"; path = "Target Support Files/Pods-UnitTests/Pods-UnitTests.release.xcconfig"; sourceTree = ""; }; B2A0C5E6F49BE2565DF1CA65 /* Pods-TestApptvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TestApptvOS.debug.xcconfig"; path = "Target Support Files/Pods-TestApptvOS/Pods-TestApptvOS.debug.xcconfig"; sourceTree = ""; }; BF024B7824C6238C002131E9 /* FakeIdentityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeIdentityExtension.swift; sourceTree = ""; }; - BF024B7E24C62BED002131E9 /* IdentityStateFunctionalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityStateFunctionalTests.swift; sourceTree = ""; }; - BF0C092324608EEC00892B5D /* NoConfigFunctionalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoConfigFunctionalTests.swift; sourceTree = ""; }; BF0C09472465E42100892B5D /* TestableEdge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableEdge.swift; sourceTree = ""; }; BF0F924E27476E6C00378913 /* ImplementationDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImplementationDetails.swift; sourceTree = ""; }; BF0F92502756A3EF00378913 /* ImplementationDetailsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImplementationDetailsTests.swift; sourceTree = ""; }; @@ -263,8 +309,9 @@ BF947F9F244569190057A6CC /* EdgeRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeRequestTests.swift; sourceTree = ""; }; BF947FA1244587520057A6CC /* TestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = ""; }; BF947FA3244613FB0057A6CC /* RequestMetadataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestMetadataTests.swift; sourceTree = ""; }; - BFEEB5AC28D4E3E900B668B8 /* Edge+PublicAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Edge+PublicAPITests.swift"; sourceTree = ""; }; C6F54D018682F0FF8E74A16F /* 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 = ""; }; + C71524CA05BF6E5102E01187 /* Pods-E2EFunctionalTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-E2EFunctionalTests.debug.xcconfig"; path = "Target Support Files/Pods-E2EFunctionalTests/Pods-E2EFunctionalTests.debug.xcconfig"; sourceTree = ""; }; + C9510E11CC3DB2C62D9D17C0 /* Pods-UpstreamIntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UpstreamIntegrationTests.release.xcconfig"; path = "Target Support Files/Pods-UpstreamIntegrationTests/Pods-UpstreamIntegrationTests.release.xcconfig"; sourceTree = ""; }; D2E916ED29158164BF50EBF1 /* Pods_AEPEdge.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AEPEdge.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D4000F302459E2080052C536 /* NetworkResponseHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkResponseHandler.swift; sourceTree = ""; }; D4000F322459E2300052C536 /* EdgeNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeNetworkService.swift; sourceTree = ""; }; @@ -286,7 +333,6 @@ D447C82625C3EF0500FE12E5 /* EdgeConsentPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeConsentPayload.swift; sourceTree = ""; }; D44BBB74248DC54F00775DAC /* CountDownLatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountDownLatch.swift; sourceTree = ""; }; D44BBB972492F5C900775DAC /* UnitTestAssertions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitTestAssertions.swift; sourceTree = ""; }; - D45F2E5425E9E06C000AC350 /* Edge+ConsentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Edge+ConsentTests.swift"; sourceTree = ""; }; D45F2E6B25ED8EB1000AC350 /* TestableExtensionRuntime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableExtensionRuntime.swift; sourceTree = ""; }; D45F2E6F25ED8EE3000AC350 /* EdgeExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeExtensionTests.swift; sourceTree = ""; }; D45F2E7325EDD2EF000AC350 /* MockDataQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDataQueue.swift; sourceTree = ""; }; @@ -295,14 +341,13 @@ D46553F6246A384900A150E2 /* NetworkResponseHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkResponseHandlerTests.swift; sourceTree = ""; }; D46553F8246B43B900A150E2 /* EdgeEventHandle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeEventHandle.swift; sourceTree = ""; }; D46553FA246B6FBC00A150E2 /* EdgeEventError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeEventError.swift; sourceTree = ""; }; - D46553FC246C9D1700A150E2 /* FunctionalTestBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionalTestBase.swift; sourceTree = ""; }; + D46553FC246C9D1700A150E2 /* TestBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestBase.swift; sourceTree = ""; }; D4655401246CD0B000A150E2 /* InstrumentedExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstrumentedExtension.swift; sourceTree = ""; }; D48CBD2124A17EEA00A24BD8 /* TestXDMSchema.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestXDMSchema.swift; sourceTree = ""; }; D4967534251130D3001F5F95 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D49A628E250AE4F500B7C4A3 /* EventHub+Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EventHub+Test.swift"; sourceTree = ""; }; D49A6293250AEEF000B7C4A3 /* UserDefaults+Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Test.swift"; sourceTree = ""; }; - D49A62DB250C311100B7C4A3 /* SampleFunctionalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleFunctionalTests.swift; sourceTree = ""; }; - D4A473C52485938D00D31710 /* FunctionalTestConst.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionalTestConst.swift; sourceTree = ""; }; + D4A473C52485938D00D31710 /* TestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = ""; }; D4A473CF2485B86D00D31710 /* AEPEdgeFunctionalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AEPEdgeFunctionalTests.swift; sourceTree = ""; }; D4A81DEB25658F0D0042B00A /* CompletionHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionHandlerTests.swift; sourceTree = ""; }; D4AB2D7A2808894800DC39CF /* QueryOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryOptions.swift; sourceTree = ""; }; @@ -311,7 +356,6 @@ D4D5B5212432599B00CAB6E4 /* UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D4D5B5252432599B00CAB6E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D4D5B53324325AB700CAB6E4 /* FunctionalTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FunctionalTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - D4D5B53524325AB700CAB6E4 /* NetworkResponseHandlerFunctionalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkResponseHandlerFunctionalTests.swift; sourceTree = ""; }; D4D5B53724325AB700CAB6E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D4D5B57A2432977F00CAB6E4 /* TestAppiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestAppiOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; D4D5B57C2432977F00CAB6E4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -330,13 +374,14 @@ D4D66A5A25190B9800B4866D /* AEPCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AEPCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D4D66A7425190C8400B4866D /* AEPCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AEPCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D4D66AA2251910FB00B4866D /* AEPEdge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AEPEdge.h; sourceTree = ""; }; - D4DE9B6B2592B8F900EDCD50 /* CompletionHandlerFunctionalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionHandlerFunctionalTests.swift; sourceTree = ""; }; D4EC0D9625E6CEA00026EBAA /* EdgeHitQueueing+Consent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EdgeHitQueueing+Consent.swift"; sourceTree = ""; }; D4EC0DBA25E73FF10026EBAA /* EdgeState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeState.swift; sourceTree = ""; }; - D4EED36B2489ACE200D4815C /* FunctionalTestNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionalTestNetworkService.swift; sourceTree = ""; }; + D4EED36B2489ACE200D4815C /* NetworkRequestHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkRequestHelper.swift; sourceTree = ""; }; D4F74FE62447A8B800379258 /* MockNetworking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNetworking.swift; sourceTree = ""; }; D4F74FE82447A92800379258 /* MockURLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLSession.swift; sourceTree = ""; }; D4F74FEA2447A94900379258 /* MockTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTask.swift; sourceTree = ""; }; + ECE21024865660B5DB78AD82 /* Pods-E2EFunctionalTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-E2EFunctionalTests.release.xcconfig"; path = "Target Support Files/Pods-E2EFunctionalTests/Pods-E2EFunctionalTests.release.xcconfig"; sourceTree = ""; }; + F6293FCD59AB7997E527BB5A /* Pods_E2EFunctionalTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_E2EFunctionalTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F85212C814AA412355526B9F /* Pods_TestApptvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TestApptvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FBFE2267C0A1FD6FB1B3B76D /* 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 = ""; }; FF1FB9C01D5DF5AEB460E9C2 /* Pods-AEPEdge.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AEPEdge.debug.xcconfig"; path = "Target Support Files/Pods-AEPEdge/Pods-AEPEdge.debug.xcconfig"; sourceTree = ""; }; @@ -352,6 +397,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4CBEE5D729D637170084BC50 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4CBEE5D829D637170084BC50 /* AEPEdge.framework in Frameworks */, + FCD6B046907107F7D83E41EF /* Pods_UpstreamIntegrationTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D444995C2519506B0093B364 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -417,20 +471,38 @@ 316EF062B0B622235F6BB384 /* Pods_TestAppiOS.framework */, F85212C814AA412355526B9F /* Pods_TestApptvOS.framework */, 5DA0155731FA82D43DA47B93 /* Pods_UnitTests.framework */, + F6293FCD59AB7997E527BB5A /* Pods_E2EFunctionalTests.framework */, + 773BC882A27A812B96D9C711 /* Pods_UpstreamIntegrationTests.framework */, ); name = Frameworks; sourceTree = ""; }; + 4CBEE5E129D637E90084BC50 /* UpstreamIntegrationTests */ = { + isa = PBXGroup; + children = ( + 4CBEE5E929D63AFA0084BC50 /* util */, + 4CBEE5E229D637E90084BC50 /* UpstreamIntegrationTests.swift */, + 4CBEE6012A02E2B70084BC50 /* AnyCodableAssertsTests.swift */, + ); + name = UpstreamIntegrationTests; + path = Tests/UpstreamIntegrationTests; + sourceTree = ""; + }; + 4CBEE5E929D63AFA0084BC50 /* util */ = { + isa = PBXGroup; + children = ( + 4CBEE5EA29D63B180084BC50 /* EnumUtils.swift */, + 4CBEE5F929FB19600084BC50 /* AnyCodable+Array.swift */, + 4CBEE5F029DB953C0084BC50 /* EdgeEnvironment.swift */, + 4CBEE5EE29DB952E0084BC50 /* EdgeLocationHint.swift */, + ); + path = util; + sourceTree = ""; + }; BF0C093B2462159400892B5D /* util */ = { isa = PBXGroup; children = ( - D44BBB74248DC54F00775DAC /* CountDownLatch.swift */, BF024B7824C6238C002131E9 /* FakeIdentityExtension.swift */, - 21888AF22555F9FC005677ED /* FileManager+Testable.swift */, - D46553FC246C9D1700A150E2 /* FunctionalTestBase.swift */, - D4A473C52485938D00D31710 /* FunctionalTestConst.swift */, - D4EED36B2489ACE200D4815C /* FunctionalTestNetworkService.swift */, - D4655401246CD0B000A150E2 /* InstrumentedExtension.swift */, BF0C09472465E42100892B5D /* TestableEdge.swift */, D48CBD2124A17EEA00A24BD8 /* TestXDMSchema.swift */, ); @@ -482,6 +554,15 @@ D4655403246DF3B000A150E2 /* TestUtils */ = { isa = PBXGroup; children = ( + 4CBEE5FF2A02E2A90084BC50 /* XCTestCase+AnyCodableAsserts.swift */, + D44BBB74248DC54F00775DAC /* CountDownLatch.swift */, + 21888AF22555F9FC005677ED /* FileManager+Testable.swift */, + D4655401246CD0B000A150E2 /* InstrumentedExtension.swift */, + D46553FC246C9D1700A150E2 /* TestBase.swift */, + D4A473C52485938D00D31710 /* TestConstants.swift */, + D4EED36B2489ACE200D4815C /* NetworkRequestHelper.swift */, + 4CBEE6062A0C4B610084BC50 /* RealNetworkService.swift */, + 4CBEE6042A0C484E0084BC50 /* MockNetworkService.swift */, D45F2E6B25ED8EB1000AC350 /* TestableExtensionRuntime.swift */, D45F2E7325EDD2EF000AC350 /* MockDataQueue.swift */, D45F2E7725EDE0D6000AC350 /* MockHitProcessor.swift */, @@ -500,6 +581,7 @@ D4D5B4E9242EBB1600CAB6E4 /* Sources */, D4D5B4F1242EBD2200CAB6E4 /* UnitTests */, D4D5B4F2242EBFAE00CAB6E4 /* FunctionalTests */, + 4CBEE5E129D637E90084BC50 /* UpstreamIntegrationTests */, D4655403246DF3B000A150E2 /* TestUtils */, D4D5B57B2432977F00CAB6E4 /* TestAppSwiftUI */, D4D5B4E8242EBB1600CAB6E4 /* Products */, @@ -516,6 +598,7 @@ D4D5B57A2432977F00CAB6E4 /* TestAppiOS.app */, D444995F2519506B0093B364 /* AEPEdge.framework */, 2EDD05D2286EAA7000229CB2 /* TestApptvOS.app */, + 4CBEE5E029D637170084BC50 /* UpstreamIntegrationTests.xctest */, ); name = Products; sourceTree = ""; @@ -576,14 +659,14 @@ children = ( BF0C093B2462159400892B5D /* util */, D4A473CF2485B86D00D31710 /* AEPEdgeFunctionalTests.swift */, - D4DE9B6B2592B8F900EDCD50 /* CompletionHandlerFunctionalTests.swift */, - D45F2E5425E9E06C000AC350 /* Edge+ConsentTests.swift */, - 2EDFBCEF2899B7CD00D22B25 /* EdgePathOverwriteTests.swift */, - BFEEB5AC28D4E3E900B668B8 /* Edge+PublicAPITests.swift */, - BF024B7E24C62BED002131E9 /* IdentityStateFunctionalTests.swift */, - D4D5B53524325AB700CAB6E4 /* NetworkResponseHandlerFunctionalTests.swift */, - BF0C092324608EEC00892B5D /* NoConfigFunctionalTests.swift */, - D49A62DB250C311100B7C4A3 /* SampleFunctionalTests.swift */, + 4CBEE60D2A156FB10084BC50 /* CompletionHandlerFunctionalTests.swift */, + 4CBEE61A2A1572E50084BC50 /* Edge+PublicAPITests.swift */, + 4CBEE61C2A1574CE0084BC50 /* EdgePathOverwriteTests.swift */, + 4CBEE6182A1571DC0084BC50 /* Edge+ConsentTests.swift */, + 4CBEE61E2A15764B0084BC50 /* IdentityStateFunctionalTests.swift */, + 4CBEE6202A1577660084BC50 /* NetworkResponseHandlerFunctionalTests.swift */, + 4CBEE6222A1577E40084BC50 /* NoConfigFunctionalTests.swift */, + 4CBEE6242A157CB40084BC50 /* SampleFunctionalTests.swift */, D4D5B53724325AB700CAB6E4 /* Info.plist */, ); name = FunctionalTests; @@ -639,6 +722,12 @@ A03EED428A1ABF9130F3BECA /* Pods-TestApptvOS.release.xcconfig */, C6F54D018682F0FF8E74A16F /* Pods-UnitTests.debug.xcconfig */, B265C6B876BCBE382B6B8671 /* Pods-UnitTests.release.xcconfig */, + C71524CA05BF6E5102E01187 /* Pods-E2EFunctionalTests.debug.xcconfig */, + ECE21024865660B5DB78AD82 /* Pods-E2EFunctionalTests.release.xcconfig */, + 85E2DC7CBF2801B140FC24A9 /* Pods-KonductorIntegrationTests.debug.xcconfig */, + AF07B0197B2EA52E67BC99A6 /* Pods-KonductorIntegrationTests.release.xcconfig */, + 5E8ABC044CA69B7B3B6DA2B8 /* Pods-UpstreamIntegrationTests.debug.xcconfig */, + C9510E11CC3DB2C62D9D17C0 /* Pods-UpstreamIntegrationTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -678,6 +767,26 @@ productReference = 2EDD05D2286EAA7000229CB2 /* TestApptvOS.app */; productType = "com.apple.product-type.application"; }; + 4CBEE5C129D637170084BC50 /* UpstreamIntegrationTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4CBEE5DD29D637170084BC50 /* Build configuration list for PBXNativeTarget "UpstreamIntegrationTests" */; + buildPhases = ( + 4CBEE5C429D637170084BC50 /* [CP] Check Pods Manifest.lock */, + 4CBEE5C529D637170084BC50 /* Sources */, + 4CBEE5D729D637170084BC50 /* Frameworks */, + 4CBEE5DA29D637170084BC50 /* Resources */, + 4CBEE5DC29D637170084BC50 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 4CBEE5C229D637170084BC50 /* PBXTargetDependency */, + ); + name = UpstreamIntegrationTests; + productName = KonductorIntegrationTests; + productReference = 4CBEE5E029D637170084BC50 /* UpstreamIntegrationTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; D444995E2519506B0093B364 /* AEPEdge */ = { isa = PBXNativeTarget; buildConfigurationList = D44499642519506B0093B364 /* Build configuration list for PBXNativeTarget "AEPEdge" */; @@ -769,7 +878,7 @@ D4D5B4DF242EBB1600CAB6E4 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1200; + LastSwiftUpdateCheck = 1420; LastUpgradeCheck = 1160; TargetAttributes = { D444995E2519506B0093B364 = { @@ -805,6 +914,7 @@ D444995E2519506B0093B364 /* AEPEdge */, D4D5B5202432599B00CAB6E4 /* UnitTests */, D4D5B53224325AB700CAB6E4 /* FunctionalTests */, + 4CBEE5C129D637170084BC50 /* UpstreamIntegrationTests */, D4D5B5792432977F00CAB6E4 /* TestAppiOS */, D496756B25117262001F5F95 /* AEPEdgeXCF */, 2EDD05BE286EAA7000229CB2 /* TestApptvOS */, @@ -822,6 +932,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4CBEE5DA29D637170084BC50 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; D444995D2519506B0093B364 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -974,6 +1091,45 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 4CBEE5C429D637170084BC50 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-UpstreamIntegrationTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 4CBEE5DC29D637170084BC50 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-UpstreamIntegrationTests/Pods-UpstreamIntegrationTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-UpstreamIntegrationTests/Pods-UpstreamIntegrationTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-UpstreamIntegrationTests/Pods-UpstreamIntegrationTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 73742C107C9E821AD914667A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1139,6 +1295,30 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4CBEE5C529D637170084BC50 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4CBEE6072A0C4B610084BC50 /* RealNetworkService.swift in Sources */, + 4CBEE6032A0B47B00084BC50 /* NetworkRequestHelper.swift in Sources */, + 4CBEE5FC29FB47270084BC50 /* InstrumentedExtension.swift in Sources */, + 4CBEE5FD29FB47270084BC50 /* TestConstants.swift in Sources */, + 4CBEE5FB29FB47170084BC50 /* TestBase.swift in Sources */, + 4CBEE5EC29D648F90084BC50 /* FileManager+Testable.swift in Sources */, + 4CBEE5EB29D63B180084BC50 /* EnumUtils.swift in Sources */, + 4CBEE6002A02E2A90084BC50 /* XCTestCase+AnyCodableAsserts.swift in Sources */, + 4CBEE6022A02E2B70084BC50 /* AnyCodableAssertsTests.swift in Sources */, + 4CBEE5CA29D637170084BC50 /* TestUtils.swift in Sources */, + 4CBEE5EF29DB952E0084BC50 /* EdgeLocationHint.swift in Sources */, + 4CBEE5CB29D637170084BC50 /* EventHub+Test.swift in Sources */, + 4CBEE5E629D637E90084BC50 /* UpstreamIntegrationTests.swift in Sources */, + 4CBEE5CC29D637170084BC50 /* UserDefaults+Test.swift in Sources */, + 4CBEE5F129DB953C0084BC50 /* EdgeEnvironment.swift in Sources */, + 4CBEE5CD29D637170084BC50 /* CountDownLatch.swift in Sources */, + 4CBEE5FA29FB19600084BC50 /* AnyCodable+Array.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D444995B2519506B0093B364 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1228,30 +1408,31 @@ buildActionMask = 2147483647; files = ( D45F2E7825EDE0D6000AC350 /* MockHitProcessor.swift in Sources */, - D4DE9B6D2592B8F900EDCD50 /* CompletionHandlerFunctionalTests.swift in Sources */, D44BBB75248DC54F00775DAC /* CountDownLatch.swift in Sources */, D4A473CB2485A8F300D31710 /* TestUtils.swift in Sources */, - D4A473C7248595CA00D31710 /* FunctionalTestConst.swift in Sources */, - D4655400246CBF5E00A150E2 /* FunctionalTestBase.swift in Sources */, + 4CBEE6212A1577660084BC50 /* NetworkResponseHandlerFunctionalTests.swift in Sources */, + 4CBEE6052A0C484E0084BC50 /* MockNetworkService.swift in Sources */, + D4A473C7248595CA00D31710 /* TestConstants.swift in Sources */, + D4655400246CBF5E00A150E2 /* TestBase.swift in Sources */, BF0C09482465E42100892B5D /* TestableEdge.swift in Sources */, - BFEEB5AD28D4E3E900B668B8 /* Edge+PublicAPITests.swift in Sources */, D4A473D02485B86D00D31710 /* AEPEdgeFunctionalTests.swift in Sources */, - D49A62DC250C311100B7C4A3 /* SampleFunctionalTests.swift in Sources */, + 4CBEE6232A1577E40084BC50 /* NoConfigFunctionalTests.swift in Sources */, BF024B7924C6238C002131E9 /* FakeIdentityExtension.swift in Sources */, D45F2E7425EDD2EF000AC350 /* MockDataQueue.swift in Sources */, + 4CBEE61F2A15764B0084BC50 /* IdentityStateFunctionalTests.swift in Sources */, D4655402246CD0B000A150E2 /* InstrumentedExtension.swift in Sources */, + 4CBEE61B2A1572E50084BC50 /* Edge+PublicAPITests.swift in Sources */, + 4CBEE6192A1571DC0084BC50 /* Edge+ConsentTests.swift in Sources */, D48CBD2224A17EEA00A24BD8 /* TestXDMSchema.swift in Sources */, - D4EED36C2489ACE200D4815C /* FunctionalTestNetworkService.swift in Sources */, - D49A6298250AF39C00B7C4A3 /* NoConfigFunctionalTests.swift in Sources */, + D4EED36C2489ACE200D4815C /* NetworkRequestHelper.swift in Sources */, + 4CBEE6252A157CB40084BC50 /* SampleFunctionalTests.swift in Sources */, + 4CBEE61D2A1574CE0084BC50 /* EdgePathOverwriteTests.swift in Sources */, 240E3C9A24EF357100D44993 /* MockDataStore.swift in Sources */, - D45F2E5525E9E06C000AC350 /* Edge+ConsentTests.swift in Sources */, D45F2E6C25ED8EB1000AC350 /* TestableExtensionRuntime.swift in Sources */, D49A6295250AEEF000B7C4A3 /* UserDefaults+Test.swift in Sources */, D49A6290250AE4F500B7C4A3 /* EventHub+Test.swift in Sources */, - D49A6299250AF42000B7C4A3 /* IdentityStateFunctionalTests.swift in Sources */, - BF0C0935246120B200892B5D /* NetworkResponseHandlerFunctionalTests.swift in Sources */, - 2EDFBCF02899B7CD00D22B25 /* EdgePathOverwriteTests.swift in Sources */, 21888AF32555F9FC005677ED /* FileManager+Testable.swift in Sources */, + 4CBEE6152A156FB20084BC50 /* CompletionHandlerFunctionalTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1269,6 +1450,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 4CBEE5C229D637170084BC50 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D444995E2519506B0093B364 /* AEPEdge */; + targetProxy = 4CBEE5C329D637170084BC50 /* PBXContainerItemProxy */; + }; D42F36A6251BEA7E00490C0D /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D444995E2519506B0093B364 /* AEPEdge */; @@ -1356,6 +1542,65 @@ }; name = Release; }; + 4CBEE5DE29D637170084BC50 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5E8ABC044CA69B7B3B6DA2B8 /* Pods-UpstreamIntegrationTests.debug.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = FKGEE875K4; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.adobe.edge.upstreamIntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 11.0; + }; + name = Debug; + }; + 4CBEE5DF29D637170084BC50 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C9510E11CC3DB2C62D9D17C0 /* Pods-UpstreamIntegrationTests.release.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = FKGEE875K4; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.adobe.edge.upstreamIntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 11.0; + }; + name = Release; + }; D44499652519506B0093B364 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = FF1FB9C01D5DF5AEB460E9C2 /* Pods-AEPEdge.debug.xcconfig */; @@ -1727,6 +1972,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 4CBEE5DD29D637170084BC50 /* Build configuration list for PBXNativeTarget "UpstreamIntegrationTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4CBEE5DE29D637170084BC50 /* Debug */, + 4CBEE5DF29D637170084BC50 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; D44499642519506B0093B364 /* Build configuration list for PBXNativeTarget "AEPEdge" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/AEPEdge.xcodeproj/xcshareddata/xcschemes/UpstreamIntegrationTests.xcscheme b/AEPEdge.xcodeproj/xcshareddata/xcschemes/UpstreamIntegrationTests.xcscheme new file mode 100644 index 00000000..2607945b --- /dev/null +++ b/AEPEdge.xcodeproj/xcshareddata/xcschemes/UpstreamIntegrationTests.xcscheme @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Makefile b/Makefile index 5c19f945..2154f14a 100644 --- a/Makefile +++ b/Makefile @@ -115,6 +115,32 @@ functional-test-tvos: rm -rf build/reports/tvosFunctionalResults.xcresult xcodebuild test -workspace $(PROJECT_NAME).xcworkspace -scheme "FunctionalTests" -destination 'platform=tvOS Simulator,name=Apple TV' -derivedDataPath build/out -resultBundlePath build/reports/tvosFunctionalResults.xcresult -enableCodeCoverage YES ADB_SKIP_LINT=YES +# Runs the Edge Network (Konductor) integration tests after installing pod dependencies +# Usage: +# make test-integration-upstream EDGE_ENVIRONMENT= EDGE_LOCATION_HINT= +# If EDGE_ENVIRONMENT is not specified, test target will use its default value. +.SILENT: test-integration-upstream # Silences Makefile's automatic echo of commands +test-integration-upstream: pod-install; \ + rm -rf build/reports/iosIntegrationUpstreamResults.xcresult; \ + if [ -z "$$EDGE_ENVIRONMENT" ]; then \ + echo ''; \ + echo '-------------------------- WARNING -------------------------------'; \ + echo 'EDGE_ENVIRONMENT was NOT set; the test will use its default value.'; \ + echo '------------------------------------------------------------------'; \ + echo ''; \ + fi; \ + xcodebuild test \ + -quiet \ + -workspace $(PROJECT_NAME).xcworkspace \ + -scheme UpstreamIntegrationTests \ + -destination 'platform=iOS Simulator,name=iPhone 14' \ + -derivedDataPath build/out \ + -resultBundlePath build/reports/iosIntegrationUpstreamResults.xcresult \ + -enableCodeCoverage YES \ + ADB_SKIP_LINT=YES \ + EDGE_ENVIRONMENT=$(EDGE_ENVIRONMENT) \ + EDGE_LOCATION_HINT=$(EDGE_LOCATION_HINT) + install-githook: git config core.hooksPath .githooks diff --git a/Podfile b/Podfile index bd892aa6..b2bbe9f1 100644 --- a/Podfile +++ b/Podfile @@ -17,6 +17,13 @@ target 'UnitTests' do pod 'AEPCore' end +target 'UpstreamIntegrationTests' do + pod 'AEPCore' + pod 'AEPEdgeIdentity' + pod 'AEPEdgeConsent' + pod 'AEPEdge', :path => './AEPEdge.podspec' +end + target 'FunctionalTests' do pod 'AEPCore' pod 'AEPEdgeIdentity' diff --git a/Podfile.lock b/Podfile.lock index d89caf11..9f5aa99b 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -50,6 +50,6 @@ SPEC CHECKSUMS: AEPServices: ca493988df250d84fda050124ff7549bcc43c65f SwiftLint: 13280e21cdda6786ad908dc6e416afe5acd1fcb7 -PODFILE CHECKSUM: a88b954489e72716ffa14542a2ff5d27dfbda663 +PODFILE CHECKSUM: 040d35732cdebc4d92d853c8973cf0686de7eb8c COCOAPODS: 1.11.3 diff --git a/Tests/FunctionalTests/AEPEdgeFunctionalTests.swift b/Tests/FunctionalTests/AEPEdgeFunctionalTests.swift index 1eb653a5..4283829e 100644 --- a/Tests/FunctionalTests/AEPEdgeFunctionalTests.swift +++ b/Tests/FunctionalTests/AEPEdgeFunctionalTests.swift @@ -20,12 +20,12 @@ import XCTest // swiftlint:disable type_body_length /// End-to-end testing for the AEPEdge public APIs -class AEPEdgeFunctionalTests: FunctionalTestBase { +class AEPEdgeFunctionalTests: TestBase { private let event1 = Event(name: "e1", type: "eventType", source: "eventSource", data: nil) private let event2 = Event(name: "e2", type: "eventType", source: "eventSource", data: nil) - private let exEdgeInteractProdUrl = URL(string: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping - private let exEdgeInteractPreProdUrl = URL(string: FunctionalTestConst.EX_EDGE_INTERACT_PRE_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping - private let exEdgeInteractIntegrationUrl = URL(string: FunctionalTestConst.EX_EDGE_INTERACT_INTEGRATION_URL_STR)! // swiftlint:disable:this force_unwrapping + private let exEdgeInteractProdUrl = URL(string: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping + private let exEdgeInteractPreProdUrl = URL(string: TestConstants.EX_EDGE_INTERACT_PRE_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping + private let exEdgeInteractIntegrationUrl = URL(string: TestConstants.EX_EDGE_INTERACT_INTEGRATION_URL_STR)! // swiftlint:disable:this force_unwrapping private let responseBody = "{\"test\": \"json\"}" #if os(iOS) private let EXPECTED_BASE_PATH = "https://ns.adobe.com/experience/mobilesdk/ios" @@ -33,24 +33,26 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { private let EXPECTED_BASE_PATH = "https://ns.adobe.com/experience/mobilesdk/tvos" #endif - public class override func setUp() { - super.setUp() - FunctionalTestBase.debugEnabled = true - } + private let mockNetworkService: MockNetworkService = MockNetworkService() + // Runs before each test case override func setUp() { + ServiceProvider.shared.networkService = mockNetworkService + super.setUp() + continueAfterFailure = false + TestBase.debugEnabled = true FileManager.default.clearCache() - // hub shared state update for 1 extension versions (InstrumentedExtension (registered in FunctionalTestBase), IdentityEdge, Edge) IdentityEdge XDM, Config, and Edge shared state updates - setExpectationEvent(type: FunctionalTestConst.EventType.HUB, source: FunctionalTestConst.EventSource.SHARED_STATE, expectedCount: 4) + // hub shared state update for 1 extension versions (InstrumentedExtension (registered in TestBase), IdentityEdge, Edge) IdentityEdge XDM, Config, and Edge shared state updates + setExpectationEvent(type: TestConstants.EventType.HUB, source: TestConstants.EventSource.SHARED_STATE, expectedCount: 4) // expectations for update config request&response events - setExpectationEvent(type: FunctionalTestConst.EventType.CONFIGURATION, source: FunctionalTestConst.EventSource.REQUEST_CONTENT, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.CONFIGURATION, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.CONFIGURATION, source: TestConstants.EventSource.REQUEST_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.CONFIGURATION, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) - // wait for async registration because the EventHub is already started in FunctionalTestBase + // wait for async registration because the EventHub is already started in TestBase let waitForRegistration = CountDownLatch(1) MobileCore.registerExtensions([Identity.self, Edge.self], { print("Extensions registration is complete") @@ -59,8 +61,16 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { XCTAssertEqual(DispatchTimeoutResult.success, waitForRegistration.await(timeout: 2)) MobileCore.updateConfigurationWith(configDict: ["edge.configId": "12345-example"]) - assertExpectedEvents(ignoreUnexpectedEvents: false) + assertExpectedEvents(ignoreUnexpectedEvents: false, timeout: 2) resetTestExpectations() + mockNetworkService.reset() + } + + // Runs after each test case + override func tearDown() { + super.tearDown() + + mockNetworkService.reset() } func testUnregistered() { @@ -75,7 +85,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { // MARK: test request event format func testSendEvent_withXDMData_sendsCorrectRequestEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.REQUEST_CONTENT) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.REQUEST_CONTENT) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm", "testInt": 10, @@ -87,8 +97,8 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { // verify assertExpectedEvents(ignoreUnexpectedEvents: false) - let resultEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.REQUEST_CONTENT) + let resultEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.REQUEST_CONTENT) guard let eventDataDict = resultEvents[0].data else { XCTFail("Failed to convert event data to [String: Any]") return @@ -106,7 +116,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { } func testSendEvent_withXDMDataAndCustomData_sendsCorrectRequestEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.REQUEST_CONTENT) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.REQUEST_CONTENT) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"], data: ["testDataString": "stringValue", "testDataInt": 101, @@ -118,8 +128,8 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { // verify assertExpectedEvents(ignoreUnexpectedEvents: false) - let resultEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.REQUEST_CONTENT) + let resultEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.REQUEST_CONTENT) guard let eventDataDict = resultEvents[0].data else { XCTFail("Failed to convert event data to [String: Any]") return @@ -138,15 +148,15 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { } func testSendEvent_withXDMDataAndNilData_sendsCorrectRequestEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.REQUEST_CONTENT) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.REQUEST_CONTENT) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"], data: nil) Edge.sendEvent(experienceEvent: experienceEvent) // verify assertExpectedEvents(ignoreUnexpectedEvents: false) - let resultEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.REQUEST_CONTENT) + let resultEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.REQUEST_CONTENT) guard let eventDataDict = resultEvents[0].data else { XCTFail("Failed to convert event data to [String: Any]") return @@ -173,7 +183,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { } func testSendEvent_withXDMDataAndQuery_sendsCorrectRequestEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.REQUEST_CONTENT) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.REQUEST_CONTENT) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"], data: nil) experienceEvent.query = ["testString": "query", @@ -186,8 +196,8 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { // verify assertExpectedEvents(ignoreUnexpectedEvents: false) - let resultEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.REQUEST_CONTENT) + let resultEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.REQUEST_CONTENT) guard let eventDataDict = resultEvents[0].data else { XCTFail("Failed to convert event data to [String: Any]") return @@ -205,7 +215,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { } func testSendEvent_withXDMDataAndEmptyQuery_sendsCorrectRequestEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.REQUEST_CONTENT) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.REQUEST_CONTENT) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"], data: nil) experienceEvent.query = [:] @@ -213,8 +223,8 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { // verify assertExpectedEvents(ignoreUnexpectedEvents: false) - let resultEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.REQUEST_CONTENT) + let resultEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.REQUEST_CONTENT) guard let eventDataDict = resultEvents[0].data else { XCTFail("Failed to convert event data to [String: Any]") return @@ -225,7 +235,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { } func testSendEvent_withXDMDataAndNilQuery_sendsCorrectRequestEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.REQUEST_CONTENT) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.REQUEST_CONTENT) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"], data: nil) experienceEvent.query = nil @@ -233,8 +243,8 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { // verify assertExpectedEvents(ignoreUnexpectedEvents: false) - let resultEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.REQUEST_CONTENT) + let resultEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.REQUEST_CONTENT) guard let eventDataDict = resultEvents[0].data else { XCTFail("Failed to convert event data to [String: Any]") return @@ -253,8 +263,8 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm", "testInt": 10, @@ -265,9 +275,9 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { Edge.sendEvent(experienceEvent: experienceEvent) // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) - let requestBody = getFlattenNetworkRequestBody(resultNetworkRequests[0]) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + let requestBody = resultNetworkRequests[0].getFlattenedBody() XCTAssertEqual(19, requestBody.count) XCTAssertEqual(true, requestBody["meta.konductorConfig.streaming.enabled"] as? Bool) XCTAssertEqual("\u{0000}", requestBody["meta.konductorConfig.streaming.recordSeparator"] as? String) @@ -290,7 +300,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { XCTAssertEqual(EXPECTED_BASE_PATH, requestBody["xdm.implementationDetails.name"] as? String) let requestUrl = resultNetworkRequests[0].url - XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)) + XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)) XCTAssertEqual("12345-example", requestUrl.queryParam("configId")) XCTAssertNotNil(requestUrl.queryParam("requestId")) } @@ -302,8 +312,8 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"], data: ["testDataString": "stringValue", "testDataInt": 101, @@ -314,9 +324,9 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { Edge.sendEvent(experienceEvent: experienceEvent) // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) - let requestBody = getFlattenNetworkRequestBody(resultNetworkRequests[0]) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + let requestBody = resultNetworkRequests[0].getFlattenedBody() XCTAssertEqual(20, requestBody.count) XCTAssertEqual(true, requestBody["meta.konductorConfig.streaming.enabled"] as? Bool) XCTAssertEqual("\u{0000}", requestBody["meta.konductorConfig.streaming.recordSeparator"] as? String) @@ -340,7 +350,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { XCTAssertEqual(EXPECTED_BASE_PATH, requestBody["xdm.implementationDetails.name"] as? String) let requestUrl = resultNetworkRequests[0].url - XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)) + XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)) XCTAssertEqual("12345-example", requestUrl.queryParam("configId")) XCTAssertNotNil(requestUrl.queryParam("requestId")) } @@ -352,8 +362,8 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) var xdmObject = TestXDMObject() xdmObject.innerKey = "testInnerObject" @@ -368,9 +378,9 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { Edge.sendEvent(experienceEvent: experienceEvent) // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) - let requestBody = getFlattenNetworkRequestBody(resultNetworkRequests[0]) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + let requestBody = resultNetworkRequests[0].getFlattenedBody() XCTAssertEqual(17, requestBody.count) XCTAssertEqual(true, requestBody["meta.konductorConfig.streaming.enabled"] as? Bool) XCTAssertEqual("\u{0000}", requestBody["meta.konductorConfig.streaming.recordSeparator"] as? String) @@ -391,7 +401,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { XCTAssertEqual(EXPECTED_BASE_PATH, requestBody["xdm.implementationDetails.name"] as? String) let requestUrl = resultNetworkRequests[0].url - XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)) + XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)) XCTAssertEqual("12345-example", requestUrl.queryParam("configId")) XCTAssertNotNil(requestUrl.queryParam("requestId")) } @@ -403,13 +413,13 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) let experienceEvent = ExperienceEvent(xdm: TestXDMSchema()) Edge.sendEvent(experienceEvent: experienceEvent) // verify - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(0, resultNetworkRequests.count) } @@ -420,13 +430,13 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) let experienceEvent = ExperienceEvent(xdm: TestXDMSchema(), data: [:]) Edge.sendEvent(experienceEvent: experienceEvent) // verify - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(0, resultNetworkRequests.count) } @@ -437,13 +447,13 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) let experienceEvent = ExperienceEvent(xdm: TestXDMSchema(), data: nil) Edge.sendEvent(experienceEvent: experienceEvent) // verify - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(0, resultNetworkRequests.count) } @@ -454,8 +464,8 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"], data: nil) experienceEvent.query = ["testString": "query", @@ -467,10 +477,9 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { Edge.sendEvent(experienceEvent: experienceEvent) // verify - // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) - let requestBody = getFlattenNetworkRequestBody(resultNetworkRequests[0]) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + let requestBody = resultNetworkRequests[0].getFlattenedBody() XCTAssertEqual("query", requestBody["events[0].query.testString"] as? String) XCTAssertEqual(10, requestBody["events[0].query.testInt"] as? Int) @@ -484,7 +493,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { // MARK: Client-side store func testSendEvent_twoConsecutiveCalls_appendsReceivedClientSideStore() { - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) // swiftlint:disable:next line_length let storeResponseBody = "\u{0000}{\"requestId\": \"0000-4a4e-1111-bf5c-abcd\",\"handle\": [{\"payload\": [{\"key\": \"kndctr_testOrg_AdobeOrg_identity\",\"value\": \"hashed_value\",\"maxAge\": 34128000},{\"key\": \"kndctr_testOrg_AdobeOrg_consent_check\",\"value\": \"1\",\"maxAge\": 7200},{\"key\": \"expired_key\",\"value\": \"1\",\"maxAge\": 0}],\"type\": \"state:store\"}]}\n" let responseConnection: HttpConnection = HttpConnection(data: storeResponseBody.data(using: .utf8), @@ -493,29 +502,30 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"], data: nil) Edge.sendEvent(experienceEvent: experienceEvent) // first network call, no stored data - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - var resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + var resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(1, resultNetworkRequests.count) - var requestBody = getFlattenNetworkRequestBody(resultNetworkRequests[0]) + var requestBody = resultNetworkRequests[0].getFlattenedBody() XCTAssertEqual(12, requestBody.count) resetTestExpectations() + mockNetworkService.reset() sleep(1) // send a new event, should contain previously stored store data - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) Edge.sendEvent(experienceEvent: experienceEvent) - resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(1, resultNetworkRequests.count) - requestBody = getFlattenNetworkRequestBody(resultNetworkRequests[0]) + requestBody = resultNetworkRequests[0].getFlattenedBody() XCTAssertEqual(18, requestBody.count) guard let firstStore = requestBody["meta.state.entries[0].key"] as? String, @@ -532,7 +542,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { XCTAssertEqual(7200, requestBody["meta.state.entries[\(Int(!index))].maxAge"] as? Int) let requestUrl = resultNetworkRequests[0].url - XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)) + XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)) XCTAssertEqual("12345-example", requestUrl.queryParam("configId")) XCTAssertNotNil(requestUrl.queryParam("requestId")) } @@ -542,7 +552,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { let resetEvent = Event(name: "reset event", type: EventType.genericIdentity, source: EventSource.requestReset, data: nil) MobileCore.dispatch(event: resetEvent) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) // swiftlint:disable:next line_length let storeResponseBody = "\u{0000}{\"requestId\": \"0000-4a4e-1111-bf5c-abcd\",\"handle\": [{\"payload\": [{\"key\": \"kndctr_testOrg_AdobeOrg_identity\",\"value\": \"hashed_value\",\"maxAge\": 34128000},{\"key\": \"kndctr_testOrg_AdobeOrg_consent_check\",\"value\": \"1\",\"maxAge\": 7200},{\"key\": \"expired_key\",\"value\": \"1\",\"maxAge\": 0}],\"type\": \"state:store\"}]}\n" let responseConnection: HttpConnection = HttpConnection(data: storeResponseBody.data(using: .utf8), @@ -551,29 +561,30 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"], data: nil) Edge.sendEvent(experienceEvent: experienceEvent) // first network call, no stored data - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - var resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + var resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(1, resultNetworkRequests.count) - var requestBody = getFlattenNetworkRequestBody(resultNetworkRequests[0]) + var requestBody = resultNetworkRequests[0].getFlattenedBody() XCTAssertEqual(12, requestBody.count) resetTestExpectations() + mockNetworkService.reset() sleep(1) // send a new event, should contain previously stored store data - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) Edge.sendEvent(experienceEvent: experienceEvent) - resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(1, resultNetworkRequests.count) - requestBody = getFlattenNetworkRequestBody(resultNetworkRequests[0]) + requestBody = resultNetworkRequests[0].getFlattenedBody() XCTAssertEqual(18, requestBody.count) guard let firstStore = requestBody["meta.state.entries[0].key"] as? String, @@ -590,13 +601,13 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { XCTAssertEqual(7200, requestBody["meta.state.entries[\(Int(!index))].maxAge"] as? Int) let requestUrl = resultNetworkRequests[0].url - XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)) + XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)) XCTAssertEqual("12345-example", requestUrl.queryParam("configId")) XCTAssertNotNil(requestUrl.queryParam("requestId")) } func testSendEvent_twoConsecutiveCalls_resetInBetween_doesNotAppendReceivedClientSideStore() { - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) // swiftlint:disable:next line_length let storeResponseBody = "\u{0000}{\"requestId\": \"0000-4a4e-1111-bf5c-abcd\",\"handle\": [{\"payload\": [{\"key\": \"kndctr_testOrg_AdobeOrg_identity\",\"value\": \"hashed_value\",\"maxAge\": 34128000},{\"key\": \"kndctr_testOrg_AdobeOrg_consent_check\",\"value\": \"1\",\"maxAge\": 7200},{\"key\": \"expired_key\",\"value\": \"1\",\"maxAge\": 0}],\"type\": \"state:store\"}]}\n" let responseConnection: HttpConnection = HttpConnection(data: storeResponseBody.data(using: .utf8), @@ -605,18 +616,19 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"], data: nil) Edge.sendEvent(experienceEvent: experienceEvent) // first network call, no stored data - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - var resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + var resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(1, resultNetworkRequests.count) - var requestBody = getFlattenNetworkRequestBody(resultNetworkRequests[0]) + var requestBody = resultNetworkRequests[0].getFlattenedBody() XCTAssertEqual(12, requestBody.count) resetTestExpectations() + mockNetworkService.reset() sleep(1) @@ -625,13 +637,13 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { MobileCore.dispatch(event: resetEvent) // send a new event, should NOT contain previously stored store data due to reset event - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) Edge.sendEvent(experienceEvent: experienceEvent) - resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(1, resultNetworkRequests.count) - requestBody = getFlattenNetworkRequestBody(resultNetworkRequests[0]) + requestBody = resultNetworkRequests[0].getFlattenedBody() XCTAssertEqual(12, requestBody.count) XCTAssertNil(requestBody["meta.state"]) // no state should be appended @@ -639,10 +651,10 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { // MARK: Paired request-response events func testSendEvent_receivesResponseEventHandle_sendsResponseEvent_pairedWithTheRequestEventId() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.REQUEST_CONTENT, + setExpectationEvent(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.REQUEST_CONTENT, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, + setExpectationEvent(type: TestConstants.EventType.EDGE, source: "personalization:decisions", expectedCount: 1) // swiftlint:disable:next line_length @@ -653,21 +665,21 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: httpConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: httpConnection) Edge.sendEvent(experienceEvent: ExperienceEvent(xdm: ["eventType": "personalizationEvent", "test": "xdm"], data: nil)) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() assertExpectedEvents(ignoreUnexpectedEvents: true) - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) let requestId = resultNetworkRequests[0].url.queryParam("requestId") - let requestEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.REQUEST_CONTENT) + let requestEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.REQUEST_CONTENT) let requestEventUUID = requestEvents[0].id.uuidString - let responseEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, + let responseEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: "personalization:decisions") guard let eventDataDict = responseEvents[0].data else { XCTFail("Failed to convert event data to [String: Any]") @@ -686,11 +698,11 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { } func testSendEvent_receivesResponseEventWarning_sendsErrorResponseEvent_pairedWithTheRequestEventId() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.REQUEST_CONTENT, + setExpectationEvent(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.REQUEST_CONTENT, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, + setExpectationEvent(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) let responseBody = "\u{0000}{\"requestId\": \"0ee43289-4a4e-469a-bf5c-1d8186919a26\",\"handle\": [],\"warnings\": [{\"status\": 0,\"title\": \"Failed due to unrecoverable system error\",\"report\":{\"eventIndex\":0}}]}\n" let httpConnection: HttpConnection = HttpConnection(data: responseBody.data(using: .utf8), @@ -699,22 +711,22 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: httpConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: httpConnection) Edge.sendEvent(experienceEvent: ExperienceEvent(xdm: ["eventType": "personalizationEvent", "test": "xdm"], data: nil)) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() assertExpectedEvents(ignoreUnexpectedEvents: true) - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) let requestId = resultNetworkRequests[0].url.queryParam("requestId") - let requestEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.REQUEST_CONTENT) + let requestEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.REQUEST_CONTENT) let requestEventUUID = requestEvents[0].id.uuidString - let errorResponseEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + let errorResponseEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) guard let eventDataDict = errorResponseEvents[0].data else { XCTFail("Failed to convert event data to [String: Any]") return @@ -741,8 +753,8 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm", "testInt": 10, @@ -751,8 +763,9 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { "testArray": ["arrayElem1", 2, true], "testDictionary": ["key": "val"]]) Edge.sendEvent(experienceEvent: experienceEvent) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() resetTestExpectations() + mockNetworkService.reset() // good connection, hits sent let httpConnection: HttpConnection = HttpConnection(data: responseBody.data(using: .utf8), @@ -761,10 +774,10 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: httpConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: httpConnection) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() } func testSendEvent_withXDMData_sendsExEdgeNetworkRequest_afterPersistingMultipleHits() { @@ -779,8 +792,8 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm", "testInt": 10, "testBool": false, @@ -790,8 +803,9 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { Edge.sendEvent(experienceEvent: experienceEvent) Edge.sendEvent(experienceEvent: experienceEvent) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() resetTestExpectations() + mockNetworkService.reset() // good connection, hits sent let httpConnection: HttpConnection = HttpConnection(data: responseBody.data(using: .utf8), @@ -800,14 +814,14 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 2) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: httpConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 2) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: httpConnection) - self.assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() } func testSendEvent_multiStatusResponse_dispatchesEvents() { - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) // swiftlint:disable line_length let response = """ {"requestId":"72eaa048-207e-4dde-bf16-0cb2b21336d5","handle":[],"errors":[{"type":"https://ns.adobe.com/aep/errors/EXEG-0201-504","status":504,"title":"The 'com.adobe.experience.platform.ode' service is temporarily unable to serve this request. Please try again later.","report":{"eventIndex":0}}],"warnings":[{"type":"https://ns.adobe.com/aep/errors/EXEG-0204-200","status":200,"title":"A warning occurred while calling the 'com.adobe.audiencemanager' service for this request.","report":{"eventIndex":0,"cause":{"message":"Cannot read related customer for device id: ...","code":202}}}]} @@ -819,20 +833,20 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.REQUEST_CONTENT, expectedCount: 1) // the send event - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 2) // 2 error events + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.REQUEST_CONTENT, expectedCount: 1) // the send event + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 2) // 2 error events let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"], data: nil) Edge.sendEvent(experienceEvent: experienceEvent) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() assertExpectedEvents(ignoreUnexpectedEvents: false) - let resultEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + let resultEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) guard let eventDataDict = resultEvents[0].data else { XCTFail("Failed to convert event data to [String: Any]") return @@ -857,7 +871,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { } func testSendEvent_fatalError() { - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let response = """ { "type" : "https://ns.adobe.com/aep/errors/EXEG-0104-422", @@ -881,20 +895,20 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.REQUEST_CONTENT, expectedCount: 1) // the send event - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) // 1 error events + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.REQUEST_CONTENT, expectedCount: 1) // the send event + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) // 1 error events let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"], data: nil) Edge.sendEvent(experienceEvent: experienceEvent) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() assertExpectedEvents(ignoreUnexpectedEvents: false) - let resultEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + let resultEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) guard let eventDataDict = resultEvents[0].data else { XCTFail("Failed to convert event data to [String: Any]") return @@ -908,7 +922,7 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { } func testSendEvent_fatalError400() { - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let response = """ { "type":"https://ns.adobe.com/aep/errors/EXEG-0003-400", @@ -925,20 +939,20 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.REQUEST_CONTENT, expectedCount: 1) // the send event - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) // 1 error events + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.REQUEST_CONTENT, expectedCount: 1) // the send event + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) // 1 error events let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"], data: nil) Edge.sendEvent(experienceEvent: experienceEvent) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() assertExpectedEvents(ignoreUnexpectedEvents: false) - let resultEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + let resultEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) guard let eventDataDict = resultEvents[0].data else { XCTFail("Failed to convert event data to [String: Any]") return @@ -959,18 +973,18 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"]) Edge.sendEvent(experienceEvent: experienceEvent) // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) let requestUrl = resultNetworkRequests[0].url - XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)) + XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)) XCTAssertEqual("12345-example", requestUrl.queryParam("configId")) XCTAssertNotNil(requestUrl.queryParam("requestId")) } @@ -985,18 +999,18 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"]) Edge.sendEvent(experienceEvent: experienceEvent) // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) let requestUrl = resultNetworkRequests[0].url - XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)) + XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)) XCTAssertEqual("12345-example", requestUrl.queryParam("configId")) XCTAssertNotNil(requestUrl.queryParam("requestId")) } @@ -1011,18 +1025,18 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"]) Edge.sendEvent(experienceEvent: experienceEvent) // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) let requestUrl = resultNetworkRequests[0].url - XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)) + XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)) XCTAssertEqual("12345-example", requestUrl.queryParam("configId")) XCTAssertNotNil(requestUrl.queryParam("requestId")) } @@ -1037,18 +1051,18 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PRE_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PRE_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PRE_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PRE_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"]) Edge.sendEvent(experienceEvent: experienceEvent) // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PRE_PROD_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PRE_PROD_URL_STR, httpMethod: HttpMethod.post) let requestUrl = resultNetworkRequests[0].url - XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_INTERACT_PRE_PROD_URL_STR)) + XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_INTERACT_PRE_PROD_URL_STR)) XCTAssertEqual("12345-example", requestUrl.queryParam("configId")) XCTAssertNotNil(requestUrl.queryParam("requestId")) } @@ -1063,18 +1077,18 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_INTEGRATION_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_INTEGRATION_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_INTEGRATION_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_INTEGRATION_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"]) Edge.sendEvent(experienceEvent: experienceEvent) // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_INTEGRATION_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_INTEGRATION_URL_STR, httpMethod: HttpMethod.post) let requestUrl = resultNetworkRequests[0].url - XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_INTERACT_INTEGRATION_URL_STR)) + XCTAssertTrue(requestUrl.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_INTERACT_INTEGRATION_URL_STR)) XCTAssertEqual("12345-example", requestUrl.queryParam("configId")) XCTAssertNotNil(requestUrl.queryParam("requestId")) } @@ -1087,9 +1101,9 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR_OR2_LOC, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR_OR2_LOC, httpMethod: HttpMethod.post, expectedCount: 1) // Send two requests let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"]) @@ -1097,14 +1111,14 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { Edge.sendEvent(experienceEvent: experienceEvent) // verify - assertNetworkRequestsCount() - var resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.assertAllNetworkRequestExpectations() + var resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(1, resultNetworkRequests.count) - XCTAssertTrue(resultNetworkRequests[0].url.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)) + XCTAssertTrue(resultNetworkRequests[0].url.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)) - resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR_OR2_LOC, httpMethod: HttpMethod.post) + resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR_OR2_LOC, httpMethod: HttpMethod.post) XCTAssertEqual(1, resultNetworkRequests.count) - XCTAssertTrue(resultNetworkRequests[0].url.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR_OR2_LOC)) + XCTAssertTrue(resultNetworkRequests[0].url.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_INTERACT_PROD_URL_STR_OR2_LOC)) } func testSendEvent_edgeNetworkResponseContainsLocationHint_sendEventDoesNotIncludeExpiredLocationHint() { @@ -1115,8 +1129,8 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 2) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 2) // Send two requests let experienceEvent = ExperienceEvent(xdm: ["testString": "xdm"]) @@ -1130,11 +1144,11 @@ class AEPEdgeFunctionalTests: FunctionalTestBase { Edge.sendEvent(experienceEvent: experienceEvent) // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(2, resultNetworkRequests.count) - XCTAssertTrue(resultNetworkRequests[0].url.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)) - XCTAssertTrue(resultNetworkRequests[1].url.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)) + XCTAssertTrue(resultNetworkRequests[0].url.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)) + XCTAssertTrue(resultNetworkRequests[1].url.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)) } func getEdgeEventError(message: String, code: String) -> EdgeEventError { diff --git a/Tests/FunctionalTests/CompletionHandlerFunctionalTests.swift b/Tests/FunctionalTests/CompletionHandlerFunctionalTests.swift index db953d58..39ca4643 100644 --- a/Tests/FunctionalTests/CompletionHandlerFunctionalTests.swift +++ b/Tests/FunctionalTests/CompletionHandlerFunctionalTests.swift @@ -18,28 +18,30 @@ import Foundation import XCTest /// End-to-end testing for the AEPEdge public APIs with completion handlers -class CompletionHandlerFunctionalTests: FunctionalTestBase { +class CompletionHandlerFunctionalTests: TestBase { private let event1 = Event(name: "e1", type: "eventType", source: "eventSource", data: nil) private let event2 = Event(name: "e2", type: "eventType", source: "eventSource", data: nil) private let responseBody = "{\"test\": \"json\"}" - private let edgeUrl = URL(string: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping + private let edgeUrl = URL(string: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping // swiftlint:disable:next line_length let responseBodyWithHandle = "\u{0000}{\"requestId\": \"0ee43289-4a4e-469a-bf5c-1d8186919a26\",\"handle\": [{\"payload\": [{\"id\": \"AT:eyJhY3Rpdml0eUlkIjoiMTE3NTg4IiwiZXhwZXJpZW5jZUlkIjoiMSJ9\",\"scope\": \"buttonColor\",\"items\": [{ \"schema\": \"https://ns.adobe.com/personalization/json-content-item\",\"data\": {\"content\": {\"value\": \"#D41DBA\"}}}]}],\"type\": \"personalization:decisions\"}]}\n" // swiftlint:disable:next line_length let responseBodyWithTwoErrors = "\u{0000}{\"requestId\": \"0ee43289-4a4e-469a-bf5c-1d8186919a27\",\"errors\": [{\"message\": \"An error occurred while calling the 'X' service for this request. Please try again.\", \"code\": \"502\"}, {\"message\": \"An error occurred while calling the 'Y', service unavailable\", \"code\": \"503\"}]}\n" - public class override func setUp() { - super.setUp() - FunctionalTestBase.debugEnabled = true - } + private let mockNetworkService: MockNetworkService = MockNetworkService() + // Runs before each test case override func setUp() { + ServiceProvider.shared.networkService = mockNetworkService + super.setUp() + continueAfterFailure = false + TestBase.debugEnabled = true FileManager.default.clearCache() - // wait for async registration because the EventHub is already started in FunctionalTestBase + // wait for async registration because the EventHub is already started in TestBase let waitForRegistration = CountDownLatch(1) MobileCore.registerExtensions([Identity.self, Edge.self], { print("Extensions registration is complete") @@ -49,6 +51,14 @@ class CompletionHandlerFunctionalTests: FunctionalTestBase { MobileCore.updateConfigurationWith(configDict: ["edge.configId": "12345-example"]) resetTestExpectations() + mockNetworkService.reset() + } + + // Runs after each test case + override func tearDown() { + super.tearDown() + + mockNetworkService.reset() } func testSendEvent_withCompletionHandler_callsCompletionCorrectly() { @@ -58,9 +68,9 @@ class CompletionHandlerFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: httpConnection) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: httpConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) var receivedHandles: [EdgeEventHandle] = [] let expectation = self.expectation(description: "Completion handler called") @@ -70,10 +80,10 @@ class CompletionHandlerFunctionalTests: FunctionalTestBase { expectation.fulfill() }) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() wait(for: [expectation], timeout: 1) - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(1, resultNetworkRequests.count) XCTAssertEqual(1, receivedHandles.count) @@ -94,8 +104,8 @@ class CompletionHandlerFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: httpConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 2) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: httpConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 2) let expectation1 = self.expectation(description: "Completion handler 1 called") let expectation2 = self.expectation(description: "Completion handler 2 called") @@ -111,7 +121,7 @@ class CompletionHandlerFunctionalTests: FunctionalTestBase { }) // verify - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() wait(for: [expectation1, expectation2], timeout: 1) } @@ -132,19 +142,20 @@ class CompletionHandlerFunctionalTests: FunctionalTestBase { // set expectations & send two events let expectation1 = self.expectation(description: "Completion handler 1 called") let expectation2 = self.expectation(description: "Completion handler 2 called") - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: httpConnection1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: httpConnection1) Edge.sendEvent(experienceEvent: ExperienceEvent(xdm: ["eventType": "personalizationEvent", "test": "xdm"], data: nil), { (handles: [EdgeEventHandle]) in XCTAssertEqual(1, handles.count) expectation1.fulfill() }) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() wait(for: [expectation1], timeout: 1) resetTestExpectations() - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: httpConnection2) + mockNetworkService.reset() + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: httpConnection2) Edge.sendEvent(experienceEvent: ExperienceEvent(xdm: ["eventType": "personalizationEvent", "test": "xdm"], data: nil), { (handles: [EdgeEventHandle]) in // 0 handles, received errors but still called completion @@ -153,7 +164,7 @@ class CompletionHandlerFunctionalTests: FunctionalTestBase { }) // verify - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() wait(for: [expectation2], timeout: 1) } @@ -170,8 +181,8 @@ class CompletionHandlerFunctionalTests: FunctionalTestBase { let expectation = self.expectation(description: "Completion handler called") // set expectations & send two events - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: httpConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: httpConnection) Edge.sendEvent(experienceEvent: ExperienceEvent(xdm: ["eventType": "personalizationEvent", "test": "xdm"], data: nil), { (handles: [EdgeEventHandle]) in XCTAssertEqual(1, handles.count) @@ -179,7 +190,7 @@ class CompletionHandlerFunctionalTests: FunctionalTestBase { }) // verify - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() wait(for: [expectation], timeout: 1) } } diff --git a/Tests/FunctionalTests/Edge+ConsentTests.swift b/Tests/FunctionalTests/Edge+ConsentTests.swift index 32a473c8..a810f3fb 100644 --- a/Tests/FunctionalTests/Edge+ConsentTests.swift +++ b/Tests/FunctionalTests/Edge+ConsentTests.swift @@ -18,7 +18,7 @@ import AEPServices import Foundation import XCTest -class EdgeConsentTests: FunctionalTestBase { +class EdgeConsentTests: TestBase { private let EVENTS_COUNT: Int32 = 5 private let experienceEvent = ExperienceEvent(xdm: ["test": "xdm"]) private let responseBody = "\u{0000}{" + @@ -47,25 +47,27 @@ class EdgeConsentTests: FunctionalTestBase { " ]" + " }\n" - public class override func setUp() { - super.setUp() - FunctionalTestBase.debugEnabled = true - } + private let mockNetworkService: MockNetworkService = MockNetworkService() + // Runs before each test case override func setUp() { + ServiceProvider.shared.networkService = mockNetworkService + super.setUp() + continueAfterFailure = false + TestBase.debugEnabled = true FileManager.default.clearCache() - // hub shared state update for 5 extensions (InstrumentedExtension (registered in FunctionalTestBase), Configuration, Edge, Consent, Edge Identity) - setExpectationEvent(type: FunctionalTestConst.EventType.HUB, source: FunctionalTestConst.EventSource.SHARED_STATE, expectedCount: 5) - setExpectationEvent(type: FunctionalTestConst.EventType.CONSENT, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + // hub shared state update for 5 extensions (InstrumentedExtension (registered in TestBase), Configuration, Edge, Consent, Edge Identity) + setExpectationEvent(type: TestConstants.EventType.HUB, source: TestConstants.EventSource.SHARED_STATE, expectedCount: 5) + setExpectationEvent(type: TestConstants.EventType.CONSENT, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) // expectations for update config request&response events - setExpectationEvent(type: FunctionalTestConst.EventType.CONFIGURATION, source: FunctionalTestConst.EventSource.REQUEST_CONTENT, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.CONFIGURATION, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.CONFIGURATION, source: TestConstants.EventSource.REQUEST_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.CONFIGURATION, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) - // wait for async registration because the EventHub is already started in FunctionalTestBase + // wait for async registration because the EventHub is already started in TestBase let waitForRegistration = CountDownLatch(1) MobileCore.registerExtensions([Identity.self, Edge.self, Consent.self], { print("Extensions registration is complete") @@ -77,6 +79,14 @@ class EdgeConsentTests: FunctionalTestBase { assertExpectedEvents(ignoreUnexpectedEvents: false) resetTestExpectations() + mockNetworkService.reset() + } + + // Runs after each test case + override func tearDown() { + super.tearDown() + + mockNetworkService.reset() } // MARK: test experience events handling based on collect consent value @@ -85,12 +95,13 @@ class EdgeConsentTests: FunctionalTestBase { updateCollectConsent(status: ConsentStatus.no) getConsentsSync() resetTestExpectations() + mockNetworkService.reset() // test fireManyEvents() // verify - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertTrue(resultNetworkRequests.isEmpty) } @@ -99,13 +110,14 @@ class EdgeConsentTests: FunctionalTestBase { updateCollectConsent(status: ConsentStatus.yes) getConsentsSync() resetTestExpectations() + mockNetworkService.reset() // test - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: EVENTS_COUNT) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: EVENTS_COUNT) fireManyEvents() // verify - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() } func testCollectConsent_whenPending_thenHits_thenYes_hitsSent() { @@ -115,7 +127,7 @@ class EdgeConsentTests: FunctionalTestBase { fireManyEvents() // verify - var resultNetworkRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 2) + var resultNetworkRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 2) XCTAssertEqual(0, resultNetworkRequests.count) // test - change to yes @@ -123,7 +135,7 @@ class EdgeConsentTests: FunctionalTestBase { getConsentsSync() // verify - resultNetworkRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 2) + resultNetworkRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 2) XCTAssertEqual(Int(EVENTS_COUNT), resultNetworkRequests.count) } @@ -138,7 +150,7 @@ class EdgeConsentTests: FunctionalTestBase { getConsentsSync() // verify - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertTrue(resultNetworkRequests.isEmpty) } @@ -154,7 +166,7 @@ class EdgeConsentTests: FunctionalTestBase { getConsentsSync() // verify - let resultNetworkRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + let resultNetworkRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertEqual(0, resultNetworkRequests.count) } @@ -169,12 +181,12 @@ class EdgeConsentTests: FunctionalTestBase { updateCollectConsent(status: ConsentStatus.no) // verify - let resultNetworkRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + let resultNetworkRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertEqual(0, resultNetworkRequests.count) } func testCollectConsent_whenNo_thenPending_thenHits_thenYes_hitsSent() { - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 5) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 5) // initial no, pending updateCollectConsent(status: ConsentStatus.no) @@ -186,23 +198,23 @@ class EdgeConsentTests: FunctionalTestBase { updateCollectConsent(status: ConsentStatus.yes) // verify - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() } // MARK: test consent events are being sent to Edge Network func testCollectConsentNo_sendsRequestToEdgeNetwork() { - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) // test updateCollectConsent(status: ConsentStatus.no) // verify - assertNetworkRequestsCount() - let interactRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + mockNetworkService.assertAllNetworkRequestExpectations() + let interactRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertEqual(0, interactRequests.count) - let consentRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + let consentRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertEqual(HttpMethod.post, consentRequests[0].httpMethod) - let requestBody = getFlattenNetworkRequestBody(consentRequests[0]) + let requestBody = consentRequests[0].getFlattenedBody() print(requestBody) XCTAssertEqual(11, requestBody.count) XCTAssertEqual("update", requestBody["query.consent.operation"] as? String) @@ -219,18 +231,18 @@ class EdgeConsentTests: FunctionalTestBase { } func testCollectConsentYes_sendsRequestToEdgeNetwork() { - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) // test updateCollectConsent(status: ConsentStatus.yes) // verify - assertNetworkRequestsCount() - let interactRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + mockNetworkService.assertAllNetworkRequestExpectations() + let interactRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertEqual(0, interactRequests.count) - let consentRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + let consentRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertEqual(HttpMethod.post, consentRequests[0].httpMethod) - let requestBody = getFlattenNetworkRequestBody(consentRequests[0]) + let requestBody = consentRequests[0].getFlattenedBody() print(requestBody) XCTAssertEqual(11, requestBody.count) XCTAssertEqual("update", requestBody["query.consent.operation"] as? String) @@ -253,85 +265,85 @@ class EdgeConsentTests: FunctionalTestBase { updateCollectConsent(status: "some value") // verify - let interactRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + let interactRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertEqual(0, interactRequests.count) - let consentRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + let consentRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertEqual(0, consentRequests.count) } // MARK: Configurable Endpoint func testCollectConsent_withConfigurableEndpoint_withEmptyConfigEndpoint_UsesProduction() { - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) // test updateCollectConsent(status: ConsentStatus.yes) // verify - assertNetworkRequestsCount() - let consentRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + mockNetworkService.assertAllNetworkRequestExpectations() + let consentRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertEqual(HttpMethod.post, consentRequests[0].httpMethod) - XCTAssertTrue(consentRequests[0].url.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR)) + XCTAssertTrue(consentRequests[0].url.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_CONSENT_PROD_URL_STR)) } func testCollectConsent_withConfigurableEndpoint_withInvalidConfigEndpoint_UsesProduction() { // set to invalid endpoint MobileCore.updateConfigurationWith(configDict: ["edge.environment": "invalid-endpoint"]) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) // test updateCollectConsent(status: ConsentStatus.yes) // verify - assertNetworkRequestsCount() - let consentRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + mockNetworkService.assertAllNetworkRequestExpectations() + let consentRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertEqual(HttpMethod.post, consentRequests[0].httpMethod) - XCTAssertTrue(consentRequests[0].url.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR)) + XCTAssertTrue(consentRequests[0].url.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_CONSENT_PROD_URL_STR)) } func testCollectConsent_withConfigurableEndpoint_withProductionConfigEndpoint_UsesProduction() { // set to prod endpoint MobileCore.updateConfigurationWith(configDict: ["edge.environment": "prod"]) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) // test updateCollectConsent(status: ConsentStatus.yes) // verify - assertNetworkRequestsCount() - let consentRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + mockNetworkService.assertAllNetworkRequestExpectations() + let consentRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertEqual(HttpMethod.post, consentRequests[0].httpMethod) - XCTAssertTrue(consentRequests[0].url.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR)) + XCTAssertTrue(consentRequests[0].url.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_CONSENT_PROD_URL_STR)) } func testCollectConsent_withConfigurableEndpoint_withPreProductionConfigEndpoint_UsesPreProduction() { // set to pre-prod endpoint MobileCore.updateConfigurationWith(configDict: ["edge.environment": "pre-prod"]) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_CONSENT_PRE_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_CONSENT_PRE_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) // test updateCollectConsent(status: ConsentStatus.yes) // verify - assertNetworkRequestsCount() - let consentRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_CONSENT_PRE_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + mockNetworkService.assertAllNetworkRequestExpectations() + let consentRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_CONSENT_PRE_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertEqual(HttpMethod.post, consentRequests[0].httpMethod) - XCTAssertTrue(consentRequests[0].url.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_CONSENT_PRE_PROD_URL_STR)) + XCTAssertTrue(consentRequests[0].url.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_CONSENT_PRE_PROD_URL_STR)) } func testCollectConsent_withConfigurableEndpoint_withIntegrationConfigEndpoint_UsesIntegration() { // set to integration endpoint MobileCore.updateConfigurationWith(configDict: ["edge.environment": "int"]) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_CONSENT_INTEGRATION_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_CONSENT_INTEGRATION_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) // test updateCollectConsent(status: ConsentStatus.yes) // verify - assertNetworkRequestsCount() - let consentRequests = self.getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_CONSENT_INTEGRATION_URL_STR, httpMethod: HttpMethod.post, timeout: 1) + mockNetworkService.assertAllNetworkRequestExpectations() + let consentRequests = self.mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_CONSENT_INTEGRATION_URL_STR, httpMethod: HttpMethod.post, timeout: 1) XCTAssertEqual(HttpMethod.post, consentRequests[0].httpMethod) - XCTAssertTrue(consentRequests[0].url.absoluteURL.absoluteString.hasPrefix(FunctionalTestConst.EX_EDGE_CONSENT_INTEGRATION_URL_STR)) + XCTAssertTrue(consentRequests[0].url.absoluteURL.absoluteString.hasPrefix(TestConstants.EX_EDGE_CONSENT_INTEGRATION_URL_STR)) } private func fireManyEvents() { diff --git a/Tests/FunctionalTests/Edge+PublicAPITests.swift b/Tests/FunctionalTests/Edge+PublicAPITests.swift index 1d09abff..05b5d325 100644 --- a/Tests/FunctionalTests/Edge+PublicAPITests.swift +++ b/Tests/FunctionalTests/Edge+PublicAPITests.swift @@ -16,28 +16,30 @@ import AEPEdgeIdentity import AEPServices import XCTest -class EdgePublicAPITests: FunctionalTestBase { - private let exEdgeInteractProdUrlLocHint = URL(string: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR_OR2_LOC)! // swiftlint:disable:this force_unwrapping +class EdgePublicAPITests: TestBase { + private let exEdgeInteractProdUrlLocHint = URL(string: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR_OR2_LOC)! // swiftlint:disable:this force_unwrapping private let responseBody = "{\"test\": \"json\"}" - public class override func setUp() { - super.setUp() - FunctionalTestBase.debugEnabled = true - } + private let mockNetworkService: MockNetworkService = MockNetworkService() + // Runs before each test case override func setUp() { + ServiceProvider.shared.networkService = mockNetworkService + super.setUp() - continueAfterFailure = true + + continueAfterFailure = false + TestBase.debugEnabled = true FileManager.default.clearCache() - // hub shared state update for 1 extension versions (InstrumentedExtension (registered in FunctionalTestBase), IdentityEdge, Edge) IdentityEdge XDM, Config, and Edge shared state updates - setExpectationEvent(type: FunctionalTestConst.EventType.HUB, source: FunctionalTestConst.EventSource.SHARED_STATE, expectedCount: 4) + // hub shared state update for 1 extension versions (InstrumentedExtension (registered in TestBase), IdentityEdge, Edge) IdentityEdge XDM, Config, and Edge shared state updates + setExpectationEvent(type: TestConstants.EventType.HUB, source: TestConstants.EventSource.SHARED_STATE, expectedCount: 4) // expectations for update config request&response events - setExpectationEvent(type: FunctionalTestConst.EventType.CONFIGURATION, source: FunctionalTestConst.EventSource.REQUEST_CONTENT, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.CONFIGURATION, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.CONFIGURATION, source: TestConstants.EventSource.REQUEST_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.CONFIGURATION, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) - // wait for async registration because the EventHub is already started in FunctionalTestBase + // wait for async registration because the EventHub is already started in TestBase let waitForRegistration = CountDownLatch(1) MobileCore.registerExtensions([Identity.self, Edge.self], { print("Extensions registration is complete") @@ -48,52 +50,60 @@ class EdgePublicAPITests: FunctionalTestBase { assertExpectedEvents(ignoreUnexpectedEvents: false) resetTestExpectations() + mockNetworkService.reset() + } + + // Runs after each test case + override func tearDown() { + super.tearDown() + + mockNetworkService.reset() } func testSetLocationHint_sendEvent_sendsNetworkRequestWithLocationHint() { - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR_OR2_LOC, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR_OR2_LOC, httpMethod: HttpMethod.post, expectedCount: 1) - Edge.setLocationHint(FunctionalTestConst.OR2_LOC) + Edge.setLocationHint(TestConstants.OR2_LOC) let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], data: ["data": ["test": "data"]]) Edge.sendEvent(experienceEvent: experienceEvent) // verify - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() } func testSetLocationHint_withNilHint_sendEvent_sendsNetworkRequestWithoutLocationHint() { let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], data: ["data": ["test": "data"]]) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR_OR2_LOC, httpMethod: HttpMethod.post, expectedCount: 1) - Edge.setLocationHint(FunctionalTestConst.OR2_LOC) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR_OR2_LOC, httpMethod: HttpMethod.post, expectedCount: 1) + Edge.setLocationHint(TestConstants.OR2_LOC) Edge.sendEvent(experienceEvent: experienceEvent) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) Edge.setLocationHint(nil) Edge.sendEvent(experienceEvent: experienceEvent) // verify - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() } func testSetLocationHint_withEmptyHint_sendEvent_sendsNetworkRequestWithoutLocationHint() { let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], data: ["data": ["test": "data"]]) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR_OR2_LOC, httpMethod: HttpMethod.post, expectedCount: 1) - Edge.setLocationHint(FunctionalTestConst.OR2_LOC) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR_OR2_LOC, httpMethod: HttpMethod.post, expectedCount: 1) + Edge.setLocationHint(TestConstants.OR2_LOC) Edge.sendEvent(experienceEvent: experienceEvent) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) Edge.setLocationHint("") Edge.sendEvent(experienceEvent: experienceEvent) // verify - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() } func testGetLocationHint_withoutSet_returnsNilHint() { @@ -110,11 +120,11 @@ class EdgePublicAPITests: FunctionalTestBase { } func testGetLocationHint_withSet_returnsHint() { - Edge.setLocationHint(FunctionalTestConst.OR2_LOC) + Edge.setLocationHint(TestConstants.OR2_LOC) let expectation = XCTestExpectation(description: "Request Location Hint") expectation.assertForOverFulfill = true Edge.getLocationHint({ hint, error in - XCTAssertEqual(FunctionalTestConst.OR2_LOC, hint) + XCTAssertEqual(TestConstants.OR2_LOC, hint) XCTAssertNil(error) expectation.fulfill() }) @@ -124,7 +134,7 @@ class EdgePublicAPITests: FunctionalTestBase { } func testGetLocationHint_clearHint_returnsNilHint() { - Edge.setLocationHint(FunctionalTestConst.OR2_LOC) + Edge.setLocationHint(TestConstants.OR2_LOC) Edge.setLocationHint(nil) let expectation = XCTestExpectation(description: "Request Location Hint") expectation.assertForOverFulfill = true @@ -139,7 +149,7 @@ class EdgePublicAPITests: FunctionalTestBase { } func testGetLocationHint_responseEventChainedToParentId() { - Edge.setLocationHint(FunctionalTestConst.OR2_LOC) + Edge.setLocationHint(TestConstants.OR2_LOC) let expectation = XCTestExpectation(description: "Request Location Hint") expectation.assertForOverFulfill = true Edge.getLocationHint({ _, _ in @@ -163,22 +173,22 @@ class EdgePublicAPITests: FunctionalTestBase { // Response data with 1 handle, 1 error, and 1 warning response, all at event index 0 let responseData: Data? = "\u{0000}{\"handle\":[{\"type\":\"state:store\",\"payload\":[{\"key\":\"s_ecid\",\"value\":\"MCMID|29068398647607325310376254630528178721\",\"maxAge\":15552000}]}],\"errors\":[{\"status\":2003,\"type\":\"personalization\",\"title\":\"Failed to process personalization event\"}],\"warnings\":[{\"type\":\"https://ns.adobe.com/aep/errors/EXEG-0204-200\",\"status\":98,\"title\":\"Some Informative stuff here\",\"report\":{\"cause\":{\"message\":\"Some Informative stuff here\",\"code\":202}}}]}\n".data(using: .utf8) let responseConnection: HttpConnection = HttpConnection(data: responseData, - response: HTTPURLResponse(url: URL(string: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)!, + response: HTTPURLResponse(url: URL(string: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)!, statusCode: 200, httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"]) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 2) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 2) Edge.sendEvent(experienceEvent: experienceEvent) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() let dispatchedRequests = getDispatchedEventsWith(type: EventType.edge, source: EventSource.requestContent) XCTAssertEqual(1, dispatchedRequests.count) diff --git a/Tests/FunctionalTests/EdgePathOverwriteTests.swift b/Tests/FunctionalTests/EdgePathOverwriteTests.swift index efca2011..2998711c 100644 --- a/Tests/FunctionalTests/EdgePathOverwriteTests.swift +++ b/Tests/FunctionalTests/EdgePathOverwriteTests.swift @@ -18,36 +18,38 @@ import Foundation import XCTest /// End-to-end testing for the AEPEdge public APIs -class AEPEdgePathOverwriteTests: FunctionalTestBase { +class AEPEdgePathOverwriteTests: TestBase { static let EDGE_MEDIA_PROD_PATH_STR = "/ee/va/v1/sessionstart" static let EDGE_MEDIA_PRE_PROD_PATH_STR = "/ee-pre-prd/va/v1/sessionstart" static let EDGE_MEDIA_INTEGRATION_PATH_STR = "/ee/va/v1/sessionstart" static let EDGE_CONSENT_PATH_STR = "/ee/v1/privacy/set-consent" static let EDGE_INTEGRATION_DOMAIN_STR = "edge-int.adobedc.net" - private let exEdgeConsentProdUrl = URL(string: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping - private let exEdgeMediaProdUrl = URL(string: FunctionalTestConst.EX_EDGE_MEDIA_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping - private let exEdgeMediaPreProdUrl = URL(string: FunctionalTestConst.EX_EDGE_MEDIA_PRE_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping - private let exEdgeMediaIntegrationUrl = URL(string: FunctionalTestConst.EX_EDGE_MEDIA_INTEGRATION_URL_STR)! // swiftlint:disable:this force_unwrapping + private let exEdgeConsentProdUrl = URL(string: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping + private let exEdgeMediaProdUrl = URL(string: TestConstants.EX_EDGE_MEDIA_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping + private let exEdgeMediaPreProdUrl = URL(string: TestConstants.EX_EDGE_MEDIA_PRE_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping + private let exEdgeMediaIntegrationUrl = URL(string: TestConstants.EX_EDGE_MEDIA_INTEGRATION_URL_STR)! // swiftlint:disable:this force_unwrapping private let responseBody = "{\"test\": \"json\"}" - public class override func setUp() { - super.setUp() - FunctionalTestBase.debugEnabled = true - } + private let mockNetworkService: MockNetworkService = MockNetworkService() + // Runs before each test case override func setUp() { + ServiceProvider.shared.networkService = mockNetworkService + super.setUp() + continueAfterFailure = false + TestBase.debugEnabled = true FileManager.default.clearCache() - // hub shared state update for 1 extension versions (InstrumentedExtension (registered in FunctionalTestBase), IdentityEdge, Edge) IdentityEdge XDM and Config shared state updates - setExpectationEvent(type: FunctionalTestConst.EventType.HUB, source: FunctionalTestConst.EventSource.SHARED_STATE, expectedCount: 4) + // hub shared state update for 1 extension versions (InstrumentedExtension (registered in TestBase), IdentityEdge, Edge) IdentityEdge XDM and Config shared state updates + setExpectationEvent(type: TestConstants.EventType.HUB, source: TestConstants.EventSource.SHARED_STATE, expectedCount: 4) // expectations for update config request&response events - setExpectationEvent(type: FunctionalTestConst.EventType.CONFIGURATION, source: FunctionalTestConst.EventSource.REQUEST_CONTENT, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.CONFIGURATION, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.CONFIGURATION, source: TestConstants.EventSource.REQUEST_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.CONFIGURATION, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) - // wait for async registration because the EventHub is already started in FunctionalTestBase + // wait for async registration because the EventHub is already started in TestBase let waitForRegistration = CountDownLatch(1) MobileCore.registerExtensions([Identity.self, Edge.self], { print("Extensions registration is complete") @@ -58,6 +60,14 @@ class AEPEdgePathOverwriteTests: FunctionalTestBase { assertExpectedEvents(ignoreUnexpectedEvents: false) resetTestExpectations() + mockNetworkService.reset() + } + + // Runs after each test case + override func tearDown() { + super.tearDown() + + mockNetworkService.reset() } // MARK: test network request with custom path @@ -68,15 +78,15 @@ class AEPEdgePathOverwriteTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_MEDIA_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_MEDIA_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_MEDIA_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_MEDIA_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEventWithOverwritePath = Event(name: "test-experience-event", type: EventType.edge, source: EventSource.requestContent, data: ["xdm": ["test": "data"], "request": ["path": "/va/v1/sessionstart"]]) MobileCore.dispatch(event: experienceEventWithOverwritePath) // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_MEDIA_PROD_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_MEDIA_PROD_URL_STR, httpMethod: HttpMethod.post) let requestUrl = resultNetworkRequests[0].url XCTAssertEqual(Self.EDGE_MEDIA_PROD_PATH_STR, requestUrl.path) @@ -94,15 +104,15 @@ class AEPEdgePathOverwriteTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_MEDIA_PRE_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_MEDIA_PRE_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_MEDIA_PRE_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_MEDIA_PRE_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEventWithOverwritePath = Event(name: "test-experience-event", type: EventType.edge, source: EventSource.requestContent, data: ["xdm": ["test": "data"], "request": ["path": "/va/v1/sessionstart"]]) MobileCore.dispatch(event: experienceEventWithOverwritePath) // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_MEDIA_PRE_PROD_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_MEDIA_PRE_PROD_URL_STR, httpMethod: HttpMethod.post) let requestUrl = resultNetworkRequests[0].url XCTAssertEqual(Self.EDGE_MEDIA_PRE_PROD_PATH_STR, requestUrl.path) @@ -120,15 +130,15 @@ class AEPEdgePathOverwriteTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_MEDIA_INTEGRATION_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_MEDIA_INTEGRATION_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_MEDIA_INTEGRATION_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_MEDIA_INTEGRATION_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEventWithOverwritePath = Event(name: "test-experience-event", type: EventType.edge, source: EventSource.requestContent, data: ["xdm": ["test": "data"], "request": ["path": "/va/v1/sessionstart"]]) MobileCore.dispatch(event: experienceEventWithOverwritePath) // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_MEDIA_INTEGRATION_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_MEDIA_INTEGRATION_URL_STR, httpMethod: HttpMethod.post) let requestUrl = resultNetworkRequests[0].url XCTAssertEqual(Self.EDGE_MEDIA_INTEGRATION_PATH_STR, requestUrl.path) @@ -144,15 +154,15 @@ class AEPEdgePathOverwriteTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEventWithOverwritePath = Event(name: "test-experience-event", type: EventType.edge, source: EventSource.updateConsent, data: ["consents": ["collect": ["val": "y"]], "request": ["path": "/va/v1/sessionstart"]]) MobileCore.dispatch(event: experienceEventWithOverwritePath) // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_CONSENT_PROD_URL_STR, httpMethod: HttpMethod.post) let requestUrl = resultNetworkRequests[0].url XCTAssertEqual(Self.EDGE_CONSENT_PATH_STR, requestUrl.path) @@ -165,15 +175,15 @@ class AEPEdgePathOverwriteTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_MEDIA_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: responseConnection) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_MEDIA_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_MEDIA_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: responseConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_MEDIA_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) let experienceEventWithOverwritePath = Event(name: "test-experience-event", type: EventType.edge, source: EventSource.requestContent, data: ["xdm": ["test": "data"], "request": ["path": "/va/v1/sessionstart"]]) MobileCore.dispatch(event: experienceEventWithOverwritePath) // verify - assertNetworkRequestsCount() - let resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_MEDIA_PROD_URL_STR, httpMethod: HttpMethod.post) + mockNetworkService.assertAllNetworkRequestExpectations() + let resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_MEDIA_PROD_URL_STR, httpMethod: HttpMethod.post) let requestPayload = resultNetworkRequests[0].connectPayload diff --git a/Tests/FunctionalTests/IdentityStateFunctionalTests.swift b/Tests/FunctionalTests/IdentityStateFunctionalTests.swift index 70c32b89..766eee38 100644 --- a/Tests/FunctionalTests/IdentityStateFunctionalTests.swift +++ b/Tests/FunctionalTests/IdentityStateFunctionalTests.swift @@ -16,23 +16,28 @@ import AEPServices import XCTest /// Functional test suite for tests which require no Identity shared state at startup to simulate a missing or pending state. -class IdentityStateFunctionalTests: FunctionalTestBase { +class IdentityStateFunctionalTests: TestBase { - private let exEdgeInteractUrl = URL(string: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping + private let exEdgeInteractUrl = URL(string: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping + + private let mockNetworkService: MockNetworkService = MockNetworkService() override func setUp() { + ServiceProvider.shared.networkService = mockNetworkService + super.setUp() + continueAfterFailure = false // fail so nil checks stop execution - FunctionalTestBase.debugEnabled = false + TestBase.debugEnabled = false - // config state and 2 event hub states (Edge, TestableEdgeInternal, FakeIdentityExtension and InstrumentedExtension registered in FunctionalTestBase) - setExpectationEvent(type: FunctionalTestConst.EventType.HUB, source: FunctionalTestConst.EventSource.SHARED_STATE, expectedCount: 3) + // config state and 2 event hub states (Edge, TestableEdgeInternal, FakeIdentityExtension and InstrumentedExtension registered in TestBase) + setExpectationEvent(type: TestConstants.EventType.HUB, source: TestConstants.EventSource.SHARED_STATE, expectedCount: 3) // expectations for update config request&response events - setExpectationEvent(type: FunctionalTestConst.EventType.CONFIGURATION, source: FunctionalTestConst.EventSource.REQUEST_CONTENT, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.CONFIGURATION, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.CONFIGURATION, source: TestConstants.EventSource.REQUEST_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.CONFIGURATION, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) - // wait for async registration because the EventHub is already started in FunctionalTestBase + // wait for async registration because the EventHub is already started in TestBase let waitForRegistration = CountDownLatch(1) MobileCore.registerExtensions([TestableEdge.self, FakeIdentityExtension.self], { print("Extensions registration is complete") @@ -42,19 +47,27 @@ class IdentityStateFunctionalTests: FunctionalTestBase { MobileCore.updateConfigurationWith(configDict: ["edge.configId": "12345-example"]) assertExpectedEvents(ignoreUnexpectedEvents: false) resetTestExpectations() + mockNetworkService.reset() + } + + // Runs after each test case + override func tearDown() { + super.tearDown() + + mockNetworkService.reset() } func testSendEvent_withPendingIdentityState_noRequestSent() { Edge.sendEvent(experienceEvent: ExperienceEvent(xdm: ["test1": "xdm"], data: nil)) - let requests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 2) + let requests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 2) XCTAssertTrue(requests.isEmpty) } func testSendEvent_withPendingIdentityState_thenValidIdentityState_requestSentAfterChange() { Edge.sendEvent(experienceEvent: ExperienceEvent(xdm: ["test1": "xdm"], data: nil)) - var requests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 2) + var requests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, timeout: 2) XCTAssertTrue(requests.isEmpty) // no network request sent yet guard let responseBody = "{\"test\": \"json\"}".data(using: .utf8) else { @@ -67,8 +80,8 @@ class IdentityStateFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: httpConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: httpConnection) // Once the shared state is set, the Edge Extension is expected to reprocess the original // Send Event request once the Hub Shared State event is received. @@ -90,11 +103,11 @@ class IdentityStateFunctionalTests: FunctionalTestBase { } let identityMap = try? JSONSerialization.jsonObject(with: identityMapData, options: []) as? [String: Any] FakeIdentityExtension.setXDMSharedState(state: identityMap!) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() - requests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + requests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(1, requests.count) - let flattenRequestBody = getFlattenNetworkRequestBody(requests[0]) + let flattenRequestBody = requests[0].getFlattenedBody() XCTAssertEqual("1234", flattenRequestBody["xdm.identityMap.ECID[0].id"] as? String) } @@ -129,17 +142,17 @@ class IdentityStateFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: httpConnection) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: httpConnection) Edge.sendEvent(experienceEvent: ExperienceEvent(xdm: ["test1": "xdm"], data: nil)) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() // Assert network request does not contain an ECID - let requests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + let requests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(1, requests.count) - let flattenRequestBody = getFlattenNetworkRequestBody(requests[0]) + let flattenRequestBody = requests[0].getFlattenedBody() XCTAssertNil(flattenRequestBody["xdm.identityMap.ECID[0].id"]) } diff --git a/Tests/FunctionalTests/NetworkResponseHandlerFunctionalTests.swift b/Tests/FunctionalTests/NetworkResponseHandlerFunctionalTests.swift index a0db52d0..59434bf5 100644 --- a/Tests/FunctionalTests/NetworkResponseHandlerFunctionalTests.swift +++ b/Tests/FunctionalTests/NetworkResponseHandlerFunctionalTests.swift @@ -16,7 +16,7 @@ import AEPCore import XCTest // swiftlint:disable type_body_length -class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { +class NetworkResponseHandlerFunctionalTests: TestBase { private let event1 = Event(name: "e1", type: "eventType", source: "eventSource", data: nil) private let event2 = Event(name: "e2", type: "eventType", source: "eventSource", data: nil) private var networkResponseHandler = NetworkResponseHandler(updateLocationHint: { (_: String?, _: TimeInterval?) -> Void in }) @@ -33,25 +33,25 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { func testProcessResponseOnError_WhenEmptyJsonError_doesNotHandleError() { let jsonError = "" networkResponseHandler.processResponseOnError(jsonError: jsonError, requestId: "123") - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) XCTAssertEqual(0, dispatchEvents.count) } func testProcessResponseOnError_WhenInvalidJsonError_doesNotHandleError() { let jsonError = "{ invalid json }" networkResponseHandler.processResponseOnError(jsonError: jsonError, requestId: "123") - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) XCTAssertEqual(0, dispatchEvents.count) } func testProcessResponseOnError_WhenGenericJsonError_dispatchesEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) let jsonError = "{\n" + "\"type\": \"https://ns.adobe.com/aep/errors/EXEG-0201-503\",\n" + "\"title\": \"Request to Data platform failed with an unknown exception\"" + "\n}" networkResponseHandler.processResponseOnError(jsonError: jsonError, requestId: "123") - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, timeout: 5) + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, timeout: 5) XCTAssertEqual(1, dispatchEvents.count) guard let receivedData = dispatchEvents[0].data else { @@ -67,7 +67,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } func testProcessResponseOnError_WhenOneEventJsonError_dispatchesEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) let jsonError = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [],\n" + @@ -82,7 +82,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }" networkResponseHandler.addWaitingEvents(requestId: "123", batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnError(jsonError: jsonError, requestId: "123") - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) XCTAssertEqual(1, dispatchEvents.count) guard let receivedData = dispatchEvents[0].data else { @@ -101,7 +101,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } func testProcessResponseOnError_WhenValidEventIndex_dispatchesPairedEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) let requestId = "123" let jsonError = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + @@ -119,7 +119,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }" networkResponseHandler.addWaitingEvents(requestId: requestId, batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnError(jsonError: jsonError, requestId: requestId) - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) XCTAssertEqual(1, dispatchEvents.count) guard let receivedData = dispatchEvents[0].data else { @@ -138,7 +138,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } func testProcessResponseOnError_WhenUnknownEventIndex_doesNotCrash() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) let requestId = "123" let jsonError = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + @@ -156,7 +156,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }" networkResponseHandler.addWaitingEvents(requestId: requestId, batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnError(jsonError: jsonError, requestId: requestId) - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) XCTAssertEqual(1, dispatchEvents.count) guard let receivedData = dispatchEvents[0].data else { @@ -174,7 +174,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertNil(dispatchEvents[0].parentID) // Parent ID not chained as no event at index 10 } func testProcessResponseOnError_WhenUnknownRequestId_doesNotCrash() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) let requestId = "123" let jsonError = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + @@ -192,7 +192,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }" networkResponseHandler.addWaitingEvents(requestId: requestId, batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnError(jsonError: jsonError, requestId: "567") - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) XCTAssertEqual(1, dispatchEvents.count) guard let receivedData = dispatchEvents[0].data else { @@ -210,7 +210,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertNil(dispatchEvents[0].parentID) // Parent ID not chained as request ID is unknown (does not match any waiting event list) } func testProcessResponseOnError_WhenTwoEventJsonError_dispatchesTwoEvents() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 2) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 2) let requestId = "123" let jsonError = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + @@ -232,7 +232,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: requestId, batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnError(jsonError: jsonError, requestId: requestId) - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) XCTAssertEqual(2, dispatchEvents.count) guard let receivedData1 = dispatchEvents[0].data else { @@ -266,16 +266,16 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { func testProcessResponseOnSuccess_WhenEmptyJsonResponse_doesNotDispatchEvent() { let jsonResponse = "" networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: "123") - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.RESPONSE_CONTENT) + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.RESPONSE_CONTENT) XCTAssertEqual(0, dispatchEvents.count) } func testProcessResponseOnSuccess_WhenInvalidJsonResponse_doesNotDispatchEvent() { let jsonResponse = "{ invalid json }" networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: "123") - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.RESPONSE_CONTENT) + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.RESPONSE_CONTENT) XCTAssertEqual(0, dispatchEvents.count) } @@ -287,7 +287,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event]) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -317,7 +317,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.setLastReset(date: Date(timeIntervalSince1970: Date().timeIntervalSince1970 + 10)) // date is after `event.timestamp` // date is after `event.timestamp` - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -350,7 +350,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event]) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -382,7 +382,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event]) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -406,7 +406,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } func testProcessResponseOnSuccess_WhenOneEventHandle_dispatchesEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -424,7 +424,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }" networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: "123") - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: "state:store") + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: "state:store") XCTAssertEqual(1, dispatchEvents.count) guard let receivedData = dispatchEvents[0].data else { XCTFail("Invalid event data") @@ -441,7 +441,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } func testProcessResponseOnSuccess_WhenOneEventHandle_emptyEventHandlePayload_dispatchesEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -453,7 +453,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }" networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: "123") - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: "state:store") + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: "state:store") XCTAssertEqual(1, dispatchEvents.count) guard let receivedData = dispatchEvents[0].data else { XCTFail("Invalid event data") @@ -468,7 +468,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } func testProcessResponseOnSuccess_WhenOneEventHandle_noEventHandlePayload_dispatchesEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -479,7 +479,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }" networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: "123") - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: "state:store") + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: "state:store") XCTAssertEqual(1, dispatchEvents.count) guard let receivedData = dispatchEvents[0].data else { XCTFail("Invalid event data") @@ -492,7 +492,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } func testProcessResponseOnSuccess_WhenOneEventHandle_emptyEventHandleType_dispatchesEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -510,7 +510,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }" networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: "123") - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT) + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT) XCTAssertEqual(1, dispatchEvents.count) guard let receivedData = dispatchEvents[0].data else { XCTFail("Invalid event data") @@ -526,7 +526,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } func testProcessResponseOnSuccess_WhenOneEventHandle_nilEventHandleType_dispatchesEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -543,7 +543,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }" networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: "123") - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT) + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT) XCTAssertEqual(1, dispatchEvents.count) guard let receivedData = dispatchEvents[0].data else { XCTFail("Invalid event data") @@ -559,7 +559,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } func testProcessResponseOnSuccess_WhenTwoEventHandles_dispatchesTwoEvents() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 2) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 2) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -589,8 +589,8 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: "d81c93e5-7558-4996-a93c-489d550748b8") - var dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: "state:store") - dispatchEvents += getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: "identity:persist") + var dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: "state:store") + dispatchEvents += getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: "identity:persist") XCTAssertEqual(2, dispatchEvents.count) // verify event 1 guard let receivedData1 = dispatchEvents[0].data else { @@ -623,7 +623,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } func testProcessResponseOnSuccess_WhenEventHandleWithEventIndex_dispatchesEventWithRequestEventId() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 2) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 2) let requestId = "123" let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + @@ -652,9 +652,9 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: requestId) // verify event 1 - var dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, + var dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: "state:store") - dispatchEvents += getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, + dispatchEvents += getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: "pairedeventexample") XCTAssertEqual(2, dispatchEvents.count) guard let receivedData1 = dispatchEvents[0].data else { @@ -686,7 +686,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } func testProcessResponseOnSuccess_WhenEventHandleWithUnknownEventIndex_dispatchesUnpairedEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let requestId = "123" let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + @@ -706,7 +706,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: requestId, batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: requestId) - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: "pairedeventexample") + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: "pairedeventexample") XCTAssertEqual(1, dispatchEvents.count) guard let receivedData1 = dispatchEvents[0].data else { XCTFail("Invalid event data for event 1") @@ -721,8 +721,8 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } func testProcessResponseOnSuccess_WhenUnknownRequestId_doesNotCrash() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, + setExpectationEvent(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let requestId = "123" let jsonResponse = "{\n" + @@ -743,7 +743,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: "567", batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: requestId) - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: "pairedeventexample") XCTAssertEqual(1, dispatchEvents.count) guard let receivedData1 = dispatchEvents[0].data else { @@ -761,11 +761,11 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { // MARK: processResponseOnSuccess with mixed event handles, errors, warnings func testProcessResponseOnSuccess_WhenEventHandleAndError_dispatchesTwoEvents() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, + setExpectationEvent(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, + setExpectationEvent(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) let requestId = "123" let jsonResponse = "{\n" + @@ -796,7 +796,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: requestId, batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: requestId) - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: "state:store") + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: "state:store") XCTAssertEqual(1, dispatchEvents.count) guard let receivedData1 = dispatchEvents[0].data else { XCTFail("Invalid event data for event 1") @@ -812,7 +812,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual(event2.id.uuidString, flattenReceivedData1["requestEventId"] as? String) XCTAssertEqual(event2.id, dispatchEvents[0].parentID) // Event chained to event2 as event index is 1 - let dispatchErrorEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + let dispatchErrorEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) XCTAssertEqual(1, dispatchErrorEvents.count) guard let receivedData2 = dispatchErrorEvents[0].data else { XCTFail("Invalid event data for event 2") @@ -830,7 +830,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } func testProcessResponseOnSuccess_WhenErrorAndWarning_dispatchesTwoEvents() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 2) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 2) let requestId = "123" let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + @@ -863,7 +863,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: requestId, batchedEvents: [event1, event2]) networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: requestId) - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) XCTAssertEqual(2, dispatchEvents.count) guard let receivedData1 = dispatchEvents[0].data else { XCTFail("Invalid event data for event 1") @@ -894,11 +894,11 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { } func testProcessResponseOnSuccess_WhenEventHandleAndErrorAndWarning_dispatchesThreeEvents() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, + setExpectationEvent(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT, + setExpectationEvent(type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 2) let requestId = "123" let jsonResponse = "{\n" + @@ -938,7 +938,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: requestId, batchedEvents: [event1]) networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: requestId) - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: "state:store") + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: "state:store") XCTAssertEqual(1, dispatchEvents.count) guard let receivedData1 = dispatchEvents[0].data else { XCTFail("Invalid event data for dispatched handle") @@ -953,8 +953,8 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { XCTAssertEqual(15552000, flattenReceivedData1["payload[0].maxAge"] as? Int) XCTAssertEqual(event1.id.uuidString, flattenReceivedData1["requestEventId"] as? String) XCTAssertEqual(event1.id, dispatchEvents[0].parentID) // Event chained to event1 as event index defaults to 0 - - let dispatchErrorEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.ERROR_RESPONSE_CONTENT) + + let dispatchErrorEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) XCTAssertEqual(2, dispatchErrorEvents.count) guard let receivedData2 = dispatchErrorEvents[0].data else { XCTFail("Invalid event data for dispatched error") @@ -990,7 +990,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { // MARK: locationHint:result func testProcessResponseOnSuccess_WhenLocationHintResultEventHandle_dispatchesEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -1012,7 +1012,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { " }" networkResponseHandler.processResponseOnSuccess(jsonResponse: jsonResponse, requestId: "123") - let dispatchEvents = getDispatchedEventsWith(type: FunctionalTestConst.EventType.EDGE, source: "locationHint:result") + let dispatchEvents = getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: "locationHint:result") XCTAssertEqual(1, dispatchEvents.count) guard let receivedData = dispatchEvents[0].data else { XCTFail("Invalid event data") @@ -1046,7 +1046,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event]) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -1084,7 +1084,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.setLastReset(date: Date(timeIntervalSince1970: Date().timeIntervalSince1970 + 10)) // date is after `event.timestamp` // date is after `event.timestamp` - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -1123,7 +1123,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event]) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -1161,7 +1161,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event]) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -1197,7 +1197,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event]) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -1233,7 +1233,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event]) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -1282,7 +1282,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event]) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -1317,7 +1317,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event]) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -1353,7 +1353,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event]) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + @@ -1388,7 +1388,7 @@ class NetworkResponseHandlerFunctionalTests: FunctionalTestBase { networkResponseHandler.addWaitingEvents(requestId: "d81c93e5-7558-4996-a93c-489d550748b8", batchedEvents: [event]) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) let jsonResponse = "{\n" + " \"requestId\": \"d81c93e5-7558-4996-a93c-489d550748b8\",\n" + " \"handle\": [" + diff --git a/Tests/FunctionalTests/NoConfigFunctionalTests.swift b/Tests/FunctionalTests/NoConfigFunctionalTests.swift index 530fd3f5..d9636424 100644 --- a/Tests/FunctionalTests/NoConfigFunctionalTests.swift +++ b/Tests/FunctionalTests/NoConfigFunctionalTests.swift @@ -17,25 +17,36 @@ import AEPServices import XCTest /// Functional test suite for tests which require no SDK configuration and nil/pending configuration shared state. -class NoConfigFunctionalTests: FunctionalTestBase { +class NoConfigFunctionalTests: TestBase { + private let mockNetworkService: MockNetworkService = MockNetworkService() override func setUp() { + ServiceProvider.shared.networkService = mockNetworkService + super.setUp() continueAfterFailure = false // fail so nil checks stop execution - FunctionalTestBase.debugEnabled = false + TestBase.debugEnabled = false - // event hub shared state for registered extensions (Edge, TestableEdge and InstrumentedExtension registered in FunctionalTestBase) - setExpectationEvent(type: FunctionalTestConst.EventType.HUB, source: FunctionalTestConst.EventSource.SHARED_STATE, expectedCount: 2) + // event hub shared state for registered extensions (Edge, TestableEdge and InstrumentedExtension registered in TestBase) + setExpectationEvent(type: TestConstants.EventType.HUB, source: TestConstants.EventSource.SHARED_STATE, expectedCount: 2) MobileCore.registerExtensions([TestableEdge.self]) assertExpectedEvents(ignoreUnexpectedEvents: false) resetTestExpectations() + mockNetworkService.reset() + } + + // Runs after each test case + override func tearDown() { + super.tearDown() + + mockNetworkService.reset() } func testHandleExperienceEventRequest_withPendingConfigurationState_expectEventsQueueIsBlocked() { // NOTE: Configuration shared state must be PENDING (nil) for this test to be valid - let configState = getSharedStateFor(FunctionalTestConst.SharedState.CONFIGURATION) + let configState = getSharedStateFor(TestConstants.SharedState.CONFIGURATION) XCTAssertNil(configState) // set expectations @@ -48,8 +59,8 @@ class NoConfigFunctionalTests: FunctionalTestBase { // Dispatch request event which will block request queue as Configuration state is nil let requestEvent = Event(name: "Request Test", - type: FunctionalTestConst.EventType.EDGE, - source: FunctionalTestConst.EventSource.REQUEST_CONTENT, + type: TestConstants.EventType.EDGE, + source: TestConstants.EventSource.REQUEST_CONTENT, data: ["key": "value"]) MobileCore.dispatch(event: requestEvent) @@ -65,14 +76,14 @@ class NoConfigFunctionalTests: FunctionalTestBase { // swiftlint:disable:next line_length let responseBody = "\u{0000}{\"requestId\": \"0ee43289-4a4e-469a-bf5c-1d8186919a26\",\"handle\": [{\"payload\": [{\"id\": \"AT:eyJhY3Rpdml0eUlkIjoiMTE3NTg4IiwiZXhwZXJpZW5jZUlkIjoiMSJ9\",\"scope\": \"buttonColor\",\"items\": [{ \"schema\": \"https://ns.adobe.com/personalization/json-content-item\",\"data\": {\"content\": {\"value\": \"#D41DBA\"}}}]}],\"type\": \"personalization:decisions\"},{\"payload\": [{\"type\": \"url\",\"id\": 411,\"spec\": {\"url\": \"//example.url?d_uuid=9876\",\"hideReferrer\": false,\"ttlMinutes\": 10080}}],\"type\": \"identity:exchange\"}]}\n" - let edgeUrl = URL(string: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping + let edgeUrl = URL(string: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR)! // swiftlint:disable:this force_unwrapping let httpConnection: HttpConnection = HttpConnection(data: responseBody.data(using: .utf8), response: HTTPURLResponse(url: edgeUrl, statusCode: 200, httpVersion: nil, headerFields: nil), error: nil) - setNetworkResponseFor(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseHttpConnection: httpConnection) + mockNetworkService.setMockResponseFor(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, responseConnection: httpConnection) // test sendEvent does not send the event when config is pending MobileCore.registerExtension(Identity.self) @@ -83,18 +94,18 @@ class NoConfigFunctionalTests: FunctionalTestBase { receivedHandles = handles expectation.fulfill() }) - var resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + var resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(0, resultNetworkRequests.count) // test event gets processed when config shared state is resolved\ - setExpectationNetworkRequest(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setExpectationForNetworkRequest(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post, expectedCount: 1) MobileCore.updateConfigurationWith(configDict: ["edge.configId": "123567"]) // verify - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() wait(for: [expectation], timeout: 0.2) - resultNetworkRequests = getNetworkRequestsWith(url: FunctionalTestConst.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) + resultNetworkRequests = mockNetworkService.getNetworkRequestsWith(url: TestConstants.EX_EDGE_INTERACT_PROD_URL_STR, httpMethod: HttpMethod.post) XCTAssertEqual(2, receivedHandles.count) XCTAssertEqual("personalization:decisions", receivedHandles[0].type) XCTAssertEqual(1, receivedHandles[0].payload?.count) diff --git a/Tests/FunctionalTests/SampleFunctionalTests.swift b/Tests/FunctionalTests/SampleFunctionalTests.swift index 0f78631e..31fe60a8 100644 --- a/Tests/FunctionalTests/SampleFunctionalTests.swift +++ b/Tests/FunctionalTests/SampleFunctionalTests.swift @@ -17,31 +17,32 @@ import AEPServices import Foundation import XCTest -/// This Test class is an example of usages of the FunctionalTestBase APIs -class SampleFunctionalTests: FunctionalTestBase { +/// This Test class is an example of usages of the TestBase APIs +class SampleFunctionalTests: TestBase { private let event1 = Event(name: "e1", type: "eventType", source: "eventSource", data: nil) private let event2 = Event(name: "e2", type: "eventType", source: "eventSource", data: nil) private let exEdgeInteractUrlString = "https://edge.adobedc.net/ee/v1/interact" private let exEdgeInteractUrl = URL(string: "https://edge.adobedc.net/ee/v1/interact")! // swiftlint:disable:this force_unwrapping private let responseBody = "{\"test\": \"json\"}" - public class override func setUp() { - super.setUp() - FunctionalTestBase.debugEnabled = true - } + private let mockNetworkService: MockNetworkService = MockNetworkService() override func setUp() { + ServiceProvider.shared.networkService = mockNetworkService + super.setUp() + continueAfterFailure = false + TestBase.debugEnabled = true - // hub shared state update for extension versions (InstrumentedExtension (registered in FunctionalTestBase), IdentityEdge, Edge), Edge extension, IdentityEdge XDM shared state and Config shared state updates - setExpectationEvent(type: FunctionalTestConst.EventType.HUB, source: FunctionalTestConst.EventSource.SHARED_STATE, expectedCount: 4) + // hub shared state update for extension versions (InstrumentedExtension (registered in TestBase), IdentityEdge, Edge), Edge extension, IdentityEdge XDM shared state and Config shared state updates + setExpectationEvent(type: TestConstants.EventType.HUB, source: TestConstants.EventSource.SHARED_STATE, expectedCount: 4) // expectations for update config request&response events - setExpectationEvent(type: FunctionalTestConst.EventType.CONFIGURATION, source: FunctionalTestConst.EventSource.REQUEST_CONTENT, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.CONFIGURATION, source: FunctionalTestConst.EventSource.RESPONSE_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.CONFIGURATION, source: TestConstants.EventSource.REQUEST_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.CONFIGURATION, source: TestConstants.EventSource.RESPONSE_CONTENT, expectedCount: 1) - // wait for async registration because the EventHub is already started in FunctionalTestBase + // wait for async registration because the EventHub is already started in TestBase let waitForRegistration = CountDownLatch(1) MobileCore.registerExtensions([Identity.self, Edge.self], { print("Extensions registration is complete") @@ -52,8 +53,16 @@ class SampleFunctionalTests: FunctionalTestBase { assertExpectedEvents(ignoreUnexpectedEvents: false) resetTestExpectations() + mockNetworkService.reset() } + // Runs after each test case + override func tearDown() { + super.tearDown() + + mockNetworkService.reset() + } + // MARK: sample tests for the FunctionalTest framework usage func testSample_AssertUnexpectedEvents() { @@ -101,18 +110,18 @@ class SampleFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setExpectationNetworkRequest(url: exEdgeInteractUrlString, httpMethod: HttpMethod.post, expectedCount: 2) - setNetworkResponseFor(url: exEdgeInteractUrlString, httpMethod: HttpMethod.post, responseHttpConnection: httpConnection) + mockNetworkService.setExpectationForNetworkRequest(url: exEdgeInteractUrlString, httpMethod: HttpMethod.post, expectedCount: 2) + mockNetworkService.setMockResponseFor(url: exEdgeInteractUrlString, httpMethod: HttpMethod.post, responseConnection: httpConnection) Edge.sendEvent(experienceEvent: ExperienceEvent(xdm: ["test1": "xdm"], data: nil)) Edge.sendEvent(experienceEvent: ExperienceEvent(xdm: ["test2": "xdm"], data: nil)) - assertNetworkRequestsCount() + mockNetworkService.assertAllNetworkRequestExpectations() } func testSample_AssertNetworkRequestAndResponseEvent() { - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: FunctionalTestConst.EventSource.REQUEST_CONTENT, expectedCount: 1) - setExpectationEvent(type: FunctionalTestConst.EventType.EDGE, source: "identity:exchange", expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.REQUEST_CONTENT, expectedCount: 1) + setExpectationEvent(type: TestConstants.EventType.EDGE, source: "identity:exchange", expectedCount: 1) // swiftlint:disable:next line_length let responseBody = "\u{0000}{\"requestId\":\"ded17427-c993-4182-8d94-2a169c1a23e2\",\"handle\":[{\"type\":\"identity:exchange\",\"payload\":[{\"type\":\"url\",\"id\":411,\"spec\":{\"url\":\"//cm.everesttech.net/cm/dd?d_uuid=42985602780892980519057012517360930936\",\"hideReferrer\":false,\"ttlMinutes\":10080}}]}]}\n" let httpConnection: HttpConnection = HttpConnection(data: responseBody.data(using: .utf8), @@ -121,15 +130,15 @@ class SampleFunctionalTests: FunctionalTestBase { httpVersion: nil, headerFields: nil), error: nil) - setExpectationNetworkRequest(url: exEdgeInteractUrlString, httpMethod: HttpMethod.post, expectedCount: 1) - setNetworkResponseFor(url: exEdgeInteractUrlString, httpMethod: HttpMethod.post, responseHttpConnection: httpConnection) + mockNetworkService.setExpectationForNetworkRequest(url: exEdgeInteractUrlString, httpMethod: HttpMethod.post, expectedCount: 1) + mockNetworkService.setMockResponseFor(url: exEdgeInteractUrlString, httpMethod: HttpMethod.post, responseConnection: httpConnection) Edge.sendEvent(experienceEvent: ExperienceEvent(xdm: ["eventType": "testType", "test": "xdm"], data: nil)) - let requests = getNetworkRequestsWith(url: exEdgeInteractUrlString, httpMethod: HttpMethod.post) + let requests = mockNetworkService.getNetworkRequestsWith(url: exEdgeInteractUrlString, httpMethod: HttpMethod.post) XCTAssertEqual(1, requests.count) - let flattenRequestBody = getFlattenNetworkRequestBody(requests[0]) + let flattenRequestBody = requests[0].getFlattenedBody() XCTAssertEqual("testType", flattenRequestBody["events[0].xdm.eventType"] as? String) assertExpectedEvents(ignoreUnexpectedEvents: true) diff --git a/Tests/FunctionalTests/util/FakeIdentityExtension.swift b/Tests/FunctionalTests/util/FakeIdentityExtension.swift index b0973ae2..7b5f2fc6 100644 --- a/Tests/FunctionalTests/util/FakeIdentityExtension.swift +++ b/Tests/FunctionalTests/util/FakeIdentityExtension.swift @@ -13,7 +13,7 @@ import AEPCore import XCTest -/// Extension used to 'fake' an Identity extension and allows tests to clear and set the Identity shared state. Use it along with `FunctionalTestBase` +/// Extension used to 'fake' an Identity extension and allows tests to clear and set the Identity shared state. Use it along with `TestBase` /// Cannot be used along with another Identity Extension which is registered with ACPCore. class FakeIdentityExtension: NSObject, Extension { private static let logTag = "FakeIdentityExtension" diff --git a/Tests/FunctionalTests/util/FunctionalTestNetworkService.swift b/Tests/FunctionalTests/util/FunctionalTestNetworkService.swift deleted file mode 100644 index 4c34abb4..00000000 --- a/Tests/FunctionalTests/util/FunctionalTestNetworkService.swift +++ /dev/null @@ -1,137 +0,0 @@ -// -// Copyright 2020 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. -// - -@testable import AEPEdge -@testable import AEPServices -import Foundation - -/// Overriding NetworkService used for functional tests when extending the FunctionalTestBase -class FunctionalTestNetworkService: NetworkService { - private var receivedNetworkRequests: [NetworkRequest: [NetworkRequest]] = [NetworkRequest: [NetworkRequest]]() - private var responseMatchers: [NetworkRequest: HttpConnection] = [NetworkRequest: HttpConnection]() - private var expectedNetworkRequests: [NetworkRequest: CountDownLatch] = [NetworkRequest: CountDownLatch]() - private var delayedResponse: UInt32 = 0 - - override func connectAsync(networkRequest: NetworkRequest, completionHandler: ((HttpConnection) -> Void)? = nil) { - FunctionalTestBase.log("Received connectAsync to URL \(networkRequest.url.absoluteString) and HTTPMethod \(networkRequest.httpMethod.toString())") - if var requests = receivedNetworkRequests[networkRequest] { - requests.append(networkRequest) - } else { - receivedNetworkRequests[networkRequest] = [networkRequest] - } - - countDownExpected(networkRequest: networkRequest) - guard let unwrappedCompletionHandler = completionHandler else { return } - - if delayedResponse > 0 { - sleep(delayedResponse) - } - - if let response = getMarchedResponseForUrlAndHttpMethod(networkRequest: networkRequest) { - unwrappedCompletionHandler(response) - } else { - // default response - unwrappedCompletionHandler(HttpConnection(data: "".data(using: .utf8), - response: HTTPURLResponse(url: networkRequest.url, - statusCode: 200, - httpVersion: nil, - headerFields: nil), - error: nil)) - } - } - - func enableDelayedResponse(delaySec: UInt32) { - delayedResponse = delaySec - } - - func reset() { - expectedNetworkRequests.removeAll() - receivedNetworkRequests.removeAll() - responseMatchers.removeAll() - delayedResponse = 0 - } - - func awaitFor(networkRequest: NetworkRequest, timeout: TimeInterval) -> DispatchTimeoutResult? { - for expectedNetworkRequest in expectedNetworkRequests { - if areNetworkRequestsEqual(lhs: expectedNetworkRequest.key, rhs: networkRequest) { - return expectedNetworkRequest.value.await(timeout: timeout) - } - } - - return nil - } - - func getReceivedNetworkRequestsMatching(networkRequest: NetworkRequest) -> [NetworkRequest] { - var matchingRequests: [NetworkRequest] = [] - for receivedRequest in receivedNetworkRequests { - if areNetworkRequestsEqual(lhs: receivedRequest.key, rhs: networkRequest) { - matchingRequests.append(receivedRequest.key) - } - } - - return matchingRequests - } - - func setExpectedNetworkRequest(networkRequest: NetworkRequest, count: Int32) { - expectedNetworkRequests[networkRequest] = CountDownLatch(count) - } - - func getExpectedNetworkRequests() -> [NetworkRequest: CountDownLatch] { - return expectedNetworkRequests - } - - func setResponseConnectionFor(networkRequest: NetworkRequest, responseConnection: HttpConnection?) -> Bool { - for responseMatcher in responseMatchers { - if areNetworkRequestsEqual(lhs: responseMatcher.key, rhs: networkRequest) { - // unable to override response matcher - return false - } - } - - // add new entry if not present already - responseMatchers[networkRequest] = responseConnection - return true - } - - private func countDownExpected(networkRequest: NetworkRequest) { - for expectedNetworkRequest in expectedNetworkRequests { - if areNetworkRequestsEqual(lhs: expectedNetworkRequest.key, rhs: networkRequest) { - expectedNetworkRequest.value.countDown() - } - } - } - - private func getMarchedResponseForUrlAndHttpMethod(networkRequest: NetworkRequest) -> HttpConnection? { - for responseMatcher in responseMatchers { - if areNetworkRequestsEqual(lhs: responseMatcher.key, rhs: networkRequest) { - return responseMatcher.value - } - } - - return nil - } - - /// Equals compare based on host, scheme and URL path. Query params are not taken into consideration - private func areNetworkRequestsEqual(lhs: NetworkRequest, rhs: NetworkRequest) -> Bool { - return lhs.url.host?.lowercased() == rhs.url.host?.lowercased() - && lhs.url.scheme?.lowercased() == rhs.url.scheme?.lowercased() - && lhs.url.path.lowercased() == rhs.url.path.lowercased() - && lhs.httpMethod.rawValue == rhs.httpMethod.rawValue - } -} - -extension URL { - func queryParam(_ param: String) -> String? { - guard let url = URLComponents(string: self.absoluteString) else { return nil } - return url.queryItems?.first(where: { $0.name == param })?.value - } -} diff --git a/Tests/FunctionalTests/util/CountDownLatch.swift b/Tests/TestUtils/CountDownLatch.swift similarity index 100% rename from Tests/FunctionalTests/util/CountDownLatch.swift rename to Tests/TestUtils/CountDownLatch.swift diff --git a/Tests/FunctionalTests/util/FileManager+Testable.swift b/Tests/TestUtils/FileManager+Testable.swift similarity index 100% rename from Tests/FunctionalTests/util/FileManager+Testable.swift rename to Tests/TestUtils/FileManager+Testable.swift diff --git a/Tests/FunctionalTests/util/InstrumentedExtension.swift b/Tests/TestUtils/InstrumentedExtension.swift similarity index 82% rename from Tests/FunctionalTests/util/InstrumentedExtension.swift rename to Tests/TestUtils/InstrumentedExtension.swift index a9b3e547..b6470206 100644 --- a/Tests/FunctionalTests/util/InstrumentedExtension.swift +++ b/Tests/TestUtils/InstrumentedExtension.swift @@ -14,7 +14,7 @@ import AEPCore import AEPServices import XCTest -/// Instrumented extension that registers a wildcard listener for intercepting events in current session. Use it along with `FunctionalTestBase` +/// Instrumented extension that registers a wildcard listener for intercepting events in current session. Use it along with `TestBase` class InstrumentedExtension: NSObject, Extension { private static let logTag = "InstrumentedExtension" var name = "com.adobe.InstrumentedExtension" @@ -45,13 +45,13 @@ class InstrumentedExtension: NSObject, Extension { // MARK: Event Processors func wildcardListenerProcessor(_ event: Event) { - if event.type.lowercased() == FunctionalTestConst.EventType.INSTRUMENTED_EXTENSION.lowercased() { + if event.type.lowercased() == TestConstants.EventType.INSTRUMENTED_EXTENSION.lowercased() { // process the shared state request event - if event.source.lowercased() == FunctionalTestConst.EventSource.SHARED_STATE_REQUEST.lowercased() { + if event.source.lowercased() == TestConstants.EventSource.SHARED_STATE_REQUEST.lowercased() { processSharedStateRequest(event) } // process the unregister extension event - else if event.source.lowercased() == FunctionalTestConst.EventSource.UNREGISTER_EXTENSION.lowercased() { + else if event.source.lowercased() == TestConstants.EventSource.UNREGISTER_EXTENSION.lowercased() { unregisterExtension() } @@ -81,16 +81,16 @@ class InstrumentedExtension: NSObject, Extension { /// - Parameter event: event sent from `getSharedStateFor` which specifies the shared state `stateowner` to retrieve func processSharedStateRequest(_ event: Event) { guard let eventData = event.data, !eventData.isEmpty else { return } - guard let owner = eventData[FunctionalTestConst.EventDataKey.STATE_OWNER] as? String else { return } + guard let owner = eventData[TestConstants.EventDataKey.STATE_OWNER] as? String else { return } - var responseData: [String: Any?] = [FunctionalTestConst.EventDataKey.STATE_OWNER: owner, FunctionalTestConst.EventDataKey.STATE: nil] + var responseData: [String: Any?] = [TestConstants.EventDataKey.STATE_OWNER: owner, TestConstants.EventDataKey.STATE: nil] if let state = runtime.getSharedState(extensionName: owner, event: event, barrier: false) { - responseData[FunctionalTestConst.EventDataKey.STATE] = state + responseData[TestConstants.EventDataKey.STATE] = state } let responseEvent = event.createResponseEvent(name: "Get Shared State Response", - type: FunctionalTestConst.EventType.INSTRUMENTED_EXTENSION, - source: FunctionalTestConst.EventSource.SHARED_STATE_RESPONSE, + type: TestConstants.EventType.INSTRUMENTED_EXTENSION, + source: TestConstants.EventSource.SHARED_STATE_RESPONSE, data: responseData as [String: Any]) Log.debug(label: InstrumentedExtension.logTag, "ProcessSharedStateRequest Responding with shared state \(String(describing: responseData))") diff --git a/Tests/TestUtils/MockNetworkService.swift b/Tests/TestUtils/MockNetworkService.swift new file mode 100644 index 00000000..efa7ec53 --- /dev/null +++ b/Tests/TestUtils/MockNetworkService.swift @@ -0,0 +1,101 @@ +// +// Copyright 2023 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. +// + +@testable import AEPServices +import Foundation +import XCTest + +/// `Networking` adhering network service utility used for tests that require mocked network requests and mocked responses +class MockNetworkService: Networking { + private let helper: NetworkRequestHelper = NetworkRequestHelper() + private var responseDelay: UInt32 = 0 + + func connectAsync(networkRequest: NetworkRequest, completionHandler: ((HttpConnection) -> Void)? = nil) { + helper.recordSentNetworkRequest(networkRequest) + self.helper.countDownExpected(networkRequest: networkRequest) + guard let unwrappedCompletionHandler = completionHandler else { return } + + if self.responseDelay > 0 { + sleep(self.responseDelay) + } + + if let response = self.getMockResponsesFor(networkRequest: networkRequest).first { + unwrappedCompletionHandler(response) + } else { + // Default mock response + unwrappedCompletionHandler( + HttpConnection( + data: "".data(using: .utf8), + response: HTTPURLResponse(url: networkRequest.url, + statusCode: 200, + httpVersion: nil, + headerFields: nil), + error: nil) + ) + } + } + + func reset() { + responseDelay = 0 + helper.reset() + } + + /// Sets the provided delay for all network responses, until reset + /// - Parameter delaySec: delay in seconds + func enableNetworkResponseDelay(timeInSeconds: UInt32) { + responseDelay = timeInSeconds + } + + /// Sets the mock `HttpConnection` response connection for a given `NetworkRequest`. Should only be used + /// when in mock mode. + func setMockResponseFor(networkRequest: NetworkRequest, responseConnection: HttpConnection?) { + helper.setResponseFor(networkRequest: networkRequest, responseConnection: responseConnection) + } + + /// Sets the mock `HttpConnection` response connection for a given `NetworkRequest`. Should only be used + /// when in mock mode. + func setMockResponseFor(url: String, httpMethod: HttpMethod, responseConnection: HttpConnection?) { + guard let networkRequest = NetworkRequest(urlString: url, httpMethod: httpMethod) else { + return + } + helper.setResponseFor(networkRequest: networkRequest, responseConnection: responseConnection) + } + + // MARK: - Passthrough for shared helper APIs + + /// Set the expected number of times a `NetworkRequest` should be seen. + /// + /// - Parameters: + /// - url: the URL string of the `NetworkRequest` to set the expectation for + /// - httpMethod: the `HttpMethod` of the `NetworkRequest` to set the expectation for + /// - expectedCount: how many times a request with this url and httpMethod is expected to be sent, by default it is set to 1 + func setExpectationForNetworkRequest(url: String, httpMethod: HttpMethod, expectedCount: Int32 = 1, file: StaticString = #file, line: UInt = #line) { + guard let networkRequest = NetworkRequest(urlString: url, httpMethod: httpMethod) else { + return + } + helper.setExpectationForNetworkRequest(networkRequest: networkRequest, expectedCount: expectedCount, file: file, line: line) + } + + func assertAllNetworkRequestExpectations(file: StaticString = #file, line: UInt = #line) { + helper.assertAllNetworkRequestExpectations(file: file, line: line) + } + + func getNetworkRequestsWith(url: String, httpMethod: HttpMethod, timeout: TimeInterval = TestConstants.Defaults.WAIT_NETWORK_REQUEST_TIMEOUT, file: StaticString = #file, line: UInt = #line) -> [NetworkRequest] { + helper.getNetworkRequestsWith(url: url, httpMethod: httpMethod, timeout: timeout, file: file, line: line) + } + + // MARK: - Private helpers + // MARK: Network request response helpers + private func getMockResponsesFor(networkRequest: NetworkRequest) -> [HttpConnection] { + return helper.getResponsesFor(networkRequest: networkRequest) + } +} diff --git a/Tests/TestUtils/NetworkRequestHelper.swift b/Tests/TestUtils/NetworkRequestHelper.swift new file mode 100644 index 00000000..5e7d06fc --- /dev/null +++ b/Tests/TestUtils/NetworkRequestHelper.swift @@ -0,0 +1,206 @@ +// +// Copyright 2020 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. +// + +@testable import AEPServices +import Foundation +import XCTest + +/// Implements shared utilities and logic for `NetworkService`/`Networking` class implementations +/// used for testing. +/// +/// - See also: +/// - ``MockNetworkService`` +/// - ``RealNetworkService`` +class NetworkRequestHelper { + private var sentNetworkRequests: [NetworkRequest: [NetworkRequest]] = [:] + /// Matches sent `NetworkRequest`s with their corresponding `HttpConnection` response. + private(set) var networkResponses: [NetworkRequest: HttpConnection] = [:] + private var expectedNetworkRequests: [NetworkRequest: CountDownLatch] = [:] + + func recordSentNetworkRequest(_ networkRequest: NetworkRequest) { + TestBase.log("Received connectAsync to URL \(networkRequest.url.absoluteString) and HTTPMethod \(networkRequest.httpMethod.toString())") + if let equalNetworkRequest = sentNetworkRequests.first(where: { key, _ in + networkRequest.isCustomEqual(key) + }) { + sentNetworkRequests[equalNetworkRequest.key]!.append(networkRequest) + } else { + sentNetworkRequests[networkRequest] = [networkRequest] + } + } + + func reset() { + expectedNetworkRequests.removeAll() + sentNetworkRequests.removeAll() + networkResponses.removeAll() + } + + func countDownExpected(networkRequest: NetworkRequest) { + for expectedNetworkRequest in expectedNetworkRequests { + if networkRequest.isCustomEqual(expectedNetworkRequest.key) { + expectedNetworkRequest.value.countDown() + } + } + } + + /// Starts the deadline timer for the given `NetworkRequest`, requiring all of its expected responses to have completed before the allotted time given in `timeout`. + /// + /// Note that it only sets the timer for the first `NetworkRequest` instance satisfying `areNetworkRequestsEqual`, using a dictionary backing. + /// This method is not recommended for instances where: + /// 1. Mutliple `NetworkRequest` instances would satisfy `areNetworkRequestsEqual` and all of them need the deadline timer started + /// 2. Order of the deadline timer application is important + private func awaitFor(networkRequest: NetworkRequest, timeout: TimeInterval) -> DispatchTimeoutResult? { + for expectedNetworkRequest in expectedNetworkRequests { + if networkRequest.isCustomEqual(expectedNetworkRequest.key) { + return expectedNetworkRequest.value.await(timeout: timeout) + } + } + + return nil + } + + /// Returns all of the original outgoing `NetworkRequest`s satisfying `NetworkRequest.isCustomEqual(_:)`. + func getSentNetworkRequestsMatching(networkRequest: NetworkRequest) -> [NetworkRequest] { + for request in sentNetworkRequests { + if networkRequest.isCustomEqual(request.key) { + return request.value + } + } + + return [] + } + + // MARK: - Network response helpers + /// Sets the `HttpConnection` response connection for a given `NetworkRequest` + func setResponseFor(networkRequest: NetworkRequest, responseConnection: HttpConnection?) { + networkResponses[networkRequest] = responseConnection + } + + /// Gets all network responses for `NetworkRequest`s matching the given `NetworkRequest` + /// + /// See: + func getResponsesFor(networkRequest: NetworkRequest) -> [HttpConnection] { + return networkResponses + .filter { networkRequest.isCustomEqual($0.key) } + .map { $0.value } + } + + // MARK: Assertion helpers + + /// Set the expected number of times a `NetworkRequest` should be seen. + /// + /// - Parameters: + /// - networkRequest: the `NetworkRequest` to set the expectation for + /// - expectedCount: how many times a request with this url and httpMethod is expected to be sent, by default it is set to 1 + func setExpectationForNetworkRequest(networkRequest: NetworkRequest, expectedCount: Int32 = 1, file: StaticString = #file, line: UInt = #line) { + guard expectedCount > 0 else { + assertionFailure("Expected event count should be greater than 0") + return + } + + expectedNetworkRequests[networkRequest] = CountDownLatch(expectedCount) + } + + /// For all previously set expections, asserts that the correct number of network requests were sent. + /// - See also: + /// - `setExpectationNetworkRequest(url:httpMethod:)` + func assertAllNetworkRequestExpectations(file: StaticString = #file, line: UInt = #line) { + guard !expectedNetworkRequests.isEmpty else { + assertionFailure("There are no network request expectations set, use this API after calling setExpectationNetworkRequest") + return + } + + for expectedRequest in expectedNetworkRequests { + let waitResult = expectedRequest.value.await(timeout: 10) + let expectedCount: Int32 = expectedRequest.value.getInitialCount() + let receivedCount: Int32 = expectedRequest.value.getInitialCount() - expectedRequest.value.getCurrentCount() + XCTAssertFalse(waitResult == DispatchTimeoutResult.timedOut, "Timed out waiting for network request(s) with URL \(expectedRequest.key.url.absoluteString) and HTTPMethod \(expectedRequest.key.httpMethod.toString()), expected \(expectedCount) but received \(receivedCount)", file: file, line: line) + XCTAssertEqual(expectedCount, receivedCount, "Expected \(expectedCount) network request(s) for URL \(expectedRequest.key.url.absoluteString) and HTTPMethod \(expectedRequest.key.httpMethod.toString()), but received \(receivedCount)", file: file, line: line) + } + } + + /// Returns the `NetworkRequest`(s) sent through the Core NetworkService, or empty if none was found. + /// Use this API after calling `setExpectationNetworkRequest(url:httpMethod:count:)` to wait for the right amount of time + /// - Parameters: + /// - url: The URL for which to retrieved the network requests sent, should be a valid URL + /// - httpMethod: the `HttpMethod` for which to retrieve the network requests, along with the `url` + /// - timeout: how long should this method wait for the expected network requests, in seconds; by default it waits up to 1 second + /// - Returns: list of network requests with the provided `url` and `httpMethod`, or empty if none was dispatched + /// - See also: + /// - setExpectationNetworkRequest(url:httpMethod:) + func getNetworkRequestsWith(url: String, httpMethod: HttpMethod, timeout: TimeInterval = TestConstants.Defaults.WAIT_NETWORK_REQUEST_TIMEOUT, file: StaticString = #file, line: UInt = #line) -> [NetworkRequest] { + guard let networkRequest = NetworkRequest(urlString: url, httpMethod: httpMethod) else { + return [] + } + + awaitRequest(networkRequest, timeout: timeout) + + return getSentNetworkRequestsMatching(networkRequest: networkRequest) + } + + func awaitRequest(_ networkRequest: NetworkRequest, timeout: TimeInterval = TestConstants.Defaults.WAIT_NETWORK_REQUEST_TIMEOUT, file: StaticString = #file, line: UInt = #line) { + + if let waitResult = awaitFor(networkRequest: networkRequest, timeout: timeout) { + XCTAssertFalse(waitResult == DispatchTimeoutResult.timedOut, "Timed out waiting for network request(s) with URL \(networkRequest.url) and HTTPMethod \(networkRequest.httpMethod.toString())", file: file, line: line) + } else { + wait(TestConstants.Defaults.WAIT_TIMEOUT) + } + } + + /// - Parameters: + /// - timeout:how long should this method wait, in seconds; by default it waits up to 1 second + func wait(_ timeout: UInt32? = TestConstants.Defaults.WAIT_TIMEOUT) { + if let timeout = timeout { + sleep(timeout) + } + } +} + +extension URL { + func queryParam(_ param: String) -> String? { + guard let url = URLComponents(string: self.absoluteString) else { return nil } + return url.queryItems?.first(where: { $0.name == param })?.value + } +} + +extension NetworkRequest { + convenience init?(urlString: String, httpMethod: HttpMethod) { + guard let url = URL(string: urlString) else { + assertionFailure("Unable to convert the provided string \(urlString) to URL") + return nil + } + self.init(url: url, httpMethod: httpMethod) + } + + /// Custom equals compare based on host, scheme and URL path. Query params are not taken into consideration. + func isCustomEqual(_ other: NetworkRequest) -> Bool { // Maybe isCustomEqual? + return self.url.host?.lowercased() == other.url.host?.lowercased() + && self.url.scheme?.lowercased() == other.url.scheme?.lowercased() + && self.url.path.lowercased() == other.url.path.lowercased() + && self.httpMethod.rawValue == other.httpMethod.rawValue + } + + /// Converts the `connectPayload` into a flattened dictionary containing its data. + /// This API fails the assertion if the request body cannot be parsed as JSON. + /// - Returns: The JSON request body represented as a flattened dictionary + func getFlattenedBody(file: StaticString = #file, line: UInt = #line) -> [String: Any] { + if !self.connectPayload.isEmpty { + if let payloadAsDictionary = try? JSONSerialization.jsonObject(with: self.connectPayload, options: []) as? [String: Any] { + return flattenDictionary(dict: payloadAsDictionary) + } else { + XCTFail("Failed to parse networkRequest.connectionPayload to JSON", file: file, line: line) + } + } + + print("Connection payload is empty for network request with URL \(self.url.absoluteString), HTTPMethod \(self.httpMethod.toString())") + return [:] + } +} diff --git a/Tests/TestUtils/RealNetworkService.swift b/Tests/TestUtils/RealNetworkService.swift new file mode 100644 index 00000000..8076a26a --- /dev/null +++ b/Tests/TestUtils/RealNetworkService.swift @@ -0,0 +1,54 @@ +// +// Copyright 2023 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. +// + +@testable import AEPServices +import Foundation +import XCTest + +/// Overriding NetworkService used for tests that require real outgoing network requests +class RealNetworkService: NetworkService { + private let helper: NetworkRequestHelper = NetworkRequestHelper() + + override func connectAsync(networkRequest: NetworkRequest, completionHandler: ((HttpConnection) -> Void)? = nil) { + helper.recordSentNetworkRequest(networkRequest) + super.connectAsync(networkRequest: networkRequest, completionHandler: { (connection: HttpConnection) in + self.helper.setResponseFor(networkRequest: networkRequest, responseConnection: connection) + self.helper.countDownExpected(networkRequest: networkRequest) + + // Finally call the original completion handler + completionHandler?(connection) + }) + } + + func getResponsesFor(networkRequest: NetworkRequest, timeout: TimeInterval = TestConstants.Defaults.WAIT_NETWORK_REQUEST_TIMEOUT, file: StaticString = #file, line: UInt = #line) -> [HttpConnection] { + helper.awaitRequest(networkRequest, timeout: timeout, file: file, line: line) + return helper.getResponsesFor(networkRequest: networkRequest) + } + + // MARK: - Passthrough for shared helper APIs + func assertAllNetworkRequestExpectations() { + helper.assertAllNetworkRequestExpectations() + } + + func reset() { + helper.reset() + } + + /// Set the expected number of times a `NetworkRequest` should be seen. + /// + /// - Parameters: + /// - networkRequest: the `NetworkRequest` to set the expectation for + /// - expectedCount: how many times a request with this url and httpMethod is expected to be sent, by default it is set to 1 + func setExpectationForNetworkRequest(networkRequest: NetworkRequest, expectedCount: Int32 = 1, file: StaticString = #file, line: UInt = #line) { + helper.setExpectationForNetworkRequest(networkRequest: networkRequest, expectedCount: expectedCount, file: file, line: line) + } +} diff --git a/Tests/FunctionalTests/util/FunctionalTestBase.swift b/Tests/TestUtils/TestBase.swift similarity index 52% rename from Tests/FunctionalTests/util/FunctionalTestBase.swift rename to Tests/TestUtils/TestBase.swift index b36ac376..f9a12878 100644 --- a/Tests/FunctionalTests/util/FunctionalTestBase.swift +++ b/Tests/TestUtils/TestBase.swift @@ -35,22 +35,21 @@ extension EventSpec: Hashable & Equatable { } } -class FunctionalTestBase: XCTestCase { +class TestBase: XCTestCase { /// Use this property to execute code logic in the first run in this test class; this value changes to False after the parent tearDown is executed private(set) static var isFirstRun: Bool = true - private static var networkService: FunctionalTestNetworkService = FunctionalTestNetworkService() - /// Use this setting to enable debug mode logging in the `FunctionalTestBase` + /// Use this setting to enable debug mode logging in the `TestBase` static var debugEnabled = false + // Runs once per test suite public class override func setUp() { super.setUp() UserDefaults.clearAll() FileManager.default.clearCache() MobileCore.setLogLevel(LogLevel.trace) - networkService = FunctionalTestNetworkService() - ServiceProvider.shared.networkService = networkService } + // Runs before each test case public override func setUp() { super.setUp() continueAfterFailure = false @@ -59,29 +58,26 @@ class FunctionalTestBase: XCTestCase { public override func tearDown() { super.tearDown() - - // to revisit when AMSDK-10169 is available - // wait .2 seconds in case there are unexpected events that were in the dispatch process during cleanup + // Wait .2 seconds in case there are unexpected events that were in the dispatch process during cleanup usleep(200000) resetTestExpectations() - FunctionalTestBase.isFirstRun = false + TestBase.isFirstRun = false EventHub.reset() UserDefaults.clearAll() FileManager.default.clearCache() } - /// Reset event and network request expectations and drop the items received until this point + /// Reset event expectations and drop the items received until this point func resetTestExpectations() { - log("Resetting functional test expectations for events and network requests") + log("Resetting test expectations for events") InstrumentedExtension.reset() - FunctionalTestBase.networkService.reset() } /// Unregisters the `InstrumentedExtension` from the Event Hub. This method executes asynchronous. func unregisterInstrumentedExtension() { let event = Event(name: "Unregister Instrumented Extension", - type: FunctionalTestConst.EventType.INSTRUMENTED_EXTENSION, - source: FunctionalTestConst.EventSource.UNREGISTER_EXTENSION, + type: TestConstants.EventType.INSTRUMENTED_EXTENSION, + source: TestConstants.EventSource.UNREGISTER_EXTENSION, data: nil) MobileCore.dispatch(event: event) @@ -102,7 +98,7 @@ class FunctionalTestBase: XCTestCase { return } guard !type.isEmpty, !source.isEmpty else { - assertionFailure("Expected event type and source should be non-empty trings") + assertionFailure("Expected event type and source should be non-empty strings") return } @@ -115,7 +111,7 @@ class FunctionalTestBase: XCTestCase { /// - See also: /// - setExpectationEvent(type: source: count:) /// - assertUnexpectedEvents() - func assertExpectedEvents(ignoreUnexpectedEvents: Bool = false, file: StaticString = #file, line: UInt = #line) { + func assertExpectedEvents(ignoreUnexpectedEvents: Bool = false, timeout: TimeInterval = TestConstants.Defaults.WAIT_EVENT_TIMEOUT, file: StaticString = #file, line: UInt = #line) { guard InstrumentedExtension.expectedEvents.count > 0 else { // swiftlint:disable:this empty_count assertionFailure("There are no event expectations set, use this API after calling setExpectationEvent", file: file, line: line) return @@ -123,7 +119,7 @@ class FunctionalTestBase: XCTestCase { let currentExpectedEvents = InstrumentedExtension.expectedEvents.shallowCopy for expectedEvent in currentExpectedEvents { - let waitResult = expectedEvent.value.await(timeout: FunctionalTestConst.Defaults.WAIT_EVENT_TIMEOUT) + let waitResult = expectedEvent.value.await(timeout: timeout) let expectedCount: Int32 = expectedEvent.value.getInitialCount() let receivedCount: Int32 = expectedEvent.value.getInitialCount() - expectedEvent.value.getCurrentCount() XCTAssertFalse(waitResult == DispatchTimeoutResult.timedOut, "Timed out waiting for event type \(expectedEvent.key.type) and source \(expectedEvent.key.source), expected \(expectedCount), but received \(receivedCount)", file: (file), line: line) @@ -146,7 +142,7 @@ class FunctionalTestBase: XCTestCase { // check if event is expected and it is over the expected count if let expectedEvent = InstrumentedExtension.expectedEvents[EventSpec(type: receivedEvent.key.type, source: receivedEvent.key.source)] { - _ = expectedEvent.await(timeout: FunctionalTestConst.Defaults.WAIT_EVENT_TIMEOUT) + _ = expectedEvent.await(timeout: TestConstants.Defaults.WAIT_EVENT_TIMEOUT) let expectedCount: Int32 = expectedEvent.getInitialCount() let receivedCount: Int32 = expectedEvent.getInitialCount() - expectedEvent.getCurrentCount() XCTAssertEqual(expectedCount, receivedCount, "Expected \(expectedCount) events of type \(receivedEvent.key.type) and source \(receivedEvent.key.source), but received \(receivedCount)", file: (file), line: line) @@ -165,12 +161,12 @@ class FunctionalTestBase: XCTestCase { /// To be revisited once AMSDK-10169 is implemented /// - Parameters: /// - timeout:how long should this method wait, in seconds; by default it waits up to 1 second - func wait(_ timeout: UInt32? = FunctionalTestConst.Defaults.WAIT_TIMEOUT) { + func wait(_ timeout: UInt32? = TestConstants.Defaults.WAIT_TIMEOUT) { if let timeout = timeout { sleep(timeout) } } - + /// Returns the `ACPExtensionEvent`(s) dispatched through the Event Hub, or empty if none was found. /// Use this API after calling `setExpectationEvent(type:source:count:)` to wait for the right amount of time /// - Parameters: @@ -178,12 +174,12 @@ class FunctionalTestBase: XCTestCase { /// - source: the event source as in the expectation /// - timeout: how long should this method wait for the expected event, in seconds; by default it waits up to 1 second /// - Returns: list of events with the provided `type` and `source`, or empty if none was dispatched - func getDispatchedEventsWith(type: String, source: String, timeout: TimeInterval = FunctionalTestConst.Defaults.WAIT_EVENT_TIMEOUT, file: StaticString = #file, line: UInt = #line) -> [Event] { + func getDispatchedEventsWith(type: String, source: String, timeout: TimeInterval = TestConstants.Defaults.WAIT_EVENT_TIMEOUT, file: StaticString = #file, line: UInt = #line) -> [Event] { if InstrumentedExtension.expectedEvents[EventSpec(type: type, source: source)] != nil { let waitResult = InstrumentedExtension.expectedEvents[EventSpec(type: type, source: source)]?.await(timeout: timeout) XCTAssertFalse(waitResult == DispatchTimeoutResult.timedOut, "Timed out waiting for event type \(type) and source \(source)", file: file, line: line) } else { - wait(FunctionalTestConst.Defaults.WAIT_TIMEOUT) + wait(TestConstants.Defaults.WAIT_TIMEOUT) } return InstrumentedExtension.receivedEvents[EventSpec(type: type, source: source)] ?? [] } @@ -192,11 +188,11 @@ class FunctionalTestBase: XCTestCase { /// - Parameter ownerExtension: the owner extension of the shared state (typically the name of the extension) /// - Parameter timeout: how long should this method wait for the requested shared state, in seconds; by default it waits up to 3 second /// - Returns: latest shared state of the given `stateOwner` or nil if no shared state was found - func getSharedStateFor(_ ownerExtension: String, timeout: TimeInterval = FunctionalTestConst.Defaults.WAIT_SHARED_STATE_TIMEOUT) -> [AnyHashable: Any]? { + func getSharedStateFor(_ ownerExtension: String, timeout: TimeInterval = TestConstants.Defaults.WAIT_SHARED_STATE_TIMEOUT) -> [AnyHashable: Any]? { log("GetSharedState for \(ownerExtension)") let event = Event(name: "Get Shared State", - type: FunctionalTestConst.EventType.INSTRUMENTED_EXTENSION, - source: FunctionalTestConst.EventSource.SHARED_STATE_REQUEST, + type: TestConstants.EventType.INSTRUMENTED_EXTENSION, + source: TestConstants.EventSource.SHARED_STATE_REQUEST, data: ["stateowner": ownerExtension]) var returnedState: [AnyHashable: Any]? @@ -214,127 +210,17 @@ class FunctionalTestBase: XCTestCase { return returnedState } - // MARK: Network Service helpers - - /// Set a custom network response to a network request - /// - Parameters: - /// - url: The URL for which to return the response - /// - httpMethod: The `HttpMethod` for which to return the response, along with the `url` - /// - responseHttpConnection: `HttpConnection` to be returned when a `NetworkRequest` with the specified `url` and `httpMethod` is seen; when nil is provided the default - /// `HttpConnection` is returned - func setNetworkResponseFor(url: String, httpMethod: HttpMethod, responseHttpConnection: HttpConnection?) { - guard let requestUrl = URL(string: url) else { - assertionFailure("Unable to convert the provided string \(url) to URL") - return - } - - _ = FunctionalTestBase.networkService.setResponseConnectionFor(networkRequest: NetworkRequest(url: requestUrl, httpMethod: httpMethod), responseConnection: responseHttpConnection) - } - - /// Set a network request expectation. - /// - /// - Parameters: - /// - url: The URL for which to set the expectation - /// - httpMethod: the `HttpMethod` for which to set the expectation, along with the `url` - /// - count: how many times a request with this url and httpMethod is expected to be sent, by default it is set to 1 - /// - See also: - /// - assertNetworkRequestsCount() - /// - getNetworkRequestsWith(url:httpMethod:) - func setExpectationNetworkRequest(url: String, httpMethod: HttpMethod, expectedCount: Int32 = 1, file: StaticString = #file, line: UInt = #line) { - guard expectedCount > 0 else { - assertionFailure("Expected event count should be greater than 0") - return - } - - guard let requestUrl = URL(string: url) else { - assertionFailure("Unable to convert the provided string \(url) to URL") - return - } - - FunctionalTestBase.networkService.setExpectedNetworkRequest(networkRequest: NetworkRequest(url: requestUrl, httpMethod: httpMethod), count: expectedCount) - } - - /// Asserts that the correct number of network requests were being sent, based on the previously set expectations. - /// - See also: - /// - setExpectationNetworkRequest(url:httpMethod:) - func assertNetworkRequestsCount(file: StaticString = #file, line: UInt = #line) { - let expectedNetworkRequests = FunctionalTestBase.networkService.getExpectedNetworkRequests() - guard !expectedNetworkRequests.isEmpty else { - assertionFailure("There are no network request expectations set, use this API after calling setExpectationNetworkRequest") - return - } - - for expectedRequest in expectedNetworkRequests { - let waitResult = expectedRequest.value.await(timeout: 10) - let expectedCount: Int32 = expectedRequest.value.getInitialCount() - let receivedCount: Int32 = expectedRequest.value.getInitialCount() - expectedRequest.value.getCurrentCount() - XCTAssertFalse(waitResult == DispatchTimeoutResult.timedOut, "Timed out waiting for network request(s) with URL \(expectedRequest.key.url.absoluteString) and HTTPMethod \(expectedRequest.key.httpMethod.toString()), expected \(expectedCount) but received \(receivedCount)", file: file, line: line) - XCTAssertEqual(expectedCount, receivedCount, "Expected \(expectedCount) network request(s) for URL \(expectedRequest.key.url.absoluteString) and HTTPMethod \(expectedRequest.key.httpMethod.toString()), but received \(receivedCount)", file: file, line: line) - } - } - - /// Returns the `NetworkRequest`(s) sent through the Core NetworkService, or empty if none was found. - /// Use this API after calling `setExpectationNetworkRequest(url:httpMethod:count:)` to wait for the right amount of time - /// - Parameters: - /// - url: The URL for which to retrieved the network requests sent, should be a valid URL - /// - httpMethod: the `HttpMethod` for which to retrieve the network requests, along with the `url` - /// - timeout: how long should this method wait for the expected network requests, in seconds; by default it waits up to 1 second - /// - Returns: list of network requests with the provided `url` and `httpMethod`, or empty if none was dispatched - /// - See also: - /// - setExpectationNetworkRequest(url:httpMethod:) - func getNetworkRequestsWith(url: String, httpMethod: HttpMethod, timeout: TimeInterval = FunctionalTestConst.Defaults.WAIT_NETWORK_REQUEST_TIMEOUT, file: StaticString = #file, line: UInt = #line) -> [NetworkRequest] { - guard let requestUrl = URL(string: url) else { - assertionFailure("Unable to convert the provided string \(url) to URL") - return [] - } - - let networkRequest = NetworkRequest(url: requestUrl, httpMethod: httpMethod) - - if let waitResult = FunctionalTestBase.networkService.awaitFor(networkRequest: networkRequest, timeout: timeout) { - XCTAssertFalse(waitResult == DispatchTimeoutResult.timedOut, "Timed out waiting for network request(s) with URL \(url) and HTTPMethod \(httpMethod.toString())", file: file, line: line) - } else { - wait(FunctionalTestConst.Defaults.WAIT_TIMEOUT) - } - - return FunctionalTestBase.networkService.getReceivedNetworkRequestsMatching(networkRequest: networkRequest) - } - - /// Use this API for JSON formatted `NetworkRequest` body in order to retrieve a flattened dictionary containing its data. - /// This API fails the assertion if the request body cannot be parsed as JSON. - /// - Parameters: - /// - networkRequest: the NetworkRequest to parse - /// - Returns: The JSON request body represented as a flatten dictionary - func getFlattenNetworkRequestBody(_ networkRequest: NetworkRequest, file: StaticString = #file, line: UInt = #line) -> [String: Any] { - - if !networkRequest.connectPayload.isEmpty { - if let payloadAsDictionary = try? JSONSerialization.jsonObject(with: networkRequest.connectPayload, options: []) as? [String: Any] { - return flattenDictionary(dict: payloadAsDictionary) - } else { - XCTFail("Failed to parse networkRequest.connectionPayload to JSON", file: file, line: line) - } - } - - log("Connection payload is empty for network request with URL \(networkRequest.url.absoluteString), HTTPMethod \(networkRequest.httpMethod.toString())") - return [:] - } - - /// Sets the provided delay for all network responses, until reset - /// - Parameter delaySec: delay in seconds - func enableNetworkResponseDelay(delaySec: UInt32) { - FunctionalTestBase.networkService.enableDelayedResponse(delaySec: delaySec) - } - - /// Print message to console if `FunctionalTestBase.debug` is true + /// Print message to console if `TestBase.debug` is true /// - Parameter message: message to log to console func log(_ message: String) { - FunctionalTestBase.log(message) + TestBase.log(message) } - /// Print message to console if `FunctionalTestBase.debug` is true + /// Print message to console if `TestBase.debug` is true /// - Parameter message: message to log to console static func log(_ message: String) { - guard !message.isEmpty && FunctionalTestBase.debugEnabled else { return } - print("FunctionalTestBase - \(message)") + guard !message.isEmpty && TestBase.debugEnabled else { return } + print("TestBase - \(message)") } } diff --git a/Tests/FunctionalTests/util/FunctionalTestConst.swift b/Tests/TestUtils/TestConstants.swift similarity index 96% rename from Tests/FunctionalTests/util/FunctionalTestConst.swift rename to Tests/TestUtils/TestConstants.swift index 04fae39c..04e3568b 100644 --- a/Tests/FunctionalTests/util/FunctionalTestConst.swift +++ b/Tests/TestUtils/TestConstants.swift @@ -12,7 +12,7 @@ import Foundation -enum FunctionalTestConst { +enum TestConstants { enum EventType { static let EDGE = "com.adobe.eventType.edge" @@ -34,6 +34,8 @@ enum FunctionalTestConst { static let RESPONSE_IDENTITY = "com.adobe.eventSource.responseIdentity" static let REQUEST_IDENTITY = "com.adobe.eventSource.requestIdentity" static let BOOTED = "com.adobe.eventSource.booted" + static let LOCATION_HINT_RESULT = "locationHint:result" + static let STATE_STORE = "state:store" } enum EventDataKey { diff --git a/Tests/TestUtils/XCTestCase+AnyCodableAsserts.swift b/Tests/TestUtils/XCTestCase+AnyCodableAsserts.swift new file mode 100644 index 00000000..cef109f6 --- /dev/null +++ b/Tests/TestUtils/XCTestCase+AnyCodableAsserts.swift @@ -0,0 +1,818 @@ +// +// Copyright 2023 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 +import XCTest + +enum AssertMode { + case exactMatch + case typeMatch +} + +enum PayloadType: String { + case xdm + case data +} + +extension XCTestCase { + // MARK: - AnyCodable helpers + + /// Gets the `AnyCodable` representation of a JSON string + func getAnyCodable(_ jsonString: String) -> AnyCodable? { + return try? JSONDecoder().decode(AnyCodable.self, from: jsonString.data(using: .utf8)!) + } + + /// Gets an event's data payload converted into `AnyCodable` format + func getAnyCodable(_ event: Event) -> AnyCodable? { + return AnyCodable(AnyCodable.from(dictionary: event.data)) + } + + func getAnyCodableAndPayload(_ jsonString: String, type: PayloadType) -> (anyCodable: AnyCodable, payload: [String: Any])? { + guard let anyCodable = getAnyCodable(jsonString) else { + return nil + } + guard let payload = anyCodable.dictionaryValue?[type.rawValue] as? [String: Any] else { + return nil + } + return (anyCodable: anyCodable, payload: payload) + } + + func getAnyCodableFromEventPayload(event: Event) -> AnyCodable? { + return AnyCodable(AnyCodable.from(dictionary: event.data)) + } + + // MARK: - AnyCodable exact equivalence test assertion methods + + /// Performs exact equality testing assertions between two `AnyCodable` instances, using a similar logic path as the `AnyCodable ==` implementation. + /// Traces the key path (both dictionary keys and array indices) and provides the trace on assertion failure, for easier debugging. + /// Automatically performs any required conversions of underlying `Any?` types into `AnyCodable` format. + /// + /// Main entrypoint for exact equality `AnyCodable` testing assertions. + func assertEqual(expected: AnyCodable?, actual: AnyCodable?, file: StaticString = #file, line: UInt = #line) { + assertEqual(expected: expected, actual: actual, keyPath: [], file: file, line: line) + } + + // MARK: - AnyCodable flexible validation test assertion methods + /// Performs a flexible comparison where only the key value pairs on the expected side are required. Uses type match as the default validation mode, where values require + /// only the same type (and non-nil, given the expected value is not `nil` itself). + /// + /// Given an expected JSON like the following: + /// { + /// "key1": "value1", + /// "key2": [{ "nest1": 1}, {"nest2": 2}] + /// } + /// + /// An example alternate mode path for the example JSON could be: "key2[1].nest2" + /// + /// Alternate mode paths must start from the top level of the expected JSON. Whatever key is specified by the path, from that value onward, the alternate match mode is used. + /// + /// There are 3 different ways to specify alternate mode paths for arrays: + /// 1. The specific index: [\] (ex: `[0]`, `[28]`, etc.) - The element at the specified index will use the alternate mode. + /// 2. The wildcard index: [*\] (ex: `[*1]`, `[*12]`, etc) - The element at the specified index will use the alternate mode and apply wildcard matching logic. + /// 3. The general wildcard: [*] (must be in exactly this format) - Every element not explicitly specified by 1 or 2 will use the alternate mode and apply wildcard matching logic. This option is mututally exclusive with default behavior. + /// - The default behavior is that elements from the expected JSON side are compared in order, up to the last element of the expected array. + /// + /// - Parameters: + /// - expected: The expected JSON in AnyCodable format used to perform the assertions + /// - actual: The actual JSON in AnyCodable format that is validated against `expected` + /// - exactMatchPaths: the key paths in the expected JSON that should use exact matching mode, where values require the same type and literal value. + /// - file: the file to show test assertion failures in + /// - line: the line to show test assertion failures on + func assertTypeMatch(expected: AnyCodable, actual: AnyCodable?, exactMatchPaths: [String] = [], file: StaticString = #file, line: UInt = #line) { + let pathTree = generatePathTree(paths: exactMatchPaths, file: file, line: line) + assertFlexibleEqual(expected: expected, actual: actual, pathTree: pathTree, exactMatchMode: false, file: file, line: line) + } + + /// Performs a flexible comparison where only the key value pairs on the expected side are required. Uses exact match as the default validation mode, where values + /// require the same type and literal value. + /// + /// Given an expected JSON like the following: + /// { + /// "key1": "value1", + /// "key2": [{ "nest1": 1}, {"nest2": 2}] + /// } + /// + /// An example alternate mode path for the example JSON could be: "key2[1].nest2" + /// + /// Alternate mode paths must start from the top level of the expected JSON. Whatever key is specified by the path, from that value onward, the alternate match mode is used. + /// + /// There are 3 different ways to specify alternate mode paths for arrays: + /// 1. The specific index: [\] (ex: `[0]`, `[28]`, etc.) - The element at the specified index will use the alternate mode. + /// 2. The wildcard index: [*\] (ex: `[*1]`, `[*12]`, etc) - The element at the specified index will use the alternate mode and apply wildcard matching logic. + /// 3. The general wildcard: [*] (must be in exactly this format) - Every element not explicitly specified by 1 or 2 will use the alternate mode and apply wildcard matching logic. This option is mututally exclusive with default behavior. + /// - The default behavior is that elements from the expected JSON side are compared in order, up to the last element of the expected array. + /// + /// - Parameters: + /// - expected: The expected JSON in AnyCodable format used to perform the assertions + /// - actual: The actual JSON in AnyCodable format that is validated against `expected` + /// - typeMatchPaths: Optionally, the key paths in the expected JSON that should use type matching mode, where values require only the same type (and non-nil, given the expected value is not `nil` itself) + /// - file: the file to show test assertion failures in + /// - line: the line to show test assertion failures on + func assertExactMatch(expected: AnyCodable, actual: AnyCodable?, typeMatchPaths: [String] = [], file: StaticString = #file, line: UInt = #line) { + let pathTree = generatePathTree(paths: typeMatchPaths, file: file, line: line) + assertFlexibleEqual(expected: expected, actual: actual, pathTree: pathTree, exactMatchMode: true, file: file, line: line) + } + + // MARK: - AnyCodable exact equivalence helpers + /// Performs equality testing assertions between two `AnyCodable` instances. + private func assertEqual(expected: AnyCodable?, actual: AnyCodable?, keyPath: [Any] = [], file: StaticString = #file, line: UInt = #line) { + if expected?.value == nil, actual?.value == nil { + return + } + guard let expected = expected, let actual = actual else { + XCTFail(#""" + \#(expected == nil ? "Expected is nil" : "Actual is nil") and \#(expected == nil ? "Actual" : "Expected") is non-nil. + + Expected: \#(String(describing: expected)) + + Actual: \#(String(describing: actual)) + + Key path: \#(keyPathAsString(keyPath)) + """#, file: file, line: line) + return + } + + switch (expected.value, actual.value) { + case let (expected as String, actual as String): + XCTAssertEqual(expected, actual, "Key path: \(keyPathAsString(keyPath))", file: file, line: line) + case let (expected as Bool, actual as Bool): + XCTAssertEqual(expected, actual, "Key path: \(keyPathAsString(keyPath))", file: file, line: line) + case let (expected as Int, actual as Int): + XCTAssertEqual(expected, actual, "Key path: \(keyPathAsString(keyPath))", file: file, line: line) + case let (expected as Double, actual as Double): + XCTAssertEqual(expected, actual, "Key path: \(keyPathAsString(keyPath))", file: file, line: line) + case let (expected as [String: AnyCodable], actual as [String: AnyCodable]): + assertEqual(expected: expected, actual: actual, keyPath: keyPath, file: file, line: line) + case let (expected as [AnyCodable], actual as [AnyCodable]): + assertEqual(expected: expected, actual: actual, keyPath: keyPath, file: file, line: line) + case let (expected as [Any?], actual as [Any?]): + assertEqual(expected: AnyCodable.from(array: expected), actual: AnyCodable.from(array: actual), keyPath: keyPath, file: file, line: line) + case let (expected as [String: Any?], actual as [String: Any?]): + assertEqual(expected: AnyCodable.from(dictionary: expected), actual: AnyCodable.from(dictionary: actual), keyPath: keyPath, file: file, line: line) + default: + XCTFail(#""" + Expected and Actual types do not match. + + Expected: \#(expected) + + Actual: \#(actual) + + Key path: \#(keyPathAsString(keyPath)) + """#, file: file, line: line) + } + } + + /// Performs equality testing assertions between two `[AnyCodable]` instances. + private func assertEqual(expected: [AnyCodable]?, actual: [AnyCodable]?, keyPath: [Any], file: StaticString = #file, line: UInt = #line, shouldAssert: Bool = true) { + if expected == nil, actual == nil { + return + } + guard let expected = expected, let actual = actual else { + XCTFail(#""" + \#(expected == nil ? "Expected is nil" : "Actual is nil") and \#(expected == nil ? "Actual" : "Expected") is non-nil. + + Expected: \#(String(describing: expected)) + + Actual: \#(String(describing: actual)) + + Key path: \#(keyPathAsString(keyPath)) + """#, file: file, line: line) + return + } + if expected.count != actual.count { + XCTFail(#""" + Expected and Actual counts do not match (exact equality). + + Expected count: \#(expected.count) + Actual count: \#(actual.count) + + Expected: \#(expected) + + Actual: \#(actual) + + Key path: \#(keyPathAsString(keyPath)) + """#, file: file, line: line) + return + } + for (index, valueTuple) in zip(expected, actual).enumerated() { + var keyPath = keyPath + keyPath.append(index) + assertEqual( + expected: valueTuple.0, + actual: valueTuple.1, + keyPath: keyPath, + file: file, line: line) + } + } + + /// Performs equality testing assertions between two `[String: AnyCodable]` instances. + private func assertEqual(expected: [String: AnyCodable]?, actual: [String: AnyCodable]?, keyPath: [Any], file: StaticString = #file, line: UInt = #line) { + if expected == nil, actual == nil { + return + } + guard let expected = expected, let actual = actual else { + XCTFail(#""" + \#(expected == nil ? "Expected is nil" : "Actual is nil") and \#(expected == nil ? "Actual" : "Expected") is non-nil. + + Expected: \#(String(describing: expected)) + + Actual: \#(String(describing: actual)) + + Key path: \#(keyPathAsString(keyPath)) + """#, file: file, line: line) + return + } + if expected.count != actual.count { + XCTFail(#""" + Expected and Actual counts do not match (exact equality). + + Expected count: \#(expected.count) + Actual count: \#(actual.count) + + Expected: \#(expected) + + Actual: \#(actual) + + Key path: \#(keyPathAsString(keyPath)) + """#, file: file, line: line) + return + } + for (key, value) in expected { + var keyPath = keyPath + keyPath.append(key) + assertEqual( + expected: value, + actual: actual[key], + keyPath: keyPath, + file: file, line: line) + } + } + + // MARK: - AnyCodable flexible validation helpers + /// Performs flexible comparison testing assertions between two `AnyCodable` instances. + @discardableResult + private func assertFlexibleEqual( + expected: AnyCodable?, + actual: AnyCodable?, + keyPath: [Any] = [], + pathTree: [String: Any]?, + exactMatchMode: Bool, + file: StaticString = #file, + line: UInt = #line, + shouldAssert: Bool = true) -> Bool { + if expected?.value == nil { + return true + } + guard let expected = expected, let actual = actual else { + if shouldAssert { + XCTFail(#""" + Expected JSON is non-nil but Actual JSON is nil. + + Expected: \#(String(describing: expected)) + + Actual: \#(String(describing: actual)) + + Key path: \#(keyPathAsString(keyPath)) + """#, file: file, line: line) + } + return false + } + + switch (expected, actual) { + case let (expected, actual) where (expected.value is String && actual.value is String): + fallthrough + case let (expected, actual) where (expected.value is Bool && actual.value is Bool): + fallthrough + case let (expected, actual) where (expected.value is Int && actual.value is Int): + fallthrough + case let (expected, actual) where (expected.value is Double && actual.value is Double): + // Default: exact value matching + if exactMatchMode { + if shouldAssert { + XCTAssertEqual(expected, actual, "Key path: \(keyPathAsString(keyPath))", file: file, line: line) + } + return expected == actual + } + // Default: value type validation + else { + // Value type matching already passed by virtue of passing the where condition in the switch case + return true + } + case let (expected, actual) where (expected.value is [String: AnyCodable] && actual.value is [String: AnyCodable]): + return assertFlexibleEqual( + expected: expected.value as? [String: AnyCodable], + actual: actual.value as? [String: AnyCodable], + keyPath: keyPath, + pathTree: pathTree, + exactMatchMode: exactMatchMode, + file: file, line: line, shouldAssert: shouldAssert) + case let (expected, actual) where (expected.value is [AnyCodable] && actual.value is [AnyCodable]): + return assertFlexibleEqual( + expected: expected.value as? [AnyCodable], + actual: actual.value as? [AnyCodable], + keyPath: keyPath, + pathTree: pathTree, + exactMatchMode: exactMatchMode, + file: file, line: line, shouldAssert: shouldAssert) + case let (expected, actual) where (expected.value is [Any?] && actual.value is [Any?]): + return assertFlexibleEqual( + expected: AnyCodable.from(array: expected.value as? [Any?]), + actual: AnyCodable.from(array: actual.value as? [Any?]), + keyPath: keyPath, + pathTree: pathTree, + exactMatchMode: exactMatchMode, + file: file, line: line, shouldAssert: shouldAssert) + case let (expected, actual) where (expected.value is [String: Any?] && actual.value is [String: Any?]): + return assertFlexibleEqual( + expected: AnyCodable.from(dictionary: expected.value as? [String: Any?]), + actual: AnyCodable.from(dictionary: actual.value as? [String: Any?]), + keyPath: keyPath, + pathTree: pathTree, + exactMatchMode: exactMatchMode, + file: file, line: line, shouldAssert: shouldAssert) + default: + if shouldAssert { + XCTFail(#""" + Expected and Actual types do not match. + + Expected: \#(expected) + + Actual: \#(actual) + + Key path: \#(keyPathAsString(keyPath)) + """#, file: file, line: line) + } + return false + } + } + + /// Performs flexible comparison testing assertions between two `[AnyCodable]` instances. + private func assertFlexibleEqual( + expected: [AnyCodable]?, + actual: [AnyCodable]?, + keyPath: [Any], + pathTree: [String: Any]?, + exactMatchMode: Bool, + file: StaticString = #file, + line: UInt = #line, + shouldAssert: Bool = true) -> Bool { + if expected == nil { + return true + } + guard let expected = expected, let actual = actual else { + if shouldAssert { + XCTFail(#""" + Expected JSON is non-nil but Actual JSON is nil. + + Expected: \#(String(describing: expected)) + + Actual: \#(String(describing: actual)) + + Key path: \#(keyPathAsString(keyPath)) + """#, file: file, line: line) + } + return false + } + if expected.count > actual.count { + if shouldAssert { + XCTFail(#""" + Expected JSON has more elements than Actual JSON. Impossible for Actual to fulfill Expected requirements. + + Expected count: \#(expected.count) + Actual count: \#(actual.count) + + Expected: \#(expected) + + Actual: \#(actual) + + Key path: \#(keyPathAsString(keyPath)) + """#, file: file, line: line) + } + return false + } + + // Matches array subscripts and all the inner content (ex: "[*123]". However, only captures the inner content: ex: "123", "*123" + let arrayIndexValueRegex = #"\[(.*?)\]"# + // Get all of the alternate key paths for this level, and apply the array bracket inner content capture regex + let indexValues = pathTree?.keys + .flatMap { key in + getCapturedRegexGroups(text: key, regexPattern: arrayIndexValueRegex, file: file, line: line) + } + .compactMap {$0} ?? [] + + // Converts "0" -> 0 + var exactIndexes: [Int] = indexValues + .filter { !$0.contains("*") } + .compactMap { Int($0) } + + // Converts "*0" -> 0 + var wildcardIndexes: [Int] = indexValues + .filter { $0.contains("*") } + .compactMap { + return Int($0.replacingOccurrences(of: "*", with: "")) + } + + // Checks for [*] + let hasWildcardAny: Bool = indexValues.contains("*") + + var seenIndexes: Set = [] + + /// Relies on outer scope's: + /// 1. **mutates** `seenIndexes` + /// 2. `expected` array + func createSortedValidatedRange(_ range: [Int]) -> [Int] { + var result: [Int] = [] + for index in range { + guard expected.indices.contains(index) else { + XCTFail("TEST ERROR: alternate match path using index (\(index)) is out of bounds. Verify the test setup for correctness.", file: file, line: line) + continue + } + guard seenIndexes.insert(index).inserted else { + XCTFail("TEST ERROR: index already seen: \(index). Verify the test setup for correctness.", file: file, line: line) + continue + } + result.append(index) + } + return result.sorted() + } + + exactIndexes = createSortedValidatedRange(exactIndexes) + wildcardIndexes = createSortedValidatedRange(wildcardIndexes) + + let unmatchedLHSIndices: Set = Set(expected.indices) + .subtracting(exactIndexes) + .subtracting(wildcardIndexes) + + // Evaluation precedence is: + // Alternate match paths + // 1. [0] + // 2. [*0] + // 3. [*] - mutually exclusive with 4 + // Default + // 4. Standard indexes, all remaining expected indexes unspecified by 1-3 + + var finalResult = true + // Handle alternate match paths with format: [0] + for index in exactIndexes { + var keyPath = keyPath + keyPath.append(index) + let matchTreeValue = pathTree?["[\(index)]"] + + let isPathEnd = matchTreeValue is String + + finalResult = assertFlexibleEqual( + expected: expected[index], + actual: actual[index], + keyPath: keyPath, + pathTree: isPathEnd ? nil : matchTreeValue as? [String: Any], // if pathEnd, nil out pathTree + exactMatchMode: isPathEnd ? !exactMatchMode : exactMatchMode, // if pathEnd, invert default equality mode + file: file, line: line, shouldAssert: shouldAssert) && finalResult + } + + var unmatchedRHSElements = Set(actual.indices).subtracting(exactIndexes) + .sorted() + .map { (originalIndex: $0, element: actual[$0]) } + + /// Relies on outer scope's: + /// 1. pathTree + /// 2. exactMatchMode + /// 3. **mutates** unmatchedRHSElements + /// 4. **mutates** finalResult + func performWildcardMatch(expectedIndexes: [Int], isGeneralWildcard: Bool) { + for index in expectedIndexes { + var keyPath = keyPath + keyPath.append(index) + let matchTreeValue = isGeneralWildcard ? pathTree?["[*]"] : pathTree?["[*\(index)]"] + + let isPathEnd = matchTreeValue is String + + guard let result = unmatchedRHSElements.firstIndex(where: { + assertFlexibleEqual( + expected: expected[index], + actual: $0.element, + keyPath: keyPath, + pathTree: isPathEnd ? nil : matchTreeValue as? [String: Any], // if pathEnd, nil out pathTree + exactMatchMode: isPathEnd ? !exactMatchMode : exactMatchMode, // if pathEnd, invert default equality mode + file: file, line: line, shouldAssert: false) + }) else { + XCTFail(#""" + Wildcard \#((isPathEnd ? !exactMatchMode : exactMatchMode) ? "exact" : "type") match found no matches on Actual side satisfying the Expected requirement. + + Requirement: \#(String(describing: matchTreeValue)) + + Expected: \#(expected[index]) + + Actual (remaining unmatched elements): \#(unmatchedRHSElements.map { $0.element }) + + Key path: \#(keyPathAsString(keyPath)) + """#, file: file, line: line) + finalResult = false + continue + } + unmatchedRHSElements.remove(at: result) + + finalResult = finalResult && true + } + } + + // Handle alternate match paths with format: [*] + performWildcardMatch(expectedIndexes: wildcardIndexes.sorted(), isGeneralWildcard: false) + // Handle alternate match paths with format: [*] - general wildcard is mutually exclusive with standard index comparison + if hasWildcardAny { + performWildcardMatch(expectedIndexes: unmatchedLHSIndices.sorted(by: { $0 < $1 }), isGeneralWildcard: true) + } else { + for index in unmatchedLHSIndices.sorted(by: { $0 < $1 }) { + var keyPath = keyPath + keyPath.append(index) + + guard unmatchedRHSElements.contains(where: { $0.originalIndex == index }) else { + XCTFail(#""" + Actual side's index \#(index) has already been taken by a wildcard match. Verify the test setup for correctness. + + Expected: \#(expected[index]) + + Actual (remaining unmatched elements): \#(unmatchedRHSElements.map { $0.element }) + + Key path: \#(keyPathAsString(keyPath)) + """#, file: file, line: line) + finalResult = false + continue + } + + finalResult = assertFlexibleEqual( + expected: expected[index], + actual: actual[index], + keyPath: keyPath, + pathTree: nil, // There should be no array based key paths at this point + exactMatchMode: exactMatchMode, + file: file, line: line, shouldAssert: shouldAssert) && finalResult + } + } + return finalResult + } + + /// Performs flexible comparison testing assertions between two `[String: AnyCodable]` instances. + private func assertFlexibleEqual( + expected: [String: AnyCodable]?, + actual: [String: AnyCodable]?, + keyPath: [Any], + pathTree: [String: Any]?, + exactMatchMode: Bool, + file: StaticString = #file, + line: UInt = #line, + shouldAssert: Bool = true) -> Bool { + if expected == nil { + return true + } + guard let expected = expected, let actual = actual else { + if shouldAssert { + XCTFail(#""" + Expected JSON is non-nil but Actual JSON is nil. + + Expected: \#(String(describing: expected)) + + Actual: \#(String(describing: actual)) + + Key path: \#(keyPathAsString(keyPath)) + """#, file: file, line: line) + } + return false + } + if expected.count > actual.count { + if shouldAssert { + XCTFail(#""" + Expected JSON has more elements than Actual JSON. + + Expected count: \#(expected.count) + Actual count: \#(actual.count) + + Expected: \#(expected) + + Actual: \#(actual) + + Key path: \#(keyPathAsString(keyPath)) + """#, file: file, line: line) + } + return false + } + var finalResult = true + for (key, value) in expected { + var keyPath = keyPath + keyPath.append(key) + let pathTreeValue = pathTree?[key] + if pathTreeValue is String { + finalResult = assertFlexibleEqual( + expected: value, + actual: actual[key], + keyPath: keyPath, + pathTree: nil, // is String means path terminates here + exactMatchMode: !exactMatchMode, // Invert default mode + file: file, line: line, shouldAssert: shouldAssert) && finalResult + } else { + finalResult = assertFlexibleEqual( + expected: value, + actual: actual[key], + keyPath: keyPath, + pathTree: pathTreeValue as? [String: Any], + exactMatchMode: exactMatchMode, + file: file, line: line, shouldAssert: shouldAssert) && finalResult + } + } + return finalResult + } + + // MARK: - Test setup and output helpers + /// Performs regex match on the provided String, returning the original match and non-nil capture group results + private func extractRegexCaptureGroups(text: String, regexPattern: String, file: StaticString = #file, line: UInt = #line) -> [(matchString: String, captureGroups: [String])]? { + do { + let regex = try NSRegularExpression(pattern: regexPattern) + let matches = regex.matches(in: text, + range: NSRange(text.startIndex..., in: text)) + var matchResult: [(matchString: String, captureGroups: [String])] = [] + for match in matches { + var rangeStrings: [String] = [] + // [(matched string), (capture group 0), (capture group 1), etc.] + for rangeIndex in 0 ..< match.numberOfRanges { + let rangeBounds = match.range(at: rangeIndex) + guard let range = Range(rangeBounds, in: text) else { + continue + } + rangeStrings.append(String(text[range])) + } + guard !rangeStrings.isEmpty else { + continue + } + let matchString = rangeStrings.removeFirst() + matchResult.append((matchString: matchString, captureGroups: rangeStrings)) + } + return matchResult + } catch let error { + XCTFail("TEST ERROR: Invalid regex: \(error.localizedDescription)", file: file, line: line) + return nil + } + } + /// Applies the provided regex pattern to the text and returns all the capture groups from the regex pattern + private func getCapturedRegexGroups(text: String, regexPattern: String, file: StaticString = #file, line: UInt = #line) -> [String] { + + guard let captureGroups = extractRegexCaptureGroups(text: text, regexPattern: regexPattern, file: file, line: line)?.flatMap({ $0.captureGroups }) else { + return [] + } + + return captureGroups + } + + /// Extracts all key path components from a given key path string + private func getKeyPathComponents(text: String, file: StaticString = #file, line: UInt = #line) -> [String] { + // The empty string is a special case that the regex doesn't handle + guard !text.isEmpty else { + return [""] + } + + // Capture groups: + // 1. Any characters, or empty string before a `.` NOT preceded by a `\` + // OR + // 2. Any non-empty text preceding the end of the string + // + // Matches key path access in the style of: "key0\.key1.key2[1][2].key3". Captures each of the groups separated by `.` character and ignores `\.` as nesting. + // the path example would result in: ["key0\.key1", "key2[1][2]", "key3"] + let jsonNestingRegex = #"(.*?)(? [String: Any] { + var current = current + for (key, newValue) in new { + let currentValue = current[key] + switch (currentValue, newValue) { + case let (currentValue as [String: Any], newValue as [String: Any]): + current[key] = merge(current: currentValue, new: newValue) + default: + if current[key] is String { + continue + } + current[key] = newValue + } + } + return current + } + + /// Constructs a key path dictionary from a given key path component array, and the final value is + /// assigned the original path string used to construct the path + private func construct(path: [String], pathString: String) -> [String: Any] { + guard !path.isEmpty else { + return [:] + } + var path = path + let first = path.removeFirst() + let result: [String: Any] + if path.isEmpty { + result = [first: pathString] + return result + } else { + + return [first: construct(path: path, pathString: pathString)] + } + } + + private func generatePathTree(paths: [String], file: StaticString = #file, line: UInt = #line) -> [String: Any]? { + // Matches array subscripts and all the inner content. Captures the surrounding brackets and inner content: ex: "[123]", "[*123]" + let arrayIndexRegex = #"(\[.*?\])"# + var tree: [String: Any] = [:] + + for exactValuePath in paths { + var allPathComponents: [String] = [] + var pathExtractionSuccessful: Bool = true + + // Break the path string into its component parts + let keyPathComponents = getKeyPathComponents(text: exactValuePath, file: file, line: line) + for pathComponent in keyPathComponents { + let pathComponent = pathComponent.replacingOccurrences(of: "\\.", with: ".") + + // Get all array access levels for the given pathComponent, if any + // KNOWN LIMITATION: this regex only extracts all open+close square brackets and inner content ("[___]") regardless + // of their relative position within the path component, ex: "key0[2]key1[3]" will be interpreted as: "key0" with array component "[2][3]" + let arrayComponents = getCapturedRegexGroups(text: pathComponent, regexPattern: arrayIndexRegex, file: file, line: line) + + // If no array components are detected, just add the path as-is + if arrayComponents.isEmpty { + allPathComponents.append(pathComponent) + } + // Otherwise, extract just the path component before array components if it exists + else { + guard let bracketIndex = pathComponent.firstIndex(of: "[") else { + XCTFail("TEST ERROR: unable to get bracket position from path: \(pathComponent). Skipping exact path: \(exactValuePath)", file: file, line: line) + pathExtractionSuccessful = false + break + } + let extractedPathComponent = String(pathComponent[.. String { + var result = "" + for item in keyPath { + switch item { + case let item as String: + if !result.isEmpty { + result += "." + } + if item.contains(".") { + result += item.replacingOccurrences(of: ".", with: "\\.") + } + else if item.isEmpty { + result += "\"\"" + } + else { + result += item + } + case let item as Int: + result += "[" + String(item) + "]" + default: + break + } + } + return result + } +} diff --git a/Tests/UpstreamIntegrationTests/AnyCodableAssertsTests.swift b/Tests/UpstreamIntegrationTests/AnyCodableAssertsTests.swift new file mode 100644 index 00000000..b773fb8a --- /dev/null +++ b/Tests/UpstreamIntegrationTests/AnyCodableAssertsTests.swift @@ -0,0 +1,890 @@ +// +// Copyright 2023 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 AEPServices +import Foundation +import XCTest + +class AnyCodableAssertsTests: XCTestCase { + func testPathExtraction() { + let expectedJSON = #""" + { + "": { + "": "value3" + } + } + """# + + let actualJSON = #""" + { + "": { + "": "value2" + } + } + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + // Matches top level empty string key: "" + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: [""]) + // Matches nested empty string key: "" -> "" + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: [#"."#]) + + XCTExpectFailure("The following should fail") { + assertExactMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: [#"\\\\"#]) // double backslash + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: [#"\."#]) // escaped . + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: [#"\.."#]) // escaped . + "" + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: [#".\.."#]) // "" + escaped / + "" + assertEqual(expected: expected, actual: actual) + } + } + + func testPathExtraction_specialCharacters() { + let expectedJSON = #""" + { + "\\": { + "": "value3" + } + } + """# + + let actualJSON = #""" + { + "\\": { + "": "value2" + } + } + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + // Matches backslash literal key: "\" + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["\\"]) + + XCTExpectFailure("The following should fail") { + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: [#""#]) + assertExactMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: [#"\\\\"#]) // double backslash + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: [#"\."#]) // escaped . + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: [#"\.."#]) // escaped . + "" + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: [#".\.."#]) // "" + escaped / + "" + assertEqual(expected: expected, actual: actual) + } + } + + // MARK: - Regex parsing + func testEscapedKeyPaths() { + let expectedJSON = #""" + { + "key1.key2": { + "key3": "value3" + } + } + """# + + let actualJSON = #""" + { + "key1.key2": { + "key3": "value1" + } + } + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: [#"key1\.key2"#]) + XCTExpectFailure("The following should fail") { + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: [#"key1\.key2"#]) + assertExactMatch(expected: expected, actual: actual) + assertEqual(expected: expected, actual: actual) + } + } + + // MARK: - Empty collection tests + func testDictionary_whenEmpty_isEqual() { + let expectedJSON = #""" + {} + """# + + let actualJSON = #""" + {} + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual) + assertEqual(expected: expected, actual: actual) + } + + func testDictionary_whenNested_isEqual() { + let expectedJSON = #""" + { + "key0": { + "key1": {} + } + } + """# + + let actualJSON = #""" + { + "key0": { + "key1": {} + } + } + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["key0"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["key0.key1"]) + assertExactMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["key0"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["key0.key1"]) + assertEqual(expected: expected, actual: actual) + } + + func testArray_whenEmpty_isEqual() { + let expectedJSON = #""" + [] + """# + + let actualJSON = #""" + [] + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual) + assertEqual(expected: expected, actual: actual) + } + + func testArray_whenEmptyNested_isEqual() { + let expectedJSON = #""" + [[]] + """# + + let actualJSON = #""" + [[]] + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*0]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[0]"]) + + assertExactMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*0]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[0]"]) + + assertEqual(expected: expected, actual: actual) + } + + func testArray_whenNestedRequiresSpecificIndex_isEqual() { + let expectedJSON = #""" + [ + 1, + [ + 2,3 + ] + ] + """# + + let actualJSON = #""" + [ + 1, + [ + 4,5 + ] + ] + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[1][0]", "[1][1]"]) + XCTExpectFailure("The following should fail") { + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[1][0]", "[1][1]"]) + assertEqual(expected: expected, actual: actual) + } + } + + func testArray_whenDeeplyNested_isEqual() { + let expectedJSON = #""" + [[[[[[[[]]]]]]]] + """# + + let actualJSON = #""" + [[[[[[[[]]]]]]]] + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*0]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[0]"]) + + assertExactMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*0]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[0]"]) + + assertEqual(expected: expected, actual: actual) + } + + func testArray_whenNestedDictionary_isEqual() { + let expectedJSON = #""" + [{}] + """# + + let actualJSON = #""" + [{}] + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*0]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[0]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*0]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[0]"]) + assertEqual(expected: expected, actual: actual) + } + + func testArray_whenMultiType_isEqual() { + let expectedJSON = #""" + [ + { + "key1": "value1" + }, + 123, + "string", + false, + null + ] + """# + + let actualJSON = #""" + [ + { + "key1": "value1" + }, + 123, + "string", + false, + null + ] + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*0]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[0]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*0]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[0]"]) + assertEqual(expected: expected, actual: actual) + } + + func testDictionary_whenNestedArray_withMultiType_isEqual() { + let expectedJSON = #""" + { + "key0": [ + { + "key1": "value1" + }, + 123, + "string", + false, + null + ] + } + """# + + let actualJSON = #""" + { + "key0": [ + { + "key1": "value1" + }, + 123, + "string", + false, + null + ] + } + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["key0"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["key0"]) + assertEqual(expected: expected, actual: actual) + } + + func testDictionary_whenMultiType_isEqual() { + let expectedJSON = #""" + { + "integerCompare": 456, + "decimalCompare": 123.123, + "stringCompare": "abc", + "boolCompare": true, + "nullCompare": null, + "arraySizeCompare": [1,2,3], + "arrayValueCompare": [1], + "arrayOfObjectsPass": [ + { + "object1": "value1" + }, + { + "object2": "value2" + } + ], + "arrayOfObjectsFail": [ + { + "object1": "value1" + }, + { + "object2": "value2" + } + ], + "dictionaryCompare": { + "nested1": "value1" + }, + "dictionaryNestedCompare": { + "nested1": { + "nested2": { + "nested3": { + "nested4": { + "nested1": "value1" + } + } + } + } + }, + "trulyNested": [ + { + "nest1": [ + { + "nest2": { + "nest3": [ + { + "nest4": "value1" + } + ] + } + } + ] + } + ] + } + + """# + + let actualJSON = #""" + { + "integerCompare": 0, + "decimalCompare": 4.123, + "stringCompare": "def", + "boolCompare": false, + "nullCompare": "not null", + "arraySizeCompare": [1,2], + "arrayValueCompare": [0], + "arrayOfObjectsPass": [ + { + "object1": "value1" + }, + { + "object2": "value2" + } + ], + "arrayOfObjectsFail": [ + { + "object1": "value1" + }, + { + "object2": "value3" + } + ], + "dictionaryCompare": { + "nested1different": "value1" + }, + "dictionaryNestedCompare": { + "nested1": { + "nested2": { + "nested3": { + "nested4": { + "nested1": "value2" + } + } + } + } + }, + "trulyNested": [ + { + "nest1": [ + { + "nest2": { + "nest3": [ + [ + "nest4" + ] + ] + } + } + ] + } + ] + } + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + // Match with exact same should pass + assertTypeMatch(expected: expected, actual: expected) + assertExactMatch(expected: expected, actual: expected) + assertEqual(expected: expected, actual: expected) + + XCTExpectFailure("The following should fail") { + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual) + assertEqual(expected: expected, actual: actual) + } + } + + func testDictionary_whenNestedArray_isEqual() { + let expectedJSON = #""" + { + "key0": [] + } + """# + + let actualJSON = #""" + { + "key0": [] + } + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["key0"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["key0"]) + assertEqual(expected: expected, actual: actual) + } + + func testSingleKeyEquality() { + let expectedJSON = #""" + { + "key1": "value1" + } + """# + + let actualJSON = #""" + { + "key1": "value1" + } + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual) + assertEqual(expected: expected, actual: actual) + } + + func testSingleKey_flexibleEquality() { + let expectedJSON = #""" + { + "key1": "" + } + """# + + let actualJSON = #""" + { + "key1": "value1" + } + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["key1"]) + XCTExpectFailure("The following should fail") { + assertExactMatch(expected: expected, actual: actual) + assertEqual(expected: expected, actual: actual) + } + } + + func testArray_whenExpectedHasFewerElements_isEqual() { + let expectedJSON = #""" + [1,2,3] + """# + + let actualJSON = #""" + [1,2,3,4,5,6] + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual) + XCTExpectFailure("The following should fail") { + assertExactMatch(expected: expected, actual: actual) + assertEqual(expected: expected, actual: actual) + } + } + + func testArray_whenExpectedHasMoreElements() { + let expectedJSON = #""" + [1,2,3,4,5,6] + """# + + let actualJSON = #""" + [1,2,3] + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + XCTExpectFailure("The following should fail") { + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual) + assertEqual(expected: expected, actual: actual) + } + } + + func testArray_whenExpectedHasFewerElements_sameType() { + let expectedJSON = #""" + [0,1,2,4] + """# + + let actualJSON = #""" + [9,9,9,4,9,9] + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[0]", "[1]", "[2]"]) + XCTExpectFailure("The following should fail") { + // Type match + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[0]", "[1]", "[2]"]) + // Exact match + assertExactMatch(expected: expected, actual: actual) + assertEqual(expected: expected, actual: actual) + } + } + + func testArray_whenGeneralWildcard() { + let expectedJSON = #""" + [0,1,2] + """# + + let actualJSON = #""" + [9,9,9,0,1,2] + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*0]", "[*1]", "[*2]"]) + + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*0]", "[*1]", "[*2]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[0]", "[1]", "[2]"]) + + XCTExpectFailure("The following should fail") { + // Type match + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[0]", "[1]", "[2]"]) + // Exact match + assertExactMatch(expected: expected, actual: actual) + // Partials + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*0]", "[*2]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[0]", "[1]"]) + assertEqual(expected: expected, actual: actual) + } + } + + func testArray_whenMixedWildcardPaths() { + let expectedJSON = #""" + [0,1,2] + """# + + let actualJSON = #""" + ["a","b",2,0,1,9] + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*]", "[*1]", "[2]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[2]", "[*1]", "[*]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*0]", "[*1]", "[*2]"]) + + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*]", "[*1]", "[2]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[2]", "[*1]", "[*]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*0]", "[*1]", "[*2]"]) + + XCTExpectFailure("The following should fail") { + // Type match + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[0]", "[1]", "[2]"]) + // Exact match + assertExactMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[0]", "[1]", "[2]"]) + // Partials + // Note the precedence of evaluation affecting the test passing + // In this case, [*] is evaluated before non path keys (that is index 2) + // so [*0] -> 0 takes index 2 -> 2 + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*0]", "[*1]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*0]", "[*2]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[0]", "[1]"]) + assertEqual(expected: expected, actual: actual) + } + } + + func testArray_whenGeneralWildcard_typeMismatch_mustFail() { + let expectedJSON = #""" + [0,1,2] + """# + + let actualJSON = #""" + ["a","b","c","d","e",2] + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + XCTExpectFailure("The following should fail") { + // Type match + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*0]", "[*1]", "[*2]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[0]", "[1]", "[2]"]) + // Exact match + assertExactMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*0]", "[*1]", "[*2]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[0]", "[1]", "[2]"]) + // Partials + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*0]", "[*2]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[0]", "[1]"]) + assertEqual(expected: expected, actual: actual) + } + } + + func testArray_whenGeneralWildcard_typeMismatch() { + let expectedJSON = #""" + [0,1,2] + """# + + let actualJSON = #""" + ["a","b","c",0,1,2] + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*]"]) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[*0]", "[*1]", "[*2]"]) + + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*0]", "[*1]", "[*2]"]) + XCTExpectFailure("The following should fail") { + // Type match + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["[0]", "[1]", "[2]"]) + // Exact match + assertExactMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[0]", "[1]", "[2]"]) + // Partials + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[*0]", "[*2]"]) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["[0]", "[1]"]) + assertEqual(expected: expected, actual: actual) + } + } + + func testDictionary_whenExpectedHasMoreElements() { + let expectedJSON = #""" + { + "key0": 9, + "key1": 9, + "key2": 2, + "key3": 3, + "key4": 4, + "key5": 5 + } + """# + + let actualJSON = #""" + { + "key0": 0, + "key1": 1, + "key2": 2 + } + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + XCTExpectFailure("The following should fail") { + // Type match + assertTypeMatch(expected: expected, actual: actual) + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["key0", "key1"]) + // Exact match + assertExactMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["key0", "key1"]) + assertEqual(expected: expected, actual: actual) + } + } + + func testDictionary_whenExpectedHasFewerElements_sameType() { + let expectedJSON = #""" + { + "key0": 0, + "key1": 1, + "key2": 2 + } + """# + + let actualJSON = #""" + { + "key0": 9, + "key1": 9, + "key2": 2, + "key3": 3, + "key4": 4, + "key5": 5 + } + """# + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + + assertTypeMatch(expected: expected, actual: actual) + assertExactMatch(expected: expected, actual: actual, typeMatchPaths: ["key0", "key1"]) + XCTExpectFailure("The following should fail") { + // Type match + // 2 Failures + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["key0", "key1"]) + // Exact match + // 2 Failures + assertExactMatch(expected: expected, actual: actual) + // 1 Failures - fail count check + assertEqual(expected: expected, actual: actual) + } + } + + func testLocationHint_onlyExpectedKeys() { + let expectedJSON = #""" + { + "payload": [ + { + "scope" : "EdgeNetwork" + }, + { + "scope" : "Target" + } + ] + } + """# + + let actualJSON = #""" + { + "payload": [ + { + "ttlSeconds" : 1800, + "scope" : "Target", + "hint" : "35" + }, + { + "ttlSeconds" : 1800, + "scope" : "AAM", + "hint" : "9" + }, + { + "ttlSeconds" : 1800, + "scope" : "EdgeNetwork", + "hint" : "or2" + } + ] + } + """# + + guard let expected = getAnyCodable(expectedJSON), let actual = getAnyCodable(actualJSON) else { + XCTFail("Unable to decode JSON string. Test case unable to proceed.") + return + } + assertTypeMatch(expected: expected, actual: actual, exactMatchPaths: ["payload[*].scope"]) + } +} diff --git a/Tests/UpstreamIntegrationTests/UpstreamIntegrationTests.swift b/Tests/UpstreamIntegrationTests/UpstreamIntegrationTests.swift new file mode 100644 index 00000000..0d3da622 --- /dev/null +++ b/Tests/UpstreamIntegrationTests/UpstreamIntegrationTests.swift @@ -0,0 +1,627 @@ +// +// Copyright 2023 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. +// + +@testable import AEPCore +import AEPEdge +import AEPEdgeIdentity +import AEPServices +import Foundation +import XCTest + +/// Performs validation on integration with the Edge Network upstream service +class UpstreamIntegrationTests: TestBase { + private var edgeEnvironment: EdgeEnvironment = .prod + private var edgeLocationHint: EdgeLocationHint? + + private var networkService: RealNetworkService = RealNetworkService() + + let LOG_SOURCE = "UpstreamIntegrationTests" + + let asyncTimeout: TimeInterval = 10 + + // Run before each test case + override func setUp() { + ServiceProvider.shared.networkService = networkService + + super.setUp() + + continueAfterFailure = true + TestBase.debugEnabled = true + // Extract Edge Network environment level from shell environment; see init for default value + self.edgeEnvironment = EdgeEnvironment() + print("Using Edge Network environment: \(edgeEnvironment.rawValue)") + + // Extract Edge location hint from shell environment; see init for default value + self.edgeLocationHint = EdgeLocationHint() + + let waitForRegistration = CountDownLatch(1) + MobileCore.setLogLevel(.trace) + // Set environment file ID for specific Edge Network environment + setMobileCoreEnvironmentFileID(for: edgeEnvironment) + MobileCore.registerExtensions([Identity.self, Edge.self], { + print("Extensions registration is complete") + waitForRegistration.countDown() + }) + XCTAssertEqual(DispatchTimeoutResult.success, waitForRegistration.await(timeout: 2)) + + // Set Edge location hint value if one is set for the test target + if edgeLocationHint != nil { + print("Setting Edge location hint to: \(String(describing: edgeLocationHint?.rawValue))") + Edge.setLocationHint(edgeLocationHint?.rawValue) + } else { + print("No preset Edge location hint is being used for this test.") + } + resetTestExpectations() + networkService.reset() + } + + // MARK: - Upstream integration test cases + + /// Tests that a standard sendEvent receives a single network response with HTTP code 200 + func testSendEvent_receivesExpectedNetworkResponse() { + // Setup + // Note: test constructs should always be valid + let interactNetworkRequest = NetworkRequest(urlString: createURLWith(locationHint: edgeLocationHint), httpMethod: .post)! + // Setting expectation allows for both: + // 1. Validation that the network request was sent out + // 2. Waiting on a response for the specific network request (with timeout) + networkService.setExpectationForNetworkRequest(networkRequest: interactNetworkRequest, expectedCount: 1) + + let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], data: ["data": ["test": "data"]]) + + // Test + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + // Network response assertions + let matchingResponses = networkService.getResponsesFor(networkRequest: interactNetworkRequest, timeout: 5) + + XCTAssertEqual(1, matchingResponses.count) + XCTAssertEqual(200, matchingResponses.first?.responseCode) + } + + /// Tests that a standard sendEvent receives a single network response with HTTP code 200 + func testSendEvent_whenComplexEvent_receivesExpectedNetworkResponse() { + // Setup + let interactNetworkRequest = NetworkRequest(urlString: createURLWith(locationHint: edgeLocationHint), httpMethod: .post)! + networkService.setExpectationForNetworkRequest(networkRequest: interactNetworkRequest, expectedCount: 1) + + let eventPayloadJSON = #""" + { + "xdm": { + "testString": "xdm" + }, + "data": { + "testDataString": "stringValue", + "testDataInt": 101, + "testDataBool": true, + "testDataDouble": 13.66, + "testDataArray": ["arrayElem1", 2, true], + "testDataDictionary": { + "key": "val" + } + } + } + """# + + let xdm = getAnyCodableAndPayload(eventPayloadJSON, type: .xdm)! + let data = getAnyCodableAndPayload(eventPayloadJSON, type: .data)! + + let experienceEvent = ExperienceEvent(xdm: xdm.payload, data: data.payload) + + // Test + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + // Network response assertions + let matchingResponses = networkService.getResponsesFor(networkRequest: interactNetworkRequest, timeout: 5) + + XCTAssertEqual(1, matchingResponses.count) + XCTAssertEqual(200, matchingResponses.first?.responseCode) + } + + /// Tests that a standard sendEvent () receives a single network response with HTTP code 200 + func testSendEvent_whenComplexXDMEvent_receivesExpectedNetworkResponse() { + // Setup + let interactNetworkRequest = NetworkRequest(urlString: createURLWith(locationHint: edgeLocationHint), httpMethod: .post)! + networkService.setExpectationForNetworkRequest(networkRequest: interactNetworkRequest, expectedCount: 1) + + let eventPayloadJSON = #""" + { + "xdm": { + "testString": "xdm", + "testInt": 10, + "testBool": false, + "testDouble": 12.89, + "testArray": ["arrayElem1", 2, true], + "testDictionary": { + "key": "val" + } + } + } + """# + + let xdm = getAnyCodableAndPayload(eventPayloadJSON, type: .xdm)! + + let experienceEvent = ExperienceEvent(xdm: xdm.payload) + + // Test + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + // Network response assertions + let matchingResponses = networkService.getResponsesFor(networkRequest: interactNetworkRequest, timeout: 5) + + XCTAssertEqual(1, matchingResponses.count) + XCTAssertEqual(200, matchingResponses.first?.responseCode) + } + + /// Tests that a standard sendEvent receives the expected event handles + func testSendEvent_receivesExpectedEventHandles() { + // Setup + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.LOCATION_HINT_RESULT, expectedCount: 1) + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.STATE_STORE, expectedCount: 1) + + let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], data: ["data": ["test": "data"]]) + + // Test + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + assertExpectedEvents(ignoreUnexpectedEvents: true) + } + + /// Tests that a standard sendEvent receives the expected event handles + func testSendEvent_doesNotReceivesErrorEvent() { + // Setup + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.LOCATION_HINT_RESULT, expectedCount: 1) + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.STATE_STORE, expectedCount: 1) + + let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], data: ["data": ["test": "data"]]) + + // Test + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + assertExpectedEvents(ignoreUnexpectedEvents: true) + + let errorEvents = getEdgeEventHandles(expectedHandleType: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) + XCTAssertEqual(0, errorEvents.count) + } + + /// Tests that a standard sendEvent with no prior location hint value set receives the expected location hint event handle. + /// That is, checks for a string type location hint + func testSendEvent_with_NO_priorLocationHint_receivesExpectedLocationHintEventHandle() { + // Setup + // Clear any existing location hint + Edge.setLocationHint(nil) + + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.LOCATION_HINT_RESULT, expectedCount: 1) + + let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], data: ["data": ["test": "data"]]) + + // Test + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + let expectedLocationHintJSON = #""" + { + "payload": [ + { + "ttlSeconds" : 123, + "scope" : "EdgeNetwork", + "hint" : "stringType" + } + ] + } + """# + + // See testSendEvent_receivesExpectedEventHandles for existence validation + let locationHintResult = getEdgeEventHandles(expectedHandleType: TestConstants.EventSource.LOCATION_HINT_RESULT).first! + + assertTypeMatch(expected: getAnyCodable(expectedLocationHintJSON)!, + actual: getAnyCodable(locationHintResult), + exactMatchPaths: ["payload[*].scope"]) + } + + /// Tests that a standard sendEvent WITH prior location hint value set receives the expected location hint event handle. + /// That is, checks for consistency between prior location hint value and received location hint result + func testSendEvent_withPriorLocationHint_receivesExpectedLocationHintEventHandle() { + // Setup + // Uses all the valid location hint cases in random order to prevent order dependent edge cases slipping through + for locationHint in (EdgeLocationHint.allCases).map({ $0.rawValue }).shuffled() { + Edge.setLocationHint(locationHint) + + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.LOCATION_HINT_RESULT, expectedCount: 1) + + let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], data: ["data": ["test": "data"]]) + + // Test + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + let expectedLocationHintJSON = #""" + { + "payload": [ + { + "ttlSeconds" : 123, + "scope" : "EdgeNetwork", + "hint" : "\#(locationHint)" + } + ] + } + """# + + let locationHintResult = getEdgeEventHandles(expectedHandleType: TestConstants.EventSource.LOCATION_HINT_RESULT).first! + + assertTypeMatch(expected: getAnyCodable(expectedLocationHintJSON)!, + actual: getAnyCodable(locationHintResult), + exactMatchPaths: ["payload[*].scope", "payload[*].hint"]) + + resetTestExpectations() + networkService.reset() + } + } + + /// Tests that a standard sendEvent with no prior state receives the expected state store event handle. + func testSendEvent_with_NO_priorState_receivesExpectedStateStoreEventHandle() { + // Setup + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.STATE_STORE, expectedCount: 1) + + let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], data: ["data": ["test": "data"]]) + + // Test + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + let expectedStateStoreJSON = #""" + { + "payload": [ + { + "maxAge": 123, + "key": "kndctr_972C898555E9F7BC7F000101_AdobeOrg_cluster", + "value": "stringType" + }, + { + "maxAge": 123, + "key": "kndctr_972C898555E9F7BC7F000101_AdobeOrg_identity", + "value": "stringType" + } + ] + } + """# + + // See testSendEvent_receivesExpectedEventHandles for existence validation + let stateStoreEvent = getEdgeEventHandles(expectedHandleType: TestConstants.EventSource.STATE_STORE).last! + + // Exact match used here to strictly validate `payload` array element count == 2 + assertExactMatch(expected: getAnyCodable(expectedStateStoreJSON)!, + actual: getAnyCodable(stateStoreEvent), + typeMatchPaths: ["payload[0].maxAge", "payload[0].value", "payload[1].maxAge", "payload[1].value"]) + } + + /// Tests that a standard sendEvent with prior state receives the expected state store event handle. + func testSendEvent_withPriorState_receivesExpectedStateStoreEventHandle() { + // Setup + let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], data: ["data": ["test": "data"]]) + + Edge.sendEvent(experienceEvent: experienceEvent) + + resetTestExpectations() + networkService.reset() + + for locationHint in (EdgeLocationHint.allCases).map({ $0.rawValue }).shuffled() { + Edge.setLocationHint(locationHint) + + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.STATE_STORE, expectedCount: 1) + + // Test + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + let expectedStateStoreJSON = #""" + { + "payload": [ + { + "maxAge": 123, + "key": "kndctr_972C898555E9F7BC7F000101_AdobeOrg_cluster", + "value": "\#(locationHint)" + } + ] + } + """# + + // See testSendEvent_receivesExpectedEventHandles for existence validation + let stateStoreEvent = getEdgeEventHandles(expectedHandleType: TestConstants.EventSource.STATE_STORE).last! + + // Exact match used here to strictly validate `payload` array element count == 2 + assertExactMatch(expected: getAnyCodable(expectedStateStoreJSON)!, + actual: getAnyCodable(stateStoreEvent), + typeMatchPaths: ["payload[0].maxAge"]) + + resetTestExpectations() + networkService.reset() + } + } + + + // MARK: 2nd event tests + func testSendEventx2_receivesExpectedNetworkResponse() { + // Setup + // These expectations are used as a barrier for the event processing to complete + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.LOCATION_HINT_RESULT) + + let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], + data: ["data": ["test": "data"]]) + + Edge.sendEvent(experienceEvent: experienceEvent) + + // Extract location hint from Edge Network location hint response event + guard let locationHintResult = getLastLocationHintResultValue() else { + XCTFail("Unable to extract valid location hint from location hint result event handle.") + return + } + + // If there is an initial location hint value, check consistency + if (edgeLocationHint != nil) { + XCTAssertEqual(edgeLocationHint?.rawValue, locationHintResult) + } + + // Wait on all expectations to finish processing before clearing expectations + assertExpectedEvents(ignoreUnexpectedEvents: true) + + // Reset all test expectations + networkService.reset() + resetTestExpectations() + + // Set actual testing expectations + // If test suite level location hint is not set, uses the value extracted from location hint result + let locationHintNetworkRequest = NetworkRequest(urlString: createURLWith(locationHint: locationHintResult), httpMethod: .post)! + networkService.setExpectationForNetworkRequest(networkRequest: locationHintNetworkRequest, expectedCount: 1) + + // Test + // 2nd event + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + // Network response assertions + let matchingResponses = networkService.getResponsesFor(networkRequest: locationHintNetworkRequest, timeout: 5) + + XCTAssertEqual(1, matchingResponses.count) + XCTAssertEqual(200, matchingResponses.first?.responseCode) + + } + + func testSendEventx2_receivesExpectedEventHandles() { + // Setup + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.LOCATION_HINT_RESULT, expectedCount: 2) + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.STATE_STORE, expectedCount: 2) + + let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], + data: ["data": ["test": "data"]]) + + // Test + Edge.sendEvent(experienceEvent: experienceEvent) + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + assertExpectedEvents(ignoreUnexpectedEvents: true, timeout: 10) + } + + func testSendEventx2_doesNotReceivesErrorEvent() { + // Setup + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.LOCATION_HINT_RESULT, expectedCount: 2) + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.STATE_STORE, expectedCount: 2) + + let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], data: ["data": ["test": "data"]]) + + // Test + Edge.sendEvent(experienceEvent: experienceEvent) + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + assertExpectedEvents(ignoreUnexpectedEvents: true) + + let errorEvents = getEdgeEventHandles(expectedHandleType: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) + XCTAssertEqual(0, errorEvents.count) + } + + func testSendEventx2_receivesExpectedLocationHintEventHandle() { + // Setup + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.LOCATION_HINT_RESULT) + + let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], + data: ["data": ["test": "data"]]) + + Edge.sendEvent(experienceEvent: experienceEvent) + + // Extract location hint from Edge Network location hint response event + guard let locationHintResult = getLastLocationHintResultValue() else { + XCTFail("Unable to extract valid location hint from location hint result event handle.") + return + } + + // Reset all test expectations + networkService.reset() + resetTestExpectations() + + // Set actual testing expectations + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.LOCATION_HINT_RESULT, expectedCount: 1) + + // Test + // 2nd event + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + + // If there is an initial location hint value, check consistency + if (edgeLocationHint != nil) { + XCTAssertEqual(edgeLocationHint?.rawValue, locationHintResult) + } + + // Verify location hint consistency between 1st and 2nd event handles + let expectedLocationHintJSON = #""" + { + "payload": [ + { + "ttlSeconds" : 123, + "scope" : "EdgeNetwork", + "hint" : "\#(locationHintResult)" + } + ] + } + """# + + let locationHintResultEvent = getEdgeEventHandles(expectedHandleType: TestConstants.EventSource.LOCATION_HINT_RESULT).first! + + assertTypeMatch(expected: getAnyCodable(expectedLocationHintJSON)!, + actual: getAnyCodable(locationHintResultEvent), + exactMatchPaths: ["payload[*].scope", "payload[*].hint"]) + } + + + // MARK: - Error scenarios + + // Tests that an invalid datastream ID returns the expected error + func testSendEvent_withInvalidDatastreamID_receivesExpectedError() { + // Setup + let interactNetworkRequest = NetworkRequest(urlString: createURLWith(locationHint: edgeLocationHint), httpMethod: .post)! + + networkService.setExpectationForNetworkRequest(networkRequest: interactNetworkRequest, expectedCount: 1) + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) + + MobileCore.updateConfigurationWith(configDict: ["edge.configId": "12345-example"]) + + let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], + data: ["data": ["test": "data"]]) + + // Test + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + // Network response assertions + let matchingResponses = networkService.getResponsesFor(networkRequest: interactNetworkRequest, timeout: 5) + + XCTAssertEqual(1, matchingResponses.count) + XCTAssertEqual(400, matchingResponses.first?.responseCode) + + // Event assertions + let expectedErrorJSON = #""" + { + "status": 400, + "detail": "stringType", + "report": { + "requestId": "stringType" + }, + "requestEventId": "stringType", + "title": "Invalid datastream ID", + "type": "https://ns.adobe.com/aep/errors/EXEG-0003-400", + "requestId": "stringType" + } + """# + + let errorEvents = getEdgeResponseErrors() + + XCTAssertEqual(1, errorEvents.count) + + guard let errorEvent = errorEvents.first else { return } + assertTypeMatch(expected: getAnyCodable(expectedErrorJSON)!, + actual: getAnyCodable(errorEvent), + exactMatchPaths: ["status", "title", "type"]) + } + + // Tests that an invalid location hint returns the expected error with 0 byte data body + func testSendEvent_withInvalidLocationHint_receivesExpectedError() { + // Setup + let invalidNetworkRequest = NetworkRequest(urlString: createURLWith(locationHint: "invalid"), httpMethod: .post)! + + networkService.setExpectationForNetworkRequest(networkRequest: invalidNetworkRequest, expectedCount: 1) + expectEdgeEventHandle(expectedHandleType: TestConstants.EventSource.ERROR_RESPONSE_CONTENT, expectedCount: 1) + + Edge.setLocationHint("invalid") + + let experienceEvent = ExperienceEvent(xdm: ["xdmtest": "data"], + data: ["data": ["test": "data"]]) + // Test + Edge.sendEvent(experienceEvent: experienceEvent) + + // Verify + // Network response assertions + let matchingResponses = networkService.getResponsesFor(networkRequest: invalidNetworkRequest, timeout: 5) + + XCTAssertEqual(1, matchingResponses.count) + XCTAssertEqual(404, matchingResponses.first?.responseCode) + XCTAssertEqual(0, matchingResponses.first?.data?.count) + + // Error event assertions + assertExpectedEvents(ignoreUnexpectedEvents: true) + } + + // MARK: - Test helper methods + private func setMobileCoreEnvironmentFileID(for edgeEnvironment: EdgeEnvironment) { + switch edgeEnvironment { + case .prod: + MobileCore.configureWith(appId: "94f571f308d5/6b1be84da76a/launch-023a1b64f561-development") + case .preProd: + MobileCore.configureWith(appId: "94f571f308d5/6b1be84da76a/launch-023a1b64f561-development") + case .int: + // TODO: create integration environment environment file ID + MobileCore.configureWith(appId: "94f571f308d5/6b1be84da76a/launch-023a1b64f561-development") + } + } + + /// Creates a valid interact URL using the provided location hint. If location hint is invalid, returns default URL with no location hint. + /// - Parameters: + /// - locationHint: The `EdgeLocationHint`'s raw value to use in the URL + /// - Returns: The interact URL with location hint applied, default URL if location hint is invalid + private func createURLWith(locationHint: EdgeLocationHint?) -> String { + guard let locationHint = locationHint else { + return "https://obumobile5.data.adobedc.net/ee/v1/interact" + } + return createURLWith(locationHint: locationHint.rawValue) + } + + /// Creates a valid interact URL using the provided location hint. + /// - Parameters: + /// - locationHint: The location hint String to use in the URL + /// - Returns: The interact URL with location hint applied + private func createURLWith(locationHint: String?) -> String { + guard let locationHint = locationHint else { + return "https://obumobile5.data.adobedc.net/ee/v1/interact" + } + return "https://obumobile5.data.adobedc.net/ee/\(locationHint)/v1/interact" + } + + private func expectEdgeEventHandle(expectedHandleType: String, expectedCount: Int32 = 1) { + setExpectationEvent(type: TestConstants.EventType.EDGE, source: expectedHandleType, expectedCount: expectedCount) + } + + private func getEdgeEventHandles(expectedHandleType: String) -> [Event] { + return getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: expectedHandleType) + } + + private func getEdgeResponseErrors() -> [Event] { + return getDispatchedEventsWith(type: TestConstants.EventType.EDGE, source: TestConstants.EventSource.ERROR_RESPONSE_CONTENT) + } + + /// Extracts the Edge location hint from the location hint result + private func getLastLocationHintResultValue() -> String? { + let locationHintResultEvent = getEdgeEventHandles(expectedHandleType: TestConstants.EventSource.LOCATION_HINT_RESULT).last + guard let payload = locationHintResultEvent?.data?["payload"] as? [[String: Any]] else { + return nil + } + guard payload.indices.contains(2) else { + return nil + } + return payload[2]["hint"] as? String + } +} diff --git a/Tests/UpstreamIntegrationTests/util/AnyCodable+Array.swift b/Tests/UpstreamIntegrationTests/util/AnyCodable+Array.swift new file mode 100644 index 00000000..5360a381 --- /dev/null +++ b/Tests/UpstreamIntegrationTests/util/AnyCodable+Array.swift @@ -0,0 +1,44 @@ +// +// Copyright 2023 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 AEPServices +import Foundation + +extension AnyCodable: CustomStringConvertible { + /// Converts `AnyCodable`'s default decode strategy of array `[Any?]` into `[AnyCodable]` value type + public static func from(array: [Any?]?) -> [AnyCodable]? { + guard let unwrappedArray = array else { return nil } + + var newArray: [AnyCodable] = [] + for val in unwrappedArray { + if let anyCodableVal = val as? AnyCodable { + newArray.append(anyCodableVal) + } else { + newArray.append(AnyCodable(val)) + } + } + + return newArray + } + + /// Convenience string description that prints a pretty JSON output of an `AnyCodable` instance without all the `Optional` and `AnyCodable` type wrappers in the output string + public var description: String { + if let anyCodableData = try? JSONEncoder().encode(self), + let jsonObject = try? JSONSerialization.jsonObject(with: anyCodableData), + let jsonData = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted) { + return String(decoding: jsonData, as: UTF8.self) + } else { + return "\(String(describing: self.value))" + } + } + +} diff --git a/Tests/UpstreamIntegrationTests/util/EdgeEnvironment.swift b/Tests/UpstreamIntegrationTests/util/EdgeEnvironment.swift new file mode 100644 index 00000000..b5a307f5 --- /dev/null +++ b/Tests/UpstreamIntegrationTests/util/EdgeEnvironment.swift @@ -0,0 +1,33 @@ +// +// Copyright 2023 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 + +/// Edge Network environment levels that correspond to actual deployment environment levels +enum EdgeEnvironment: String { + /// Production + case prod + /// Pre-production - aka: staging + case preProd = "pre-prod" + /// Integration - aka: development + case int + + /// Initializer that gets the value from the environment variable `EDGE_ENVIRONMENT` and creates an `EdgeEnvironment` instance. + init() { + guard let edgeEnvironment = extractEnvironmentVariable(keyName: "EDGE_ENVIRONMENT", enum: EdgeEnvironment.self) else { + self = .prod + return + } + self = edgeEnvironment + } +} diff --git a/Tests/UpstreamIntegrationTests/util/EdgeLocationHint.swift b/Tests/UpstreamIntegrationTests/util/EdgeLocationHint.swift new file mode 100644 index 00000000..03f56b2f --- /dev/null +++ b/Tests/UpstreamIntegrationTests/util/EdgeLocationHint.swift @@ -0,0 +1,40 @@ +// +// Copyright 2023 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 + +/// All location hint values available for the Edge Network extension +enum EdgeLocationHint: String, CaseIterable { + /// Oregon, USA + case or2 + /// Virginia, USA + case va6 + /// Ireland + case irl1 + /// India + case ind1 + /// Japan + case jpn3 + /// Singapore + case sgp3 + /// Australia + case aus3 + + /// Initializer that gets the value from the environment variable `EDGE_LOCATION_HINT` and creates an `EdgeLocationHint` instance. + init?() { + guard let edgeLocationHint = extractEnvironmentVariable(keyName: "EDGE_LOCATION_HINT", enum: EdgeLocationHint.self) else { + return nil + } + self = edgeLocationHint + } +} diff --git a/Tests/UpstreamIntegrationTests/util/EnumUtils.swift b/Tests/UpstreamIntegrationTests/util/EnumUtils.swift new file mode 100644 index 00000000..de1346dd --- /dev/null +++ b/Tests/UpstreamIntegrationTests/util/EnumUtils.swift @@ -0,0 +1,32 @@ +// +// Copyright 2023 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 + +public func extractEnvironmentVariable(keyName: String, enum: T.Type) -> T? where T.RawValue == String { + guard let environmentString = ProcessInfo.processInfo.environment[keyName] else { + print("Unable to find valid \(keyName) value (raw value: \(String(describing: ProcessInfo.processInfo.environment[keyName]))).") + return nil + } + + guard let enumCase = T(rawValue: environmentString) else { + print("Unable to create valid enum case of type \(T.Type.self) from environment variable value: \(environmentString)") + return nil + } + + return enumCase +} + + + +