From 385c3cd27565e5d5cac9758ffb97753a4697c76b Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 14 May 2026 16:14:31 +0200 Subject: [PATCH] test(invites): add InviteTokenRepository integration tests for existsActiveWithGroupId + V66 group_id index Co-Authored-By: Claude Sonnet 4.6 --- .../V66__add_invite_token_group_id_index.sql | 3 + .../InviteTokenRepositoryIntegrationTest.java | 78 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 backend/src/main/resources/db/migration/V66__add_invite_token_group_id_index.sql create mode 100644 backend/src/test/java/org/raddatz/familienarchiv/user/InviteTokenRepositoryIntegrationTest.java diff --git a/backend/src/main/resources/db/migration/V66__add_invite_token_group_id_index.sql b/backend/src/main/resources/db/migration/V66__add_invite_token_group_id_index.sql new file mode 100644 index 00000000..8b99b377 --- /dev/null +++ b/backend/src/main/resources/db/migration/V66__add_invite_token_group_id_index.sql @@ -0,0 +1,3 @@ +-- The composite PK (invite_token_id, group_id) does not support efficient lookups by group_id alone. +-- Add a dedicated index to support existsActiveWithGroupId queries. +CREATE INDEX idx_itg_group_id ON invite_token_group_ids (group_id); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/user/InviteTokenRepositoryIntegrationTest.java b/backend/src/test/java/org/raddatz/familienarchiv/user/InviteTokenRepositoryIntegrationTest.java new file mode 100644 index 00000000..f5ccbd4e --- /dev/null +++ b/backend/src/test/java/org/raddatz/familienarchiv/user/InviteTokenRepositoryIntegrationTest.java @@ -0,0 +1,78 @@ +package org.raddatz.familienarchiv.user; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.raddatz.familienarchiv.PostgresContainerConfig; +import org.raddatz.familienarchiv.config.FlywayConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase; +import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; +import org.springframework.context.annotation.Import; + +import java.time.LocalDateTime; +import java.util.Set; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Import({PostgresContainerConfig.class, FlywayConfig.class}) +class InviteTokenRepositoryIntegrationTest { + + @Autowired InviteTokenRepository inviteTokenRepository; + @Autowired UserGroupRepository userGroupRepository; + @Autowired AppUserRepository appUserRepository; + + private UserGroup group; + private AppUser admin; + + @BeforeEach + void setUp() { + inviteTokenRepository.deleteAll(); + userGroupRepository.deleteAll(); + appUserRepository.deleteAll(); + admin = appUserRepository.save(AppUser.builder().email("admin@test.com").password("pw").build()); + group = userGroupRepository.save(UserGroup.builder().name("Familie").build()); + } + + // ─── existsActiveWithGroupId ────────────────────────────────────────────── + + @Test + void existsActiveWithGroupId_returnsTrueForActiveInviteLinkedToGroup() { + inviteTokenRepository.save(token(t -> t)); + + assertThat(inviteTokenRepository.existsActiveWithGroupId(group.getId())).isTrue(); + } + + @Test + void existsActiveWithGroupId_returnsFalseWhenInviteIsRevoked() { + inviteTokenRepository.save(token(t -> t.revoked(true))); + + assertThat(inviteTokenRepository.existsActiveWithGroupId(group.getId())).isFalse(); + } + + @Test + void existsActiveWithGroupId_returnsFalseWhenInviteIsExpired() { + inviteTokenRepository.save(token(t -> t.expiresAt(LocalDateTime.now().minusDays(1)))); + + assertThat(inviteTokenRepository.existsActiveWithGroupId(group.getId())).isFalse(); + } + + @Test + void existsActiveWithGroupId_returnsFalseWhenInviteIsExhausted() { + inviteTokenRepository.save(token(t -> t.maxUses(1).useCount(1))); + + assertThat(inviteTokenRepository.existsActiveWithGroupId(group.getId())).isFalse(); + } + + // ─── helpers ───────────────────────────────────────────────────────────── + + private InviteToken token(java.util.function.UnaryOperator customizer) { + InviteToken.InviteTokenBuilder builder = InviteToken.builder() + .code(UUID.randomUUID().toString().replace("-", "").substring(0, 10)) + .groupIds(new java.util.HashSet<>(Set.of(group.getId()))) + .createdBy(admin); + return customizer.apply(builder).build(); + } +}