67 lines
2.6 KiB
Swift
67 lines
2.6 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import IMessageSync
|
|
|
|
/// Covers the two pure/bounded primitives introduced by the send-pipeline
|
|
/// async-hardening refactor: the iMessage-vs-SMS routing decision, and the
|
|
/// `bounded` timeout wrapper that guarantees no blocking operation can stall
|
|
/// the send-queue poller.
|
|
@Suite("SendService routing & bounded primitive")
|
|
struct SendServiceRoutingTests {
|
|
|
|
// MARK: - decideService (pure routing logic)
|
|
|
|
@Test("SMS-only history routes to SMS")
|
|
func decideServiceSMSOnly() {
|
|
#expect(SendService.decideService(imessageCount: 0, smsCount: 5) == .sms)
|
|
#expect(SendService.decideService(imessageCount: 0, smsCount: 1) == .sms)
|
|
}
|
|
|
|
@Test("any iMessage history routes to iMessage")
|
|
func decideServiceHasIMessage() {
|
|
#expect(SendService.decideService(imessageCount: 3, smsCount: 0) == .iMessage)
|
|
// iMessage history wins even when SMS messages also exist.
|
|
#expect(SendService.decideService(imessageCount: 1, smsCount: 20) == .iMessage)
|
|
}
|
|
|
|
@Test("a brand-new number with no history defaults to iMessage")
|
|
func decideServiceNoHistory() {
|
|
#expect(SendService.decideService(imessageCount: 0, smsCount: 0) == .iMessage)
|
|
}
|
|
|
|
// MARK: - bounded (timeout wrapper)
|
|
|
|
@Test("bounded returns the work's value when it finishes within the deadline")
|
|
func boundedReturnsValue() async {
|
|
let result = await SendService.bounded(5, fallback: "fallback") { "real" }
|
|
#expect(result == "real")
|
|
}
|
|
|
|
@Test("bounded returns the fallback when the work exceeds the deadline")
|
|
func boundedTimesOut() async {
|
|
let start = Date()
|
|
let result = await SendService.bounded(0.2, fallback: "fallback") {
|
|
Thread.sleep(forTimeInterval: 3) // deliberately exceeds the 0.2s cap
|
|
return "real"
|
|
}
|
|
let elapsed = Date().timeIntervalSince(start)
|
|
#expect(result == "fallback")
|
|
// The caller is released at the deadline — it does NOT wait out the
|
|
// 3s of blocking work.
|
|
#expect(elapsed < 2.0)
|
|
}
|
|
|
|
@Test("bounded resumes exactly once even when work and timeout race")
|
|
func boundedRacesCleanly() async {
|
|
// Work finishes right around the deadline — the ResumeOnce guard must
|
|
// prevent a double-resume crash regardless of which path wins.
|
|
for _ in 0..<20 {
|
|
_ = await SendService.bounded(0.05, fallback: -1) {
|
|
Thread.sleep(forTimeInterval: 0.05)
|
|
return 1
|
|
}
|
|
}
|
|
// Reaching here without a crash is the assertion.
|
|
#expect(Bool(true))
|
|
}
|
|
}
|