import XCTest import Foundation @testable import ForgeOffload final class MHLTests: XCTestCase { var root: String! override func setUpWithError() throws { root = NSTemporaryDirectory() + "forge-mhl-\(UUID().uuidString)" try FileManager.default.createDirectory(atPath: root + "/CLIPS", withIntermediateDirectories: true) try Data("clip one contents".utf8).write(to: URL(fileURLWithPath: root + "/CLIPS/C001.mov")) try Data("clip two contents, longer".utf8).write(to: URL(fileURLWithPath: root + "/CLIPS/C002.mov")) } override func tearDown() { try? FileManager.default.removeItem(atPath: root) } // MHL XML structure: hashlist root, creatorinfo, hash entries with file/size/xxh64. func testWriteStructure() throws { let xml = try MHL.generate(root: root, creator: "Forge 0.1", files: ["CLIPS/C001.mov", "CLIPS/C002.mov"]) XCTAssertTrue(xml.hasPrefix("\n")) XCTAssertTrue(xml.contains("")) XCTAssertTrue(xml.contains("Forge 0.1")) XCTAssertTrue(xml.contains("CLIPS/C001.mov")) XCTAssertTrue(xml.contains("17")) XCTAssertTrue(xml.contains("")) XCTAssertTrue(xml.hasSuffix("\n")) } // Verify: clean tree passes. func testVerifyClean() throws { let xml = try MHL.generate(root: root, creator: "Forge", files: ["CLIPS/C001.mov", "CLIPS/C002.mov"]) let result = try MHL.verify(manifest: xml, root: root) XCTAssertTrue(result.passed) XCTAssertEqual(result.checked, 2) XCTAssertTrue(result.failures.isEmpty) } // Tamper detection: flipped byte fails that file. func testVerifyTamperDetected() throws { let xml = try MHL.generate(root: root, creator: "Forge", files: ["CLIPS/C001.mov", "CLIPS/C002.mov"]) try Data("clip one CONTENTS".utf8).write(to: URL(fileURLWithPath: root + "/CLIPS/C001.mov")) let result = try MHL.verify(manifest: xml, root: root) XCTAssertFalse(result.passed) XCTAssertEqual(result.failures.count, 1) XCTAssertEqual(result.failures[0].path, "CLIPS/C001.mov") XCTAssertEqual(result.failures[0].reason, .hashMismatch) } // Missing file fails verify. func testVerifyMissingFile() throws { let xml = try MHL.generate(root: root, creator: "Forge", files: ["CLIPS/C001.mov", "CLIPS/C002.mov"]) try FileManager.default.removeItem(atPath: root + "/CLIPS/C002.mov") let result = try MHL.verify(manifest: xml, root: root) XCTAssertFalse(result.passed) XCTAssertEqual(result.failures[0].reason, .missing) } // Round trip through file on disk. func testManifestFileRoundTrip() throws { let mhlPath = root + "/A001.mhl" try MHL.write(root: root!, creator: "Forge", files: ["CLIPS/C001.mov"], to: mhlPath) let loaded = try String(contentsOfFile: mhlPath, encoding: .utf8) let result = try MHL.verify(manifest: loaded, root: root) XCTAssertTrue(result.passed) } // Malformed manifest rejected. func testMalformedManifestRejected() { XCTAssertThrowsError(try MHL.verify(manifest: "", root: root)) } // XML escaping in paths. func testPathEscaping() throws { try Data("x".utf8).write(to: URL(fileURLWithPath: root + "/a&b.mov")) let xml = try MHL.generate(root: root, creator: "Forge", files: ["a&b.mov"]) XCTAssertTrue(xml.contains("a&b.mov")) let result = try MHL.verify(manifest: xml, root: root) XCTAssertTrue(result.passed) } }