From 751ac1c4a320149563b70501f0f033d6d2e2443b Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 7 Jun 2026 01:29:29 +0200 Subject: [PATCH] test(person): close fetch-to-classify seam for alias matches on real Postgres (#763 review) AC#4 (maiden alias -> direct) and AC#5 (alias first name -> fetchable + classifiable) were each split across PersonRepositoryTest (the fetch) and PersonServiceTest (the classifier with stubs) -- nothing walked searchByName -> resolveByName end-to-end on real Postgres. Add two tests in the existing @DataJpaTest slice that build a real PersonService over the autowired repositories, persist a person with a MAIDEN_NAME alias and one with an alias firstName, and assert both classify as direct. Co-Authored-By: Claude Opus 4.8 --- .../person/PersonRepositoryTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/backend/src/test/java/org/raddatz/familienarchiv/person/PersonRepositoryTest.java b/backend/src/test/java/org/raddatz/familienarchiv/person/PersonRepositoryTest.java index 131bfb1d..48483476 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/person/PersonRepositoryTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/person/PersonRepositoryTest.java @@ -453,6 +453,42 @@ class PersonRepositoryTest { .containsExactly("Anna", "Bernd", "Clara"); } + // ─── resolveByName fetch→classify, end-to-end on real Postgres (#763 review) ─── + // The classifier unit tests in PersonServiceTest stub searchByName, so they never prove the + // fetch query actually finds an alias-only match and feeds it into classification. These walk + // the whole searchByName → resolveByName path over the real Postgres slice, closing AC#4/#5. + + @Test + void resolveByName_maidenAlias_classifiesAsDirect_endToEnd() { + PersonService personService = new PersonService(personRepository, aliasRepository); + Person clara = personRepository.save(Person.builder().firstName("Clara").lastName("Müller").build()); + aliasRepository.save(PersonNameAlias.builder() + .person(clara).lastName("Cram").type(PersonNameAliasType.MAIDEN_NAME).sortOrder(0).build()); + // Detach so resolveByName re-fetches with its lazy nameAliases loaded from the DB — + // the fresh-session behaviour the @Transactional(readOnly=true) path has in production. + entityManager.flush(); + entityManager.clear(); + + NameMatches matches = personService.resolveByName("Clara Cram"); + + assertThat(matches.direct()).extracting(Person::getId).containsExactly(clara.getId()); + } + + @Test + void resolveByName_aliasFirstName_classifiesAsDirect_endToEnd() { + PersonService personService = new PersonService(personRepository, aliasRepository); + Person clara = personRepository.save(Person.builder().firstName("Clara").lastName("Cram").build()); + aliasRepository.save(PersonNameAlias.builder() + .person(clara).firstName("Wilhelmina").lastName("de Gruyter") + .type(PersonNameAliasType.BIRTH).sortOrder(0).build()); + entityManager.flush(); + entityManager.clear(); + + NameMatches matches = personService.resolveByName("Wilhelmina"); + + assertThat(matches.direct()).extracting(Person::getId).containsExactly(clara.getId()); + } + // ─── searchWithDocumentCount with aliases ──────────────────────────────── @Test