Files
Termind/FrameworkTests/SessionReconnectControllerTests.swift
T
2026-09-11 23:39:34 +08:00

100 lines
3.0 KiB
Swift

import Foundation
import TermindApplication
import Testing
@Suite
@MainActor
struct SessionReconnectControllerTests {
@Test
func retriesUseCappedBackoffAndRetainLatestFailure() {
let clock = ReconnectTestClock()
let controller = SessionReconnectController(
sleep: { _ in try await Task.sleep(for: .seconds(3_600)) },
now: { clock.value }
)
let expectedDelays: [TimeInterval] = [1, 2, 5, 10, 30, 30]
for (index, expectedDelay) in expectedDelays.enumerated() {
controller.schedule(after: "failure-\(index + 1)") {}
#expect(controller.state?.attempt == index + 1)
#expect(controller.state?.lastError == "failure-\(index + 1)")
#expect(
controller.state?.nextRetry.timeIntervalSince(clock.value)
== expectedDelay
)
}
controller.stop()
}
@Test
func stableConnectionResetsAttemptCounter() {
let clock = ReconnectTestClock()
let controller = SessionReconnectController(
sleep: { _ in try await Task.sleep(for: .seconds(3_600)) },
now: { clock.value }
)
controller.schedule(after: "first") {}
controller.schedule(after: "second") {}
#expect(controller.state?.attempt == 2)
controller.markConnected(initial: false)
clock.advance(by: 61)
controller.schedule(after: "after stable connection") {}
#expect(controller.state?.attempt == 1)
controller.stop()
}
@Test
func retryNowRunsImmediatelyAndStopCancelsFutureRetry() async {
let controller = SessionReconnectController(
sleep: { _ in try await Task.sleep(for: .seconds(3_600)) }
)
var retryCount = 0
controller.schedule(after: "offline") { retryCount += 1 }
controller.retryNow()
await Task.yield()
#expect(retryCount == 1)
controller.stop()
#expect(controller.state == nil)
try? await Task.sleep(for: .milliseconds(10))
#expect(retryCount == 1)
}
@Test
func scheduledRetryReturnsToMainActorAfterDetachedSleep() async {
let controller = SessionReconnectController(
policy: ReconnectPolicy(delays: [.zero]),
sleep: { _ in await Task.detached(priority: .userInitiated) {}.value }
)
var ran = false
var ranOnMainThread = false
controller.schedule(after: "offline") {
ranOnMainThread = Thread.isMainThread
ran = true
}
for _ in 0..<100 where !ran {
await Task.yield()
}
#expect(ran)
#expect(ranOnMainThread)
controller.stop()
}
}
private nonisolated final class ReconnectTestClock: @unchecked Sendable {
private let lock = NSLock()
private var date = Date(timeIntervalSince1970: 1_000)
var value: Date { lock.withLock { date } }
func advance(by interval: TimeInterval) {
lock.withLock { date.addTimeInterval(interval) }
}
}