Files
conjure/Tests/ConjureKitTests/CurrencyCalculationTests.swift
2026-10-16 15:05:33 +02:00

75 lines
3.2 KiB
Swift

import ConjureKit
import Foundation
import Testing
@MainActor
private final class RateRequests {
var pending: [CheckedContinuation<CurrencyRates, any Error>] = []
func fetch() async throws -> CurrencyRates {
try await withCheckedThrowingContinuation { pending.append($0) }
}
}
@MainActor
private func waitForRates(_ condition: () -> Bool) async throws {
// Scheduling deadline only; calculation freshness uses the injected clock.
for _ in 0..<1_000 {
if condition() { return }
try await Task.sleep(for: .milliseconds(1))
}
try #require(condition(), "Currency calculation did not reach the expected state")
}
@Test @MainActor
func currencyLifetimeSharesRefreshAndPreservesStaleCopyUntilFailure() async throws {
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(secondsFromGMT: 0)!
var now = calendar.date(from: DateComponents(year: 2026, month: 1, day: 2, hour: 12))!
let requests = RateRequests()
let calculation = CurrencyCalculation(now: { now }, calendar: calendar, fetch: { try await requests.fetch() })
var completions = 0
calculation.changed = { completions += 1 }
let snapshot = CurrencyRates(date: "2026-01-02", rates: ["eur": 1, "usd": 2, "dkk": 8])
calculation.update("100 USD to DKK")
#expect(calculation.isCurrency)
#expect(calculation.result?.copyValue == nil)
try await waitForRates { requests.pending.count == 1 }
calculation.update("50 USD to DKK")
requests.pending[0].resume(returning: snapshot)
try await waitForRates { completions == 1 }
#expect(calculation.result?.copyValue == "200 DKK")
calculation.update("100 USD to DKK")
#expect(calculation.result?.copyValue == "400 DKK")
#expect(requests.pending.count == 1)
now = calendar.date(byAdding: .day, value: 1, to: now)!
calculation.update("100 USD to DKK")
#expect(calculation.result?.copyValue == "400 DKK")
#expect(calculation.result?.detail.contains("Stale cache; refreshing") == true)
try await waitForRates { requests.pending.count == 2 }
calculation.update("50 USD to DKK")
requests.pending[1].resume(throwing: URLError(.notConnectedToInternet))
try await waitForRates { completions == 2 }
#expect(calculation.result?.copyValue == nil)
#expect(calculation.result?.displayValue == "Currency rates unavailable")
calculation.update("50 USD to DKK")
#expect(calculation.result?.copyValue == nil)
#expect(requests.pending.count == 2)
calculation.update("25 USD to DKK")
try await waitForRates { requests.pending.count == 3 }
calculation.update("Safari") // Leaving currency does not cancel shared work.
#expect(!calculation.isCurrency)
#expect(calculation.result == nil)
requests.pending[2].resume(returning: snapshot)
try await waitForRates { completions == 3 }
#expect(calculation.result == nil)
calculation.update("25 USD to DKK")
#expect(calculation.result?.copyValue == "100 DKK")
#expect(requests.pending.count == 3) // Fetch day, not snapshot metadata, controls freshness.
calculation.update("1 km to mi")
#expect(!calculation.isCurrency)
#expect(calculation.result?.copyValue == "0.621371192237 mi")
}