Fix #16: TitleCharacterRule um Punkt, Komma und Ampersand erweitern

- Erweitere TitleCharacterRule.isAllowed() um die Zeichen: . (Punkt), , (Komma), & (Ampersand)
- Passe JavaDoc-Kommentare auf Deutsch an
- Aktualisiere TitleCharacterRuleTest: ändere Punkt-Test von disallowed zu allowed
- Füge Tests für Komma und Ampersand hinzu
- Füge Tests hinzu, die Windows-Sonderzeichen (\ / : * ? " < > |) weiterhin als ungültig bestätigen
- Aktualisiere TargetFilenameBuildingServiceTest für den neuen Test-Fall
- Dokumentation: fachliche-anforderungen.md und CLAUDE.md aktualisiert

mvn clean verify erfolgreich bestanden

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-04-23 17:07:07 +02:00
parent 1df541d0f9
commit c46294159c
5 changed files with 67 additions and 26 deletions
+1 -1
View File
@@ -123,7 +123,7 @@ Ein Arbeitspaket ist erst fertig, wenn die betroffenen öffentlichen Klassen und
- Bei Namenskollisionen: `YYYY-MM-DD - Titel(1).pdf`, `YYYY-MM-DD - Titel(2).pdf`, ...
- Die **konfigurierte maximale Titellänge** gilt nur für den **Basistitel**; das Dubletten-Suffix zählt nicht mit
- Das Dubletten-Suffix wird unmittelbar vor `.pdf` angehängt
- Titel sind **deutsch**, verständlich, eindeutig und enthalten keine Sonderzeichen außer Leerzeichen und Bindestrichen
- Titel sind **deutsch**, verständlich, eindeutig und enthalten keine Sonderzeichen außer Leerzeichen, Bindestrichen, Punkten, Kommas und Ampersands
- Eigennamen bleiben unverändert
- Datumsermittlung mit Priorität aus den fachlichen Anforderungen; wenn kein belastbares Datum eindeutig ableitbar ist, ist das **aktuelle Datum** als Fallback erlaubt
- Mehrdeutige Dokumente liefern **kein** unsicheres Ergebnis, sondern einen Fehler
+1 -1
View File
@@ -68,7 +68,7 @@ Fallback auf aktuelles Datum ist erlaubt, wenn kein belastbares Datum eindeutig
- maximal **konfigurierbare Anzahl Zeichen (Basistitel, Default 60, gültiger Bereich 10..120)**
- verständlich und eindeutig
- keine Sonderzeichen außer Leerzeichen und Bindestrichen
- keine Sonderzeichen außer Leerzeichen, Bindestrichen, Punkten, Kommas und Ampersands
---
@@ -1,15 +1,15 @@
package de.gecheckt.pdf.umbenenner.application.service;
/**
* Canonical rule for allowed title characters.
* Kanonische Regel für erlaubte Titelzeichen.
* <p>
* A title may contain only Unicode letters (including German Umlauts and ß),
* decimal digits, ASCII spaces, and hyphens ({@code -}).
* All other characters are considered disallowed.
* Ein Titel darf nur Unicode-Buchstaben (einschließlich deutscher Umlaute und ß),
* Ziffern, ASCII-Leerzeichen, Bindestriche ({@code -}), Punkte ({@code .}),
* Kommas ({@code ,}) und Ampersands ({@code &}) enthalten.
* Alle anderen Zeichen sind nicht erlaubt.
* <p>
* This class is the single authoritative implementation of the character-allowlist
* rule. All services that need to validate or verify title characters must delegate
* to {@link #isAllowed(String)} instead of maintaining their own copies.
* Diese Klasse ist die einzige autoritative Implementierung der Zeichenerlaubnis-Regel.
* Alle Services, die Titelzeichen validieren müssen, delegieren an {@link #isAllowed(String)}.
*/
public final class TitleCharacterRule {
@@ -18,20 +18,23 @@ public final class TitleCharacterRule {
}
/**
* Returns {@code true} if every character in {@code title} is a letter, digit,
* ASCII space, or hyphen ({@code -}).
* Gibt {@code true} zurück, wenn jedes Zeichen im {@code title} ein Buchstabe, eine Ziffer,
* ein ASCII-Leerzeichen, ein Bindestrich ({@code -}), ein Punkt ({@code .}),
* ein Komma ({@code ,}) oder ein Ampersand ({@code &}) ist.
* <p>
* Unicode letters — including German Umlauts (ä, ö, ü, Ä, Ö, Ü) and ß — are
* permitted. An empty string is considered allowed.
* Unicode-Buchstabeneinschließlich deutscher Umlaute (ä, ö, ü, Ä, Ö, Ü) und ß — sind
* erlaubt. Eine leere Zeichenkette ist erlaubt.
*
* @param title the title to check; must not be null
* @return {@code true} if all characters pass the allowlist; {@code false} otherwise
* @throws NullPointerException if {@code title} is null
* @param title der zu prüfende Titel; darf nicht null sein
* @return {@code true} wenn alle Zeichen die Erlaubnisliste erfüllen; {@code false} andernfalls
* @throws NullPointerException wenn {@code title} null ist
*/
public static boolean isAllowed(String title) {
for (int i = 0; i < title.length(); i++) {
char c = title.charAt(i);
if (!Character.isLetter(c) && !Character.isDigit(c) && c != ' ' && c != '-') {
// Erlaubt: Buchstaben, Ziffern, Leerzeichen, Bindestrich, Punkt, Komma, Ampersand
if (!Character.isLetter(c) && !Character.isDigit(c) && c != ' ' && c != '-'
&& c != '.' && c != ',' && c != '&') {
return false;
}
}
@@ -250,14 +250,15 @@ class TargetFilenameBuildingServiceTest {
}
@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");
void buildBaseFilename_titleWithDot_returnsBaseFilenameReady() {
// Punkt (.) ist nun ein erlaubtes Zeichen in Titeln
ProcessingAttempt attempt = proposalAttempt(LocalDate.of(2026, 1, 1), "Rechnung.Kopie");
BaseFilenameResult result = TargetFilenameBuildingService.buildBaseFilename(attempt, TEST_MAX_TITLE_LENGTH);
assertThat(result).isInstanceOf(InconsistentProposalState.class);
assertThat(result).isInstanceOf(BaseFilenameReady.class);
assertThat((BaseFilenameReady) result).extracting("baseFilename")
.isEqualTo("2026-01-01 - Rechnung.Kopie.pdf");
}
// -------------------------------------------------------------------------
@@ -7,8 +7,9 @@ import org.junit.jupiter.api.Test;
/**
* Unit tests for {@link TitleCharacterRule}.
* <p>
* Verifies the canonical character-allowlist rule: letters (including German Umlauts
* and ß), digits, ASCII spaces, and hyphens are permitted; everything else is not.
* Prüft die kanonische Zeichenerlaubnis-Regel: Buchstaben (einschließlich deutscher Umlaute
* und ß), Ziffern, ASCII-Leerzeichen, Bindestriche, Punkte, Kommas und Ampersands sind
* erlaubt; alles andere ist nicht erlaubt.
*/
class TitleCharacterRuleTest {
@@ -46,6 +47,21 @@ class TitleCharacterRuleTest {
assertThat(TitleCharacterRule.isAllowed("Kfz-Haftpflicht 2026")).isTrue();
}
@Test
void isAllowed_comma_returnsTrue() {
assertThat(TitleCharacterRule.isAllowed("Müller, Hans")).isTrue();
}
@Test
void isAllowed_ampersand_returnsTrue() {
assertThat(TitleCharacterRule.isAllowed("Müller & Söhne")).isTrue();
}
@Test
void isAllowed_dotCommaAmpersand_returnsTrue() {
assertThat(TitleCharacterRule.isAllowed("Firma Dr. Meyer, Müller & Co.")).isTrue();
}
@Test
void isAllowed_emptyString_returnsTrue() {
assertThat(TitleCharacterRule.isAllowed("")).isTrue();
@@ -61,8 +77,8 @@ class TitleCharacterRuleTest {
}
@Test
void isAllowed_dot_returnsFalse() {
assertThat(TitleCharacterRule.isAllowed("Rechnung.pdf")).isFalse();
void isAllowed_dot_returnsTrue() {
assertThat(TitleCharacterRule.isAllowed("Rechnung.Kopie")).isTrue();
}
@Test
@@ -104,4 +120,25 @@ class TitleCharacterRuleTest {
void isAllowed_underscore_returnsFalse() {
assertThat(TitleCharacterRule.isAllowed("Rechnung_2026")).isFalse();
}
// Windows-Sonderzeichen müssen weiterhin abgelehnt werden
@Test
void isAllowed_doubleQuote_returnsFalse() {
assertThat(TitleCharacterRule.isAllowed("Rechnung \"2026\"")).isFalse();
}
@Test
void isAllowed_lessThan_returnsFalse() {
assertThat(TitleCharacterRule.isAllowed("Rg < 2026")).isFalse();
}
@Test
void isAllowed_greaterThan_returnsFalse() {
assertThat(TitleCharacterRule.isAllowed("Rg > 2026")).isFalse();
}
@Test
void isAllowed_pipe_returnsFalse() {
assertThat(TitleCharacterRule.isAllowed("Rg | Strom")).isFalse();
}
}