129 lines
4.6 KiB
Swift
129 lines
4.6 KiB
Swift
import XCTest
|
|
import Foundation
|
|
@testable import ForgeLibrary
|
|
|
|
final class LibraryStoreTests: XCTestCase {
|
|
|
|
var dbPath: String!
|
|
var store: LibraryStore!
|
|
|
|
override func setUp() async throws {
|
|
dbPath = NSTemporaryDirectory() + "forge-test-\(UUID().uuidString).db"
|
|
store = try LibraryStore(path: dbPath)
|
|
}
|
|
|
|
override func tearDown() async throws {
|
|
store = nil
|
|
try? FileManager.default.removeItem(atPath: dbPath)
|
|
}
|
|
|
|
// Schema migration table exists with v1.
|
|
func testMigrationVersion() throws {
|
|
XCTAssertEqual(try store.schemaVersion(), 1)
|
|
}
|
|
|
|
// Project CRUD.
|
|
func testProjectCRUD() throws {
|
|
let p = try store.createProject(name: "Feature X")
|
|
XCTAssertFalse(p.id.uuidString.isEmpty)
|
|
XCTAssertEqual(p.name, "Feature X")
|
|
|
|
let all = try store.listProjects()
|
|
XCTAssertEqual(all.count, 1)
|
|
XCTAssertEqual(all[0].name, "Feature X")
|
|
|
|
try store.deleteProject(id: p.id)
|
|
XCTAssertTrue(try store.listProjects().isEmpty)
|
|
}
|
|
|
|
// Day belongs to project.
|
|
func testDayCRUD() throws {
|
|
let p = try store.createProject(name: "P")
|
|
let d = try store.createDay(projectID: p.id, label: "Day 01", date: "2026-07-10")
|
|
let days = try store.listDays(projectID: p.id)
|
|
XCTAssertEqual(days.count, 1)
|
|
XCTAssertEqual(days[0].label, "Day 01")
|
|
XCTAssertEqual(days[0].date, "2026-07-10")
|
|
XCTAssertEqual(days[0].id, d.id)
|
|
}
|
|
|
|
// Shot CRUD with metadata + grade snapshot blob round-trip.
|
|
func testShotRoundTrip() throws {
|
|
let p = try store.createProject(name: "P")
|
|
let d = try store.createDay(projectID: p.id, label: "D1", date: "2026-07-10")
|
|
let snapshot = Data(#"{"nodes":[],"version":1}"#.utf8)
|
|
|
|
var shot = Shot(
|
|
dayID: d.id,
|
|
clipName: "A001C001_260710_R1CD",
|
|
cameraSlot: "A-Cam",
|
|
timecodeIn: "10:00:00:00",
|
|
timecodeOut: nil,
|
|
exposureIndex: 800,
|
|
whiteBalance: 5600,
|
|
tint: 0,
|
|
fps: 24,
|
|
gradeSnapshot: snapshot)
|
|
shot = try store.createShot(shot)
|
|
|
|
var fetched = try XCTUnwrap(store.shot(id: shot.id))
|
|
XCTAssertEqual(fetched.clipName, "A001C001_260710_R1CD")
|
|
XCTAssertEqual(fetched.cameraSlot, "A-Cam")
|
|
XCTAssertEqual(fetched.exposureIndex, 800)
|
|
XCTAssertEqual(fetched.gradeSnapshot, snapshot)
|
|
XCTAssertNil(fetched.timecodeOut)
|
|
|
|
// Update TC out on rec stop.
|
|
fetched.timecodeOut = "10:02:30:12"
|
|
try store.updateShot(fetched)
|
|
let updated = try XCTUnwrap(store.shot(id: shot.id))
|
|
XCTAssertEqual(updated.timecodeOut, "10:02:30:12")
|
|
}
|
|
|
|
// Shots listed per day in insert order.
|
|
func testListShotsByDay() throws {
|
|
let p = try store.createProject(name: "P")
|
|
let d = try store.createDay(projectID: p.id, label: "D1", date: "2026-07-10")
|
|
for i in 1...3 {
|
|
_ = try store.createShot(Shot(dayID: d.id, clipName: "A001C00\(i)", cameraSlot: "A-Cam"))
|
|
}
|
|
let shots = try store.listShots(dayID: d.id)
|
|
XCTAssertEqual(shots.map(\.clipName), ["A001C001", "A001C002", "A001C003"])
|
|
}
|
|
|
|
// Concurrent writers serialized safely.
|
|
func testConcurrentWrites() async throws {
|
|
let p = try store.createProject(name: "P")
|
|
let d = try store.createDay(projectID: p.id, label: "D1", date: "2026-07-10")
|
|
let s = store!
|
|
let dayID = d.id
|
|
await withTaskGroup(of: Void.self) { group in
|
|
for i in 0..<50 {
|
|
group.addTask {
|
|
_ = try? s.createShot(Shot(dayID: dayID, clipName: "C\(i)", cameraSlot: "A"))
|
|
}
|
|
}
|
|
}
|
|
XCTAssertEqual(try store.listShots(dayID: d.id).count, 50)
|
|
}
|
|
|
|
// Reopen db: data persists.
|
|
func testPersistence() throws {
|
|
let p = try store.createProject(name: "Persist")
|
|
store = nil
|
|
store = try LibraryStore(path: dbPath)
|
|
let all = try store.listProjects()
|
|
XCTAssertEqual(all.count, 1)
|
|
XCTAssertEqual(all[0].id, p.id)
|
|
}
|
|
|
|
// Cascade: deleting project removes days + shots.
|
|
func testCascadeDelete() throws {
|
|
let p = try store.createProject(name: "P")
|
|
let d = try store.createDay(projectID: p.id, label: "D1", date: "2026-07-10")
|
|
_ = try store.createShot(Shot(dayID: d.id, clipName: "C1", cameraSlot: "A"))
|
|
try store.deleteProject(id: p.id)
|
|
XCTAssertTrue(try store.listDays(projectID: p.id).isEmpty)
|
|
XCTAssertTrue(try store.listShots(dayID: d.id).isEmpty)
|
|
}
|
|
}
|