import XCTest @testable import DropTwoCore final class FileNameParserTests: XCTestCase { // MARK: - Erfolgreiches Parsen func testParsesStandardFileName() { let result = FileNameParser.parse("SOMOS-9x16-v3-DE.mp4") guard case .parsed(let parsed) = result.outcome else { return XCTFail("Erwartete .parsed, bekam \(result.outcome)") } XCTAssertEqual(parsed.projectName, "SOMOS") XCTAssertEqual(parsed.sourceFormatToken, "9x16") XCTAssertEqual(parsed.remainder, "v3-DE") XCTAssertEqual(parsed.fileExtension, "mp4") XCTAssertEqual(parsed.originalFileName, "SOMOS-9x16-v3-DE.mp4") XCTAssertEqual(parsed.targetFileName, "SOMOS-4x5-v3-DE.mp4") } func testProjectNameWithHyphenIsPreserved() { let result = FileNameParser.parse("Somos-Pendant-9x16-v3-DE.mp4") guard case .parsed(let parsed) = result.outcome else { return XCTFail("Erwartete .parsed, bekam \(result.outcome)") } XCTAssertEqual(parsed.projectName, "Somos-Pendant") XCTAssertEqual(parsed.targetFileName, "Somos-Pendant-4x5-v3-DE.mp4") } func testLanguageSegmentWithHyphenIsPreserved() { let result = FileNameParser.parse("SOMOS-9x16-v3-EN-US.mp4") guard case .parsed(let parsed) = result.outcome else { return XCTFail("Erwartete .parsed, bekam \(result.outcome)") } XCTAssertEqual(parsed.projectName, "SOMOS") XCTAssertEqual(parsed.remainder, "v3-EN-US") XCTAssertEqual(parsed.targetFileName, "SOMOS-4x5-v3-EN-US.mp4") } func testCaseInsensitiveFormatTokenIsRecognized() { let result = FileNameParser.parse("somos-9X16-v3-de.mp4") guard case .parsed(let parsed) = result.outcome else { return XCTFail("Erwartete .parsed, bekam \(result.outcome)") } // Sonst unveränderte Schreibweise bleibt erhalten. XCTAssertEqual(parsed.projectName, "somos") XCTAssertEqual(parsed.sourceFormatToken, "9X16") XCTAssertEqual(parsed.remainder, "v3-de") XCTAssertEqual(parsed.targetFileName, "somos-4x5-v3-de.mp4") } func testFileExtensionIsPreservedVerbatim() { for ext in ["mp4", "mov", "mxf", "MOV"] { let fileName = "SOMOS-9x16-v3-DE.\(ext)" let result = FileNameParser.parse(fileName) guard case .parsed(let parsed) = result.outcome else { return XCTFail("Erwartete .parsed für \(fileName), bekam \(result.outcome)") } XCTAssertEqual(parsed.fileExtension, ext) XCTAssertEqual(parsed.targetFileName, "SOMOS-4x5-v3-DE.\(ext)") } } // MARK: - Skip func testAlreadyTargetFormatIsSkipped() { let result = FileNameParser.parse("SOMOS-4x5-v3-DE.mp4") XCTAssertEqual(result.outcome, .skipped(.alreadyTargetFormat)) } // MARK: - Rejection func testMissingFormatSegmentIsRejected() { let result = FileNameParser.parse("SOMOS-v3-DE.mp4") XCTAssertEqual(result.outcome, .rejected(.formatSegmentMissing)) } func testFinderDuplicateIsRejected() { let result = FileNameParser.parse("SOMOS-9x16-v3-DE (1).mp4") XCTAssertEqual(result.outcome, .rejected(.finderDuplicate)) } func testFinderDuplicateTakesPriorityOverMissingFormatSegment() { // Beide Aussagen wären technisch wahr; die handlungsleitende // Meldung (falsche Kopie erwischt) muss gewinnen. let result = FileNameParser.parse("SOMOS-v3-DE (1).mp4") XCTAssertEqual(result.outcome, .rejected(.finderDuplicate)) } // MARK: - a) Rechtestes Vorkommen gewinnt func testRightmostFormatOccurrenceWins() { let result = FileNameParser.parse("Kampagne-9x16-Teaser-9x16-v3-DE.mp4") guard case .parsed(let parsed) = result.outcome else { return XCTFail("Erwartete .parsed, bekam \(result.outcome)") } XCTAssertEqual(parsed.projectName, "Kampagne-9x16-Teaser") XCTAssertEqual(parsed.remainder, "v3-DE") XCTAssertEqual(parsed.targetFileName, "Kampagne-9x16-Teaser-4x5-v3-DE.mp4") } func testRightmostAlreadyTargetFormatOccurrenceWins() { let result = FileNameParser.parse("Kampagne-4x5-Teaser-4x5-v3-DE.mp4") XCTAssertEqual(result.outcome, .skipped(.alreadyTargetFormat)) } // MARK: - b) Projekt und Rest müssen existieren func testRejectsWhenNoProjectSegment() { let result = FileNameParser.parse("9x16-v3-DE.mp4") XCTAssertEqual(result.outcome, .rejected(.formatSegmentMissing)) } func testParsesWithoutRemainderSegment() { let result = FileNameParser.parse("SOMOS-9x16.mp4") guard case .parsed(let parsed) = result.outcome else { return XCTFail("Erwartete .parsed, bekam \(result.outcome)") } XCTAssertEqual(parsed.projectName, "SOMOS") XCTAssertEqual(parsed.remainder, "") XCTAssertEqual(parsed.targetFileName, "SOMOS-4x5.mp4") } // MARK: - d) Lockere Schreibweisen func testUnderscoreAndSpaceSeparatorsArePreserved() { let cases = [ ("SOMOS_9x16_v3_DE.mp4", "SOMOS", "v3_DE", "SOMOS_4x5_v3_DE.mp4"), ("SOMOS 9x16 v3 DE.mov", "SOMOS", "v3 DE", "SOMOS 4x5 v3 DE.mov"), ("Somos Pendant - 9X16_v3-DE.mp4", "Somos Pendant", "v3-DE", "Somos Pendant - 4x5_v3-DE.mp4"), ("SOMOS-9×16-v3.mp4", "SOMOS", "v3", "SOMOS-4x5-v3.mp4"), ("SOMOS (9 x 16) v3.mp4", "SOMOS", "v3", "SOMOS (4x5) v3.mp4") ] for (fileName, project, remainder, target) in cases { guard case .parsed(let parsed) = FileNameParser.parse(fileName).outcome else { XCTFail("Erwartete .parsed für \(fileName)") continue } XCTAssertEqual(parsed.projectName, project, fileName) XCTAssertEqual(parsed.remainder, remainder, fileName) XCTAssertEqual(parsed.targetFileName, target, fileName) } } func testTokenInsideWordIsNotAFormat() { XCTAssertEqual(FileNameParser.parse("SOMOS-19x160-v3.mp4").outcome, .rejected(.formatSegmentMissing)) XCTAssertEqual(FileNameParser.parse("SOMOSx9x16v3.mp4").outcome, .rejected(.formatSegmentMissing)) } func testTargetFormatInAnySpellingIsSkipped() { for fileName in ["SOMOS_4X5_v3.mp4", "SOMOS 4x5.mp4", "SOMOS-4×5-v3-DE.mp4"] { XCTAssertEqual(FileNameParser.parse(fileName).outcome, .skipped(.alreadyTargetFormat), fileName) } } // MARK: - e) Format anhängen, wenn es fehlt func testAppendingFormatUsesWholeStemAsProject() { let parsed = FileNameParser.appendingTargetFormat(to: "Sommerfest Teaser.mov") XCTAssertEqual(parsed?.projectName, "Sommerfest Teaser") XCTAssertEqual(parsed?.targetFileName, "Sommerfest Teaser 4x5.mov") XCTAssertEqual(parsed?.originalFileName, "Sommerfest Teaser.mov") } func testAppendingFormatPicksSeparatorFromName() { XCTAssertEqual(FileNameParser.appendingTargetFormat(to: "SOMOS-v3-DE.mp4")?.targetFileName, "SOMOS-v3-DE-4x5.mp4") XCTAssertEqual(FileNameParser.appendingTargetFormat(to: "SOMOS_v3.mp4")?.targetFileName, "SOMOS_v3_4x5.mp4") XCTAssertEqual(FileNameParser.appendingTargetFormat(to: "SOMOS.mp4")?.targetFileName, "SOMOS-4x5.mp4") } func testAppendingFormatOnlyForMissingFormat() { XCTAssertNil(FileNameParser.appendingTargetFormat(to: "SOMOS-9x16-v3-DE.mp4")) XCTAssertNil(FileNameParser.appendingTargetFormat(to: "SOMOS-4x5-v3-DE.mp4")) XCTAssertNil(FileNameParser.appendingTargetFormat(to: "SOMOS-v3-DE (1).mp4")) } // MARK: - c) Format-Segment in der Ausgabe immer klein geschrieben func testTargetFormatTokenIsAlwaysLowercase() { let result = FileNameParser.parse("SOMOS-9X16-v3-DE.mp4") guard case .parsed(let parsed) = result.outcome else { return XCTFail("Erwartete .parsed, bekam \(result.outcome)") } XCTAssertEqual(parsed.sourceFormatToken, "9X16") XCTAssertTrue(parsed.targetFileName.contains("-4x5-")) XCTAssertFalse(parsed.targetFileName.contains("4X16")) XCTAssertEqual(parsed.targetFileName, "SOMOS-4x5-v3-DE.mp4") } }