From 3a174dd91b2ec515d5e67851b4589d64ec3edabe Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 13 Jun 2026 16:24:34 +0200 Subject: [PATCH] test(timeline): add integration tests for TimelineService + findByGeneration Verifies PersonRepository.findByGeneration handles match, no-match (empty list not NPE), and null-generation persons (excluded). Also confirms TimelineService.assemble() returns a persisted curated event in the correct year band against real Postgres via Testcontainers. Refs #777 Co-Authored-By: Claude Sonnet 4.6 --- .../TimelineServiceIntegrationTest.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 backend/src/test/java/org/raddatz/familienarchiv/timeline/TimelineServiceIntegrationTest.java diff --git a/backend/src/test/java/org/raddatz/familienarchiv/timeline/TimelineServiceIntegrationTest.java b/backend/src/test/java/org/raddatz/familienarchiv/timeline/TimelineServiceIntegrationTest.java new file mode 100644 index 00000000..60274f8d --- /dev/null +++ b/backend/src/test/java/org/raddatz/familienarchiv/timeline/TimelineServiceIntegrationTest.java @@ -0,0 +1,105 @@ +package org.raddatz.familienarchiv.timeline; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.junit.jupiter.api.Test; +import org.raddatz.familienarchiv.PostgresContainerConfig; +import org.raddatz.familienarchiv.document.DatePrecision; +import org.raddatz.familienarchiv.person.Person; +import org.raddatz.familienarchiv.person.PersonRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.transaction.annotation.Transactional; +import software.amazon.awssdk.services.s3.S3Client; + +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link TimelineService} and {@link PersonRepository#findByGeneration} + * against real Postgres. Verifies that assembled output reflects persisted curated events and + * that the generation query handles null-generation rows correctly. + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) +@ActiveProfiles("test") +@Import(PostgresContainerConfig.class) +@Transactional +class TimelineServiceIntegrationTest { + + @MockitoBean S3Client s3Client; + + @Autowired TimelineService timelineService; + @Autowired TimelineEventRepository timelineEventRepository; + @Autowired PersonRepository personRepository; + + @PersistenceContext EntityManager em; + + // ─── PersonRepository.findByGeneration ──────────────────────────────────── + + @Test + void findByGeneration_returns_matching_persons() { + personRepository.save(Person.builder().lastName("Gen2A").generation(2).build()); + personRepository.save(Person.builder().lastName("Gen2B").generation(2).build()); + personRepository.save(Person.builder().lastName("Gen3").generation(3).build()); + em.flush(); + + List result = personRepository.findByGeneration(2); + + assertThat(result).extracting(Person::getLastName) + .containsExactlyInAnyOrder("Gen2A", "Gen2B"); + } + + @Test + void findByGeneration_returns_empty_list_not_npe_when_no_match() { + personRepository.save(Person.builder().lastName("Gen1").generation(1).build()); + em.flush(); + + List result = personRepository.findByGeneration(99); + + assertThat(result).isNotNull().isEmpty(); + } + + @Test + void findByGeneration_does_not_return_null_generation_persons() { + personRepository.save(Person.builder().lastName("NullGen").build()); // generation stays null + em.flush(); + + List result = personRepository.findByGeneration(1); + + assertThat(result).extracting(Person::getLastName).doesNotContain("NullGen"); + } + + // ─── TimelineService.assemble end-to-end ───────────────────────────────── + + @Test + void assemble_includes_persisted_curated_event_in_correct_year_band() { + UUID actorId = UUID.randomUUID(); + TimelineEvent event = timelineEventRepository.save(TimelineEvent.builder() + .title("Sarajevo") + .type(EventType.HISTORICAL) + .eventDate(LocalDate.of(1914, 6, 28)) + .precision(DatePrecision.DAY) + .createdBy(actorId) + .updatedBy(actorId) + .build()); + em.flush(); + em.clear(); + + TimelineDTO result = timelineService.assemble(new TimelineFilter(null, null, null, null, null)); + + assertThat(result.years()).anySatisfy(y -> { + assertThat(y.year()).isEqualTo(1914); + assertThat(y.entries()).anySatisfy(e -> { + assertThat(e.title()).isEqualTo("Sarajevo"); + assertThat(e.kind()).isEqualTo(Kind.EVENT); + assertThat(e.eventId()).isEqualTo(event.getId()); + }); + }); + } +}