299 lines
9.2 KiB
Swift
299 lines
9.2 KiB
Swift
// AccountCenterUITestHarness.swift
|
|
// OSGKeyboard · Main App
|
|
//
|
|
// Deterministic account and StoreKit UI integration harness. It exercises the
|
|
// production coordinator and views while keeping Apple and backend services
|
|
// outside XCUITest.
|
|
|
|
#if DEBUG
|
|
import OSGKeyboardShared
|
|
import SwiftUI
|
|
|
|
@MainActor
|
|
struct AccountCenterUITestHarness: View {
|
|
@StateObject private var coordinator: AccountSessionCoordinator
|
|
private let startsSignIn: Bool
|
|
|
|
init() {
|
|
let startsSignIn = ProcessInfo.processInfo.arguments.contains(
|
|
"--account-signin-loading"
|
|
)
|
|
let service = AccountCenterUITestService(
|
|
shouldFailSnapshot: ProcessInfo.processInfo.arguments.contains(
|
|
"--account-snapshot-failure"
|
|
),
|
|
startsSignedOut: startsSignIn,
|
|
signInDelay: startsSignIn ? .seconds(5) : .zero
|
|
)
|
|
self.startsSignIn = startsSignIn
|
|
let store = AccountCenterUITestCreditStore(accountID: service.account.accountID)
|
|
_coordinator = StateObject(
|
|
wrappedValue: AccountSessionCoordinator(
|
|
dependencies: AccountDependencies(
|
|
sessionService: service,
|
|
centerService: service,
|
|
referralService: service
|
|
),
|
|
creditStore: store
|
|
)
|
|
)
|
|
}
|
|
|
|
var body: some View {
|
|
ThemedRoot {
|
|
NavigationStack {
|
|
AccountCenterView()
|
|
.environmentObject(coordinator)
|
|
}
|
|
}
|
|
.task {
|
|
await coordinator.restoreIfNeeded()
|
|
if startsSignIn {
|
|
await coordinator.signIn(
|
|
with: AppleAuthorizationPayload(
|
|
identityToken: "ui-test-identity",
|
|
authorizationCode: "ui-test-authorization",
|
|
nonce: "ui-test-nonce"
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
struct ManagedCloudConsentUITestHarness: View {
|
|
@ObservedObject private var config = ProviderConfig.shared
|
|
@StateObject private var coordinator: AccountSessionCoordinator
|
|
|
|
init() {
|
|
let service = AccountCenterUITestService()
|
|
let configuration = ProviderConfig.shared
|
|
configuration.hasAcknowledgedCloudSharing = false
|
|
configuration.credentialSource = .byok
|
|
_coordinator = StateObject(
|
|
wrappedValue: AccountSessionCoordinator(
|
|
dependencies: AccountDependencies(
|
|
sessionService: service,
|
|
centerService: service,
|
|
referralService: service
|
|
),
|
|
creditStore: AccountCenterUITestCreditStore(
|
|
accountID: service.account.accountID
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
var body: some View {
|
|
ThemedRoot {
|
|
NavigationStack {
|
|
ScrollView {
|
|
EnginePickerSection(config: config)
|
|
.environmentObject(coordinator)
|
|
.padding()
|
|
}
|
|
.navigationTitle("settings.aiService.title")
|
|
}
|
|
}
|
|
.task {
|
|
await coordinator.restoreIfNeeded()
|
|
}
|
|
}
|
|
}
|
|
|
|
private actor AccountCenterUITestService:
|
|
AccountSessionServicing,
|
|
AccountCenterServicing,
|
|
ReferralProfileServicing {
|
|
nonisolated let account = AccountSession(
|
|
accountID: UUID(uuidString: "00000000-0000-0000-0000-000000000200")!,
|
|
createdAtEpochSeconds: 1_700_000_000,
|
|
displayName: "UI Test Account"
|
|
)
|
|
private let shouldFailSnapshot: Bool
|
|
private let startsSignedOut: Bool
|
|
private let signInDelay: Duration
|
|
|
|
init(
|
|
shouldFailSnapshot: Bool = false,
|
|
startsSignedOut: Bool = false,
|
|
signInDelay: Duration = .zero
|
|
) {
|
|
self.shouldFailSnapshot = shouldFailSnapshot
|
|
self.startsSignedOut = startsSignedOut
|
|
self.signInDelay = signInDelay
|
|
}
|
|
|
|
func restoreSession() async throws -> AccountSession? {
|
|
startsSignedOut ? nil : account
|
|
}
|
|
|
|
func signIn(with payload: AppleAuthorizationPayload) async throws -> AccountSession {
|
|
_ = payload
|
|
if signInDelay > .zero {
|
|
try await Task.sleep(for: signInDelay)
|
|
}
|
|
return account
|
|
}
|
|
|
|
func signOut() async throws {}
|
|
|
|
func deleteAccount(with payload: AppleAuthorizationPayload) async throws {
|
|
_ = payload
|
|
}
|
|
|
|
func loadAccountCenter() async throws -> AccountCenterSnapshot {
|
|
if shouldFailSnapshot {
|
|
throw AccountCenterUITestError.snapshotUnavailable
|
|
}
|
|
return AccountCenterSnapshot(
|
|
account: account,
|
|
credits: AccountCreditSummary(balance: 1_500, usedCredits: 500),
|
|
referrals: []
|
|
)
|
|
}
|
|
|
|
func updateDisplayName(_ displayName: String) async throws -> AccountSession {
|
|
AccountSession(
|
|
accountID: account.accountID,
|
|
createdAtEpochSeconds: account.createdAtEpochSeconds,
|
|
displayName: displayName
|
|
)
|
|
}
|
|
|
|
func redeemReferral(code: String) async throws {
|
|
_ = code
|
|
}
|
|
|
|
func loadReferralProfile() async throws -> ReferralProfile {
|
|
ReferralProfile(
|
|
code: ReferralCode(
|
|
code: "UITestInvite_123456789",
|
|
inviteURL: URL(string: "https://osglab.com/i/UITestInvite_123456789")!,
|
|
campaignID: nil,
|
|
createdAt: Date(timeIntervalSince1970: 1_700_000_000)
|
|
),
|
|
binding: nil
|
|
)
|
|
}
|
|
|
|
func loadCreditProducts() async throws -> [AccountCreditProduct] {
|
|
[
|
|
AccountCreditProduct(productID: "500tks", credits: 500),
|
|
AccountCreditProduct(productID: "1500tks", credits: 1_500),
|
|
AccountCreditProduct(productID: "3000tks", credits: 3_000)
|
|
]
|
|
}
|
|
|
|
func submitCreditTransaction(_ signedTransaction: String) async throws -> AccountCreditPurchase {
|
|
_ = signedTransaction
|
|
return AccountCreditPurchase(
|
|
transactionID: "2000000000200",
|
|
productID: "500tks",
|
|
creditsGranted: 500,
|
|
balanceAfter: 2_000,
|
|
replayed: false
|
|
)
|
|
}
|
|
|
|
func loadCreditPurchaseHistory(
|
|
limit: Int,
|
|
cursor: String?
|
|
) async throws -> AccountCreditPurchaseHistoryPage {
|
|
_ = limit
|
|
_ = cursor
|
|
return AccountCreditPurchaseHistoryPage(
|
|
items: [
|
|
AccountCreditPurchaseRecord(
|
|
transactionID: "2000000000199",
|
|
productID: "1500tks",
|
|
creditsGranted: 1_500,
|
|
balanceAfter: 1_500,
|
|
purchasedAt: Date(timeIntervalSince1970: 1_700_000_000),
|
|
status: .credited
|
|
)
|
|
],
|
|
nextCursor: nil
|
|
)
|
|
}
|
|
}
|
|
|
|
private enum AccountCenterUITestError: Error {
|
|
case snapshotUnavailable
|
|
}
|
|
|
|
@MainActor
|
|
private final class AccountCenterUITestCreditStore: AccountCreditStore {
|
|
private let accountID: UUID
|
|
private var transactions: [AccountStoreVerification]
|
|
|
|
init(accountID: UUID) {
|
|
self.accountID = accountID
|
|
transactions = [
|
|
.verified(
|
|
AccountStoreTransaction(
|
|
id: 2_000_000_000_199,
|
|
productID: "1500tks",
|
|
appAccountToken: accountID,
|
|
purchaseDate: Date(timeIntervalSince1970: 1_700_000_000),
|
|
signedTransaction: String(repeating: "h", count: 100),
|
|
finishOperation: {}
|
|
)
|
|
)
|
|
]
|
|
}
|
|
|
|
func product(for productID: String) async throws -> AccountStoreProduct? {
|
|
let price: String
|
|
switch productID {
|
|
case "500tks":
|
|
price = "$0.99"
|
|
case "1500tks":
|
|
price = "$1.99"
|
|
case "3000tks":
|
|
price = "$2.99"
|
|
default:
|
|
return nil
|
|
}
|
|
return AccountStoreProduct(id: productID, displayPrice: price)
|
|
}
|
|
|
|
func purchase(
|
|
productID: String,
|
|
accountID: UUID
|
|
) async throws -> AccountStorePurchaseOutcome {
|
|
guard productID == "500tks", accountID == self.accountID else {
|
|
return .success(.unverified)
|
|
}
|
|
let transaction = AccountStoreTransaction(
|
|
id: 2_000_000_000_200,
|
|
productID: productID,
|
|
appAccountToken: accountID,
|
|
purchaseDate: Date(),
|
|
signedTransaction: String(repeating: "p", count: 100),
|
|
finishOperation: {}
|
|
)
|
|
let verification = AccountStoreVerification.verified(transaction)
|
|
transactions.append(verification)
|
|
return .success(verification)
|
|
}
|
|
|
|
func unfinishedTransactions() -> AsyncStream<AccountStoreVerification> {
|
|
AsyncStream { $0.finish() }
|
|
}
|
|
|
|
func transactionUpdates() -> AsyncStream<AccountStoreVerification> {
|
|
AsyncStream { $0.finish() }
|
|
}
|
|
|
|
func allTransactions() -> AsyncStream<AccountStoreVerification> {
|
|
let values = transactions
|
|
return AsyncStream { continuation in
|
|
values.forEach { continuation.yield($0) }
|
|
continuation.finish()
|
|
}
|
|
}
|
|
}
|
|
#endif
|