test(#240): add Testcontainers integration tests for native SQL queue queries

6 new tests covering findSegmentationQueue (excludes PLACEHOLDER, excludes
annotated docs), findTranscriptionQueue (below-90%-reviewed docs, zero-block
case), findReadyToReadQueue (>=90% reviewed), and findWeeklyStats (zeros on
empty DB). Runs against real PostgreSQL 16 via Testcontainers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-16 12:15:21 +02:00
parent adea7d498f
commit e041c75793

View File

@@ -4,8 +4,10 @@ import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.PostgresContainerConfig;
import org.raddatz.familienarchiv.config.FlywayConfig;
import org.raddatz.familienarchiv.model.Document;
import org.raddatz.familienarchiv.model.DocumentAnnotation;
import org.raddatz.familienarchiv.model.DocumentStatus;
import org.raddatz.familienarchiv.model.Person;
import org.raddatz.familienarchiv.model.TranscriptionBlock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
@@ -20,6 +22,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -34,6 +37,12 @@ class DocumentRepositoryTest {
@Autowired
private PersonRepository personRepository;
@Autowired
private AnnotationRepository annotationRepository;
@Autowired
private TranscriptionBlockRepository transcriptionBlockRepository;
// ─── save and findById ────────────────────────────────────────────────────
@Test
@@ -253,4 +262,116 @@ class DocumentRepositoryTest {
assertThat(results).hasSize(1);
}
// ─── findSegmentationQueue ────────────────────────────────────────────────
@Test
void findSegmentationQueue_excludes_PLACEHOLDER_documents() {
documentRepository.save(uploaded("Hochgeladener Brief"));
documentRepository.save(Document.builder()
.title("Platzhalter").originalFilename("placeholder.pdf")
.status(DocumentStatus.PLACEHOLDER).build());
List<TranscriptionQueueProjection> result = documentRepository.findSegmentationQueue(10);
assertThat(result).extracting(TranscriptionQueueProjection::getTitle)
.containsExactly("Hochgeladener Brief")
.doesNotContain("Platzhalter");
}
@Test
void findSegmentationQueue_excludes_documents_that_already_have_annotations() {
Document withAnnotation = documentRepository.save(uploaded("Mit Annotation"));
documentRepository.save(uploaded("Ohne Annotation"));
annotationRepository.save(annotation(withAnnotation.getId()));
List<TranscriptionQueueProjection> result = documentRepository.findSegmentationQueue(10);
assertThat(result).extracting(TranscriptionQueueProjection::getTitle)
.containsExactly("Ohne Annotation")
.doesNotContain("Mit Annotation");
}
// ─── findTranscriptionQueue ───────────────────────────────────────────────
@Test
void findTranscriptionQueue_returns_documents_with_annotations_below_90pct_reviewed() {
Document doc = documentRepository.save(uploaded("Tagebuch"));
DocumentAnnotation ann = annotationRepository.save(annotation(doc.getId()));
// One block, not reviewed → 0 / 1 = 0% < 90%
transcriptionBlockRepository.save(block(doc.getId(), ann.getId(), "Text", false));
List<TranscriptionQueueProjection> result = documentRepository.findTranscriptionQueue(10);
assertThat(result).extracting(TranscriptionQueueProjection::getTitle)
.contains("Tagebuch");
}
@Test
void findTranscriptionQueue_returns_zero_textedBlockCount_when_no_transcription_blocks() {
Document doc = documentRepository.save(uploaded("Nur Annotation"));
annotationRepository.save(annotation(doc.getId()));
// No transcription blocks at all — annotationCount=1, textedBlockCount=0
List<TranscriptionQueueProjection> result = documentRepository.findTranscriptionQueue(10);
assertThat(result).hasSize(1);
assertThat(result.get(0).getTextedBlockCount()).isEqualTo(0);
assertThat(result.get(0).getAnnotationCount()).isEqualTo(1);
}
// ─── findReadyToReadQueue ─────────────────────────────────────────────────
@Test
void findReadyToReadQueue_returns_documents_with_at_least_90pct_reviewed() {
Document doc = documentRepository.save(uploaded("Urkunde"));
DocumentAnnotation ann = annotationRepository.save(annotation(doc.getId()));
// One block, reviewed → 1 / 1 = 100% >= 90%
transcriptionBlockRepository.save(block(doc.getId(), ann.getId(), "Text", true));
List<TranscriptionQueueProjection> result = documentRepository.findReadyToReadQueue(10);
assertThat(result).extracting(TranscriptionQueueProjection::getTitle)
.contains("Urkunde");
}
// ─── findWeeklyStats ──────────────────────────────────────────────────────
@Test
void findWeeklyStats_returns_zeros_when_database_is_empty() {
TranscriptionWeeklyStatsProjection stats = documentRepository.findWeeklyStats();
assertThat(stats.getSegmentationCount()).isEqualTo(0L);
assertThat(stats.getTranscriptionCount()).isEqualTo(0L);
assertThat(stats.getReadyCount()).isEqualTo(0L);
}
// ─── seeding helpers ─────────────────────────────────────────────────────
private Document uploaded(String title) {
return Document.builder()
.title(title)
.originalFilename(title.toLowerCase().replace(" ", "_") + ".pdf")
.status(DocumentStatus.UPLOADED)
.build();
}
private DocumentAnnotation annotation(UUID documentId) {
return DocumentAnnotation.builder()
.documentId(documentId)
.pageNumber(1)
.x(0.1).y(0.1).width(0.5).height(0.3)
.color("#ff0000")
.build();
}
private TranscriptionBlock block(UUID documentId, UUID annotationId, String text, boolean reviewed) {
return TranscriptionBlock.builder()
.documentId(documentId)
.annotationId(annotationId)
.text(text)
.sortOrder(0)
.reviewed(reviewed)
.build();
}
}