import Foundation
import Testing
@testable import IrdAPI
@Suite struct PollingTests {
final class Counter: @unchecked Sendable {
private let lock = NSLock()
private var n = 0
func next() -> Int { lock.withLock { n += 1; return n } }
}
@Test func sleepsDoubleUpToTheCap() async throws {
let clock = TestClock()
let counter = Counter()
let task = Task {
try await pollUntil(PollOptions(timeout: .seconds(1000)), clock: clock,
step: { counter.next() }, done: { $0 >= 6 })
}
for d in [1, 2, 4, 8, 8] {
#expect(await eventually { clock.sleeperCount == 1 })
clock.advance(by: .seconds(d))
}
let result = try await task.value
#expect(result == 6)
#expect(clock.recordedSleeps == [.seconds(1), .seconds(2), .seconds(4), .seconds(8), .seconds(8)])
}
@Test func doneOnFirstAttemptNeverSleeps() async throws {
let clock = TestClock()
let r = try await pollUntil(PollOptions(), clock: clock, step: { "ok" }, done: { _ in true })
#expect(r == "ok")
#expect(clock.recordedSleeps.isEmpty)
}
@Test func deadlineIsCheckedAfterTheAttemptSoANegativeTimeoutPollsOnce() async {
let clock = TestClock()
let counter = Counter()
let e = await caught {
_ = try await pollUntil(PollOptions(timeout: .seconds(-1), message: "Timed out waiting for the job"), clock: clock,
step: { ["status": "processing", "n": .number(Double(counter.next()))] as [String: JSONValue] }, done: { _ in false })
}
#expect(e == .timeout(message: "Timed out waiting for the job", last: ["status": "processing", "n": 1]))
#expect(clock.recordedSleeps.isEmpty)
}
@Test func cancellationPropagatesOutOfTheSleep() async {
let clock = TestClock()
let task = Task<Int, any Error> {
try await pollUntil(PollOptions(timeout: .seconds(100)), clock: clock, step: { 1 }, done: { _ in false })
}
#expect(await eventually { clock.sleeperCount == 1 })
task.cancel()
do { _ = try await task.value; Issue.record("expected cancellation") } catch { #expect(error is CancellationError) }
}
}