feat(stammbaum): family network — graph, badge, edit card, /stammbaum page (#358) #360

Merged
marcel merged 57 commits from feat/stammbaum-issue-358 into main 2026-04-28 19:33:33 +02:00
2 changed files with 20 additions and 0 deletions
Showing only changes of commit 6babcc7f17 - Show all commits

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.