From 85cf679b3596dcbb032b9cc588fea4e61d5d608b Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Thu, 7 Sep 2023 16:45:28 -0700 Subject: [PATCH 1/5] `Paywalls`: events unit and integration tests - Added `PurchasesOrchestrator` tests for paywall data sent through post receipt - Fixed and tested state issue with cached paywall data and failed purchases - Added `Integration Tests` for tracking and flushing events - Configured `Integration Tests` with a custom documents directory to ensure it's empty on every test invocation - Changed deployment target on `Integration Tests` to iOS 16 to simplify code - Setting `Purchases.logLevel` before configuring purchases on `Integration Tests` - Added assertion to ensure `FileHandler` never runs on the main thread --- RevenueCat.xcodeproj/project.pbxproj | 10 +- Sources/Diagnostics/FileHandler.swift | 6 + Sources/Logging/Strings/PaywallsStrings.swift | 4 +- Sources/Networking/InternalAPI.swift | 7 +- .../Paywalls/Events/PaywallEventStore.swift | 12 +- .../Events/PaywallEventsManager.swift | 25 ++-- Sources/Purchasing/Purchases/Purchases.swift | 12 +- .../Purchases/PurchasesOrchestrator.swift | 98 +++++++++------ .../BaseBackendIntegrationTests.swift | 12 +- .../PaywallEventsIntegrationTests.swift | 97 +++++++++++++-- .../PurchasesOrchestratorTests.swift | 112 ++++++++++++++++++ .../Mocks/MockPaywallEventsManager.swift | 4 +- .../Events/PaywallEventStoreTests.swift | 2 +- .../Events/PaywallEventsManagerTests.swift | 59 ++++++--- 14 files changed, 370 insertions(+), 90 deletions(-) diff --git a/RevenueCat.xcodeproj/project.pbxproj b/RevenueCat.xcodeproj/project.pbxproj index 84e4226f59..e54cbb7f00 100644 --- a/RevenueCat.xcodeproj/project.pbxproj +++ b/RevenueCat.xcodeproj/project.pbxproj @@ -986,6 +986,7 @@ 4F34AEEB2A5DCCBA00F4BCB0 /* VerificationResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationResultTests.swift; sourceTree = ""; }; 4F3C98692A44FA60009AECA3 /* ErrorResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorResponse.swift; sourceTree = ""; }; 4F3D56622A1E66A10070105A /* CustomerInfoManagerPostReceiptTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerInfoManagerPostReceiptTests.swift; sourceTree = ""; }; + 4F4EECE32AAFA8DA0047DE7A /* __Snapshots__ */ = {isa = PBXFileReference; lastKnownFileType = folder; path = __Snapshots__; sourceTree = ""; }; 4F4FF3E02A3B731A0028018C /* ETagStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ETagStrings.swift; sourceTree = ""; }; 4F54DF3E2A1D8C7500FD72BF /* MockStoreKit2TransactionFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockStoreKit2TransactionFetcher.swift; sourceTree = ""; }; 4F54DF412A1D8D0700FD72BF /* MockTransactionPoster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTransactionPoster.swift; sourceTree = ""; }; @@ -2351,6 +2352,7 @@ 4FE6FEE62AA940E300780B45 /* Events */ = { isa = PBXGroup; children = ( + 4F4EECE32AAFA8DA0047DE7A /* __Snapshots__ */, 4FFCED812AA941B200118EF4 /* PaywallEventsBackendTests.swift */, 4FFFE6C72AA9467800B2955C /* PaywallEventsManagerTests.swift */, 4FFCED802AA941B200118EF4 /* PaywallEventsRequestTests.swift */, @@ -4183,7 +4185,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = Tests/BackendIntegrationTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4211,7 +4213,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = Tests/BackendIntegrationTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4477,7 +4479,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = Tests/BackendIntegrationTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4505,7 +4507,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = Tests/BackendIntegrationTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Sources/Diagnostics/FileHandler.swift b/Sources/Diagnostics/FileHandler.swift index f2726e23ec..1d0b9f9c38 100644 --- a/Sources/Diagnostics/FileHandler.swift +++ b/Sources/Diagnostics/FileHandler.swift @@ -56,6 +56,8 @@ actor FileHandler: FileHandlerType { /// - Note: this loads the entire file in memory /// For newer versions, consider using `readLines` instead. func readFile() throws -> Data { + RCTestAssertNotMainThread() + try self.moveToBeginningOfFile() return self.fileHandle.availableData @@ -64,6 +66,8 @@ actor FileHandler: FileHandlerType { /// Returns an async sequence for every line in the file @available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) func readLines() throws -> AsyncLineSequence { + RCTestAssertNotMainThread() + try self.moveToBeginningOfFile() return self.fileHandle.bytes.lines @@ -71,6 +75,8 @@ actor FileHandler: FileHandlerType { /// Adds a line at the end of the file func append(line: String) { + RCTestAssertNotMainThread() + self.fileHandle.seekToEndOfFile() self.fileHandle.write(line.asData) self.fileHandle.write(Self.lineBreakData) diff --git a/Sources/Logging/Strings/PaywallsStrings.swift b/Sources/Logging/Strings/PaywallsStrings.swift index 30c8ff72c8..3b93dda9de 100644 --- a/Sources/Logging/Strings/PaywallsStrings.swift +++ b/Sources/Logging/Strings/PaywallsStrings.swift @@ -33,7 +33,7 @@ enum PaywallsStrings { case event_flush_already_in_progress case event_flush_with_empty_store case event_flush_starting(count: Int) - case event_flush_failed(BackendError) + case event_flush_failed(Error) } @@ -78,7 +78,7 @@ extension PaywallsStrings: LogMessage { return "Paywall event flush: posting \(count) events." case let .event_flush_failed(error): - return "Paywall event flushing failed, will retry. Error: \(error.localizedDescription)" + return "Paywall event flushing failed, will retry. Error: \((error as NSError).localizedDescription)" } } diff --git a/Sources/Networking/InternalAPI.swift b/Sources/Networking/InternalAPI.swift index 193255e0c5..16159a23bc 100644 --- a/Sources/Networking/InternalAPI.swift +++ b/Sources/Networking/InternalAPI.swift @@ -56,11 +56,14 @@ class InternalAPI { extension InternalAPI { + /// - Throws: `BackendError` @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 + func postPaywallEvents(events: [PaywallStoredEvent]) async throws { + let error = await Async.call { completion in self.postPaywallEvents(events: events, completion: completion) } + + if let error { throw error } } } diff --git a/Sources/Paywalls/Events/PaywallEventStore.swift b/Sources/Paywalls/Events/PaywallEventStore.swift index 5d0ad59636..19d71e06af 100644 --- a/Sources/Paywalls/Events/PaywallEventStore.swift +++ b/Sources/Paywalls/Events/PaywallEventStore.swift @@ -85,11 +85,14 @@ internal actor PaywallEventStore: PaywallEventStoreType { @available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) extension PaywallEventStore { - static func createDefault() throws -> PaywallEventStore { - let url = try Self.documentsDirectory + static func createDefault(documentsDirectory: URL?) throws -> PaywallEventStore { + let documentsDirectory = try documentsDirectory ?? Self.documentsDirectory + let url = documentsDirectory .appendingPathComponent("revenuecat") .appendingPathComponent("paywall_event_store") + Logger.verbose(PaywallEventStoreStrings.initializing(url)) + return try .init(handler: FileHandler(url)) } @@ -116,6 +119,8 @@ extension PaywallEventStore { @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) private enum PaywallEventStoreStrings { + case initializing(URL) + case storing_event(PaywallEvent) case error_storing_event(Error) @@ -131,6 +136,9 @@ extension PaywallEventStoreStrings: LogMessage { var description: String { switch self { + case let .initializing(directory): + return "Initializing PaywallEventStore: \(directory.absoluteString)" + case let .storing_event(event): return "Storing event: \(event.debugDescription)" diff --git a/Sources/Paywalls/Events/PaywallEventsManager.swift b/Sources/Paywalls/Events/PaywallEventsManager.swift index 42e6b27144..8b6b98b1fe 100644 --- a/Sources/Paywalls/Events/PaywallEventsManager.swift +++ b/Sources/Paywalls/Events/PaywallEventsManager.swift @@ -18,8 +18,10 @@ protocol PaywallEventsManagerType { @available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) func track(paywallEvent: PaywallEvent) async + /// - Throws: if posting events fails + /// - Returns: the number of events posted @available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) - func flushEvents(count: Int) async + func flushEvents(count: Int) async throws -> Int } @@ -46,10 +48,10 @@ actor PaywallEventsManager: PaywallEventsManagerType { await self.store.store(.init(event: paywallEvent, userID: self.userProvider.currentAppUserID)) } - func flushEvents(count: Int) async { + func flushEvents(count: Int) async throws -> Int { guard !self.flushInProgress else { Logger.debug(Strings.paywalls.event_flush_already_in_progress) - return + return 0 } self.flushInProgress = true defer { self.flushInProgress = false } @@ -58,21 +60,26 @@ actor PaywallEventsManager: PaywallEventsManagerType { guard !events.isEmpty else { Logger.verbose(Strings.paywalls.event_flush_with_empty_store) - return + return 0 } Logger.verbose(Strings.paywalls.event_flush_starting(count: events.count)) - let error = await self.internalAPI.postPaywallEvents(events: events) + do { + try await self.internalAPI.postPaywallEvents(events: events) - if let error { + await self.store.clear(count) + + return events.count + } catch { Logger.error(Strings.paywalls.event_flush_failed(error)) - if error.successfullySynced { + if let backendError = error as? BackendError, + backendError.successfullySynced { await self.store.clear(count) } - } else { - await self.store.clear(count) + + throw error } } diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index 395c4f208b..32a8b80d8d 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -260,6 +260,7 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void convenience init(apiKey: String, appUserID: String?, userDefaults: UserDefaults? = nil, + documentsDirectory: URL? = nil, observerMode: Bool = false, platformInfo: PlatformInfo? = Purchases.platformInfo, responseVerificationMode: Signing.ResponseVerificationMode, @@ -359,7 +360,7 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void paywallEventsManager = PaywallEventsManager( internalAPI: backend.internalAPI, userProvider: identityManager, - store: try PaywallEventStore.createDefault() + store: try PaywallEventStore.createDefault(documentsDirectory: documentsDirectory) ) Logger.verbose(Strings.paywalls.event_manager_initialized) } else { @@ -1266,6 +1267,7 @@ public extension Purchases { appUserID: String?, observerMode: Bool, userDefaults: UserDefaults?, + documentsDirectory: URL? = nil, platformInfo: PlatformInfo?, responseVerificationMode: Signing.ResponseVerificationMode, storeKit2Setting: StoreKit2Setting, @@ -1277,6 +1279,7 @@ public extension Purchases { .init(apiKey: apiKey, appUserID: appUserID, userDefaults: userDefaults, + documentsDirectory: documentsDirectory, observerMode: observerMode, platformInfo: platformInfo, responseVerificationMode: responseVerificationMode, @@ -1531,6 +1534,13 @@ internal extension Purchases { self.offeringsManager.invalidateCachedOfferings(appUserID: self.appUserID) } + /// - Throws: if posting events fails + /// - Returns: the number of events posted + @available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) + func flushPaywallEvents(count: Int) async throws -> Int { + return try await self.paywallEventsManager?.flushEvents(count: count) ?? 0 + } + } #endif diff --git a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift index e84d7731ed..3abec67a30 100644 --- a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift +++ b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift @@ -880,27 +880,29 @@ extension PurchasesOrchestrator: StoreKit2TransactionListenerDelegate { let storefront = await self.storefront(from: transaction) let subscriberAttributes = self.unsyncedAttributes let adServicesToken = self.attribution.unsyncedAdServicesToken + let transactionData: PurchasedTransactionData = .init( + appUserID: self.appUserID, + presentedOfferingID: nil, + unsyncedAttributes: subscriberAttributes, + aadAttributionToken: adServicesToken, + storefront: storefront, + source: .init( + isRestore: self.allowSharingAppStoreAccount, + initiationSource: .queue + ) + ) let result: Result = await Async.call { completed in self.transactionPoster.handlePurchasedTransaction( StoreTransaction.from(transaction: transaction), - data: .init( - appUserID: self.appUserID, - presentedOfferingID: nil, - unsyncedAttributes: subscriberAttributes, - aadAttributionToken: adServicesToken, - storefront: storefront, - source: .init( - isRestore: self.allowSharingAppStoreAccount, - initiationSource: .queue - ) - ) + data: transactionData ) { result in completed(result) } } self.handlePostReceiptResult(result, + transactionData: transactionData, subscriberAttributes: subscriberAttributes, adServicesToken: adServicesToken) @@ -992,6 +994,7 @@ private extension PurchasesOrchestrator { } } + // swiftlint:disable:next function_body_length func syncPurchases(receiptRefreshPolicy: ReceiptRefreshPolicy, isRestore: Bool, initiationSource: ProductRequestData.InitiationSource, @@ -1038,17 +1041,20 @@ private extension PurchasesOrchestrator { } self.createProductRequestData(with: receiptData) { productRequestData in + let transactionData: PurchasedTransactionData = .init( + appUserID: currentAppUserID, + presentedOfferingID: nil, + unsyncedAttributes: unsyncedAttributes, + storefront: productRequestData?.storefront, + source: .init(isRestore: isRestore, initiationSource: initiationSource) + ) + self.backend.post(receiptData: receiptData, productData: productRequestData, - transactionData: .init( - appUserID: currentAppUserID, - presentedOfferingID: nil, - unsyncedAttributes: unsyncedAttributes, - storefront: productRequestData?.storefront, - source: .init(isRestore: isRestore, initiationSource: initiationSource) - ), + transactionData: transactionData, observerMode: self.observerMode) { result in self.handleReceiptPost(result: result, + transactionData: transactionData, subscriberAttributes: unsyncedAttributes, adServicesToken: adServicesToken, completion: completion) @@ -1059,11 +1065,13 @@ private extension PurchasesOrchestrator { } func handleReceiptPost(result: Result, + transactionData: PurchasedTransactionData, subscriberAttributes: SubscriberAttribute.Dictionary, adServicesToken: String?, completion: (@Sendable (Result) -> Void)?) { self.handlePostReceiptResult( result, + transactionData: transactionData, subscriberAttributes: subscriberAttributes, adServicesToken: adServicesToken ) @@ -1076,10 +1084,18 @@ private extension PurchasesOrchestrator { } func handlePostReceiptResult(_ result: Result, + transactionData: PurchasedTransactionData, subscriberAttributes: SubscriberAttribute.Dictionary, adServicesToken: String?) { - if let customerInfo = result.value { + switch result { + case let .success(customerInfo): self.customerInfoManager.cache(customerInfo: customerInfo, appUserID: self.appUserID) + + case .failure: + // Cache paywall again in case purchase is retried. + if let paywall = transactionData.presentedPaywall { + self.cachePresentedPaywall(paywall) + } } self.markSyncedIfNeeded(subscriberAttributes: subscriberAttributes, @@ -1094,21 +1110,23 @@ private extension PurchasesOrchestrator { let paywall = self.getAndRemovePresentedPaywall() let unsyncedAttributes = self.unsyncedAttributes let adServicesToken = self.attribution.unsyncedAdServicesToken + let transactionData: PurchasedTransactionData = .init( + appUserID: self.appUserID, + presentedOfferingID: offeringID, + presentedPaywall: paywall, + unsyncedAttributes: unsyncedAttributes, + aadAttributionToken: adServicesToken, + storefront: storefront, + source: self.purchaseSource(for: purchasedTransaction.productIdentifier, + restored: restored) + ) self.transactionPoster.handlePurchasedTransaction( purchasedTransaction, - data: .init( - appUserID: self.appUserID, - presentedOfferingID: offeringID, - presentedPaywall: paywall, - unsyncedAttributes: unsyncedAttributes, - aadAttributionToken: adServicesToken, - storefront: storefront, - source: self.purchaseSource(for: purchasedTransaction.productIdentifier, - restored: restored) - ) + data: transactionData ) { result in self.handlePostReceiptResult(result, + transactionData: transactionData, subscriberAttributes: unsyncedAttributes, adServicesToken: adServicesToken) @@ -1240,22 +1258,24 @@ extension PurchasesOrchestrator { let paywall = self.getAndRemovePresentedPaywall() let unsyncedAttributes = self.unsyncedAttributes let adServicesToken = self.attribution.unsyncedAdServicesToken + let transactionData: PurchasedTransactionData = .init( + appUserID: self.appUserID, + presentedOfferingID: offeringID, + presentedPaywall: paywall, + unsyncedAttributes: unsyncedAttributes, + aadAttributionToken: adServicesToken, + storefront: storefront, + source: .init(isRestore: self.allowSharingAppStoreAccount, + initiationSource: initiationSource) + ) let result = await self.transactionPoster.handlePurchasedTransaction( transaction, - data: .init( - appUserID: self.appUserID, - presentedOfferingID: offeringID, - presentedPaywall: paywall, - unsyncedAttributes: unsyncedAttributes, - aadAttributionToken: adServicesToken, - storefront: storefront, - source: .init(isRestore: self.allowSharingAppStoreAccount, - initiationSource: initiationSource) - ) + data: transactionData ) self.handlePostReceiptResult(result, + transactionData: transactionData, subscriberAttributes: unsyncedAttributes, adServicesToken: adServicesToken) diff --git a/Tests/BackendIntegrationTests/BaseBackendIntegrationTests.swift b/Tests/BackendIntegrationTests/BaseBackendIntegrationTests.swift index 28a8db289b..bc29401a7a 100644 --- a/Tests/BackendIntegrationTests/BaseBackendIntegrationTests.swift +++ b/Tests/BackendIntegrationTests/BaseBackendIntegrationTests.swift @@ -61,10 +61,14 @@ class BaseBackendIntegrationTests: TestCase { var proxyURL: String? { return Constants.proxyURL } func configurePurchases() { + Purchases.proxyURL = self.proxyURL.flatMap(URL.init(string:)) + Purchases.logLevel = .verbose + Purchases.configure(withAPIKey: self.apiKey, appUserID: nil, observerMode: Self.observerMode, userDefaults: self.userDefaults, + documentsDirectory: self.documentsDirectory, platformInfo: nil, responseVerificationMode: Self.responseVerificationMode, storeKit2Setting: Self.storeKit2Setting, @@ -173,8 +177,6 @@ private extension BaseBackendIntegrationTests { self.simulateForegroundingApp() Purchases.shared.delegate = self.purchasesDelegate - Purchases.proxyURL = self.proxyURL.flatMap(URL.init(string:)) - Purchases.logLevel = .verbose await self.waitForAnonymousUser() } @@ -218,6 +220,12 @@ private extension BaseBackendIntegrationTests { internalSettings: self) } + var documentsDirectory: URL { + return URL + .cachesDirectory + .appendingPathComponent(UUID().uuidString, conformingTo: .directory) + } + } extension BaseBackendIntegrationTests: InternalDangerousSettingsType { diff --git a/Tests/BackendIntegrationTests/PaywallEventsIntegrationTests.swift b/Tests/BackendIntegrationTests/PaywallEventsIntegrationTests.swift index 81b31e8fe5..583fdf1e0d 100644 --- a/Tests/BackendIntegrationTests/PaywallEventsIntegrationTests.swift +++ b/Tests/BackendIntegrationTests/PaywallEventsIntegrationTests.swift @@ -19,30 +19,105 @@ import XCTest class PaywallEventsIntegrationTests: BaseStoreKitIntegrationTests { - func testPurchasingPackageWithPresentedPaywall() async throws { - let offering = try await self.currentOffering - let paywall = try XCTUnwrap(offering.paywall) - let package = try XCTUnwrap(offering.monthly) - let event: PaywallEvent.Data = .init( - offering: offering, - paywall: paywall, + private var offering: Offering! + private var package: Package! + private var paywall: PaywallData! + private var eventData: PaywallEvent.Data! + + override func setUp() async throws { + try await super.setUp() + + self.offering = try await XCTAsyncUnwrap(try await self.currentOffering) + self.package = try XCTUnwrap(self.offering.monthly) + self.paywall = try XCTUnwrap(self.offering.paywall) + + self.eventData = .init( + offering: self.offering, + paywall: self.paywall, sessionID: .init(), displayMode: .fullScreen, locale: .current, darkMode: true ) + } - try await self.purchases.track(paywallEvent: .view(event)) + func testPurchasingPackageWithPresentedPaywall() async throws { + try await self.purchases.track(paywallEvent: .view(self.eventData)) let transaction = try await XCTAsyncUnwrap(try await self.purchases.purchase(package: package).transaction) + self.verifyTransactionHandled(with: transaction, sessionID: self.eventData.sessionIdentifier) + } + + func testPurchasingPackageAfterClearingPresentedPaywall() async throws { + try await self.purchases.track(paywallEvent: .view(self.eventData)) + try await self.purchases.track(paywallEvent: .close(self.eventData)) + + let transaction = try await XCTAsyncUnwrap(try await self.purchases.purchase(package: self.package).transaction) + + self.verifyTransactionHandled(with: transaction, sessionID: nil) + } + + func testPurchasingAfterAFailureRemembersPresentedPaywall() async throws { + self.testSession.failTransactionsEnabled = true + self.testSession.failureError = .unknown + + try await self.purchases.track(paywallEvent: .view(self.eventData)) + + do { + _ = try await self.purchases.purchase(package: self.package) + fail("Expected error") + } catch { + // Expected error + } + + self.logger.clearMessages() + + self.testSession.failTransactionsEnabled = false + let transaction = try await XCTAsyncUnwrap(try await self.purchases.purchase(package: self.package).transaction) + + self.verifyTransactionHandled(with: transaction, sessionID: self.eventData.sessionIdentifier) + } + + func testFlushingEmptyEvents() async throws { + let result = try await self.purchases.flushPaywallEvents(count: 1) + expect(result) == 0 + } + + func testFlushingEvents() async throws { + try await self.purchases.track(paywallEvent: .view(self.eventData)) + try await self.purchases.track(paywallEvent: .cancel(self.eventData)) + try await self.purchases.track(paywallEvent: .close(self.eventData)) + + let result = try await self.purchases.flushPaywallEvents(count: 3) + expect(result) == 3 + } + + func testFlushingEventsClearsThem() async throws { + try await self.purchases.track(paywallEvent: .view(self.eventData)) + try await self.purchases.track(paywallEvent: .cancel(self.eventData)) + try await self.purchases.track(paywallEvent: .close(self.eventData)) + + _ = try await self.purchases.flushPaywallEvents(count: 3) + let result = try await self.purchases.flushPaywallEvents(count: 10) + expect(result) == 0 + } + +} + +private extension PaywallEventsIntegrationTests { + + func verifyTransactionHandled( + with transaction: StoreTransaction, + sessionID: PaywallEvent.SessionID? + ) { self.logger.verifyMessageWasLogged( Strings.purchase.transaction_poster_handling_transaction( transactionID: transaction.transactionIdentifier, - productID: package.storeProduct.productIdentifier, + productID: self.package.storeProduct.productIdentifier, transactionDate: transaction.purchaseDate, - offeringID: package.offeringIdentifier, - paywallSessionID: event.sessionIdentifier + offeringID: self.package.offeringIdentifier, + paywallSessionID: sessionID ) ) } diff --git a/Tests/StoreKitUnitTests/PurchasesOrchestratorTests.swift b/Tests/StoreKitUnitTests/PurchasesOrchestratorTests.swift index 8635c8dd1b..5aa4abeceb 100644 --- a/Tests/StoreKitUnitTests/PurchasesOrchestratorTests.swift +++ b/Tests/StoreKitUnitTests/PurchasesOrchestratorTests.swift @@ -259,6 +259,58 @@ class PurchasesOrchestratorTests: StoreKitConfigTestCase { expect(self.backend.invokedPostReceiptDataParameters?.transactionData.source.initiationSource) == .purchase } + func testPurchaseSK1PackageWithPresentedPaywall() async throws { + self.customerInfoManager.stubbedCachedCustomerInfoResult = self.mockCustomerInfo + self.backend.stubbedPostReceiptResult = .success(self.mockCustomerInfo) + + let product = try await self.fetchSk1Product() + let payment = self.storeKit1Wrapper.payment(with: product) + + self.orchestrator.track(paywallEvent: .view(Self.paywallEvent)) + + _ = await withCheckedContinuation { continuation in + self.orchestrator.purchase( + sk1Product: product, + payment: payment, + package: nil, + wrapper: self.storeKit1Wrapper + ) { transaction, customerInfo, error, userCancelled in + continuation.resume(returning: (transaction, customerInfo, error, userCancelled)) + } + } + + expect(self.backend.invokedPostReceiptDataParameters?.transactionData.presentedPaywall) == Self.paywallEvent + } + + func testFailedSK1PurchaseRemembersPresentedPaywall() async throws { + func purchase() async throws { + let product = try await self.fetchSk1Product() + let payment = self.storeKit1Wrapper.payment(with: product) + + _ = await withCheckedContinuation { continuation in + self.orchestrator.purchase( + sk1Product: product, + payment: payment, + package: nil, + wrapper: self.storeKit1Wrapper + ) { transaction, customerInfo, error, userCancelled in + continuation.resume(returning: (transaction, customerInfo, error, userCancelled)) + } + } + } + + self.orchestrator.track(paywallEvent: .view(Self.paywallEvent)) + self.customerInfoManager.stubbedCachedCustomerInfoResult = self.mockCustomerInfo + + self.backend.stubbedPostReceiptResult = .failure(.unexpectedBackendResponse(.customerInfoNil)) + try await purchase() + + self.backend.stubbedPostReceiptResult = .success(self.mockCustomerInfo) + try await purchase() + + expect(self.backend.invokedPostReceiptDataParameters?.transactionData.presentedPaywall) == Self.paywallEvent + } + func testPurchaseSK1PackageDoesNotPostAdServicesTokenIfNotEnabled() async throws { self.customerInfoManager.stubbedCachedCustomerInfoResult = self.mockCustomerInfo self.backend.stubbedPostReceiptResult = .success(self.mockCustomerInfo) @@ -662,6 +714,56 @@ class PurchasesOrchestratorTests: StoreKitConfigTestCase { expect(self.backend.invokedPostReceiptDataParameters?.transactionData.source.initiationSource) == .purchase } + @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) + func testPurchaseSK2PackageWithPresentedPaywall() async throws { + try AvailabilityChecks.iOS15APIAvailableOrSkipTest() + + self.customerInfoManager.stubbedCachedCustomerInfoResult = self.mockCustomerInfo + self.backend.stubbedPostReceiptResult = .success(self.mockCustomerInfo) + self.orchestrator.track(paywallEvent: .view(Self.paywallEvent)) + + let mockListener = try XCTUnwrap( + self.orchestrator.storeKit2TransactionListener as? MockStoreKit2TransactionListener + ) + mockListener.mockTransaction = .init(try await self.simulateAnyPurchase()) + + let product = try await self.fetchSk2Product() + + _ = try await self.orchestrator.purchase(sk2Product: product, + package: nil, + promotionalOffer: nil) + + expect(self.backend.invokedPostReceiptDataParameters?.transactionData.presentedPaywall) == Self.paywallEvent + } + + @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) + func testFailedSK2PurchaseRemembersPresentedPaywall() async throws { + try AvailabilityChecks.iOS15APIAvailableOrSkipTest() + + let mockListener = try XCTUnwrap( + self.orchestrator.storeKit2TransactionListener as? MockStoreKit2TransactionListener + ) + mockListener.mockTransaction = .init(try await self.simulateAnyPurchase()) + + let product = try await self.fetchSk2Product() + + self.orchestrator.track(paywallEvent: .view(Self.paywallEvent)) + + self.customerInfoManager.stubbedCachedCustomerInfoResult = self.mockCustomerInfo + + self.backend.stubbedPostReceiptResult = .failure(.unexpectedBackendResponse(.customerInfoNil)) + _ = try? await self.orchestrator.purchase(sk2Product: product, + package: nil, + promotionalOffer: nil) + + self.backend.stubbedPostReceiptResult = .success(self.mockCustomerInfo) + _ = try await self.orchestrator.purchase(sk2Product: product, + package: nil, + promotionalOffer: nil) + + expect(self.backend.invokedPostReceiptDataParameters?.transactionData.presentedPaywall) == Self.paywallEvent + } + @available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) func testSK2PurchaseDoesNotAlwaysRefreshReceiptInProduction() async throws { try AvailabilityChecks.iOS15APIAvailableOrSkipTest() @@ -1344,4 +1446,14 @@ private extension PurchasesOrchestratorTests { localizedDescription: "Description" ).toStoreProduct() + static let paywallEvent: PaywallEvent.Data = .init( + offeringIdentifier: "offering", + paywallRevision: 5, + sessionID: .init(), + displayMode: .fullScreen, + localeIdentifier: "en_US", + darkMode: true, + date: .init(timeIntervalSince1970: 1694029328) + ) + } diff --git a/Tests/UnitTests/Mocks/MockPaywallEventsManager.swift b/Tests/UnitTests/Mocks/MockPaywallEventsManager.swift index a45ba0157f..35de67b5cb 100644 --- a/Tests/UnitTests/Mocks/MockPaywallEventsManager.swift +++ b/Tests/UnitTests/Mocks/MockPaywallEventsManager.swift @@ -26,9 +26,11 @@ actor MockPaywallEventsManager: PaywallEventsManagerType { var invokedFlushEvents = false var invokedFlushEventsCount = 0 - func flushEvents(count: Int) async { + func flushEvents(count: Int) async -> Int { self.invokedFlushEvents = true self.invokedFlushEventsCount += 1 + + return 0 } } diff --git a/Tests/UnitTests/Paywalls/Events/PaywallEventStoreTests.swift b/Tests/UnitTests/Paywalls/Events/PaywallEventStoreTests.swift index 744163eaf6..ae15cd8219 100644 --- a/Tests/UnitTests/Paywalls/Events/PaywallEventStoreTests.swift +++ b/Tests/UnitTests/Paywalls/Events/PaywallEventStoreTests.swift @@ -33,7 +33,7 @@ class PaywallEventStoreTests: TestCase { // - MARK: - func testCreateDefaultDoesNotThrow() throws { - _ = try PaywallEventStore.createDefault() + _ = try PaywallEventStore.createDefault(documentsDirectory: nil) } // - MARK: store and fetch diff --git a/Tests/UnitTests/Paywalls/Events/PaywallEventsManagerTests.swift b/Tests/UnitTests/Paywalls/Events/PaywallEventsManagerTests.swift index aa116a3ba9..a2d4277250 100644 --- a/Tests/UnitTests/Paywalls/Events/PaywallEventsManagerTests.swift +++ b/Tests/UnitTests/Paywalls/Events/PaywallEventsManagerTests.swift @@ -68,15 +68,17 @@ class PaywallEventsManagerTests: TestCase { // MARK: - flushEvents - func testFlushEmptyStore() async { - await self.manager.flushEvents(count: 1) + func testFlushEmptyStore() async throws { + let result = try await self.manager.flushEvents(count: 1) + expect(result) == 0 expect(self.api.invokedPostPaywallEvents) == false } - func testFlushOneEvent() async { + func testFlushOneEvent() async throws { let event = await self.storeRandomEvent() - await self.manager.flushEvents(count: 1) + let result = try await self.manager.flushEvents(count: 1) + expect(result) == 1 expect(self.api.invokedPostPaywallEvents) == true expect(self.api.invokedPostPaywallEventsParameters) == [[.init(event: event, userID: Self.userID)]] @@ -84,12 +86,15 @@ class PaywallEventsManagerTests: TestCase { await self.verifyEmptyStore() } - func testFlushTwice() async { + func testFlushTwice() async throws { let event1 = await self.storeRandomEvent() let event2 = await self.storeRandomEvent() - await self.manager.flushEvents(count: 1) - await self.manager.flushEvents(count: 1) + let result1 = try await self.manager.flushEvents(count: 1) + let result2 = try await self.manager.flushEvents(count: 1) + + expect(result1) == 1 + expect(result2) == 1 expect(self.api.invokedPostPaywallEvents) == true expect(self.api.invokedPostPaywallEventsParameters) == [ @@ -100,14 +105,15 @@ class PaywallEventsManagerTests: TestCase { await self.verifyEmptyStore() } - func testFlushOnlyOneEventPostsFirstOne() async { + func testFlushOnlyOneEventPostsFirstOne() async throws { let event = await self.storeRandomEvent() let storedEvent: PaywallStoredEvent = .init(event: event, userID: Self.userID) _ = await self.storeRandomEvent() _ = await self.storeRandomEvent() - await self.manager.flushEvents(count: 1) + let result = try await self.manager.flushEvents(count: 1) + expect(result) == 1 expect(self.api.invokedPostPaywallEvents) == true expect(self.api.invokedPostPaywallEventsParameters) == [[storedEvent]] @@ -117,13 +123,19 @@ class PaywallEventsManagerTests: TestCase { expect(events).toNot(contain(storedEvent)) } - func testFlushWithUnsuccessfulPostError() async { + func testFlushWithUnsuccessfulPostError() async throws { let event = await self.storeRandomEvent() let storedEvent: PaywallStoredEvent = .init(event: event, userID: Self.userID) self.api.stubbedPostPaywallEventsCompletionResult = .networkError(.offlineConnection()) - - await self.manager.flushEvents(count: 1) + do { + _ = try await self.manager.flushEvents(count: 1) + fail("Expected error") + } catch BackendError.networkError(.offlineConnection) { + // Expected + } catch { + throw error + } expect(self.api.invokedPostPaywallEvents) == true expect(self.api.invokedPostPaywallEventsParameters) == [[storedEvent]] @@ -131,20 +143,28 @@ class PaywallEventsManagerTests: TestCase { await self.verifyEvents([storedEvent]) } - func testFlushWithSuccessfullySyncedError() async { + func testFlushWithSuccessfullySyncedError() async throws { _ = await self.storeRandomEvent() self.api.stubbedPostPaywallEventsCompletionResult = .networkError( .errorResponse(.defaultResponse, .invalidRequest) ) - await self.manager.flushEvents(count: 1) + do { + _ = try await self.manager.flushEvents(count: 1) + fail("Expected error") + } catch BackendError.networkError(.errorResponse) { + // Expected + } catch { + throw error + } + expect(self.api.invokedPostPaywallEvents) == true await self.verifyEmptyStore() } - func testFlushWithSuccessfullySyncedErrorOnlyDeletesPostedEvents() async { + func testFlushWithSuccessfullySyncedErrorOnlyDeletesPostedEvents() async throws { let event1 = await self.storeRandomEvent() let event2 = await self.storeRandomEvent() @@ -152,7 +172,14 @@ class PaywallEventsManagerTests: TestCase { .errorResponse(.defaultResponse, .invalidRequest) ) - await self.manager.flushEvents(count: 1) + do { + _ = try await self.manager.flushEvents(count: 1) + fail("Expected error") + } catch BackendError.networkError(.errorResponse) { + // Expected + } catch { + throw error + } expect(self.api.invokedPostPaywallEvents) == true expect(self.api.invokedPostPaywallEventsParameters) == [[.init(event: event1, userID: Self.userID)]] From ee5bc06969db8b98d6f4021891b07542d0e5600c Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Tue, 12 Sep 2023 15:34:17 -0700 Subject: [PATCH 2/5] Fixed request format --- .../Events/Networking/PaywallEventsRequest.swift | 10 +++++----- ...OS15-testPostPaywallEventsWithMultipleEvents.1.json | 8 ++++---- .../iOS15-testPostPaywallEventsWithOneEvent.1.json | 4 ++-- ...OS16-testPostPaywallEventsWithMultipleEvents.1.json | 8 ++++---- .../iOS16-testPostPaywallEventsWithOneEvent.1.json | 4 ++-- .../PaywallEventsRequestTests/testCancelEvent.1.json | 4 ++-- .../PaywallEventsRequestTests/testCloseEvent.1.json | 4 ++-- .../PaywallEventsRequestTests/testViewEvent.1.json | 4 ++-- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Sources/Paywalls/Events/Networking/PaywallEventsRequest.swift b/Sources/Paywalls/Events/Networking/PaywallEventsRequest.swift index 478d035889..4be01d484e 100644 --- a/Sources/Paywalls/Events/Networking/PaywallEventsRequest.swift +++ b/Sources/Paywalls/Events/Networking/PaywallEventsRequest.swift @@ -33,9 +33,9 @@ extension PaywallEventsRequest { enum EventType: String { - case view - case cancel - case close + case impression = "paywall_impression" + case cancel = "paywall_cancel" + case close = "paywall_close" } @@ -85,7 +85,7 @@ private extension PaywallEvent { var eventType: PaywallEventsRequest.EventType { switch self { - case .view: return .view + case .view: return .impression case .cancel: return .cancel case .close: return .close } @@ -111,7 +111,7 @@ extension PaywallEventsRequest.Event: Encodable { case timestamp case displayMode case darkMode - case localeIdentifier + case localeIdentifier = "locale" } diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json index 4cb64d6ccc..67af901283 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json @@ -9,24 +9,24 @@ "app_user_id" : "user", "dark_mode" : true, "display_mode" : "condensed_footer", - "locale_identifier" : "es_ES", + "locale" : "es_ES", "offering_id" : "offering_1", "paywall_revision" : 5, "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", "timestamp" : 715722128, - "type" : "view", + "type" : "paywall_impression", "version" : 1 }, { "app_user_id" : "user", "dark_mode" : false, "display_mode" : "full_screen", - "locale_identifier" : "en_US", + "locale" : "en_US", "offering_id" : "offering_2", "paywall_revision" : 3, "session_id" : "10CC0F1D-7665-4093-9624-1D7308FFF4DB", "timestamp" : 715715121, - "type" : "close", + "type" : "paywall_close", "version" : 1 } ] diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json index 2f889bf93f..6c418bc7ce 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json @@ -9,12 +9,12 @@ "app_user_id" : "user", "dark_mode" : true, "display_mode" : "condensed_footer", - "locale_identifier" : "es_ES", + "locale" : "es_ES", "offering_id" : "offering_1", "paywall_revision" : 5, "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", "timestamp" : 715722128, - "type" : "view", + "type" : "paywall_impression", "version" : 1 } ] diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json index 4cb64d6ccc..67af901283 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json @@ -9,24 +9,24 @@ "app_user_id" : "user", "dark_mode" : true, "display_mode" : "condensed_footer", - "locale_identifier" : "es_ES", + "locale" : "es_ES", "offering_id" : "offering_1", "paywall_revision" : 5, "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", "timestamp" : 715722128, - "type" : "view", + "type" : "paywall_impression", "version" : 1 }, { "app_user_id" : "user", "dark_mode" : false, "display_mode" : "full_screen", - "locale_identifier" : "en_US", + "locale" : "en_US", "offering_id" : "offering_2", "paywall_revision" : 3, "session_id" : "10CC0F1D-7665-4093-9624-1D7308FFF4DB", "timestamp" : 715715121, - "type" : "close", + "type" : "paywall_close", "version" : 1 } ] diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json index 2f889bf93f..6c418bc7ce 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json @@ -9,12 +9,12 @@ "app_user_id" : "user", "dark_mode" : true, "display_mode" : "condensed_footer", - "locale_identifier" : "es_ES", + "locale" : "es_ES", "offering_id" : "offering_1", "paywall_revision" : 5, "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", "timestamp" : 715722128, - "type" : "view", + "type" : "paywall_impression", "version" : 1 } ] diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json index 48fcc6dc8a..9b232334a5 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json @@ -2,11 +2,11 @@ "app_user_id" : "Jack Shepard", "dark_mode" : true, "display_mode" : "condensed_footer", - "locale_identifier" : "es_ES", + "locale" : "es_ES", "offering_id" : "offering", "paywall_revision" : 5, "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", "timestamp" : 715722128, - "type" : "cancel", + "type" : "paywall_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 index 560c54c576..e98555c7e5 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCloseEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCloseEvent.1.json @@ -2,11 +2,11 @@ "app_user_id" : "Jack Shepard", "dark_mode" : true, "display_mode" : "condensed_footer", - "locale_identifier" : "es_ES", + "locale" : "es_ES", "offering_id" : "offering", "paywall_revision" : 5, "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", "timestamp" : 715722128, - "type" : "close", + "type" : "paywall_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 index 60bbe0cbe2..982d47f079 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testViewEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testViewEvent.1.json @@ -2,11 +2,11 @@ "app_user_id" : "Jack Shepard", "dark_mode" : true, "display_mode" : "condensed_footer", - "locale_identifier" : "es_ES", + "locale" : "es_ES", "offering_id" : "offering", "paywall_revision" : 5, "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", "timestamp" : 715722128, - "type" : "view", + "type" : "paywall_impression", "version" : 1 } \ No newline at end of file From 2644c52fdee2fd4855364244562fc64a58e37b64 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Tue, 12 Sep 2023 15:36:47 -0700 Subject: [PATCH 3/5] `Paywalls`: send event timestamps in ms (#3191) Requested by the backend. --- Sources/Paywalls/Events/Networking/PaywallEventsRequest.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Paywalls/Events/Networking/PaywallEventsRequest.swift b/Sources/Paywalls/Events/Networking/PaywallEventsRequest.swift index 4be01d484e..ec60f77e2a 100644 --- a/Sources/Paywalls/Events/Networking/PaywallEventsRequest.swift +++ b/Sources/Paywalls/Events/Networking/PaywallEventsRequest.swift @@ -47,7 +47,7 @@ extension PaywallEventsRequest { var sessionID: String var offeringID: String var paywallRevision: Int - var timestamp: Date + var timestamp: UInt64 var displayMode: PaywallViewMode var darkMode: Bool var localeIdentifier: String @@ -69,7 +69,7 @@ extension PaywallEventsRequest.Event { sessionID: data.sessionIdentifier.uuidString, offeringID: data.offeringIdentifier, paywallRevision: data.paywallRevision, - timestamp: data.date, + timestamp: data.date.millisecondsSince1970, displayMode: data.displayMode, darkMode: data.darkMode, localeIdentifier: data.localeIdentifier From 46cc777940aa9bee1936f05ce59daff02c846ea8 Mon Sep 17 00:00:00 2001 From: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com> Date: Wed, 13 Sep 2023 00:58:00 +0200 Subject: [PATCH 4/5] Generating new test snapshots for `paywalls-events-tests` - ios-16 (#3199) Requested by @NachoSoto for [paywalls-events-tests](https://github.com/RevenueCat/purchases-ios/tree/paywalls-events-tests) Co-authored-by: Distiller --- .../iOS16-testPostPaywallEventsWithMultipleEvents.1.json | 4 ++-- .../iOS16-testPostPaywallEventsWithOneEvent.1.json | 2 +- .../PaywallEventsRequestTests/testCancelEvent.1.json | 2 +- .../PaywallEventsRequestTests/testCloseEvent.1.json | 2 +- .../PaywallEventsRequestTests/testViewEvent.1.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json index 67af901283..69649975b2 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json @@ -13,7 +13,7 @@ "offering_id" : "offering_1", "paywall_revision" : 5, "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", - "timestamp" : 715722128, + "timestamp" : 1694029328000, "type" : "paywall_impression", "version" : 1 }, @@ -25,7 +25,7 @@ "offering_id" : "offering_2", "paywall_revision" : 3, "session_id" : "10CC0F1D-7665-4093-9624-1D7308FFF4DB", - "timestamp" : 715715121, + "timestamp" : 1694022321000, "type" : "paywall_close", "version" : 1 } diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json index 6c418bc7ce..582fba440d 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json @@ -13,7 +13,7 @@ "offering_id" : "offering_1", "paywall_revision" : 5, "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", - "timestamp" : 715722128, + "timestamp" : 1694029328000, "type" : "paywall_impression", "version" : 1 } diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json index 9b232334a5..bb26f70c6f 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json @@ -6,7 +6,7 @@ "offering_id" : "offering", "paywall_revision" : 5, "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", - "timestamp" : 715722128, + "timestamp" : 1694029328000, "type" : "paywall_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 index e98555c7e5..393e188158 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCloseEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCloseEvent.1.json @@ -6,7 +6,7 @@ "offering_id" : "offering", "paywall_revision" : 5, "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", - "timestamp" : 715722128, + "timestamp" : 1694029328000, "type" : "paywall_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 index 982d47f079..275dc18d9b 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testViewEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testViewEvent.1.json @@ -6,7 +6,7 @@ "offering_id" : "offering", "paywall_revision" : 5, "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", - "timestamp" : 715722128, + "timestamp" : 1694029328000, "type" : "paywall_impression", "version" : 1 } \ No newline at end of file From 1aaf48ee8dd6797e845298a99a35440dca1d202a Mon Sep 17 00:00:00 2001 From: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com> Date: Wed, 13 Sep 2023 00:58:10 +0200 Subject: [PATCH 5/5] Generating new test snapshots for `paywalls-events-tests` - ios-15 (#3198) Requested by @NachoSoto for [paywalls-events-tests](https://github.com/RevenueCat/purchases-ios/tree/paywalls-events-tests) Co-authored-by: Distiller --- .../iOS15-testPostPaywallEventsWithMultipleEvents.1.json | 4 ++-- .../iOS15-testPostPaywallEventsWithOneEvent.1.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json index 67af901283..69649975b2 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json @@ -13,7 +13,7 @@ "offering_id" : "offering_1", "paywall_revision" : 5, "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", - "timestamp" : 715722128, + "timestamp" : 1694029328000, "type" : "paywall_impression", "version" : 1 }, @@ -25,7 +25,7 @@ "offering_id" : "offering_2", "paywall_revision" : 3, "session_id" : "10CC0F1D-7665-4093-9624-1D7308FFF4DB", - "timestamp" : 715715121, + "timestamp" : 1694022321000, "type" : "paywall_close", "version" : 1 } diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json index 6c418bc7ce..582fba440d 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json @@ -13,7 +13,7 @@ "offering_id" : "offering_1", "paywall_revision" : 5, "session_id" : "98CC0F1D-7665-4093-9624-1D7308FFF4DB", - "timestamp" : 715722128, + "timestamp" : 1694029328000, "type" : "paywall_impression", "version" : 1 }