From f9ae6a91ba335b86a5a6858bc0d4ef115fcbdeec Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 8 Jun 2026 20:49:23 +0200 Subject: [PATCH] test(journeyitem): add integration tests for append and reorder against real PostgreSQL Add two service-level integration tests to JourneyItemIntegrationTest: - append_persists_item_at_position_10: verifies that the first append to an empty journey creates an item at position 10 in the DB. - reorder_swaps_positions_atomically: appends two items then reorders them, asserting the DB reflects the new position assignment. Both tests use the SecurityContextHolder authentication pattern from GeschichteServiceIntegrationTest and mock S3Client to avoid MinIO connections. Co-Authored-By: Claude Sonnet 4.6 --- .../JourneyItemIntegrationTest.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemIntegrationTest.java b/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemIntegrationTest.java index 00c2591c..b6c9fcbe 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemIntegrationTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/geschichte/journeyitem/JourneyItemIntegrationTest.java @@ -2,6 +2,7 @@ package org.raddatz.familienarchiv.geschichte.journeyitem; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.raddatz.familienarchiv.PostgresContainerConfig; @@ -12,9 +13,15 @@ import org.raddatz.familienarchiv.geschichte.Geschichte; import org.raddatz.familienarchiv.geschichte.GeschichteRepository; import org.raddatz.familienarchiv.geschichte.GeschichteStatus; import org.raddatz.familienarchiv.geschichte.GeschichteType; +import org.raddatz.familienarchiv.security.Permission; +import org.raddatz.familienarchiv.user.AppUser; +import org.raddatz.familienarchiv.user.AppUserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.transaction.annotation.Transactional; @@ -40,13 +47,20 @@ class JourneyItemIntegrationTest { @Autowired GeschichteRepository geschichteRepository; @Autowired JourneyItemRepository journeyItemRepository; + @Autowired JourneyItemService journeyItemService; @Autowired DocumentRepository documentRepository; + @Autowired AppUserRepository appUserRepository; Geschichte journey; Document doc; + AppUser writer; @BeforeEach void seed() { + writer = appUserRepository.save(AppUser.builder() + .email("journey-writer@test") + .password("hash") + .build()); doc = documentRepository.save(Document.builder() .title("Testbrief") .originalFilename("testbrief.pdf") @@ -61,6 +75,19 @@ class JourneyItemIntegrationTest { em.clear(); } + @AfterEach + void clearSecurity() { + SecurityContextHolder.clearContext(); + } + + private void authenticateAs(AppUser user, Permission... permissions) { + var authorities = java.util.Arrays.stream(permissions) + .map(p -> new SimpleGrantedAuthority(p.name())) + .toList(); + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken(user.getEmail(), null, authorities)); + } + // ─── @OrderBy ───────────────────────────────────────────────────────────── @Test @@ -206,4 +233,62 @@ class JourneyItemIntegrationTest { journeyItemRepository.flush(); }).isInstanceOf(Exception.class); } + + // ─── JourneyItemService.append — end-to-end persistence ────────────────── + + @Test + void append_persists_item_at_position_10() { + // Arrange: authenticate as a user with BLOG_WRITE + authenticateAs(writer, Permission.BLOG_WRITE); + + JourneyItemCreateDTO dto = new JourneyItemCreateDTO(); + dto.setNote("First stop"); + + // Act + JourneyItemView view = journeyItemService.append(journey.getId(), dto); + em.flush(); + em.clear(); + + // Assert: item exists in DB at position 10 + assertThat(view.position()).isEqualTo(10); + assertThat(view.note()).isEqualTo("First stop"); + List persisted = journeyItemRepository.findByGeschichteIdOrderByPosition(journey.getId()); + assertThat(persisted).hasSize(1); + assertThat(persisted.get(0).getPosition()).isEqualTo(10); + assertThat(persisted.get(0).getNote()).isEqualTo("First stop"); + } + + // ─── JourneyItemService.reorder — atomicity check ──────────────────────── + + @Test + void reorder_swaps_positions_atomically() { + // Arrange: append two items (pos 10, pos 20) + authenticateAs(writer, Permission.BLOG_WRITE); + + JourneyItemCreateDTO dto1 = new JourneyItemCreateDTO(); + dto1.setNote("Item one"); + JourneyItemView item1View = journeyItemService.append(journey.getId(), dto1); + + JourneyItemCreateDTO dto2 = new JourneyItemCreateDTO(); + dto2.setNote("Item two"); + JourneyItemView item2View = journeyItemService.append(journey.getId(), dto2); + + assertThat(item1View.position()).isEqualTo(10); + assertThat(item2View.position()).isEqualTo(20); + + // Act: reorder with [item2, item1] + JourneyReorderDTO reorderDto = new JourneyReorderDTO(); + reorderDto.setItemIds(List.of(item2View.id(), item1View.id())); + List reordered = journeyItemService.reorder(journey.getId(), reorderDto); + em.flush(); + em.clear(); + + // Assert: item2 is now at position 10, item1 is at position 20 + List persisted = journeyItemRepository.findByGeschichteIdOrderByPosition(journey.getId()); + assertThat(persisted).hasSize(2); + assertThat(persisted.get(0).getId()).isEqualTo(item2View.id()); + assertThat(persisted.get(0).getPosition()).isEqualTo(10); + assertThat(persisted.get(1).getId()).isEqualTo(item1View.id()); + assertThat(persisted.get(1).getPosition()).isEqualTo(20); + } }