test(person): address PR #736 review nits
Some checks failed
CI / Unit & Component Tests (pull_request) Successful in 3m21s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Failing after 3m20s
CI / fail2ban Regex (pull_request) Successful in 49s
CI / Semgrep Security Scan (pull_request) Successful in 23s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m17s
Some checks failed
CI / Unit & Component Tests (pull_request) Successful in 3m21s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Failing after 3m20s
CI / fail2ban Regex (pull_request) Successful in 49s
CI / Semgrep Security Scan (pull_request) Successful in 23s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m17s
- AC-3 cascade test: assert an innocent bystander's mention row survives the delete, proving the cascade is scoped to the deleted person (Nora). - Fix integration-test comment: receivers is @ManyToMany(LAZY), not an EAGER @ElementCollection (Sara). - ADR-032: note the @ prefix is kept in the degraded path, stripped in live mentions (Leonie). - Add trailing newline to PersonRepository.java (Felix). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -207,4 +207,4 @@ public interface PersonRepository extends JpaRepository<Person, UUID> {
|
|||||||
)
|
)
|
||||||
""", nativeQuery = true)
|
""", nativeQuery = true)
|
||||||
void insertMissingReceiverReference(@Param("source") UUID source, @Param("target") UUID target);
|
void insertMissingReceiverReference(@Param("source") UUID source, @Param("target") UUID target);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -776,6 +776,7 @@ class PersonRepositoryTest {
|
|||||||
// AC-3: the @-mention sidecar is a CASCADE soft reference, but the literal "@Name" lives
|
// AC-3: the @-mention sidecar is a CASCADE soft reference, but the literal "@Name" lives
|
||||||
// in transcription_blocks.text and must stay visible as plain text after the person goes.
|
// in transcription_blocks.text and must stay visible as plain text after the person goes.
|
||||||
Person mentioned = personRepository.save(Person.builder().firstName("Auguste").lastName("Raddatz").build());
|
Person mentioned = personRepository.save(Person.builder().firstName("Auguste").lastName("Raddatz").build());
|
||||||
|
Person survivor = personRepository.save(Person.builder().firstName("Clara").lastName("Cram").build());
|
||||||
Document doc = documentRepository.save(Document.builder()
|
Document doc = documentRepository.save(Document.builder()
|
||||||
.title("Brief").originalFilename("brief.pdf")
|
.title("Brief").originalFilename("brief.pdf")
|
||||||
.status(DocumentStatus.UPLOADED).build());
|
.status(DocumentStatus.UPLOADED).build());
|
||||||
@@ -790,12 +791,18 @@ class PersonRepositoryTest {
|
|||||||
entityManager.createNativeQuery(
|
entityManager.createNativeQuery(
|
||||||
"INSERT INTO transcription_blocks (id, annotation_id, document_id, text) VALUES (?1, ?2, ?3, ?4)")
|
"INSERT INTO transcription_blocks (id, annotation_id, document_id, text) VALUES (?1, ?2, ?3, ?4)")
|
||||||
.setParameter(1, blockId).setParameter(2, annotationId).setParameter(3, doc.getId())
|
.setParameter(1, blockId).setParameter(2, annotationId).setParameter(3, doc.getId())
|
||||||
.setParameter(4, "Brief an @Auguste Raddatz").executeUpdate();
|
.setParameter(4, "Brief an @Auguste Raddatz und @Clara Cram").executeUpdate();
|
||||||
|
// Two mention rows on the same block: the deleted person and an innocent bystander.
|
||||||
entityManager.createNativeQuery(
|
entityManager.createNativeQuery(
|
||||||
"INSERT INTO transcription_block_mentioned_persons (block_id, person_id, display_name) "
|
"INSERT INTO transcription_block_mentioned_persons (block_id, person_id, display_name) "
|
||||||
+ "VALUES (?1, ?2, ?3)")
|
+ "VALUES (?1, ?2, ?3)")
|
||||||
.setParameter(1, blockId).setParameter(2, mentioned.getId())
|
.setParameter(1, blockId).setParameter(2, mentioned.getId())
|
||||||
.setParameter(3, "Auguste Raddatz").executeUpdate();
|
.setParameter(3, "Auguste Raddatz").executeUpdate();
|
||||||
|
entityManager.createNativeQuery(
|
||||||
|
"INSERT INTO transcription_block_mentioned_persons (block_id, person_id, display_name) "
|
||||||
|
+ "VALUES (?1, ?2, ?3)")
|
||||||
|
.setParameter(1, blockId).setParameter(2, survivor.getId())
|
||||||
|
.setParameter(3, "Clara Cram").executeUpdate();
|
||||||
entityManager.flush();
|
entityManager.flush();
|
||||||
entityManager.clear();
|
entityManager.clear();
|
||||||
|
|
||||||
@@ -808,9 +815,15 @@ class PersonRepositoryTest {
|
|||||||
.setParameter(1, mentioned.getId()).getSingleResult();
|
.setParameter(1, mentioned.getId()).getSingleResult();
|
||||||
assertThat(mentionRows.longValue()).isZero();
|
assertThat(mentionRows.longValue()).isZero();
|
||||||
|
|
||||||
|
// The cascade is scoped to the deleted person — the bystander's mention row is untouched.
|
||||||
|
Number survivorRows = (Number) entityManager.createNativeQuery(
|
||||||
|
"SELECT count(*) FROM transcription_block_mentioned_persons WHERE person_id = ?1")
|
||||||
|
.setParameter(1, survivor.getId()).getSingleResult();
|
||||||
|
assertThat(survivorRows.longValue()).isEqualTo(1);
|
||||||
|
|
||||||
String text = (String) entityManager.createNativeQuery(
|
String text = (String) entityManager.createNativeQuery(
|
||||||
"SELECT text FROM transcription_blocks WHERE id = ?1")
|
"SELECT text FROM transcription_blocks WHERE id = ?1")
|
||||||
.setParameter(1, blockId).getSingleResult();
|
.setParameter(1, blockId).getSingleResult();
|
||||||
assertThat(text).isEqualTo("Brief an @Auguste Raddatz");
|
assertThat(text).isEqualTo("Brief an @Auguste Raddatz und @Clara Cram");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ class PersonServiceIntegrationTest {
|
|||||||
|
|
||||||
// The ON DELETE cascade fires beneath Hibernate — flush the delete and clear the L1
|
// The ON DELETE cascade fires beneath Hibernate — flush the delete and clear the L1
|
||||||
// cache so the asserting reads observe the post-delete database state, not stale
|
// cache so the asserting reads observe the post-delete database state, not stale
|
||||||
// managed entities (the EAGER @ElementCollection on receivers makes this load-bearing).
|
// managed entities still holding the dropped sender/receiver associations.
|
||||||
entityManager.flush();
|
entityManager.flush();
|
||||||
entityManager.clear();
|
entityManager.clear();
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ letters. This is pinned by a non-negotiable document-survival assertion in
|
|||||||
stay thin (`deleteById` + the cascade); `reassignSenderToNull` and `deleteReceiverReferences`
|
stay thin (`deleteById` + the cascade); `reassignSenderToNull` and `deleteReceiverReferences`
|
||||||
are deleted.
|
are deleted.
|
||||||
- This *fixes* the pre-existing dead-link-on-deleted-person case — it is not a purely
|
- This *fixes* the pre-existing dead-link-on-deleted-person case — it is not a purely
|
||||||
invisible refactor.
|
invisible refactor. Note the read renderer strips the `@` prefix when it emits a live
|
||||||
|
mention link, but the degraded (deleted-person) path leaves the literal `@Name` in the
|
||||||
|
block text as-is — the reader sees `@Auguste Raddatz` as plain text, never a dead link.
|
||||||
- DB cascades run below `AuditService`, so the row-level cleanup is intentionally not
|
- DB cascades run below `AuditService`, so the row-level cleanup is intentionally not
|
||||||
audit-logged; the person-delete action itself is still logged at the service layer.
|
audit-logged; the person-delete action itself is still logged at the service layer.
|
||||||
- The V71 FK validation requires cleaning pre-existing orphan mention rows first; the
|
- The V71 FK validation requires cleaning pre-existing orphan mention rows first; the
|
||||||
|
|||||||
Reference in New Issue
Block a user