mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-10 22:45:55 +05:45
* Attribute checkpoint offering purchases to the hit trace id Put the hit's trace id on the matched offering's presented context and send it as presented_trace_id on receipts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Require traceID in withCheckpointIdentifier Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Use the checkpoint trace id for the offering's workflow Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Carry the hit trace id on matchedOffering Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Require traceID in withPresentedCheckpointIdentifier and tighten trace id tests Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Create the matched offering and its trace id in one place Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
157 lines
5.3 KiB
Swift
157 lines
5.3 KiB
Swift
//
|
|
// 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
|
|
//
|
|
// CheckpointEventDataTests.swift
|
|
|
|
import Foundation
|
|
import Nimble
|
|
import XCTest
|
|
|
|
@_spi(Internal) @testable import RevenueCat
|
|
|
|
class CheckpointEventDataTests: TestCase {
|
|
|
|
private let date = Date(timeIntervalSince1970: 1_699_270_688.995)
|
|
|
|
func testMatchedWorkflowReportsPresentUIWithTheWorkflow() throws {
|
|
let workflow = Self.workflow(id: "wf_123")
|
|
let resolved = ResolvedCheckpoint(
|
|
.matchedWorkflow(.init(workflow: workflow,
|
|
uiConfig: .empty,
|
|
offerings: Self.offerings)),
|
|
checkpointRuleID: "rule_123"
|
|
)
|
|
|
|
let data = CheckpointEvent.Data(identifier: "onboarding_complete", date: self.date, resolved: resolved)
|
|
|
|
expect(data.result) == .presentUI
|
|
expect(data.workflowID) == "wf_123"
|
|
expect(data.checkpointRuleID) == "rule_123"
|
|
expect(data.offeringID).to(beNil())
|
|
}
|
|
|
|
/// The workflow's step events and receipt carry its trace id, so the hit has to report the same one.
|
|
func testMatchedWorkflowReportsTheWorkflowTraceId() throws {
|
|
let matched = ResolvedCheckpointWorkflow(workflow: Self.workflow(id: "wf_123"),
|
|
uiConfig: .empty,
|
|
offerings: Self.offerings)
|
|
|
|
let data = CheckpointEvent.Data(identifier: "onboarding_complete",
|
|
date: self.date,
|
|
resolved: .init(.matchedWorkflow(matched)))
|
|
|
|
expect(data.traceID) == matched.traceId
|
|
}
|
|
|
|
func testEveryResultReportsATraceId() throws {
|
|
let resolutions: [CheckpointResolution] = [
|
|
.matchedOffering(Self.offering, traceID: "trace_id"),
|
|
.noAction(.noMatch),
|
|
.noAction(.configurationUnavailable),
|
|
.noAction(.unknownCheckpoint)
|
|
]
|
|
|
|
for resolution in resolutions {
|
|
let data = CheckpointEvent.Data(identifier: "onboarding_complete",
|
|
date: self.date,
|
|
resolved: .init(resolution))
|
|
|
|
expect(data.traceID).toNot(beNil())
|
|
}
|
|
}
|
|
|
|
func testEachEvaluationGetsItsOwnTraceId() throws {
|
|
let first = ResolvedCheckpoint(.noAction(.noMatch))
|
|
let second = ResolvedCheckpoint(.noAction(.noMatch))
|
|
|
|
expect(first.traceID) != second.traceID
|
|
}
|
|
|
|
func testMatchedOfferingReportsReturnDataWithTheOffering() throws {
|
|
let resolved = ResolvedCheckpoint(
|
|
.matchedOffering(Self.offering, traceID: "trace_id"),
|
|
checkpointRuleID: "rule_123"
|
|
)
|
|
|
|
let data = CheckpointEvent.Data(identifier: "onboarding_complete", date: self.date, resolved: resolved)
|
|
|
|
expect(data.result) == .returnData
|
|
expect(data.offeringID) == "onboarding"
|
|
expect(data.checkpointRuleID) == "rule_123"
|
|
expect(data.workflowID).to(beNil())
|
|
}
|
|
|
|
func testEachNoActionReasonMapsToItsResult() throws {
|
|
let expected: [CheckpointResolutionReason: CheckpointHitResult] = [
|
|
.noMatch: .noMatch,
|
|
.configurationUnavailable: .configurationUnavailable,
|
|
.unknownCheckpoint: .unknownCheckpoint
|
|
]
|
|
|
|
for (reason, result) in expected {
|
|
let data = CheckpointEvent.Data(identifier: "onboarding_complete",
|
|
date: self.date,
|
|
resolved: .init(.noAction(reason)))
|
|
|
|
expect(data.result) == result
|
|
expect(data.workflowID).to(beNil())
|
|
expect(data.offeringID).to(beNil())
|
|
expect(data.checkpointRuleID).to(beNil())
|
|
}
|
|
}
|
|
|
|
func testEveryHitIsCustom() throws {
|
|
let data = CheckpointEvent.Data(identifier: "onboarding_complete",
|
|
date: self.date,
|
|
resolved: .init(.noAction(.noMatch)))
|
|
|
|
expect(data.checkpointType) == .custom
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
private static let offering = Offering(
|
|
identifier: "onboarding",
|
|
serverDescription: "Onboarding offering",
|
|
availablePackages: [],
|
|
webCheckoutUrl: nil
|
|
)
|
|
|
|
private static var offerings: Offerings {
|
|
let response = OfferingsResponse(
|
|
currentOfferingId: nil,
|
|
offerings: [],
|
|
placements: nil,
|
|
targeting: nil,
|
|
uiConfig: nil
|
|
)
|
|
|
|
return Offerings(
|
|
offerings: [Self.offering.identifier: Self.offering],
|
|
currentOfferingID: nil,
|
|
placements: nil,
|
|
targeting: nil,
|
|
contents: Offerings.Contents(response: response, httpResponseOriginalSource: .mainServer),
|
|
loadedFromDiskCache: false
|
|
)
|
|
}
|
|
|
|
private static func workflow(id: String) -> PublishedWorkflow {
|
|
return PublishedWorkflow(
|
|
id: id,
|
|
displayName: "Test",
|
|
initialStepId: "step_1",
|
|
singleStepFallbackId: nil,
|
|
steps: [:],
|
|
screens: [:]
|
|
)
|
|
}
|
|
|
|
}
|