1
0

Windows-Zeichenbereinigung im Basis-Dateinamen wirksam gemacht und Tests

korrigiert
This commit is contained in:
2026-04-07 14:18:18 +02:00
parent 7e4201b651
commit df0a3ad07b
2 changed files with 37 additions and 16 deletions

View File

@@ -91,16 +91,16 @@ public final class TargetFilenameBuildingService {
* <ul> * <ul>
* <li>Resolved date must be non-null.</li> * <li>Resolved date must be non-null.</li>
* <li>Validated title must be non-null and non-blank.</li> * <li>Validated title must be non-null and non-blank.</li>
* <li>Validated title must not exceed 20 characters.</li> * <li>Validated title must not exceed 20 characters (before Windows cleaning).</li>
* <li>Validated title must contain only letters, digits, and spaces.</li> * <li>After Windows-character cleaning, title must contain only letters, digits, and spaces.</li>
* </ul> * </ul>
* If any rule is violated, the state is treated as an * If any rule is violated, the state is treated as an
* {@link InconsistentProposalState}. * {@link InconsistentProposalState}.
* <p> * <p>
* Windows compatibility: The final filename is cleaned of Windows-incompatible characters * Windows compatibility: Windows-incompatible characters
* (e.g., {@code < > : " / \ | ? *}) to ensure the resulting filename can be created on * (e.g., {@code < > : " / \ | ? *}) are removed from the title before final validation.
* Windows systems. This is a defensive measure; the validated title is already expected * This ensures the resulting filename can be created on Windows systems.
* to contain only letters, digits, and spaces. * The 20-character rule is applied to the original title before cleaning.
* <p> * <p>
* The 20-character limit applies exclusively to the base title. A duplicate-avoidance * 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 * suffix (e.g., {@code (1)}) may be appended by the target folder adapter after this
@@ -132,21 +132,23 @@ public final class TargetFilenameBuildingService {
+ title + "'"); + title + "'");
} }
if (!isAllowedTitleCharacters(title)) { // Remove Windows-incompatible characters to enable technical Windows compatibility
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
String cleanedTitle = removeWindowsIncompatibleCharacters(title); String cleanedTitle = removeWindowsIncompatibleCharacters(title);
if (cleanedTitle.isBlank()) { if (cleanedTitle.isBlank()) {
return new InconsistentProposalState( return new InconsistentProposalState(
"Title becomes empty after Windows-compatibility cleaning: '" "Title becomes empty after Windows-compatibility cleaning: '"
+ title + "'"); + 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 // Build: YYYY-MM-DD - Titel.pdf
String baseFilename = date + " - " + cleanedTitle + ".pdf"; String baseFilename = date + " - " + cleanedTitle + ".pdf";
return new BaseFilenameReady(baseFilename); return new BaseFilenameReady(baseFilename);

View File

@@ -202,16 +202,35 @@ class TargetFilenameBuildingServiceTest {
} }
@Test @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"); ProcessingAttempt attempt = proposalAttempt(LocalDate.of(2026, 1, 1), "Rg/Strom");
BaseFilenameResult result = TargetFilenameBuildingService.buildBaseFilename(attempt); 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 @Test
void buildBaseFilename_titleWithDot_returnsInconsistentProposalState() { 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"); ProcessingAttempt attempt = proposalAttempt(LocalDate.of(2026, 1, 1), "Rechnung.pdf");
BaseFilenameResult result = TargetFilenameBuildingService.buildBaseFilename(attempt); BaseFilenameResult result = TargetFilenameBuildingService.buildBaseFilename(attempt);