diff --git a/pdf-umbenenner-coverage/pom.xml b/pdf-umbenenner-coverage/pom.xml index 92be938..76c1ca9 100644 --- a/pdf-umbenenner-coverage/pom.xml +++ b/pdf-umbenenner-coverage/pom.xml @@ -74,6 +74,16 @@ report-aggregate + + + ${pitest.aggregate.skip} + diff --git a/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/AiRawResponseTest.java b/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/AiRawResponseTest.java new file mode 100644 index 0000000..29aab76 --- /dev/null +++ b/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/AiRawResponseTest.java @@ -0,0 +1,144 @@ +package de.gecheckt.pdf.umbenenner.domain.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for {@link AiRawResponse} value object. + *

+ * Verifies immutability, value semantics, and correct handling of raw AI responses. + */ +class AiRawResponseTest { + + private static final String VALID_JSON_RESPONSE = "{\"date\": \"2026-03-05\", \"title\": \"Stromabrechnung\", \"reasoning\": \"...\"}"; + private static final String MALFORMED_JSON = "{invalid json}"; + private static final String EMPTY_RESPONSE = ""; + private static final String WHITESPACE_RESPONSE = " "; + + @Test + void constructor_createsAiRawResponseWithValidContent() { + AiRawResponse response = new AiRawResponse(VALID_JSON_RESPONSE); + assertNotNull(response); + assertEquals(VALID_JSON_RESPONSE, response.content()); + } + + @Test + void constructor_throwsNullPointerExceptionWhenContentIsNull() { + assertThrows(NullPointerException.class, () -> new AiRawResponse(null), + "Constructor should throw NullPointerException for null content"); + } + + @Test + void constructor_acceptsEmptyContent() { + AiRawResponse response = new AiRawResponse(EMPTY_RESPONSE); + assertEquals(EMPTY_RESPONSE, response.content()); + } + + @Test + void constructor_acceptsMalformedJsonContent() { + AiRawResponse response = new AiRawResponse(MALFORMED_JSON); + assertEquals(MALFORMED_JSON, response.content()); + } + + @Test + void constructor_acceptsWhitespaceOnlyContent() { + AiRawResponse response = new AiRawResponse(WHITESPACE_RESPONSE); + assertEquals(WHITESPACE_RESPONSE, response.content()); + } + + @Test + void content_returnsTheConstructorValue() { + AiRawResponse response = new AiRawResponse(VALID_JSON_RESPONSE); + assertEquals(VALID_JSON_RESPONSE, response.content()); + } + + @Test + void equals_returnsTrueForIdenticalContent() { + AiRawResponse response1 = new AiRawResponse(VALID_JSON_RESPONSE); + AiRawResponse response2 = new AiRawResponse(VALID_JSON_RESPONSE); + assertEquals(response1, response2); + } + + @Test + void equals_returnsFalseForDifferentContent() { + AiRawResponse response1 = new AiRawResponse(VALID_JSON_RESPONSE); + AiRawResponse response2 = new AiRawResponse(MALFORMED_JSON); + assertNotEquals(response1, response2); + } + + @Test + void equals_returnsFalseWhenComparedWithNull() { + AiRawResponse response = new AiRawResponse(VALID_JSON_RESPONSE); + assertNotEquals(null, response); + } + + @Test + void equals_returnsFalseWhenComparedWithDifferentType() { + AiRawResponse response = new AiRawResponse(VALID_JSON_RESPONSE); + assertNotEquals(VALID_JSON_RESPONSE, response); + } + + @Test + void hashCode_isSameForIdenticalContent() { + AiRawResponse response1 = new AiRawResponse(VALID_JSON_RESPONSE); + AiRawResponse response2 = new AiRawResponse(VALID_JSON_RESPONSE); + assertEquals(response1.hashCode(), response2.hashCode()); + } + + @Test + void hashCode_isDifferentForDifferentContent() { + AiRawResponse response1 = new AiRawResponse(VALID_JSON_RESPONSE); + AiRawResponse response2 = new AiRawResponse(MALFORMED_JSON); + assertNotEquals(response1.hashCode(), response2.hashCode()); + } + + @Test + void toString_containsTheContent() { + AiRawResponse response = new AiRawResponse(VALID_JSON_RESPONSE); + assertTrue(response.toString().contains(VALID_JSON_RESPONSE)); + } + + @Test + void aiRawResponseCanBeUsedAsMapKey() { + AiRawResponse response1 = new AiRawResponse(VALID_JSON_RESPONSE); + AiRawResponse response2 = new AiRawResponse(VALID_JSON_RESPONSE); + AiRawResponse response3 = new AiRawResponse(MALFORMED_JSON); + + var map = new java.util.HashMap(); + map.put(response1, "first"); + map.put(response2, "second"); // Should overwrite + map.put(response3, "third"); + + assertEquals(2, map.size()); + assertEquals("second", map.get(response1)); + } + + @Test + void aiRawResponseCanBeUsedInSets() { + AiRawResponse response1 = new AiRawResponse(VALID_JSON_RESPONSE); + AiRawResponse response2 = new AiRawResponse(VALID_JSON_RESPONSE); + AiRawResponse response3 = new AiRawResponse(MALFORMED_JSON); + + var set = new java.util.HashSet(); + set.add(response1); + set.add(response2); // Should not add duplicate + set.add(response3); + + assertEquals(2, set.size()); + } + + @Test + void constructor_preservesExactContentWithSpecialCharacters() { + String contentWithSpecialChars = "{\"text\": \"Special chars: äöü€\\n\\t\"}"; + AiRawResponse response = new AiRawResponse(contentWithSpecialChars); + assertEquals(contentWithSpecialChars, response.content()); + } + + @Test + void constructor_preservesVeryLongContent() { + String longContent = "x".repeat(10000); + AiRawResponse response = new AiRawResponse(longContent); + assertEquals(longContent, response.content()); + } +} diff --git a/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/DocumentFingerprintTest.java b/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/DocumentFingerprintTest.java new file mode 100644 index 0000000..aeacf7b --- /dev/null +++ b/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/DocumentFingerprintTest.java @@ -0,0 +1,152 @@ +package de.gecheckt.pdf.umbenenner.domain.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for {@link DocumentFingerprint} value object. + *

+ * Verifies immutability, value semantics, validation, and correct behavior + * as the stable identity for a document based on its content hash. + */ +class DocumentFingerprintTest { + + private static final String VALID_SHA256 = "0000000000000000000000000000000000000000000000000000000000000000"; + private static final String VALID_SHA256_2 = "a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3"; + private static final String VALID_SHA256_3 = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"; + + @Test + void constructor_createsDocumentFingerprintWithValidSha256() { + DocumentFingerprint fingerprint = new DocumentFingerprint(VALID_SHA256); + assertNotNull(fingerprint); + assertEquals(VALID_SHA256, fingerprint.sha256Hex()); + } + + @Test + void constructor_throwsNullPointerExceptionWhenSha256IsNull() { + assertThrows(NullPointerException.class, () -> new DocumentFingerprint(null), + "Constructor should throw NullPointerException for null sha256Hex"); + } + + @Test + void constructor_throwsIllegalArgumentExceptionWhenSha256IsTooShort() { + String tooShort = "356a192b7913b04c54574d18c28d46e6395428a"; // 39 chars + assertThrows(IllegalArgumentException.class, () -> new DocumentFingerprint(tooShort), + "Constructor should throw IllegalArgumentException for sha256Hex that is too short"); + } + + @Test + void constructor_throwsIllegalArgumentExceptionWhenSha256IsTooLong() { + String tooLong = "356a192b7913b04c54574d18c28d46e6395428abXX"; // 66 chars + assertThrows(IllegalArgumentException.class, () -> new DocumentFingerprint(tooLong), + "Constructor should throw IllegalArgumentException for sha256Hex that is too long"); + } + + @Test + void constructor_throwsIllegalArgumentExceptionWhenSha256ContainsInvalidCharacters() { + String invalid = "356a192b7913b04c54574d18c28d46e6395428ab" + "ZZZZZZZZZZZZZZZZZZZZZZ"; // Has uppercase letters + assertThrows(IllegalArgumentException.class, () -> new DocumentFingerprint(invalid), + "Constructor should throw IllegalArgumentException for sha256Hex with non-hex characters"); + } + + @Test + void constructor_throwsIllegalArgumentExceptionWhenSha256HasUppercaseLetters() { + String uppercase = "356A192B7913B04C54574D18C28D46E6395428AB"; + assertThrows(IllegalArgumentException.class, () -> new DocumentFingerprint(uppercase), + "Constructor should throw IllegalArgumentException for sha256Hex with uppercase letters"); + } + + @Test + void constructor_acceptsValidLowercaseHexadecimal() { + DocumentFingerprint fingerprint1 = new DocumentFingerprint(VALID_SHA256); + DocumentFingerprint fingerprint2 = new DocumentFingerprint(VALID_SHA256_2); + DocumentFingerprint fingerprint3 = new DocumentFingerprint(VALID_SHA256_3); + + assertEquals(VALID_SHA256, fingerprint1.sha256Hex()); + assertEquals(VALID_SHA256_2, fingerprint2.sha256Hex()); + assertEquals(VALID_SHA256_3, fingerprint3.sha256Hex()); + } + + @Test + void constructor_acceptsAllHexDigits() { + String allDigits = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + DocumentFingerprint fingerprint = new DocumentFingerprint(allDigits); + assertEquals(allDigits, fingerprint.sha256Hex()); + } + + @Test + void equals_returnsTrueForIdenticalSha256Values() { + DocumentFingerprint fingerprint1 = new DocumentFingerprint(VALID_SHA256); + DocumentFingerprint fingerprint2 = new DocumentFingerprint(VALID_SHA256); + assertEquals(fingerprint1, fingerprint2); + } + + @Test + void equals_returnsFalseForDifferentSha256Values() { + DocumentFingerprint fingerprint1 = new DocumentFingerprint(VALID_SHA256); + DocumentFingerprint fingerprint2 = new DocumentFingerprint(VALID_SHA256_2); + assertNotEquals(fingerprint1, fingerprint2); + } + + @Test + void equals_returnsFalseWhenComparedWithNull() { + DocumentFingerprint fingerprint = new DocumentFingerprint(VALID_SHA256); + assertNotEquals(null, fingerprint); + } + + @Test + void equals_returnsFalseWhenComparedWithDifferentType() { + DocumentFingerprint fingerprint = new DocumentFingerprint(VALID_SHA256); + assertNotEquals(VALID_SHA256, fingerprint); + } + + @Test + void hashCode_isSameForIdenticalValues() { + DocumentFingerprint fingerprint1 = new DocumentFingerprint(VALID_SHA256); + DocumentFingerprint fingerprint2 = new DocumentFingerprint(VALID_SHA256); + assertEquals(fingerprint1.hashCode(), fingerprint2.hashCode()); + } + + @Test + void hashCode_isDifferentForDifferentValues() { + DocumentFingerprint fingerprint1 = new DocumentFingerprint(VALID_SHA256); + DocumentFingerprint fingerprint2 = new DocumentFingerprint(VALID_SHA256_2); + assertNotEquals(fingerprint1.hashCode(), fingerprint2.hashCode()); + } + + @Test + void toString_containsTheSha256HexValue() { + DocumentFingerprint fingerprint = new DocumentFingerprint(VALID_SHA256); + assertTrue(fingerprint.toString().contains(VALID_SHA256)); + } + + @Test + void documentFingerprintCanBeUsedAsMapKey() { + DocumentFingerprint fingerprint1 = new DocumentFingerprint(VALID_SHA256); + DocumentFingerprint fingerprint2 = new DocumentFingerprint(VALID_SHA256); + DocumentFingerprint fingerprint3 = new DocumentFingerprint(VALID_SHA256_2); + + var map = new java.util.HashMap(); + map.put(fingerprint1, "file1"); + map.put(fingerprint2, "file2"); // Should overwrite since equal + map.put(fingerprint3, "file3"); + + assertEquals(2, map.size()); + assertEquals("file2", map.get(fingerprint1)); + } + + @Test + void documentFingerprintCanBeUsedInSets() { + DocumentFingerprint fingerprint1 = new DocumentFingerprint(VALID_SHA256); + DocumentFingerprint fingerprint2 = new DocumentFingerprint(VALID_SHA256); + DocumentFingerprint fingerprint3 = new DocumentFingerprint(VALID_SHA256_2); + + var set = new java.util.HashSet(); + set.add(fingerprint1); + set.add(fingerprint2); // Should not add duplicate + set.add(fingerprint3); + + assertEquals(2, set.size()); + } +} diff --git a/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/NamingProposalTest.java b/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/NamingProposalTest.java new file mode 100644 index 0000000..d91f88b --- /dev/null +++ b/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/NamingProposalTest.java @@ -0,0 +1,309 @@ +package de.gecheckt.pdf.umbenenner.domain.model; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for {@link NamingProposal} value object. + *

+ * Verifies immutability, value semantics, and correct validation of naming proposals. + */ +class NamingProposalTest { + + private static final LocalDate SAMPLE_DATE = LocalDate.of(2026, 3, 31); + private static final LocalDate DIFFERENT_DATE = LocalDate.of(2025, 12, 25); + private static final String SAMPLE_TITLE = "Stromabrechnung"; + private static final String DIFFERENT_TITLE = "Gasabrechnung"; + private static final String SAMPLE_REASONING = "Found electricity bill dated 2026-03-31 in document text."; + private static final String EMPTY_REASONING = ""; + + @Test + void constructor_createsNamingProposalWithValidValues() { + NamingProposal proposal = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + + assertNotNull(proposal); + assertEquals(SAMPLE_DATE, proposal.resolvedDate()); + assertEquals(DateSource.AI_PROVIDED, proposal.dateSource()); + assertEquals(SAMPLE_TITLE, proposal.validatedTitle()); + assertEquals(SAMPLE_REASONING, proposal.aiReasoning()); + } + + @Test + void constructor_throwsNullPointerExceptionWhenResolvedDateIsNull() { + assertThrows(NullPointerException.class, + () -> new NamingProposal(null, DateSource.AI_PROVIDED, SAMPLE_TITLE, SAMPLE_REASONING), + "Constructor should throw NullPointerException for null resolvedDate"); + } + + @Test + void constructor_throwsNullPointerExceptionWhenDateSourceIsNull() { + assertThrows(NullPointerException.class, + () -> new NamingProposal(SAMPLE_DATE, null, SAMPLE_TITLE, SAMPLE_REASONING), + "Constructor should throw NullPointerException for null dateSource"); + } + + @Test + void constructor_throwsNullPointerExceptionWhenValidatedTitleIsNull() { + assertThrows(NullPointerException.class, + () -> new NamingProposal(SAMPLE_DATE, DateSource.AI_PROVIDED, null, SAMPLE_REASONING), + "Constructor should throw NullPointerException for null validatedTitle"); + } + + @Test + void constructor_throwsIllegalArgumentExceptionWhenValidatedTitleIsEmpty() { + assertThrows(IllegalArgumentException.class, + () -> new NamingProposal(SAMPLE_DATE, DateSource.AI_PROVIDED, "", SAMPLE_REASONING), + "Constructor should throw IllegalArgumentException for empty validatedTitle"); + } + + @Test + void constructor_throwsNullPointerExceptionWhenAiReasoningIsNull() { + assertThrows(NullPointerException.class, + () -> new NamingProposal(SAMPLE_DATE, DateSource.AI_PROVIDED, SAMPLE_TITLE, null), + "Constructor should throw NullPointerException for null aiReasoning"); + } + + @Test + void constructor_acceptsEmptyAiReasoning() { + NamingProposal proposal = new NamingProposal( + SAMPLE_DATE, + DateSource.FALLBACK_CURRENT, + SAMPLE_TITLE, + EMPTY_REASONING); + + assertEquals(EMPTY_REASONING, proposal.aiReasoning()); + } + + @Test + void constructor_acceptsFallbackCurrentDateSource() { + NamingProposal proposal = new NamingProposal( + SAMPLE_DATE, + DateSource.FALLBACK_CURRENT, + SAMPLE_TITLE, + SAMPLE_REASONING); + + assertEquals(DateSource.FALLBACK_CURRENT, proposal.dateSource()); + } + + @Test + void resolvedDate_returnsTheConstructorValue() { + NamingProposal proposal = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + + assertEquals(SAMPLE_DATE, proposal.resolvedDate()); + } + + @Test + void dateSource_returnsTheConstructorValue() { + NamingProposal proposal = new NamingProposal( + SAMPLE_DATE, + DateSource.FALLBACK_CURRENT, + SAMPLE_TITLE, + SAMPLE_REASONING); + + assertEquals(DateSource.FALLBACK_CURRENT, proposal.dateSource()); + } + + @Test + void validatedTitle_returnsTheConstructorValue() { + NamingProposal proposal = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + + assertEquals(SAMPLE_TITLE, proposal.validatedTitle()); + } + + @Test + void aiReasoning_returnsTheConstructorValue() { + NamingProposal proposal = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + + assertEquals(SAMPLE_REASONING, proposal.aiReasoning()); + } + + @Test + void equals_returnsTrueForIdenticalValues() { + NamingProposal proposal1 = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + NamingProposal proposal2 = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + + assertEquals(proposal1, proposal2); + } + + @Test + void equals_returnsFalseForDifferentDates() { + NamingProposal proposal1 = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + NamingProposal proposal2 = new NamingProposal( + DIFFERENT_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + + assertNotEquals(proposal1, proposal2); + } + + @Test + void equals_returnsFalseForDifferentDateSources() { + NamingProposal proposal1 = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + NamingProposal proposal2 = new NamingProposal( + SAMPLE_DATE, + DateSource.FALLBACK_CURRENT, + SAMPLE_TITLE, + SAMPLE_REASONING); + + assertNotEquals(proposal1, proposal2); + } + + @Test + void equals_returnsFalseForDifferentTitles() { + NamingProposal proposal1 = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + NamingProposal proposal2 = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + DIFFERENT_TITLE, + SAMPLE_REASONING); + + assertNotEquals(proposal1, proposal2); + } + + @Test + void equals_returnsFalseForDifferentReasoning() { + NamingProposal proposal1 = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + NamingProposal proposal2 = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + "Different reasoning"); + + assertNotEquals(proposal1, proposal2); + } + + @Test + void equals_returnsFalseWhenComparedWithNull() { + NamingProposal proposal = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + + assertNotEquals(null, proposal); + } + + @Test + void equals_returnsFalseWhenComparedWithDifferentType() { + NamingProposal proposal = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + + assertNotEquals(SAMPLE_TITLE, proposal); + } + + @Test + void hashCode_isSameForIdenticalValues() { + NamingProposal proposal1 = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + NamingProposal proposal2 = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + + assertEquals(proposal1.hashCode(), proposal2.hashCode()); + } + + @Test + void hashCode_isDifferentForDifferentValues() { + NamingProposal proposal1 = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + NamingProposal proposal2 = new NamingProposal( + DIFFERENT_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + + assertNotEquals(proposal1.hashCode(), proposal2.hashCode()); + } + + @Test + void toString_containsAllFields() { + NamingProposal proposal = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + SAMPLE_TITLE, + SAMPLE_REASONING); + + String str = proposal.toString(); + assertNotNull(str); + assertFalse(str.isEmpty()); + } + + @Test + void constructor_acceptsTitleWithSpecialCharacters() { + String titleWithUmlauts = "Stromabrechnung Üben"; + NamingProposal proposal = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + titleWithUmlauts, + SAMPLE_REASONING); + + assertEquals(titleWithUmlauts, proposal.validatedTitle()); + } + + @Test + void constructor_acceptsTitleWithSpaces() { + String titleWithSpaces = "Rechnung für März"; + NamingProposal proposal = new NamingProposal( + SAMPLE_DATE, + DateSource.AI_PROVIDED, + titleWithSpaces, + SAMPLE_REASONING); + + assertEquals(titleWithSpaces, proposal.validatedTitle()); + } +} diff --git a/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/PromptIdentifierTest.java b/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/PromptIdentifierTest.java new file mode 100644 index 0000000..16a11f9 --- /dev/null +++ b/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/PromptIdentifierTest.java @@ -0,0 +1,142 @@ +package de.gecheckt.pdf.umbenenner.domain.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for {@link PromptIdentifier} value object. + *

+ * Verifies immutability, value semantics, and correct handling of prompt identifiers. + */ +class PromptIdentifierTest { + + private static final String VALID_IDENTIFIER_1 = "prompt_de_v1.txt"; + private static final String VALID_IDENTIFIER_2 = "2026-03-v2"; + private static final String VALID_IDENTIFIER_3 = "sha256:abc123def456"; + + @Test + void constructor_createsPromptIdentifierWithValidIdentifier() { + PromptIdentifier identifier = new PromptIdentifier(VALID_IDENTIFIER_1); + assertNotNull(identifier); + assertEquals(VALID_IDENTIFIER_1, identifier.identifier()); + } + + @Test + void constructor_throwsNullPointerExceptionWhenIdentifierIsNull() { + assertThrows(NullPointerException.class, () -> new PromptIdentifier(null), + "Constructor should throw NullPointerException for null identifier"); + } + + @Test + void constructor_acceptsEmptyIdentifier() { + PromptIdentifier identifier = new PromptIdentifier(""); + assertEquals("", identifier.identifier()); + } + + @Test + void constructor_acceptsVariousIdentifierFormats() { + PromptIdentifier id1 = new PromptIdentifier(VALID_IDENTIFIER_1); + PromptIdentifier id2 = new PromptIdentifier(VALID_IDENTIFIER_2); + PromptIdentifier id3 = new PromptIdentifier(VALID_IDENTIFIER_3); + + assertEquals(VALID_IDENTIFIER_1, id1.identifier()); + assertEquals(VALID_IDENTIFIER_2, id2.identifier()); + assertEquals(VALID_IDENTIFIER_3, id3.identifier()); + } + + @Test + void identifier_returnsTheConstructorValue() { + PromptIdentifier identifier = new PromptIdentifier(VALID_IDENTIFIER_1); + assertEquals(VALID_IDENTIFIER_1, identifier.identifier()); + } + + @Test + void equals_returnsTrueForIdenticalIdentifiers() { + PromptIdentifier id1 = new PromptIdentifier(VALID_IDENTIFIER_1); + PromptIdentifier id2 = new PromptIdentifier(VALID_IDENTIFIER_1); + assertEquals(id1, id2); + } + + @Test + void equals_returnsFalseForDifferentIdentifiers() { + PromptIdentifier id1 = new PromptIdentifier(VALID_IDENTIFIER_1); + PromptIdentifier id2 = new PromptIdentifier(VALID_IDENTIFIER_2); + assertNotEquals(id1, id2); + } + + @Test + void equals_returnsFalseWhenComparedWithNull() { + PromptIdentifier identifier = new PromptIdentifier(VALID_IDENTIFIER_1); + assertNotEquals(null, identifier); + } + + @Test + void equals_returnsFalseWhenComparedWithDifferentType() { + PromptIdentifier identifier = new PromptIdentifier(VALID_IDENTIFIER_1); + assertNotEquals(VALID_IDENTIFIER_1, identifier); + } + + @Test + void hashCode_isSameForIdenticalIdentifiers() { + PromptIdentifier id1 = new PromptIdentifier(VALID_IDENTIFIER_1); + PromptIdentifier id2 = new PromptIdentifier(VALID_IDENTIFIER_1); + assertEquals(id1.hashCode(), id2.hashCode()); + } + + @Test + void hashCode_isDifferentForDifferentIdentifiers() { + PromptIdentifier id1 = new PromptIdentifier(VALID_IDENTIFIER_1); + PromptIdentifier id2 = new PromptIdentifier(VALID_IDENTIFIER_2); + assertNotEquals(id1.hashCode(), id2.hashCode()); + } + + @Test + void toString_containsTheIdentifier() { + PromptIdentifier identifier = new PromptIdentifier(VALID_IDENTIFIER_1); + assertTrue(identifier.toString().contains(VALID_IDENTIFIER_1)); + } + + @Test + void promptIdentifierCanBeUsedAsMapKey() { + PromptIdentifier id1 = new PromptIdentifier(VALID_IDENTIFIER_1); + PromptIdentifier id2 = new PromptIdentifier(VALID_IDENTIFIER_1); + PromptIdentifier id3 = new PromptIdentifier(VALID_IDENTIFIER_2); + + var map = new java.util.HashMap(); + map.put(id1, "first"); + map.put(id2, "second"); // Should overwrite + map.put(id3, "third"); + + assertEquals(2, map.size()); + assertEquals("second", map.get(id1)); + } + + @Test + void promptIdentifierCanBeUsedInSets() { + PromptIdentifier id1 = new PromptIdentifier(VALID_IDENTIFIER_1); + PromptIdentifier id2 = new PromptIdentifier(VALID_IDENTIFIER_1); + PromptIdentifier id3 = new PromptIdentifier(VALID_IDENTIFIER_2); + + var set = new java.util.HashSet(); + set.add(id1); + set.add(id2); // Should not add duplicate + set.add(id3); + + assertEquals(2, set.size()); + } + + @Test + void constructor_acceptsIdentifierWithSpecialCharacters() { + String specialIdentifier = "prompt-de_v1.2.3@stable"; + PromptIdentifier identifier = new PromptIdentifier(specialIdentifier); + assertEquals(specialIdentifier, identifier.identifier()); + } + + @Test + void constructor_acceptsVeryLongIdentifier() { + String longIdentifier = "x".repeat(1000); + PromptIdentifier identifier = new PromptIdentifier(longIdentifier); + assertEquals(longIdentifier, identifier.identifier()); + } +} diff --git a/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/SourceDocumentCandidateTest.java b/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/SourceDocumentCandidateTest.java new file mode 100644 index 0000000..3916a7b --- /dev/null +++ b/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/SourceDocumentCandidateTest.java @@ -0,0 +1,264 @@ +package de.gecheckt.pdf.umbenenner.domain.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for {@link SourceDocumentCandidate} value object. + *

+ * Verifies immutability, value semantics, and correct validation of source document candidates. + */ +class SourceDocumentCandidateTest { + + private static final String VALID_IDENTIFIER = "document.pdf"; + private static final String VALID_IDENTIFIER_2 = "another_file.PDF"; + private static final long VALID_FILE_SIZE = 1024L; + private static final long ZERO_FILE_SIZE = 0L; + private static final String VALID_LOCATOR_VALUE = "/home/user/docs/document.pdf"; + + @Test + void constructor_createsSourceDocumentCandidateWithValidValues() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentCandidate candidate = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator); + + assertNotNull(candidate); + assertEquals(VALID_IDENTIFIER, candidate.uniqueIdentifier()); + assertEquals(VALID_FILE_SIZE, candidate.fileSizeBytes()); + assertEquals(locator, candidate.locator()); + } + + @Test + void constructor_throwsNullPointerExceptionWhenUniqueIdentifierIsNull() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + assertThrows(NullPointerException.class, + () -> new SourceDocumentCandidate(null, VALID_FILE_SIZE, locator), + "Constructor should throw NullPointerException for null uniqueIdentifier"); + } + + @Test + void constructor_throwsIllegalArgumentExceptionWhenUniqueIdentifierIsEmpty() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + assertThrows(IllegalArgumentException.class, + () -> new SourceDocumentCandidate("", VALID_FILE_SIZE, locator), + "Constructor should throw IllegalArgumentException for empty uniqueIdentifier"); + } + + @Test + void constructor_throwsIllegalArgumentExceptionWhenFileSizeIsNegative() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + assertThrows(IllegalArgumentException.class, + () -> new SourceDocumentCandidate(VALID_IDENTIFIER, -1L, locator), + "Constructor should throw IllegalArgumentException for negative fileSizeBytes"); + } + + @Test + void constructor_throwsNullPointerExceptionWhenLocatorIsNull() { + assertThrows(NullPointerException.class, + () -> new SourceDocumentCandidate(VALID_IDENTIFIER, VALID_FILE_SIZE, null), + "Constructor should throw NullPointerException for null locator"); + } + + @Test + void constructor_acceptsZeroFileSize() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentCandidate candidate = new SourceDocumentCandidate( + VALID_IDENTIFIER, + ZERO_FILE_SIZE, + locator); + + assertEquals(ZERO_FILE_SIZE, candidate.fileSizeBytes()); + } + + @Test + void constructor_acceptsVeryLargeFileSize() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + long largeSize = Long.MAX_VALUE; + SourceDocumentCandidate candidate = new SourceDocumentCandidate( + VALID_IDENTIFIER, + largeSize, + locator); + + assertEquals(largeSize, candidate.fileSizeBytes()); + } + + @Test + void uniqueIdentifier_returnsTheConstructorValue() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentCandidate candidate = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator); + + assertEquals(VALID_IDENTIFIER, candidate.uniqueIdentifier()); + } + + @Test + void fileSizeBytes_returnsTheConstructorValue() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentCandidate candidate = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator); + + assertEquals(VALID_FILE_SIZE, candidate.fileSizeBytes()); + } + + @Test + void locator_returnsTheConstructorValue() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentCandidate candidate = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator); + + assertEquals(locator, candidate.locator()); + } + + @Test + void equals_returnsTrueForIdenticalValues() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentCandidate candidate1 = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator); + SourceDocumentCandidate candidate2 = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator); + + assertEquals(candidate1, candidate2); + } + + @Test + void equals_returnsFalseForDifferentUniqueIdentifiers() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentCandidate candidate1 = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator); + SourceDocumentCandidate candidate2 = new SourceDocumentCandidate( + VALID_IDENTIFIER_2, + VALID_FILE_SIZE, + locator); + + assertNotEquals(candidate1, candidate2); + } + + @Test + void equals_returnsFalseForDifferentFileSizes() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentCandidate candidate1 = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator); + SourceDocumentCandidate candidate2 = new SourceDocumentCandidate( + VALID_IDENTIFIER, + 2048L, + locator); + + assertNotEquals(candidate1, candidate2); + } + + @Test + void equals_returnsFalseForDifferentLocators() { + SourceDocumentLocator locator1 = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentLocator locator2 = new SourceDocumentLocator("/other/path/document.pdf"); + SourceDocumentCandidate candidate1 = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator1); + SourceDocumentCandidate candidate2 = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator2); + + assertNotEquals(candidate1, candidate2); + } + + @Test + void equals_returnsFalseWhenComparedWithNull() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentCandidate candidate = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator); + + assertNotEquals(null, candidate); + } + + @Test + void equals_returnsFalseWhenComparedWithDifferentType() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentCandidate candidate = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator); + + assertNotEquals(VALID_IDENTIFIER, candidate); + } + + @Test + void hashCode_isSameForIdenticalValues() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentCandidate candidate1 = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator); + SourceDocumentCandidate candidate2 = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator); + + assertEquals(candidate1.hashCode(), candidate2.hashCode()); + } + + @Test + void hashCode_isDifferentForDifferentValues() { + SourceDocumentLocator locator1 = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentLocator locator2 = new SourceDocumentLocator("/other/path/document.pdf"); + SourceDocumentCandidate candidate1 = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator1); + SourceDocumentCandidate candidate2 = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator2); + + assertNotEquals(candidate1.hashCode(), candidate2.hashCode()); + } + + @Test + void toString_containsAllFields() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentCandidate candidate = new SourceDocumentCandidate( + VALID_IDENTIFIER, + VALID_FILE_SIZE, + locator); + + String str = candidate.toString(); + assertNotNull(str); + assertFalse(str.isEmpty()); + } + + @Test + void sourceDocumentCandidateCanBeUsedInCollections() { + SourceDocumentLocator locator1 = new SourceDocumentLocator(VALID_LOCATOR_VALUE); + SourceDocumentLocator locator2 = new SourceDocumentLocator("/other/path/document.pdf"); + + SourceDocumentCandidate candidate1 = new SourceDocumentCandidate(VALID_IDENTIFIER, VALID_FILE_SIZE, locator1); + SourceDocumentCandidate candidate2 = new SourceDocumentCandidate(VALID_IDENTIFIER, VALID_FILE_SIZE, locator1); + SourceDocumentCandidate candidate3 = new SourceDocumentCandidate(VALID_IDENTIFIER_2, VALID_FILE_SIZE, locator2); + + var set = new java.util.HashSet(); + set.add(candidate1); + set.add(candidate2); // Should not add duplicate + set.add(candidate3); + + assertEquals(2, set.size()); + } +} diff --git a/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/SourceDocumentLocatorTest.java b/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/SourceDocumentLocatorTest.java new file mode 100644 index 0000000..a28294c --- /dev/null +++ b/pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/SourceDocumentLocatorTest.java @@ -0,0 +1,149 @@ +package de.gecheckt.pdf.umbenenner.domain.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for {@link SourceDocumentLocator} value object. + *

+ * Verifies immutability, value semantics, and validation of the opaque document locator. + */ +class SourceDocumentLocatorTest { + + private static final String VALID_LOCATOR = "C:\\Users\\test\\documents\\file.pdf"; + private static final String VALID_LOCATOR_2 = "/home/user/documents/another.pdf"; + private static final String VALID_LOCATOR_3 = "relative/path/to/document.pdf"; + + @Test + void constructor_createsSourceDocumentLocatorWithValidValue() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR); + assertNotNull(locator); + assertEquals(VALID_LOCATOR, locator.value()); + } + + @Test + void constructor_throwsNullPointerExceptionWhenValueIsNull() { + assertThrows(NullPointerException.class, () -> new SourceDocumentLocator(null), + "Constructor should throw NullPointerException for null value"); + } + + @Test + void constructor_throwsIllegalArgumentExceptionWhenValueIsEmpty() { + assertThrows(IllegalArgumentException.class, () -> new SourceDocumentLocator(""), + "Constructor should throw IllegalArgumentException for empty value"); + } + + @Test + void constructor_acceptsValidPaths() { + SourceDocumentLocator locator1 = new SourceDocumentLocator(VALID_LOCATOR); + SourceDocumentLocator locator2 = new SourceDocumentLocator(VALID_LOCATOR_2); + SourceDocumentLocator locator3 = new SourceDocumentLocator(VALID_LOCATOR_3); + + assertEquals(VALID_LOCATOR, locator1.value()); + assertEquals(VALID_LOCATOR_2, locator2.value()); + assertEquals(VALID_LOCATOR_3, locator3.value()); + } + + @Test + void constructor_acceptsOpaqueValue() { + // The locator is intentionally opaque - it can contain any non-empty string + String opaqueValue = "adapter-internal-encoding:12345:abcdef"; + SourceDocumentLocator locator = new SourceDocumentLocator(opaqueValue); + assertEquals(opaqueValue, locator.value()); + } + + @Test + void value_returnsTheConstructorValue() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR); + assertEquals(VALID_LOCATOR, locator.value()); + } + + @Test + void equals_returnsTrueForIdenticalValues() { + SourceDocumentLocator locator1 = new SourceDocumentLocator(VALID_LOCATOR); + SourceDocumentLocator locator2 = new SourceDocumentLocator(VALID_LOCATOR); + assertEquals(locator1, locator2); + } + + @Test + void equals_returnsFalseForDifferentValues() { + SourceDocumentLocator locator1 = new SourceDocumentLocator(VALID_LOCATOR); + SourceDocumentLocator locator2 = new SourceDocumentLocator(VALID_LOCATOR_2); + assertNotEquals(locator1, locator2); + } + + @Test + void equals_returnsFalseWhenComparedWithNull() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR); + assertNotEquals(null, locator); + } + + @Test + void equals_returnsFalseWhenComparedWithDifferentType() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR); + assertNotEquals(VALID_LOCATOR, locator); + } + + @Test + void hashCode_isSameForIdenticalValues() { + SourceDocumentLocator locator1 = new SourceDocumentLocator(VALID_LOCATOR); + SourceDocumentLocator locator2 = new SourceDocumentLocator(VALID_LOCATOR); + assertEquals(locator1.hashCode(), locator2.hashCode()); + } + + @Test + void hashCode_isDifferentForDifferentValues() { + SourceDocumentLocator locator1 = new SourceDocumentLocator(VALID_LOCATOR); + SourceDocumentLocator locator2 = new SourceDocumentLocator(VALID_LOCATOR_2); + assertNotEquals(locator1.hashCode(), locator2.hashCode()); + } + + @Test + void toString_containsTheValue() { + SourceDocumentLocator locator = new SourceDocumentLocator(VALID_LOCATOR); + assertTrue(locator.toString().contains(VALID_LOCATOR)); + } + + @Test + void sourceDocumentLocatorCanBeUsedAsMapKey() { + SourceDocumentLocator locator1 = new SourceDocumentLocator(VALID_LOCATOR); + SourceDocumentLocator locator2 = new SourceDocumentLocator(VALID_LOCATOR); + SourceDocumentLocator locator3 = new SourceDocumentLocator(VALID_LOCATOR_2); + + var map = new java.util.HashMap(); + map.put(locator1, "file1"); + map.put(locator2, "file2"); // Should overwrite + map.put(locator3, "file3"); + + assertEquals(2, map.size()); + assertEquals("file2", map.get(locator1)); + } + + @Test + void sourceDocumentLocatorCanBeUsedInSets() { + SourceDocumentLocator locator1 = new SourceDocumentLocator(VALID_LOCATOR); + SourceDocumentLocator locator2 = new SourceDocumentLocator(VALID_LOCATOR); + SourceDocumentLocator locator3 = new SourceDocumentLocator(VALID_LOCATOR_2); + + var set = new java.util.HashSet(); + set.add(locator1); + set.add(locator2); // Should not add duplicate + set.add(locator3); + + assertEquals(2, set.size()); + } + + @Test + void constructor_acceptsWhitespaceInValue() { + String valueWithWhitespace = "path with spaces/to/file.pdf"; + SourceDocumentLocator locator = new SourceDocumentLocator(valueWithWhitespace); + assertEquals(valueWithWhitespace, locator.value()); + } + + @Test + void constructor_acceptsSingleCharacterValue() { + SourceDocumentLocator locator = new SourceDocumentLocator("a"); + assertEquals("a", locator.value()); + } +} diff --git a/pom.xml b/pom.xml index e02aaa3..02d365f 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,11 @@ UTF-8 21 + + + false + 2.23.1 3.0.2