diff --git a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/service/TargetFilenameBuildingService.java b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/service/TargetFilenameBuildingService.java index 4641da3..88d5d6a 100644 --- a/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/service/TargetFilenameBuildingService.java +++ b/pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/service/TargetFilenameBuildingService.java @@ -91,16 +91,16 @@ public final class TargetFilenameBuildingService { * * If any rule is violated, the state is treated as an * {@link InconsistentProposalState}. *

- * Windows compatibility: The final filename is cleaned of Windows-incompatible characters - * (e.g., {@code < > : " / \ | ? *}) to ensure the resulting filename can be created on - * Windows systems. This is a defensive measure; the validated title is already expected - * to contain only letters, digits, and spaces. + * Windows compatibility: Windows-incompatible characters + * (e.g., {@code < > : " / \ | ? *}) are removed from the title before final validation. + * This ensures the resulting filename can be created on Windows systems. + * The 20-character rule is applied to the original title before cleaning. *

* The 20-character limit applies exclusively to the base title. A duplicate-avoidance * suffix (e.g., {@code (1)}) may be appended by the target folder adapter after this @@ -132,21 +132,23 @@ public final class TargetFilenameBuildingService { + title + "'"); } - if (!isAllowedTitleCharacters(title)) { - return new InconsistentProposalState( - "Leading PROPOSAL_READY attempt has title with disallowed characters " - + "(only letters, digits, and spaces are permitted): '" - + title + "'"); - } - - // Defensive Windows compatibility: remove Windows-incompatible characters + // Remove Windows-incompatible characters to enable technical Windows compatibility String cleanedTitle = removeWindowsIncompatibleCharacters(title); + if (cleanedTitle.isBlank()) { return new InconsistentProposalState( "Title becomes empty after Windows-compatibility cleaning: '" + title + "'"); } + // After cleaning, verify that only letters, digits, and spaces remain + if (!isAllowedTitleCharacters(cleanedTitle)) { + return new InconsistentProposalState( + "After Windows-compatibility cleaning, title contains disallowed characters " + + "(only letters, digits, and spaces are permitted): '" + + cleanedTitle + "'"); + } + // Build: YYYY-MM-DD - Titel.pdf String baseFilename = date + " - " + cleanedTitle + ".pdf"; return new BaseFilenameReady(baseFilename); diff --git a/pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/service/TargetFilenameBuildingServiceTest.java b/pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/service/TargetFilenameBuildingServiceTest.java index 0b1e49e..e70ffca 100644 --- a/pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/service/TargetFilenameBuildingServiceTest.java +++ b/pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/service/TargetFilenameBuildingServiceTest.java @@ -202,16 +202,35 @@ class TargetFilenameBuildingServiceTest { } @Test - void buildBaseFilename_titleWithSlash_returnsInconsistentProposalState() { + void buildBaseFilename_titleWithSlash_removesWindowsIncompatibleCharacterAndSucceeds() { + // Slash (/) is a Windows-incompatible character. It should be removed, + // leaving "RgStrom" which is valid (letters only) ProcessingAttempt attempt = proposalAttempt(LocalDate.of(2026, 1, 1), "Rg/Strom"); BaseFilenameResult result = TargetFilenameBuildingService.buildBaseFilename(attempt); - assertThat(result).isInstanceOf(InconsistentProposalState.class); + assertThat(result).isInstanceOf(BaseFilenameReady.class); + assertThat(((BaseFilenameReady) result).baseFilename()) + .isEqualTo("2026-01-01 - RgStrom.pdf"); + } + + @Test + void buildBaseFilename_titleWithMultipleWindowsChars_removesAllAndSucceeds() { + // Multiple Windows-incompatible characters (: and ") should be removed, + // leaving "Rechnung 2026" which is valid (letters, digits, and spaces) + ProcessingAttempt attempt = proposalAttempt(LocalDate.of(2026, 1, 1), "Rechnung: \"2026\""); + + BaseFilenameResult result = TargetFilenameBuildingService.buildBaseFilename(attempt); + + assertThat(result).isInstanceOf(BaseFilenameReady.class); + assertThat(((BaseFilenameReady) result).baseFilename()) + .isEqualTo("2026-01-01 - Rechnung 2026.pdf"); } @Test void buildBaseFilename_titleWithDot_returnsInconsistentProposalState() { + // Dot (.) is NOT a Windows-incompatible character (as per our list < > : " / \ | ? *) + // So it remains in the cleaned title and causes validation to fail ProcessingAttempt attempt = proposalAttempt(LocalDate.of(2026, 1, 1), "Rechnung.pdf"); BaseFilenameResult result = TargetFilenameBuildingService.buildBaseFilename(attempt);