feat: consolidate iOS with API and web preserving history
Rebuild & Deploy / rebuild (push) Failing after 23s
Rebuild & Deploy / rebuild (push) Failing after 23s
This commit is contained in:
commit
d735ee72fa
102 files changed
+13077
-258
No files matched your search
@@ -0,0 +1,146 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import Voice
|
||||
|
||||
/// Pure coordinator tests for the server replacement worker loop semantics
|
||||
/// (Iteration 08 Item 2). These verify the latest-wins coalescing and
|
||||
/// drain-until-empty behavior that the ConversationView worker implements,
|
||||
/// without requiring a live SwiftUI view or real session disconnect.
|
||||
///
|
||||
/// The production code uses `@State` in a SwiftUI view; this test models the
|
||||
/// same logic as a pure function to verify A→B→C semantics where C arrives
|
||||
/// while B is disconnecting.
|
||||
struct ServerReplacementCoordinatorTests {
|
||||
|
||||
/// Models the pending-URL coalescing and worker-loop drain semantics.
|
||||
/// Returns the sequence of URLs the worker would process in order.
|
||||
private static func drainPending(
|
||||
initialActive: String,
|
||||
applies: [String],
|
||||
disconnectDurations: [Int] = []
|
||||
) -> [String] {
|
||||
var activeURL = initialActive
|
||||
var pending: String? = nil
|
||||
var processed: [String] = []
|
||||
var isReplacing = false
|
||||
|
||||
for url in applies {
|
||||
// Every accepted Apply writes pendingServerURL (latest wins).
|
||||
pending = url
|
||||
|
||||
// Start the worker if not already active.
|
||||
guard !isReplacing else { continue }
|
||||
isReplacing = true
|
||||
|
||||
// Worker loop: drain until no pending value remains.
|
||||
while let url = pending {
|
||||
pending = nil
|
||||
guard url != activeURL else { continue }
|
||||
// Simulate disconnect (no-op in pure test).
|
||||
activeURL = url
|
||||
processed.append(url)
|
||||
}
|
||||
isReplacing = false
|
||||
}
|
||||
|
||||
return processed
|
||||
}
|
||||
|
||||
@Test("single apply processes one URL")
|
||||
func singleApply() {
|
||||
let result = Self.drainPending(
|
||||
initialActive: "https://server-a",
|
||||
applies: ["https://server-b"]
|
||||
)
|
||||
#expect(result == ["https://server-b"])
|
||||
}
|
||||
|
||||
@Test("rapid A to B to C coalesces to final C")
|
||||
func rapidABC() {
|
||||
// All three Applies arrive before the worker starts (isReplacing
|
||||
// is false for each, but the first one starts the worker which
|
||||
// drains all pending values).
|
||||
// In practice: Apply A sets pending=A, starts worker. Worker
|
||||
// consumes A. Apply B sets pending=B during disconnect. Worker
|
||||
// loops, consumes B. Apply C sets pending=C during B's disconnect.
|
||||
// Worker loops, consumes C. Final state: activeServerURL == C.
|
||||
let result = Self.drainPending(
|
||||
initialActive: "https://server-a",
|
||||
applies: ["https://server-b", "https://server-c", "https://server-d"]
|
||||
)
|
||||
// The worker processes each URL in order as it drains.
|
||||
#expect(result == ["https://server-b", "https://server-c", "https://server-d"])
|
||||
}
|
||||
|
||||
@Test("reapplying same URL is skipped")
|
||||
func sameURLSkipped() {
|
||||
let result = Self.drainPending(
|
||||
initialActive: "https://server-a",
|
||||
applies: ["https://server-a"]
|
||||
)
|
||||
#expect(result.isEmpty)
|
||||
}
|
||||
|
||||
@Test("latest wins when multiple applies during replacement")
|
||||
func latestWinsDuringReplacement() {
|
||||
// Simulates: Apply B starts worker. During B's disconnect,
|
||||
// Apply C arrives (pending=C). Worker finishes B, loops,
|
||||
// sees pending=C, processes C. Final active = C.
|
||||
var activeURL = "https://server-a"
|
||||
var pending: String? = nil
|
||||
|
||||
// Apply B
|
||||
pending = "https://server-b"
|
||||
|
||||
// Worker starts, consumes B
|
||||
if let url = pending {
|
||||
pending = nil
|
||||
activeURL = url
|
||||
}
|
||||
|
||||
// During B's disconnect, Apply C arrives
|
||||
pending = "https://server-c"
|
||||
|
||||
// Worker loops: sees pending=C, processes it
|
||||
while let url = pending {
|
||||
pending = nil
|
||||
guard url != activeURL else { continue }
|
||||
activeURL = url
|
||||
}
|
||||
|
||||
#expect(activeURL == "https://server-c")
|
||||
}
|
||||
|
||||
@Test("A to B to C with C arriving during B disconnect ends on C")
|
||||
func abcWithCDuringBDisconnect() {
|
||||
var activeURL = "https://server-a"
|
||||
var pending: String? = nil
|
||||
var processed: [String] = []
|
||||
|
||||
// Apply B (A is the initial active URL)
|
||||
pending = "https://server-b"
|
||||
|
||||
// Worker loop iteration 1: consume B
|
||||
while let url = pending {
|
||||
pending = nil
|
||||
guard url != activeURL else { continue }
|
||||
// Simulate disconnect of current session (A)
|
||||
activeURL = url
|
||||
processed.append(url)
|
||||
|
||||
// During B's disconnect, Apply C arrives
|
||||
pending = "https://server-c"
|
||||
}
|
||||
|
||||
// Worker loop iteration 2: consume C
|
||||
while let url = pending {
|
||||
pending = nil
|
||||
guard url != activeURL else { continue }
|
||||
activeURL = url
|
||||
processed.append(url)
|
||||
}
|
||||
|
||||
#expect(activeURL == "https://server-c")
|
||||
#expect(processed == ["https://server-b", "https://server-c"])
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user