fix(stammbaum): V55 adds unique_spouse_pair index — symmetric SPOUSE_OF enforced at DB level

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-28 17:00:50 +02:00
committed by marcel
parent 1754b96b18
commit 6babcc7f17
2 changed files with 20 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
-- Symmetric SPOUSE_OF: enforce only one row per unordered pair, mirroring the
-- SIBLING_OF index added in V54.
CREATE UNIQUE INDEX unique_spouse_pair ON person_relationships (
LEAST(person_id, related_person_id),
GREATEST(person_id, related_person_id)
) WHERE relation_type = 'SPOUSE_OF';

View File

@@ -117,6 +117,20 @@ class RelationshipServiceIntegrationTest {
assertThat(relationshipRepository.findById(created.id())).isPresent();
}
@Test
void addRelationship_throws_409_when_reverse_SPOUSE_OF_pair_already_exists() {
// V55 enforces symmetric uniqueness for SPOUSE_OF. Inserting (alice, bob, SPOUSE_OF)
// and then (bob, alice, SPOUSE_OF) must be rejected, just like reverse SIBLING_OF.
relationshipService.addRelationship(alice.getId(),
new CreateRelationshipRequest(bob.getId(), "SPOUSE_OF", null, null, null));
var reverse = new CreateRelationshipRequest(alice.getId(), "SPOUSE_OF", null, null, null);
assertThatThrownBy(() -> relationshipService.addRelationship(bob.getId(), reverse))
.isInstanceOf(DomainException.class)
.extracting("code")
.isEqualTo(ErrorCode.DUPLICATE_RELATIONSHIP);
}
@Test
void deleteRelationship_succeeds_for_symmetric_type_from_either_side() {
// alice SPOUSE_OF bob. Bob deletes from his side.