import XCTest import Foundation import ForgeCamera import ForgeCameraARRI import ForgeColor import ForgeGrade import ForgeLibrary import ForgeExport import ForgeSim @testable import ForgeLogger /// Full pipeline: ARRI sim -> driver poll -> ClipLogger -> LibraryStore -> exports. final class EndToEndExportTests: XCTestCase { func testSimSessionToExports() async throws { // Infra. let sim = ArriSimulator() try await sim.start(port: 0) defer { Task { try? await sim.stop() } } let port = await sim.boundPort! let dbPath = NSTemporaryDirectory() + "forge-e2e-\(UUID().uuidString).db" defer { try? FileManager.default.removeItem(atPath: dbPath) } let store = try LibraryStore(path: dbPath) let project = try store.createProject(name: "E2E") let day = try store.createDay(projectID: project.id, label: "Day 01", date: "2026-07-10") let driver = ArriDriver(host: "127.0.0.1", port: port, pollInterval: .milliseconds(15)) let logger = ClipLogger(store: store, dayID: day.id) let grade = GradeStack(nodes: [GradeNode(kind: .cdl(CDL( slope: SIMD3(1.15, 1.0, 0.92), offset: SIMD3(0.01, 0, -0.01), power: .one, saturation: 1.1)))]) let snapshotForProvider = grade await logger.attach(slotName: "A-Cam", driver: driver) { snapshotForProvider } try await driver.connect() // Three takes. let takes: [(clip: String, tcIn: String, tcOut: String)] = [ ("A001C001_260710", "10:00:00:00", "10:00:30:00"), ("A001C002_260710", "10:05:00:00", "10:05:45:00"), ("A001C003_260710", "10:12:00:00", "10:13:00:00"), ] for take in takes { await sim.setMetadata(ei: 800, wb: 5600, tint: 0, fps: 24, timecode: take.tcIn) try await Task.sleep(for: .milliseconds(60)) await sim.setRecording(true, clipName: take.clip) try await Task.sleep(for: .milliseconds(80)) await sim.setMetadata(ei: 800, wb: 5600, tint: 0, fps: 24, timecode: take.tcOut) try await Task.sleep(for: .milliseconds(60)) await sim.setRecording(false) try await Task.sleep(for: .milliseconds(80)) } await driver.disconnect() await logger.stop() // Three shots logged with TCs + snapshots. let shots = try store.listShots(dayID: day.id) XCTAssertEqual(shots.count, 3) XCTAssertEqual(shots.map(\.clipName), takes.map(\.clip)) for (shot, take) in zip(shots, takes) { XCTAssertEqual(shot.timecodeIn, take.tcIn) XCTAssertEqual(shot.timecodeOut, take.tcOut) XCTAssertNotNil(shot.gradeSnapshot) } // All export formats produce sane output. let ale = PostExport.writeALE(shots: shots, fps: 24) XCTAssertEqual(ale.components(separatedBy: "A001C").count - 1, 3) let csv = PostExport.writeCSV(shots: shots) XCTAssertEqual(csv.split(separator: "\n").count, 4) // header + 3 let edl = PostExport.writeEDL(shots: shots, title: "DAY01", fps: 24) XCTAssertTrue(edl.contains("003 A001C003_260710")) XCTAssertEqual(edl.components(separatedBy: "* ASC_SOP").count - 1, 3) let ccc = CDLExport.writeCCC(shots.compactMap { s in guard let data = s.gradeSnapshot, let stack = try? GradeSnapshot.decode(data), case .cdl(let c)? = stack.nodes.first?.kind else { return nil } return (s.clipName, c) }) XCTAssertEqual(ccc.components(separatedBy: "