rainbow-dragon/Tests/ForgeExportTests/IngestMatchTests.swift

146 lines
6.3 KiB
Swift
Raw Normal View History

import XCTest
import Foundation
import ForgeColor
import ForgeGrade
import ForgeLibrary
@testable import ForgeExport
final class IngestMatchTests: XCTestCase {
var mediaDir: String!
var dbPath: String!
var store: LibraryStore!
var day: ForgeLibrary.Day!
override func setUpWithError() throws {
mediaDir = NSTemporaryDirectory() + "forge-ingest-\(UUID().uuidString)"
try FileManager.default.createDirectory(atPath: mediaDir + "/CLIPS", withIntermediateDirectories: true)
dbPath = NSTemporaryDirectory() + "forge-ingest-\(UUID().uuidString).db"
store = try LibraryStore(path: dbPath)
let p = try store.createProject(name: "P")
day = try store.createDay(projectID: p.id, label: "Day 03", date: "2026-07-11")
}
override func tearDown() {
try? FileManager.default.removeItem(atPath: mediaDir)
try? FileManager.default.removeItem(atPath: dbPath)
}
func makeShot(clip: String, withGrade: Bool = true) throws {
var snapshot: Data?
if withGrade {
let stack = GradeStack(nodes: [GradeNode(kind: .cdl(CDL(
slope: SIMD3(1.2, 1.0, 0.9), offset: SIMD3(0.01, 0, 0),
power: .one, saturation: 1.1)))])
snapshot = try GradeSnapshot.encode(stack)
}
_ = try store.createShot(Shot(
dayID: day.id, clipName: clip, cameraSlot: "A-Cam",
timecodeIn: "10:00:00:00", timecodeOut: "10:01:00:00",
gradeSnapshot: snapshot))
}
func makeMedia(_ name: String) throws {
try Data("fake clip".utf8).write(to: URL(fileURLWithPath: mediaDir + "/CLIPS/\(name)"))
}
// Clip file matches shot by basename -> sidecar .cdl + .cube written next to media.
func testMatchWritesSidecars() throws {
try makeShot(clip: "R001C001")
try makeMedia("R001C001.mov")
let result = try IngestMatcher.match(
mediaRoot: mediaDir, store: store, dayID: day.id,
options: IngestMatcher.Options(writeCDL: true, writeCube: true, latticeSize: 17))
XCTAssertEqual(result.matched.count, 1)
XCTAssertEqual(result.matched[0].clipName, "R001C001")
XCTAssertTrue(FileManager.default.fileExists(atPath: mediaDir + "/CLIPS/R001C001.cdl"))
XCTAssertTrue(FileManager.default.fileExists(atPath: mediaDir + "/CLIPS/R001C001.cube"))
// CDL sidecar parses back to shot's grade.
let cdlText = try String(contentsOfFile: mediaDir + "/CLIPS/R001C001.cdl", encoding: .utf8)
let parsed = try CDLExport.parseCDL(cdlText)
XCTAssertEqual(parsed.slope.x, 1.2, accuracy: 1e-5)
XCTAssertEqual(parsed.saturation, 1.1, accuracy: 1e-5)
}
// Case-insensitive + extension-agnostic matching.
func testMatchIsCaseAndExtensionAgnostic() throws {
try makeShot(clip: "R001C002")
try makeMedia("r001c002.MXF")
let result = try IngestMatcher.match(
mediaRoot: mediaDir, store: store, dayID: day.id,
options: .init(writeCDL: true, writeCube: false, latticeSize: 17))
XCTAssertEqual(result.matched.count, 1)
XCTAssertTrue(FileManager.default.fileExists(atPath: mediaDir + "/CLIPS/r001c002.cdl"))
}
// Media with no shot record -> unmatched list.
func testUnmatchedMediaListed() throws {
try makeShot(clip: "R001C003")
try makeMedia("R001C003.mov")
try makeMedia("R001C099.mov") // no shot
let result = try IngestMatcher.match(
mediaRoot: mediaDir, store: store, dayID: day.id,
options: .init(writeCDL: false, writeCube: false, latticeSize: 17))
XCTAssertEqual(result.matched.count, 1)
XCTAssertEqual(result.unmatchedMedia, ["CLIPS/R001C099.mov"])
}
// Shot with no media -> missing list.
func testShotsWithoutMediaListed() throws {
try makeShot(clip: "R001C004")
try makeShot(clip: "R001C005")
try makeMedia("R001C004.mov")
let result = try IngestMatcher.match(
mediaRoot: mediaDir, store: store, dayID: day.id,
options: .init(writeCDL: false, writeCube: false, latticeSize: 17))
XCTAssertEqual(result.shotsWithoutMedia, ["R001C005"])
}
// Shot without grade snapshot matches but writes no sidecars.
func testNoGradeNoSidecar() throws {
try makeShot(clip: "R001C006", withGrade: false)
try makeMedia("R001C006.mov")
let result = try IngestMatcher.match(
mediaRoot: mediaDir, store: store, dayID: day.id,
options: .init(writeCDL: true, writeCube: true, latticeSize: 17))
XCTAssertEqual(result.matched.count, 1)
XCTAssertFalse(result.matched[0].sidecarsWritten)
XCTAssertFalse(FileManager.default.fileExists(atPath: mediaDir + "/CLIPS/R001C006.cdl"))
}
// Non-media files ignored.
func testNonMediaIgnored() throws {
try makeShot(clip: "R001C007")
try makeMedia("R001C007.mov")
try Data("x".utf8).write(to: URL(fileURLWithPath: mediaDir + "/CLIPS/notes.txt"))
try Data("x".utf8).write(to: URL(fileURLWithPath: mediaDir + "/manifest.mhl"))
let result = try IngestMatcher.match(
mediaRoot: mediaDir, store: store, dayID: day.id,
options: .init(writeCDL: false, writeCube: false, latticeSize: 17))
XCTAssertEqual(result.matched.count, 1)
XCTAssertTrue(result.unmatchedMedia.isEmpty)
}
// Existing sidecar not overwritten unless force.
func testExistingSidecarPreserved() throws {
try makeShot(clip: "R001C008")
try makeMedia("R001C008.mov")
try Data("existing".utf8).write(to: URL(fileURLWithPath: mediaDir + "/CLIPS/R001C008.cdl"))
_ = try IngestMatcher.match(
mediaRoot: mediaDir, store: store, dayID: day.id,
options: .init(writeCDL: true, writeCube: false, latticeSize: 17))
let content = try String(contentsOfFile: mediaDir + "/CLIPS/R001C008.cdl", encoding: .utf8)
XCTAssertEqual(content, "existing")
_ = try IngestMatcher.match(
mediaRoot: mediaDir, store: store, dayID: day.id,
options: .init(writeCDL: true, writeCube: false, latticeSize: 17, force: true))
let after = try String(contentsOfFile: mediaDir + "/CLIPS/R001C008.cdl", encoding: .utf8)
XCTAssertTrue(after.contains("ColorDecisionList"))
}
}