From 61ca5a6e4039c52da4a487f879c2b8f0d96ec7a6 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 28 May 2026 16:19:13 +0200 Subject: [PATCH] test(person): tighten generation null-clear coverage (#689) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sara's QA concerns: 1. PersonControllerTest.updatePerson_returns200_whenGenerationNull was asymmetric — only checked status 200, no body assertion. Now also asserts `$.generation` is null in the JSON response, mirroring the in-range test's body check. 2. New full-stack PUT→DB→GET round-trip in PersonServiceIntegrationTest (updatePerson_clearGenerationToNull_readsBackNullFromDb) seeds a person with generation=3, calls updatePerson with generation=null, flushes the persistence context, and asserts the column reads back null from the DB. Without this we only had the mocked WebMvcTest boundary; nothing proved JPA actually wrote SQL NULL. 3. Sibling test (updatePerson_setGenerationToZero_readsBackZeroFromDb) pins the G 0 end-to-end so a primitive zero can't silently coerce to null anywhere along controller → service → JPA. Refs #689 Co-Authored-By: Claude Opus 4.7 --- .../person/PersonControllerTest.java | 7 ++- .../person/PersonServiceIntegrationTest.java | 53 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/backend/src/test/java/org/raddatz/familienarchiv/person/PersonControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/person/PersonControllerTest.java index 9bbd75be..783924c0 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/person/PersonControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/person/PersonControllerTest.java @@ -746,6 +746,10 @@ class PersonControllerTest { @Test @WithMockUser(authorities = "WRITE_ALL") void updatePerson_returns200_whenGenerationNull() throws Exception { + // Symmetric body assertion: the response must echo generation as null (not + // absent), so the frontend re-hydrates the "(none)" option after a clear. + // Without this, the in-range test below would be the only end-to-end proof + // that the field flows through the controller. Person saved = Person.builder().id(UUID.randomUUID()).firstName("Hans").lastName("Müller").build(); when(personService.updatePerson(any(), any())).thenReturn(saved); @@ -753,7 +757,8 @@ class PersonControllerTest { .contentType(MediaType.APPLICATION_JSON) .content("{\"firstName\":\"Hans\",\"lastName\":\"Müller\"," + "\"personType\":\"PERSON\",\"generation\":null}")) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(jsonPath("$.generation").value(org.hamcrest.Matchers.nullValue())); } @Test diff --git a/backend/src/test/java/org/raddatz/familienarchiv/person/PersonServiceIntegrationTest.java b/backend/src/test/java/org/raddatz/familienarchiv/person/PersonServiceIntegrationTest.java index 0578f5fb..5b9d0386 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/person/PersonServiceIntegrationTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/person/PersonServiceIntegrationTest.java @@ -124,6 +124,59 @@ class PersonServiceIntegrationTest { assertThat(personRepository.findById(target.getId())).isEmpty(); } + // ─── generation full-stack round-trip (#689) ────────────────────────────── + + @Test + void updatePerson_clearGenerationToNull_readsBackNullFromDb() { + // Sara's QA concern: pin the full PUT→DB→GET round-trip for the + // null-clear path. Without this we only have the WebMvcTest mocked + // boundary; nothing proved the JPA flush actually wrote SQL NULL. + Person seeded = personRepository.save(Person.builder() + .firstName("Hans").lastName("Raddatz") + .personType(PersonType.PERSON).generation(3).build()); + entityManager.flush(); + entityManager.clear(); + + PersonUpdateDTO dto = new PersonUpdateDTO(); + dto.setPersonType(PersonType.PERSON); + dto.setFirstName("Hans"); + dto.setLastName("Raddatz"); + dto.setGeneration(null); + + personService.updatePerson(seeded.getId(), dto); + entityManager.flush(); + entityManager.clear(); + + Person reloaded = personRepository.findById(seeded.getId()).orElseThrow(); + assertThat(reloaded.getGeneration()).isNull(); + } + + @Test + void updatePerson_setGenerationToZero_readsBackZeroFromDb() { + // Pin the G 0 case end-to-end. The form-action spec covers that 0 + // doesn't get spread-dropped at the SvelteKit boundary; this test + // covers that the controller + service + JPA chain preserves the + // primitive zero (not coerced to null somewhere along the way). + Person seeded = personRepository.save(Person.builder() + .firstName("Walter").lastName("Raddatz") + .personType(PersonType.PERSON).build()); + entityManager.flush(); + entityManager.clear(); + + PersonUpdateDTO dto = new PersonUpdateDTO(); + dto.setPersonType(PersonType.PERSON); + dto.setFirstName("Walter"); + dto.setLastName("Raddatz"); + dto.setGeneration(0); + + personService.updatePerson(seeded.getId(), dto); + entityManager.flush(); + entityManager.clear(); + + Person reloaded = personRepository.findById(seeded.getId()).orElseThrow(); + assertThat(reloaded.getGeneration()).isEqualTo(0); + } + @Test void deletePerson_detachesSentAndReceivedReferences_beforeDelete_noOrphan() { // A person referenced as BOTH a document sender and a document receiver must delete