diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/PersonServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/PersonServiceTest.java index a90ac002..4e5bf99d 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/PersonServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/PersonServiceTest.java @@ -13,15 +13,19 @@ import org.raddatz.familienarchiv.exception.DomainException; import org.raddatz.familienarchiv.exception.ErrorCode; import org.raddatz.familienarchiv.model.Person; import org.raddatz.familienarchiv.model.PersonDisplayNameChangedEvent; +import org.raddatz.familienarchiv.model.PersonMention; import org.raddatz.familienarchiv.model.PersonNameAlias; import org.raddatz.familienarchiv.model.PersonNameAliasType; import org.raddatz.familienarchiv.model.PersonType; +import org.raddatz.familienarchiv.model.TranscriptionBlock; import org.raddatz.familienarchiv.repository.PersonNameAliasRepository; import org.raddatz.familienarchiv.repository.PersonRepository; +import org.raddatz.familienarchiv.repository.TranscriptionBlockRepository; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.dao.OptimisticLockingFailureException; +import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.web.server.ResponseStatusException; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -300,16 +304,38 @@ class PersonServiceTest { } @Test - void updatePerson_throwsConflict_whenListenerSignalsOptimisticLockFailure() { + void updatePerson_throwsConflict_whenBlockSaveAllAndFlushHitsOptimisticLock() { + // Wire a real PersonMentionPropagationListener with a mocked block repository + // that throws on saveAllAndFlush. The publisher mock routes events to the + // listener so the catch path traverses the same call chain as production: + // PersonService → publishEvent → listener → saveAllAndFlush throws → catch. UUID id = UUID.randomUUID(); Person existing = Person.builder() .id(id).firstName("Auguste").lastName("Raddatz") .personType(PersonType.PERSON).build(); + TranscriptionBlock referencingBlock = TranscriptionBlock.builder() + .id(UUID.randomUUID()).documentId(UUID.randomUUID()).annotationId(UUID.randomUUID()) + .text("Brief von @Auguste Raddatz").sortOrder(0) + .mentionedPersons(new ArrayList<>(List.of(new PersonMention(id, "Auguste Raddatz")))) + .build(); + + TranscriptionBlockRepository blockRepo = mock(TranscriptionBlockRepository.class); + when(blockRepo.findByMentionedPersons_PersonId(id)) + .thenReturn(List.of(referencingBlock)); + when(blockRepo.saveAllAndFlush(any())) + .thenThrow(new ObjectOptimisticLockingFailureException( + TranscriptionBlock.class, referencingBlock.getId())); + + PersonMentionPropagationListener realListener = + new PersonMentionPropagationListener(blockRepo); + when(personRepository.findById(id)).thenReturn(Optional.of(existing)); when(personRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); - doThrow(new OptimisticLockingFailureException("simulated concurrent block save")) - .when(eventPublisher).publishEvent(any(PersonDisplayNameChangedEvent.class)); + doAnswer(inv -> { + realListener.onPersonDisplayNameChanged(inv.getArgument(0)); + return null; + }).when(eventPublisher).publishEvent(any(PersonDisplayNameChangedEvent.class)); PersonUpdateDTO dto = new PersonUpdateDTO(); dto.setPersonType(PersonType.PERSON);