Files
bitchat/bitchatTests/Services/TransferProgressManagerTests.swift
T

148 lines
4.9 KiB
Swift

import Foundation
import Combine
import Testing
@testable import bitchat
@Suite("TransferProgressManager Tests")
struct TransferProgressManagerTests {
@Test("Start publishes started event and stores snapshot")
@MainActor
func startPublishesAndStoresSnapshot() async throws {
let manager = TransferProgressManager()
let transferID = "transfer-start"
var cancellable: AnyCancellable?
let recorder = EventRecorder()
cancellable = manager.publisher.sink { event in
if case .started(let id, let total) = event {
recorder.append("started:\(id):\(total)")
}
}
manager.start(id: transferID, totalFragments: 3)
let didReceive = await TestHelpers.waitUntil({
recorder.values == ["started:\(transferID):3"]
}, timeout: 5.0)
#expect(didReceive)
#expect(recorder.values == ["started:\(transferID):3"])
#expect(manager.snapshot(id: transferID)?.sent == 0)
#expect(manager.snapshot(id: transferID)?.total == 3)
_ = cancellable
}
@Test("Sending final fragment publishes update and completion then clears snapshot")
@MainActor
func recordFragmentSentPublishesProgressAndCompletion() async throws {
let manager = TransferProgressManager()
let transferID = "transfer-complete"
var cancellable: AnyCancellable?
let recorder = EventRecorder()
cancellable = manager.publisher.sink { event in
switch event {
case .started(let id, let total):
recorder.append("started:\(id):\(total)")
case .updated(let id, let sent, let total):
recorder.append("updated:\(id):\(sent):\(total)")
case .completed(let id, let total):
recorder.append("completed:\(id):\(total)")
case .cancelled, .rejected:
break
}
}
manager.start(id: transferID, totalFragments: 1)
manager.recordFragmentSent(id: transferID)
let didReceive = await TestHelpers.waitUntil({
recorder.values.count == 3
}, timeout: 5.0)
#expect(didReceive)
#expect(recorder.values == [
"started:\(transferID):1",
"updated:\(transferID):1:1",
"completed:\(transferID):1"
])
#expect(manager.snapshot(id: transferID) == nil)
_ = cancellable
}
@Test("Cancel publishes cancelled event and clears state")
@MainActor
func cancelPublishesAndClearsState() async throws {
let manager = TransferProgressManager()
let transferID = "transfer-cancel"
var cancellable: AnyCancellable?
let recorder = EventRecorder()
cancellable = manager.publisher.sink { event in
switch event {
case .started(let id, let total):
recorder.append("started:\(id):\(total)")
case .cancelled(let id, let sent, let total):
recorder.append("cancelled:\(id):\(sent):\(total)")
case .updated, .completed, .rejected:
break
}
}
manager.start(id: transferID, totalFragments: 4)
manager.recordFragmentSent(id: transferID)
manager.cancel(id: transferID)
let didReceive = await TestHelpers.waitUntil({
recorder.values.contains("started:\(transferID):4") &&
recorder.values.contains("cancelled:\(transferID):1:4")
}, timeout: 5.0)
#expect(didReceive)
#expect(recorder.values.contains("started:\(transferID):4"))
#expect(recorder.values.contains("cancelled:\(transferID):1:4"))
#expect(manager.snapshot(id: transferID) == nil)
_ = cancellable
}
@Test("Preflight policy rejection publishes a visible failure reason")
@MainActor
func rejectBeforeStartPublishesReason() async {
let manager = TransferProgressManager()
let transferID = "transfer-visible-reject"
let recorder = EventRecorder()
let cancellable = manager.publisher.sink { event in
if case .rejected(let id, let reason) = event {
recorder.append("rejected:\(id):\(reason)")
}
}
manager.rejectBeforeStart(id: transferID, reason: "upgrade required")
let didReceive = await TestHelpers.waitUntil({
recorder.values == ["rejected:\(transferID):upgrade required"]
}, timeout: 5.0)
#expect(didReceive)
#expect(manager.snapshot(id: transferID) == nil)
_ = cancellable
}
}
private final class EventRecorder: @unchecked Sendable {
private let lock = NSLock()
private var storage: [String] = []
var values: [String] {
lock.lock()
defer { lock.unlock() }
return storage
}
func append(_ value: String) {
lock.lock()
storage.append(value)
lock.unlock()
}
}