macsync/@packages/shared/Tests/MacSyncSharedTests/SendQueueClientTests.swift

113 lines
3.3 KiB
Swift

import XCTest
@testable import MacSyncShared
private struct FakeItem: Sendable, Equatable {
let id: String
let body: String
}
private actor FakeTransportState {
var pending: [FakeItem]
var reportedSent: [String] = []
var reportedFailed: [(id: String, error: String?)] = []
var fetchCalls: Int = 0
init(pending: [FakeItem]) {
self.pending = pending
}
func popAll() -> [FakeItem] {
fetchCalls += 1
let items = pending
pending = []
return items
}
func recordSent(_ id: String) { reportedSent.append(id) }
func recordFailed(_ id: String, _ error: String?) { reportedFailed.append((id, error)) }
}
private struct FakeTransport: SendQueueTransport {
typealias PendingItem = FakeItem
let state: FakeTransportState
func id(of item: FakeItem) -> String { item.id }
func fetchPending() async throws -> [FakeItem] { await state.popAll() }
func reportResult(id: String, status: String, error: String?) async throws {
if status == "sent" {
await state.recordSent(id)
} else {
await state.recordFailed(id, error)
}
}
}
final class SendQueueClientTests: XCTestCase {
@MainActor
func test_drainOnce_appliesAndAcksEachItem() async {
let state = FakeTransportState(pending: [
FakeItem(id: "a", body: "hello"),
FakeItem(id: "b", body: "world"),
])
let transport = FakeTransport(state: state)
let client = SendQueueClient(
label: "test",
transport: transport,
interval: 60
) { item in
item.body == "world" ? .failed(reason: "bad world") : .sent
}
await client.drainOnce()
let sent = await state.reportedSent
let failed = await state.reportedFailed
XCTAssertEqual(sent, ["a"])
XCTAssertEqual(failed.count, 1)
XCTAssertEqual(failed.first?.id, "b")
XCTAssertEqual(failed.first?.error, "bad world")
}
@MainActor
func test_drainOnce_isReentrantSafe() async {
let state = FakeTransportState(pending: (0..<5).map { FakeItem(id: "\($0)", body: "x") })
let transport = FakeTransport(state: state)
let client = SendQueueClient(
label: "test",
transport: transport,
interval: 60
) { _ in
try? await Task.sleep(nanoseconds: 20_000_000)
return .sent
}
async let a: Void = client.drainOnce()
async let b: Void = client.drainOnce()
async let c: Void = client.drainOnce()
_ = await (a, b, c)
let calls = await state.fetchCalls
XCTAssertEqual(calls, 1, "concurrent drains must coalesce")
let sent = await state.reportedSent
XCTAssertEqual(sent.count, 5)
}
@MainActor
func test_drainOnce_emptyQueueIsNoop() async {
let state = FakeTransportState(pending: [])
let transport = FakeTransport(state: state)
let client = SendQueueClient(
label: "test",
transport: transport,
interval: 60
) { _ in .sent }
await client.drainOnce()
let sent = await state.reportedSent
let failed = await state.reportedFailed
XCTAssertTrue(sent.isEmpty)
XCTAssertTrue(failed.isEmpty)
}
}