import XCTest import ForgeGrade import ForgeColor @testable import ForgeCamera /// Scriptable in-test driver: fails N connects, then succeeds. Emits states on demand. actor FlakyDriver: CameraDriver { nonisolated let capabilities = CameraCapabilities( vendor: .arri, supportsNativeCDL: true, lut3dSizes: [33], metadataFields: [.clipName, .exposureIndex]) private var failuresRemaining: Int private(set) var connectAttempts = 0 private(set) var pushedLooks = [FlattenedLook]() private var continuation: AsyncStream.Continuation? private let stream: AsyncStream init(failFirst: Int) { failuresRemaining = failFirst var cont: AsyncStream.Continuation! stream = AsyncStream { cont = $0 } continuation = cont } nonisolated var state: AsyncStream { stream } func connect() async throws { connectAttempts += 1 if failuresRemaining > 0 { failuresRemaining -= 1 throw CameraError.connectionFailed("scripted failure") } continuation?.yield(CameraState(connection: .connected)) } func disconnect() async { continuation?.yield(CameraState(connection: .disconnected)) } func push(look: FlattenedLook) async throws { pushedLooks.append(look) } // Test hooks. func emit(_ s: CameraState) { continuation?.yield(s) } } final class CameraSeamTests: XCTestCase { // Supervisor retries with backoff until driver connects. func testSupervisorRetriesUntilConnected() async throws { let driver = FlakyDriver(failFirst: 3) let supervisor = ConnectionSupervisor( driver: driver, backoff: BackoffPolicy(initial: .milliseconds(1), multiplier: 1, maxDelay: .milliseconds(2))) try await supervisor.start() // Wait for connected. let ok = await supervisor.waitForConnection(timeout: .seconds(2)) XCTAssertTrue(ok) let attempts = await driver.connectAttempts XCTAssertEqual(attempts, 4) // 3 failures + 1 success await supervisor.stop() } // Health reflects driver state stream. func testHealthTracksState() async throws { let driver = FlakyDriver(failFirst: 0) let supervisor = ConnectionSupervisor( driver: driver, backoff: BackoffPolicy(initial: .milliseconds(1), multiplier: 1, maxDelay: .milliseconds(2))) try await supervisor.start() _ = await supervisor.waitForConnection(timeout: .seconds(2)) var health = await supervisor.health XCTAssertEqual(health, .connected) await driver.emit(CameraState(connection: .disconnected)) try await Task.sleep(for: .milliseconds(50)) health = await supervisor.health XCTAssertEqual(health, .disconnected) await supervisor.stop() } // Recording state + metadata pass through supervisor's published stream. func testStatePassthrough() async throws { let driver = FlakyDriver(failFirst: 0) let supervisor = ConnectionSupervisor( driver: driver, backoff: BackoffPolicy(initial: .milliseconds(1), multiplier: 1, maxDelay: .milliseconds(2))) try await supervisor.start() _ = await supervisor.waitForConnection(timeout: .seconds(2)) var iterator = supervisor.states.makeAsyncIterator() await driver.emit(CameraState( connection: .connected, isRecording: true, clipName: "A001C002", metadata: CameraMetadata(exposureIndex: 800, whiteBalance: 5600, tint: 0, fps: 24, timecode: "12:00:00:00"))) // Drain until we see the recording state (connected event may arrive first). var found = false for _ in 0..<5 { if let s = await iterator.next(), s.isRecording, s.clipName == "A001C002" { XCTAssertEqual(s.metadata?.exposureIndex, 800) found = true break } } XCTAssertTrue(found) await supervisor.stop() } // Slot registry add/remove/lookup. func testSlotRegistry() async throws { let registry = SlotRegistry() let driver = FlakyDriver(failFirst: 0) let slot = await registry.addSlot(name: "A-Cam", driver: driver) let all = await registry.slots XCTAssertEqual(all.count, 1) XCTAssertEqual(all[0].name, "A-Cam") await registry.removeSlot(id: slot.id) let after = await registry.slots XCTAssertTrue(after.isEmpty) } // Capabilities surfaced through slot. func testCapabilities() async { let driver = FlakyDriver(failFirst: 0) XCTAssertTrue(driver.capabilities.supportsNativeCDL) XCTAssertEqual(driver.capabilities.vendor, .arri) XCTAssertTrue(driver.capabilities.lut3dSizes.contains(33)) } // Backoff policy caps at maxDelay and multiplies. func testBackoffPolicy() { var policy = BackoffPolicy(initial: .milliseconds(100), multiplier: 2, maxDelay: .milliseconds(350)) XCTAssertEqual(policy.nextDelay(), .milliseconds(100)) XCTAssertEqual(policy.nextDelay(), .milliseconds(200)) XCTAssertEqual(policy.nextDelay(), .milliseconds(350)) // capped from 400 XCTAssertEqual(policy.nextDelay(), .milliseconds(350)) policy.reset() XCTAssertEqual(policy.nextDelay(), .milliseconds(100)) } }