rainbow-dragon/Tests/ForgeLoggerTests/EndToEndExportTests.swift

91 lines
3.8 KiB
Swift

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)
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. CAP sim composes clip names as RxxxCxxx from reel+clip vars.
let takes: [(clipNo: UInt16, clip: String, tcIn: String, tcOut: String)] = [
(1, "R001C001", "10:00:00:00", "10:00:30:00"),
(2, "R001C002", "10:05:00:00", "10:05:45:00"),
(3, "R001C003", "10:12:00:00", "10:13:00:00"),
]
for take in takes {
await sim.setMetadata(ei: 800, colorTemp: 5600, tint: 0, fps: 24, timecode: take.tcIn)
try await Task.sleep(for: .milliseconds(60))
await sim.setRecording(true, clipNumber: take.clipNo)
try await Task.sleep(for: .milliseconds(80))
await sim.setMetadata(ei: 800, colorTemp: 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: "R001C").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 R001C003"))
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: "<ColorCorrection id=").count - 1, 3)
let cube = try CubeExport.fromSnapshot(shots[0].gradeSnapshot!, latticeSize: 17, title: shots[0].clipName)
XCTAssertTrue(cube.contains("LUT_3D_SIZE 17"))
}
}