1
0

M3-Quellordneradapter korrigiert und leere PDF-Kandidaten zugelassen

This commit is contained in:
2026-04-01 18:35:28 +02:00
parent dd282e8f7b
commit 8f138d4cfa
4 changed files with 378 additions and 6 deletions

View File

@@ -0,0 +1,209 @@
package de.gecheckt.pdf.umbenenner.adapter.outbound.sourcedocument;
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentAccessException;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests for {@link SourceDocumentCandidatesPortAdapter}.
*
* @since M3-AP-002
*/
class SourceDocumentCandidatesPortAdapterTest {
@TempDir
Path tempDir;
private SourceDocumentCandidatesPortAdapter adapter;
@BeforeEach
void setUp() {
adapter = new SourceDocumentCandidatesPortAdapter(tempDir);
}
@Test
void testLoadCandidates_EmptyFolder() throws IOException {
List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
assertNotNull(candidates);
assertTrue(candidates.isEmpty(), "Empty folder should return empty list");
}
@Test
void testLoadCandidates_OnlyPdfFiles() throws IOException {
// Create test PDF files
Path pdf1 = tempDir.resolve("document1.pdf");
Path pdf2 = tempDir.resolve("document2.pdf");
Files.write(pdf1, "pdf content".getBytes());
Files.write(pdf2, "pdf content".getBytes());
List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
assertEquals(2, candidates.size(), "Should return exactly 2 PDF candidates");
assertTrue(candidates.stream()
.allMatch(c -> c.uniqueIdentifier().endsWith(".pdf")),
"All candidates should be PDF files");
}
@Test
void testLoadCandidates_FiltersNonPdfFiles() throws IOException {
// Create mixed file types
Files.write(tempDir.resolve("document.pdf"), "content".getBytes());
Files.write(tempDir.resolve("image.png"), "content".getBytes());
Files.write(tempDir.resolve("text.txt"), "content".getBytes());
Files.write(tempDir.resolve("data.xlsx"), "content".getBytes());
List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
assertEquals(1, candidates.size(), "Should return only 1 PDF candidate");
assertEquals("document.pdf", candidates.get(0).uniqueIdentifier());
}
@Test
void testLoadCandidates_IgnoresDirectories() throws IOException {
// Create files and subdirectories
Files.write(tempDir.resolve("document.pdf"), "content".getBytes());
Files.createDirectory(tempDir.resolve("subfolder"));
Files.write(tempDir.resolve("subfolder/nested.pdf"), "content".getBytes());
List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
assertEquals(1, candidates.size(), "Should return only 1 PDF candidate (in root folder)");
assertEquals("document.pdf", candidates.get(0).uniqueIdentifier());
}
@Test
void testLoadCandidates_CaseInsensitiveExtension() throws IOException {
// Create PDFs with various case combinations
Files.write(tempDir.resolve("file1.pdf"), "content".getBytes());
Files.write(tempDir.resolve("file2.PDF"), "content".getBytes());
Files.write(tempDir.resolve("file3.Pdf"), "content".getBytes());
Files.write(tempDir.resolve("file4.pDf"), "content".getBytes());
List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
assertEquals(4, candidates.size(), "Should recognize PDF in any case combination");
}
@Test
void testLoadCandidates_DeterministicOrder() throws IOException {
// Create PDFs in non-alphabetical order
Files.write(tempDir.resolve("zebra.pdf"), "content".getBytes());
Files.write(tempDir.resolve("apple.pdf"), "content".getBytes());
Files.write(tempDir.resolve("monkey.pdf"), "content".getBytes());
List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
assertEquals(3, candidates.size());
// Files are sorted by absolute path, which will be consistent
List<SourceDocumentCandidate> candidates2 = adapter.loadCandidates();
assertEquals(candidates, candidates2, "Multiple calls should return same order");
}
@Test
void testLoadCandidates_FileSizeMetadata() throws IOException {
Path pdfFile = tempDir.resolve("test.pdf");
Files.write(pdfFile, "test content 12345".getBytes());
List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
assertEquals(1, candidates.size());
SourceDocumentCandidate candidate = candidates.get(0);
assertEquals(18, candidate.fileSizeBytes(), "File size should match written content");
}
@Test
void testLoadCandidates_UniqueIdentifier() throws IOException {
Path pdfFile = tempDir.resolve("myfile.pdf");
Files.write(pdfFile, "content".getBytes());
List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
assertEquals(1, candidates.size());
assertEquals("myfile.pdf", candidates.get(0).uniqueIdentifier(),
"uniqueIdentifier should be filename");
}
@Test
void testLoadCandidates_LocatorContainsAbsolutePath() throws IOException {
Path pdfFile = tempDir.resolve("test.pdf");
Files.write(pdfFile, "content".getBytes());
List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
assertEquals(1, candidates.size());
String locatorValue = candidates.get(0).locator().value();
assertTrue(locatorValue.contains("test.pdf"), "Locator should contain filename");
assertTrue(new java.io.File(locatorValue).isAbsolute(),
"Locator value should be an absolute path");
}
@Test
void testLoadCandidates_SourceFolderNotFound() {
Path nonExistentFolder = tempDir.resolve("does-not-exist");
SourceDocumentCandidatesPortAdapter adapterForMissing =
new SourceDocumentCandidatesPortAdapter(nonExistentFolder);
SourceDocumentAccessException ex = assertThrows(
SourceDocumentAccessException.class,
adapterForMissing::loadCandidates,
"Should throw exception for non-existent source folder");
assertTrue(ex.getMessage().contains("does not exist"));
}
@Test
void testLoadCandidates_SourceFolderIsFile() throws IOException {
Path fileInsteadOfFolder = tempDir.resolve("regular-file");
Files.createFile(fileInsteadOfFolder);
SourceDocumentCandidatesPortAdapter adapterForFile =
new SourceDocumentCandidatesPortAdapter(fileInsteadOfFolder);
SourceDocumentAccessException ex = assertThrows(
SourceDocumentAccessException.class,
adapterForFile::loadCandidates,
"Should throw exception if source path is a file, not a folder");
assertTrue(ex.getMessage().contains("not a directory"));
}
@Test
void testLoadCandidates_HasLocatorForEachCandidate() throws IOException {
Files.createFile(tempDir.resolve("file1.pdf"));
Files.createFile(tempDir.resolve("file2.pdf"));
List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
for (SourceDocumentCandidate candidate : candidates) {
assertNotNull(candidate.locator(), "Each candidate must have a locator");
assertNotNull(candidate.locator().value(), "Locator value must not be null");
assertFalse(candidate.locator().value().isEmpty(), "Locator value must not be empty");
}
}
@Test
void testLoadCandidates_EmptyPdfFilesAreIncluded() throws IOException {
// Create empty PDF files (M3-AP-002 requirement: PDF-Dateien im Quellordner)
Files.createFile(tempDir.resolve("empty1.pdf"));
Files.createFile(tempDir.resolve("empty2.pdf"));
// Also add a non-empty PDF for contrast
Files.write(tempDir.resolve("nonempty.pdf"), "content".getBytes());
List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
assertEquals(3, candidates.size(),
"Empty PDF files should be included as candidates; content evaluation happens in AP-004");
assertTrue(candidates.stream().allMatch(c -> c.uniqueIdentifier().endsWith(".pdf")),
"All candidates should be PDF files");
}
}