From e5024fc8040fec27353cf876cbdf14e2a9f7f695 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 2 May 2026 17:33:52 +0200 Subject: [PATCH] test(geschichte): add Testcontainers integration test and fix V58 author FK The end-to-end test creates a DRAFT, verifies it is hidden from a READ_ALL reader (list and getById), publishes it, verifies the reader sees it, then deletes it and confirms the join rows go with it but the linked Person remains. Also corrects the V58 author FK to reference the actual users table (not app_users). Co-Authored-By: Claude Opus 4.7 --- .../db/migration/V58__add_geschichten.sql | 2 +- .../GeschichteServiceIntegrationTest.java | 127 ++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 backend/src/test/java/org/raddatz/familienarchiv/service/GeschichteServiceIntegrationTest.java diff --git a/backend/src/main/resources/db/migration/V58__add_geschichten.sql b/backend/src/main/resources/db/migration/V58__add_geschichten.sql index 14bc8eee..ec37bdac 100644 --- a/backend/src/main/resources/db/migration/V58__add_geschichten.sql +++ b/backend/src/main/resources/db/migration/V58__add_geschichten.sql @@ -6,7 +6,7 @@ CREATE TABLE geschichten ( title VARCHAR(255) NOT NULL, body TEXT, status VARCHAR(32) NOT NULL, - author_id UUID REFERENCES app_users (id) ON DELETE SET NULL, + author_id UUID REFERENCES users (id) ON DELETE SET NULL, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, published_at TIMESTAMP diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/GeschichteServiceIntegrationTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/GeschichteServiceIntegrationTest.java new file mode 100644 index 00000000..301beb1a --- /dev/null +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/GeschichteServiceIntegrationTest.java @@ -0,0 +1,127 @@ +package org.raddatz.familienarchiv.service; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.raddatz.familienarchiv.PostgresContainerConfig; +import org.raddatz.familienarchiv.dto.GeschichteUpdateDTO; +import org.raddatz.familienarchiv.model.AppUser; +import org.raddatz.familienarchiv.model.Geschichte; +import org.raddatz.familienarchiv.model.GeschichteStatus; +import org.raddatz.familienarchiv.model.Person; +import org.raddatz.familienarchiv.repository.AppUserRepository; +import org.raddatz.familienarchiv.repository.GeschichteRepository; +import org.raddatz.familienarchiv.repository.PersonRepository; +import org.raddatz.familienarchiv.security.Permission; +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.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import software.amazon.awssdk.services.s3.S3Client; + +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) +@ActiveProfiles("test") +@Import(PostgresContainerConfig.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class GeschichteServiceIntegrationTest { + + @MockitoBean + S3Client s3Client; + + @Autowired GeschichteService geschichteService; + @Autowired GeschichteRepository geschichteRepository; + @Autowired PersonRepository personRepository; + @Autowired AppUserRepository appUserRepository; + + AppUser writer; + AppUser reader; + + @BeforeEach + void seed() { + writer = appUserRepository.save(AppUser.builder() + .email("writer-int@test") + .password("hash") + .build()); + reader = appUserRepository.save(AppUser.builder() + .email("reader-int@test") + .password("hash") + .build()); + } + + @AfterEach + void clear() { + SecurityContextHolder.clearContext(); + } + + @Test + void create_then_publish_then_read_then_delete_full_lifecycle() { + // Create as writer + authenticateAs(writer, Permission.BLOG_WRITE); + + Person franz = personRepository.save(Person.builder().firstName("Franz").lastName("Raddatz").build()); + + GeschichteUpdateDTO dto = new GeschichteUpdateDTO(); + dto.setTitle("Erinnerung an Opa Franz"); + dto.setBody("

Ich erinnere mich, wie er jeden Sonntag sang.

" + + ""); + dto.setPersonIds(List.of(franz.getId())); + + Geschichte created = geschichteService.create(dto); + + assertThat(created.getId()).isNotNull(); + assertThat(created.getStatus()).isEqualTo(GeschichteStatus.DRAFT); + assertThat(created.getBody()) + .contains("jeden Sonntag") + .doesNotContain("