From a517ef05e7803d0632d731cf6bd80b84207c4ad0 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Wed, 6 Sep 2023 16:11:13 -0700 Subject: [PATCH 1/6] `Paywalls`: implemented `PostPaywallEventsOperation` This adds the operation + `PaywallEventsRequest` and associated tests. Also added method to `InternalAPI` for posting events. --- RevenueCat.xcodeproj/project.pbxproj | 30 ++++- .../Networking/HTTPClient/HTTPRequest.swift | 4 + .../HTTPClient/HTTPRequestPath.swift | 6 + Sources/Networking/InternalAPI.swift | 37 +++++- .../Networking/PaywallEventsRequest.swift | 120 ++++++++++++++++++ .../Networking/PaywallHTTPRequestPath.swift | 55 ++++++++ .../PostPaywallEventsOperation.swift | 47 +++++++ Tests/UnitTests/Mocks/MockInternalAPI.swift | 38 ++++++ .../Events/PaywallEventsBackendTests.swift | 91 +++++++++++++ .../Events/PaywallEventsRequestTests.swift | 63 +++++++++ 10 files changed, 485 insertions(+), 6 deletions(-) create mode 100644 Sources/Paywalls/Events/Networking/PaywallEventsRequest.swift create mode 100644 Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift create mode 100644 Sources/Paywalls/Events/Networking/PostPaywallEventsOperation.swift create mode 100644 Tests/UnitTests/Mocks/MockInternalAPI.swift create mode 100644 Tests/UnitTests/Paywalls/Events/PaywallEventsBackendTests.swift create mode 100644 Tests/UnitTests/Paywalls/Events/PaywallEventsRequestTests.swift diff --git a/RevenueCat.xcodeproj/project.pbxproj b/RevenueCat.xcodeproj/project.pbxproj index 6827fa3931..359b40e13a 100644 --- a/RevenueCat.xcodeproj/project.pbxproj +++ b/RevenueCat.xcodeproj/project.pbxproj @@ -315,6 +315,11 @@ 4FE6FEEA2AA940E300780B45 /* PaywallEventStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE6FEE72AA940E300780B45 /* PaywallEventStoreTests.swift */; }; 4FE6FEEB2AA940E300780B45 /* PaywallEventSerializerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE6FEE82AA940E300780B45 /* PaywallEventSerializerTests.swift */; }; 4FF8464D2A32554300617F00 /* DiagnosticsStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF8464C2A32554300617F00 /* DiagnosticsStrings.swift */; }; + 4FFCED822AA941B200118EF4 /* PaywallEventsRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FFCED802AA941B200118EF4 /* PaywallEventsRequestTests.swift */; }; + 4FFCED832AA941B200118EF4 /* PaywallEventsBackendTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FFCED812AA941B200118EF4 /* PaywallEventsBackendTests.swift */; }; + 4FFCED882AA941D200118EF4 /* PaywallEventsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FFCED852AA941D200118EF4 /* PaywallEventsRequest.swift */; }; + 4FFCED892AA941D200118EF4 /* PaywallHTTPRequestPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FFCED862AA941D200118EF4 /* PaywallHTTPRequestPath.swift */; }; + 4FFCED8A2AA941D200118EF4 /* PostPaywallEventsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FFCED872AA941D200118EF4 /* PostPaywallEventsOperation.swift */; }; 57032ABF28C13CE4004FF47A /* StoreKit2SettingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57032ABE28C13CE4004FF47A /* StoreKit2SettingTests.swift */; }; 57045B3829C514A8001A5417 /* ProductEntitlementMappingDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57045B3729C514A8001A5417 /* ProductEntitlementMappingDecodingTests.swift */; }; 57045B3A29C51751001A5417 /* GetProductEntitlementMappingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57045B3929C51751001A5417 /* GetProductEntitlementMappingOperation.swift */; }; @@ -1041,6 +1046,11 @@ 4FE6FEE82AA940E300780B45 /* PaywallEventSerializerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaywallEventSerializerTests.swift; sourceTree = ""; }; 4FED3AD62AAA7DD4001D4D5E /* purchases-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "purchases-ios"; path = ..; sourceTree = ""; }; 4FF8464C2A32554300617F00 /* DiagnosticsStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticsStrings.swift; sourceTree = ""; }; + 4FFCED802AA941B200118EF4 /* PaywallEventsRequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaywallEventsRequestTests.swift; sourceTree = ""; }; + 4FFCED812AA941B200118EF4 /* PaywallEventsBackendTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaywallEventsBackendTests.swift; sourceTree = ""; }; + 4FFCED852AA941D200118EF4 /* PaywallEventsRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaywallEventsRequest.swift; sourceTree = ""; }; + 4FFCED862AA941D200118EF4 /* PaywallHTTPRequestPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaywallHTTPRequestPath.swift; sourceTree = ""; }; + 4FFCED872AA941D200118EF4 /* PostPaywallEventsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostPaywallEventsOperation.swift; sourceTree = ""; }; 4FFD88BE2A4B56E2008E98AC /* __Snapshots__ */ = {isa = PBXFileReference; lastKnownFileType = folder; path = __Snapshots__; sourceTree = ""; }; 57032ABE28C13CE4004FF47A /* StoreKit2SettingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKit2SettingTests.swift; sourceTree = ""; }; 57045B3729C514A8001A5417 /* ProductEntitlementMappingDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductEntitlementMappingDecodingTests.swift; sourceTree = ""; }; @@ -2315,6 +2325,7 @@ 4FD368B22AA7CFDD00F63354 /* Events */ = { isa = PBXGroup; children = ( + 4FFCED842AA941D200118EF4 /* Networking */, 4FD3688A2AA7C12600F63354 /* PaywallEvent.swift */, 4FD368B32AA7CFED00F63354 /* PaywallEventStore.swift */, 4FD368B52AA7D09C00F63354 /* PaywallEventSerializer.swift */, @@ -2326,12 +2337,24 @@ 4FE6FEE62AA940E300780B45 /* Events */ = { isa = PBXGroup; children = ( - 4FE6FEE72AA940E300780B45 /* PaywallEventStoreTests.swift */, + 4FFCED812AA941B200118EF4 /* PaywallEventsBackendTests.swift */, + 4FFCED802AA941B200118EF4 /* PaywallEventsRequestTests.swift */, 4FE6FEE82AA940E300780B45 /* PaywallEventSerializerTests.swift */, + 4FE6FEE72AA940E300780B45 /* PaywallEventStoreTests.swift */, ); path = Events; sourceTree = ""; }; + 4FFCED842AA941D200118EF4 /* Networking */ = { + isa = PBXGroup; + children = ( + 4FFCED852AA941D200118EF4 /* PaywallEventsRequest.swift */, + 4FFCED862AA941D200118EF4 /* PaywallHTTPRequestPath.swift */, + 4FFCED872AA941D200118EF4 /* PostPaywallEventsOperation.swift */, + ); + path = Networking; + sourceTree = ""; + }; 570896B627596E6E00296F1C /* APITesters */ = { isa = PBXGroup; children = ( @@ -3281,6 +3304,7 @@ 2CD72942268A823900BFC976 /* Data+Extensions.swift in Sources */, 4FD3688B2AA7C12600F63354 /* PaywallEvent.swift in Sources */, 5766AA3E283C750300FA6091 /* Operators+Extensions.swift in Sources */, + 4FFCED892AA941D200118EF4 /* PaywallHTTPRequestPath.swift in Sources */, 4F6BEDE22A26B69500CD9322 /* DebugContentViews.swift in Sources */, B3B5FBBC269D121B00104A0C /* Offerings.swift in Sources */, 9A65E03B25918B0900DE00B0 /* CustomerInfoStrings.swift in Sources */, @@ -3444,6 +3468,7 @@ 574A2F4B282D7AEA00150D40 /* PostOfferResponse.swift in Sources */, F575858D26C088FE00C12B97 /* OfferingsManager.swift in Sources */, B34605CB279A6E380031CA74 /* PostReceiptDataOperation.swift in Sources */, + 4FFCED882AA941D200118EF4 /* PaywallEventsRequest.swift in Sources */, 354895D4267AE4B4001DC5B1 /* AttributionKey.swift in Sources */, F5714EA826D7A83A00635477 /* Store+Extensions.swift in Sources */, 35F82BAB26A84E130051DF03 /* Dictionary+Extensions.swift in Sources */, @@ -3493,6 +3518,7 @@ B372EC54268FEDC60099171E /* StoreProductDiscount.swift in Sources */, 4F7D8E562A56290100F17FFC /* HTTPRequestBody.swift in Sources */, B34605BE279A6E380031CA74 /* OfferingsCallback.swift in Sources */, + 4FFCED8A2AA941D200118EF4 /* PostPaywallEventsOperation.swift in Sources */, B34605BF279A6E380031CA74 /* LogInCallback.swift in Sources */, 9A65DFDE258AD60A00DE00B0 /* LogIntent.swift in Sources */, B37815492857F1E7000A7B93 /* BackendConfiguration.swift in Sources */, @@ -3612,6 +3638,7 @@ B380D69B27726AB500984578 /* DNSCheckerTests.swift in Sources */, 5774F9C12805EA3000997128 /* BaseHTTPResponseTest.swift in Sources */, 351B51B526D450E800BD2BD7 /* ProductsFetcherSK1Tests.swift in Sources */, + 4FFCED822AA941B200118EF4 /* PaywallEventsRequestTests.swift in Sources */, 4F05876F2A5DE03F00E9A834 /* PaywallDataTests.swift in Sources */, 2DDF41CC24F6F4C3005BC22D /* AppleReceiptBuilderTests.swift in Sources */, 575A8EE52922C9F300936709 /* MockStoreKit2TransactionListenerDelegate.swift in Sources */, @@ -3754,6 +3781,7 @@ 37E352973B0901E3CAA717E1 /* DateFormatter+ExtensionsTests.swift in Sources */, B3F8418F26F3A93400E560FB /* ErrorCodeTests.swift in Sources */, 5793397228E77A6E00C1232C /* MockPaymentQueue.swift in Sources */, + 4FFCED832AA941B200118EF4 /* PaywallEventsBackendTests.swift in Sources */, 5796A38A27D6B96300653165 /* BackendGetCustomerInfoTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/Networking/HTTPClient/HTTPRequest.swift b/Sources/Networking/HTTPClient/HTTPRequest.swift index cc35f79bef..3410312700 100644 --- a/Sources/Networking/HTTPClient/HTTPRequest.swift +++ b/Sources/Networking/HTTPClient/HTTPRequest.swift @@ -27,6 +27,10 @@ struct HTTPRequest { self.init(method: method, requestPath: path, nonce: nonce) } + init(method: Method, path: HTTPRequest.PaywallPath, nonce: Data? = nil) { + self.init(method: method, requestPath: path, nonce: nonce) + } + private init(method: Method, requestPath: HTTPRequestPath, nonce: Data? = nil) { assert(nonce == nil || nonce?.count == Data.nonceLength, "Invalid nonce: \(nonce?.description ?? "")") diff --git a/Sources/Networking/HTTPClient/HTTPRequestPath.swift b/Sources/Networking/HTTPClient/HTTPRequestPath.swift index 6de7569184..309026d837 100644 --- a/Sources/Networking/HTTPClient/HTTPRequestPath.swift +++ b/Sources/Networking/HTTPClient/HTTPRequestPath.swift @@ -70,6 +70,12 @@ extension HTTPRequest { } + enum PaywallPath: Hashable { + + case postEvents + + } + } extension HTTPRequest.Path: HTTPRequestPath { diff --git a/Sources/Networking/InternalAPI.swift b/Sources/Networking/InternalAPI.swift index 2be57e68ca..2d7380e64b 100644 --- a/Sources/Networking/InternalAPI.swift +++ b/Sources/Networking/InternalAPI.swift @@ -13,29 +13,56 @@ import Foundation -final class InternalAPI { +class InternalAPI { typealias ResponseHandler = (BackendError?) -> Void private let backendConfig: BackendConfiguration - private let callbackCache: CallbackCache + private let healthCallbackCache: CallbackCache init(backendConfig: BackendConfiguration) { self.backendConfig = backendConfig - self.callbackCache = .init() + self.healthCallbackCache = .init() } func healthRequest(signatureVerification: Bool, completion: @escaping ResponseHandler) { let factory = HealthOperation.createFactory(httpClient: self.backendConfig.httpClient, - callbackCache: self.callbackCache, + callbackCache: self.healthCallbackCache, signatureVerification: signatureVerification) let callback = HealthOperation.Callback(cacheKey: factory.cacheKey, completion: completion) - let cacheStatus = self.callbackCache.add(callback) + let cacheStatus = self.healthCallbackCache.add(callback) self.backendConfig.addCacheableOperation(with: factory, delay: .none, cacheStatus: cacheStatus) } + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + func postPaywallEvents(events: [PaywallStoredEvent], completion: @escaping ResponseHandler) { + guard !events.isEmpty else { + self.backendConfig.operationDispatcher.dispatchOnMainThread { + completion(nil) + } + return + } + + let operation = PostPaywallEventsOperation(configuration: .init(httpClient: self.backendConfig.httpClient), + request: .init(events: events), + responseHandler: completion) + + self.backendConfig.operationQueue.addOperation(operation) + } + +} + +extension InternalAPI { + + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + func postPaywallEvents(events: [PaywallStoredEvent]) async -> BackendError? { + return await Async.call { completion in + self.postPaywallEvents(events: events, completion: completion) + } + } + } diff --git a/Sources/Paywalls/Events/Networking/PaywallEventsRequest.swift b/Sources/Paywalls/Events/Networking/PaywallEventsRequest.swift new file mode 100644 index 0000000000..478d035889 --- /dev/null +++ b/Sources/Paywalls/Events/Networking/PaywallEventsRequest.swift @@ -0,0 +1,120 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// PaywallEventsRequest.swift +// +// Created by Nacho Soto on 9/6/23. + +import Foundation + +/// The content of a request to the events endpoints. +struct PaywallEventsRequest { + + var events: [Event] + + init(events: [Event]) { + self.events = events + } + + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + init(events: [PaywallStoredEvent]) { + self.init(events: events.map { .init(storedEvent: $0) }) + } + +} + +extension PaywallEventsRequest { + + enum EventType: String { + + case view + case cancel + case close + + } + + struct Event { + + let version: Int + var type: EventType + var appUserID: String + var sessionID: String + var offeringID: String + var paywallRevision: Int + var timestamp: Date + var displayMode: PaywallViewMode + var darkMode: Bool + var localeIdentifier: String + + } + +} + +extension PaywallEventsRequest.Event { + + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + init(storedEvent: PaywallStoredEvent) { + let data = storedEvent.event.data + + self.init( + version: Self.version, + type: storedEvent.event.eventType, + appUserID: storedEvent.userID, + sessionID: data.sessionIdentifier.uuidString, + offeringID: data.offeringIdentifier, + paywallRevision: data.paywallRevision, + timestamp: data.date, + displayMode: data.displayMode, + darkMode: data.darkMode, + localeIdentifier: data.localeIdentifier + ) + } + + private static let version: Int = 1 + +} + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +private extension PaywallEvent { + + var eventType: PaywallEventsRequest.EventType { + switch self { + case .view: return .view + case .cancel: return .cancel + case .close: return .close + } + + } + +} + +// MARK: - Codable + +extension PaywallEventsRequest.EventType: Encodable {} + +extension PaywallEventsRequest.Event: Encodable { + + private enum CodingKeys: String, CodingKey { + + case version + case type + case appUserID = "appUserId" + case sessionID = "sessionId" + case offeringID = "offeringId" + case paywallRevision + case timestamp + case displayMode + case darkMode + case localeIdentifier + + } + +} + +extension PaywallEventsRequest: HTTPRequestBody {} diff --git a/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift b/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift new file mode 100644 index 0000000000..3bc9064eca --- /dev/null +++ b/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift @@ -0,0 +1,55 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// PaywallHTTPRequestPath.swift +// +// Created by Nacho Soto on 9/5/23. + +import Foundation + +extension HTTPRequest.PaywallPath: HTTPRequestPath { + + // TODO: TBD + static let serverHostURL = URL(string: "https://api-paywalls.revenuecat.com")! + + var authenticated: Bool { + switch self { + case .postEvents: + return true + } + } + + var shouldSendEtag: Bool { + switch self { + case .postEvents: + return false + } + } + + var supportsSignatureVerification: Bool { + switch self { + case .postEvents: + return false + } + } + + var needsNonceForSigning: Bool { + switch self { + case .postEvents: + return false + } + } + + var pathComponent: String { + switch self { + case .postEvents: + return "paywall_events" + } + } +} diff --git a/Sources/Paywalls/Events/Networking/PostPaywallEventsOperation.swift b/Sources/Paywalls/Events/Networking/PostPaywallEventsOperation.swift new file mode 100644 index 0000000000..856ae76aff --- /dev/null +++ b/Sources/Paywalls/Events/Networking/PostPaywallEventsOperation.swift @@ -0,0 +1,47 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// PostPaywallEventsOperation.swift +// +// Created by Nacho Soto on 9/6/23. + +import Foundation + +/// A `NetworkOperation` for posting ``PaywallEvent``s. +final class PostPaywallEventsOperation: NetworkOperation { + + private let configuration: Configuration + private let request: PaywallEventsRequest + private let responseHandler: CustomerAPI.SimpleResponseHandler? + + init( + configuration: Configuration, + request: PaywallEventsRequest, + responseHandler: CustomerAPI.SimpleResponseHandler? + ) { + self.request = request + self.configuration = configuration + self.responseHandler = responseHandler + + super.init(configuration: configuration) + } + + override func begin(completion: @escaping () -> Void) { + let request = HTTPRequest(method: .post(self.request), path: .postEvents) + + self.httpClient.perform(request) { (response: VerifiedHTTPResponse.Result) in + defer { + completion() + } + + self.responseHandler?(response.error.map(BackendError.networkError)) + } + } + +} diff --git a/Tests/UnitTests/Mocks/MockInternalAPI.swift b/Tests/UnitTests/Mocks/MockInternalAPI.swift new file mode 100644 index 0000000000..125d754998 --- /dev/null +++ b/Tests/UnitTests/Mocks/MockInternalAPI.swift @@ -0,0 +1,38 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// MockInternalAPI.swift +// +// Created by Nacho Soto on 9/6/23. + +import Foundation +@testable import RevenueCat + +class MockInternalAPI: InternalAPI { + + public convenience init() { + self.init(backendConfig: MockBackendConfiguration()) + } + + var invokedPostPaywallEvents: Bool = false + var invokedPostPaywallEventsParameters: [[PaywallStoredEvent]] = [] + var stubbedPostPaywallEventsCompletionResult: BackendError? + + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) + override func postPaywallEvents( + events: [PaywallStoredEvent], + completion: @escaping InternalAPI.ResponseHandler + ) { + self.invokedPostPaywallEvents = true + self.invokedPostPaywallEventsParameters.append(events) + + completion(self.stubbedPostPaywallEventsCompletionResult) + } + +} diff --git a/Tests/UnitTests/Paywalls/Events/PaywallEventsBackendTests.swift b/Tests/UnitTests/Paywalls/Events/PaywallEventsBackendTests.swift new file mode 100644 index 0000000000..b9522e8637 --- /dev/null +++ b/Tests/UnitTests/Paywalls/Events/PaywallEventsBackendTests.swift @@ -0,0 +1,91 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// PaywallEventsBackendTests.swift +// +// Created by Nacho Soto on 9/6/23. + +import Foundation +import Nimble +import XCTest + +@testable import RevenueCat + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +class BackendPaywallEventTests: BaseBackendTests { + + override func setUpWithError() throws { + try super.setUpWithError() + + try AvailabilityChecks.iOS15APIAvailableOrSkipTest() + } + + override func createClient() -> MockHTTPClient { + super.createClient(#file) + } + + func testPostPaywallEventsWithNoEventsMakesNoRequests() { + let error = waitUntilValue { completion in + self.internalAPI.postPaywallEvents(events: [], completion: completion) + } + + expect(error).to(beNil()) + expect(self.httpClient.calls).to(beEmpty()) + } + + func testPostPaywallEventsWithOneEvent() { + let event: PaywallStoredEvent = .init(event: .view(Self.event1), userID: Self.userID) + + let error = waitUntilValue { completion in + self.internalAPI.postPaywallEvents(events: [event], completion: completion) + } + + expect(error).to(beNil()) + } + + func testPostPaywallEventsWithMultipleEvents() { + let event1: PaywallStoredEvent = .init(event: .view(Self.event1), userID: Self.userID) + let event2: PaywallStoredEvent = .init(event: .close(Self.event2), userID: Self.userID) + + let error = waitUntilValue { completion in + self.internalAPI.postPaywallEvents(events: [event1, event2], + completion: completion) + } + + expect(error).to(beNil()) + } + +} + +// MARK: - + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +private extension BackendPaywallEventTests { + + static let event1: PaywallEvent.Data = .init( + offeringIdentifier: "offering_1", + paywallRevision: 5, + sessionID: .init(uuidString: "98CC0F1D-7665-4093-9624-1D7308FFF4DB")!, + displayMode: .condensedFooter, + localeIdentifier: "es_ES", + darkMode: true, + date: .init(timeIntervalSince1970: 1694029328) + ) + + static let event2: PaywallEvent.Data = .init( + offeringIdentifier: "offering_2", + paywallRevision: 3, + sessionID: .init(uuidString: "10CC0F1D-7665-4093-9624-1D7308FFF4DB")!, + displayMode: .fullScreen, + localeIdentifier: "en_US", + darkMode: false, + date: .init(timeIntervalSince1970: 1694022321) + ) + +} diff --git a/Tests/UnitTests/Paywalls/Events/PaywallEventsRequestTests.swift b/Tests/UnitTests/Paywalls/Events/PaywallEventsRequestTests.swift new file mode 100644 index 0000000000..554ad5f77d --- /dev/null +++ b/Tests/UnitTests/Paywalls/Events/PaywallEventsRequestTests.swift @@ -0,0 +1,63 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// PaywallEventsRequestTests.swift +// +// Created by Nacho Soto on 9/6/23. + +import Foundation +import Nimble +@testable import RevenueCat +import SnapshotTesting + +@available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) +class PaywallEventsRequestTests: TestCase { + + override func setUpWithError() throws { + try super.setUpWithError() + + try AvailabilityChecks.iOS15APIAvailableOrSkipTest() + } + + func testViewEvent() throws { + let event: PaywallStoredEvent = .init(event: .view(Self.eventData), userID: Self.userID) + let requestEvent: PaywallEventsRequest.Event = .init(storedEvent: event) + + assertSnapshot(matching: requestEvent, as: .formattedJson) + } + + func testCancelEvent() throws { + let event: PaywallStoredEvent = .init(event: .cancel(Self.eventData), userID: Self.userID) + let requestEvent: PaywallEventsRequest.Event = .init(storedEvent: event) + + assertSnapshot(matching: requestEvent, as: .formattedJson) + } + + func testCloseEvent() throws { + let event: PaywallStoredEvent = .init(event: .close(Self.eventData), userID: Self.userID) + let requestEvent: PaywallEventsRequest.Event = .init(storedEvent: event) + + assertSnapshot(matching: requestEvent, as: .formattedJson) + } + + // MARK: - + + private static let eventData: PaywallEvent.Data = .init( + offeringIdentifier: "offering", + paywallRevision: 5, + sessionID: .init(uuidString: "98CC0F1D-7665-4093-9624-1D7308FFF4DB")!, + displayMode: .condensedFooter, + localeIdentifier: "es_ES", + darkMode: true, + date: .init(timeIntervalSince1970: 1694029328) + ) + + private static let userID = "Jack Shepard" + +} From 34ec40bdef5630b019405baa4d6c730574b81fcb Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Fri, 8 Sep 2023 11:36:13 -0700 Subject: [PATCH 2/6] Removed TODO --- Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift b/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift index 3bc9064eca..a9405f44f7 100644 --- a/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift +++ b/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift @@ -15,7 +15,6 @@ import Foundation extension HTTPRequest.PaywallPath: HTTPRequestPath { - // TODO: TBD static let serverHostURL = URL(string: "https://api-paywalls.revenuecat.com")! var authenticated: Bool { From 345787d68ea2fbc70934b5bb0fc1b4849ce3e2ef Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Fri, 8 Sep 2023 11:38:35 -0700 Subject: [PATCH 3/6] Updated endpoint --- Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift b/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift index a9405f44f7..4973142f7a 100644 --- a/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift +++ b/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift @@ -48,7 +48,7 @@ extension HTTPRequest.PaywallPath: HTTPRequestPath { var pathComponent: String { switch self { case .postEvents: - return "paywall_events" + return "events" } } } From d595ec8213840dd215cc546aaea51fb974d5ac83 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Fri, 8 Sep 2023 11:41:37 -0700 Subject: [PATCH 4/6] Lint --- Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift b/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift index 4973142f7a..55aced5db9 100644 --- a/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift +++ b/Sources/Paywalls/Events/Networking/PaywallHTTPRequestPath.swift @@ -51,4 +51,5 @@ extension HTTPRequest.PaywallPath: HTTPRequestPath { return "events" } } + } From 51f57ac9383f7c5fe0a52d5c2b44827ca2faf394 Mon Sep 17 00:00:00 2001 From: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com> Date: Fri, 8 Sep 2023 21:03:04 +0200 Subject: [PATCH 5/6] Generating new test snapshots for `paywall-events-post-operation` - ios-15 (#3174) Requested by @NachoSoto for [paywall-events-post-operation](https://github.com/RevenueCat/purchases-ios/tree/paywall-events-post-operation) Co-authored-by: Distiller --- ...PostPaywallEventsWithMultipleEvents.1.json | 37 +++++++++++++++++++ ...5-testPostPaywallEventsWithOneEvent.1.json | 25 +++++++++++++ .../testCancelEvent.1.json | 12 ++++++ .../testCloseEvent.1.json | 12 ++++++ .../testViewEvent.1.json | 12 ++++++ 5 files changed, 98 insertions(+) create mode 100644 Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json create mode 100644 Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json create mode 100644 Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json create mode 100644 Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCloseEvent.1.json create mode 100644 Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testViewEvent.1.json diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json new file mode 100644 index 0000000000..4cb64d6ccc --- /dev/null +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json @@ -0,0 +1,37 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret" + }, + "request" : { + "body" : { + "events" : [ + { + "app_user_id" : "user", + "dark_mode" : true, + "display_mode" : "condensed_footer", + "locale_identifier" : "es_ES", + "offering_id" : "offering_1", + "paywall_revision" : 5, + "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", + "timestamp" : 715722128, + "type" : "view", + "version" : 1 + }, + { + "app_user_id" : "user", + "dark_mode" : false, + "display_mode" : "full_screen", + "locale_identifier" : "en_US", + "offering_id" : "offering_2", + "paywall_revision" : 3, + "session_id" : "10CC0F1D-7665-4093-9624-1D7308FFF4DB", + "timestamp" : 715715121, + "type" : "close", + "version" : 1 + } + ] + }, + "method" : "POST", + "url" : "https://api-paywalls.revenuecat.com/v1/events" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json new file mode 100644 index 0000000000..2f889bf93f --- /dev/null +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json @@ -0,0 +1,25 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret" + }, + "request" : { + "body" : { + "events" : [ + { + "app_user_id" : "user", + "dark_mode" : true, + "display_mode" : "condensed_footer", + "locale_identifier" : "es_ES", + "offering_id" : "offering_1", + "paywall_revision" : 5, + "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", + "timestamp" : 715722128, + "type" : "view", + "version" : 1 + } + ] + }, + "method" : "POST", + "url" : "https://api-paywalls.revenuecat.com/v1/events" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json new file mode 100644 index 0000000000..48fcc6dc8a --- /dev/null +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json @@ -0,0 +1,12 @@ +{ + "app_user_id" : "Jack Shepard", + "dark_mode" : true, + "display_mode" : "condensed_footer", + "locale_identifier" : "es_ES", + "offering_id" : "offering", + "paywall_revision" : 5, + "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", + "timestamp" : 715722128, + "type" : "cancel", + "version" : 1 +} \ No newline at end of file diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCloseEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCloseEvent.1.json new file mode 100644 index 0000000000..560c54c576 --- /dev/null +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCloseEvent.1.json @@ -0,0 +1,12 @@ +{ + "app_user_id" : "Jack Shepard", + "dark_mode" : true, + "display_mode" : "condensed_footer", + "locale_identifier" : "es_ES", + "offering_id" : "offering", + "paywall_revision" : 5, + "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", + "timestamp" : 715722128, + "type" : "close", + "version" : 1 +} \ No newline at end of file diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testViewEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testViewEvent.1.json new file mode 100644 index 0000000000..60bbe0cbe2 --- /dev/null +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testViewEvent.1.json @@ -0,0 +1,12 @@ +{ + "app_user_id" : "Jack Shepard", + "dark_mode" : true, + "display_mode" : "condensed_footer", + "locale_identifier" : "es_ES", + "offering_id" : "offering", + "paywall_revision" : 5, + "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", + "timestamp" : 715722128, + "type" : "view", + "version" : 1 +} \ No newline at end of file From 6579ec45ff8db6be807c74df78b035c5c51a1300 Mon Sep 17 00:00:00 2001 From: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com> Date: Fri, 8 Sep 2023 21:03:16 +0200 Subject: [PATCH 6/6] Generating new test snapshots for `paywall-events-post-operation` - ios-16 (#3175) Requested by @NachoSoto for [paywall-events-post-operation](https://github.com/RevenueCat/purchases-ios/tree/paywall-events-post-operation) Co-authored-by: Distiller --- ...PostPaywallEventsWithMultipleEvents.1.json | 37 +++++++++++++++++++ ...6-testPostPaywallEventsWithOneEvent.1.json | 25 +++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json create mode 100644 Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json new file mode 100644 index 0000000000..4cb64d6ccc --- /dev/null +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json @@ -0,0 +1,37 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret" + }, + "request" : { + "body" : { + "events" : [ + { + "app_user_id" : "user", + "dark_mode" : true, + "display_mode" : "condensed_footer", + "locale_identifier" : "es_ES", + "offering_id" : "offering_1", + "paywall_revision" : 5, + "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", + "timestamp" : 715722128, + "type" : "view", + "version" : 1 + }, + { + "app_user_id" : "user", + "dark_mode" : false, + "display_mode" : "full_screen", + "locale_identifier" : "en_US", + "offering_id" : "offering_2", + "paywall_revision" : 3, + "session_id" : "10CC0F1D-7665-4093-9624-1D7308FFF4DB", + "timestamp" : 715715121, + "type" : "close", + "version" : 1 + } + ] + }, + "method" : "POST", + "url" : "https://api-paywalls.revenuecat.com/v1/events" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json new file mode 100644 index 0000000000..2f889bf93f --- /dev/null +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json @@ -0,0 +1,25 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret" + }, + "request" : { + "body" : { + "events" : [ + { + "app_user_id" : "user", + "dark_mode" : true, + "display_mode" : "condensed_footer", + "locale_identifier" : "es_ES", + "offering_id" : "offering_1", + "paywall_revision" : 5, + "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", + "timestamp" : 715722128, + "type" : "view", + "version" : 1 + } + ] + }, + "method" : "POST", + "url" : "https://api-paywalls.revenuecat.com/v1/events" + } +} \ No newline at end of file