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 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-13 16:24:34 +02:00
parent afd1f0b86b
commit 3a174dd91b

View File

@@ -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<Person> 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<Person> 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<Person> 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());
});
});
}
}