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