feat(lesereisen): data model + Flyway migration — GeschichteType, JourneyItem, migrate geschichten_documents #787
@@ -2,6 +2,7 @@ package org.raddatz.familienarchiv.geschichte.journeyitem;
|
|||||||
|
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
import jakarta.persistence.PersistenceContext;
|
import jakarta.persistence.PersistenceContext;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.raddatz.familienarchiv.PostgresContainerConfig;
|
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.GeschichteRepository;
|
||||||
import org.raddatz.familienarchiv.geschichte.GeschichteStatus;
|
import org.raddatz.familienarchiv.geschichte.GeschichteStatus;
|
||||||
import org.raddatz.familienarchiv.geschichte.GeschichteType;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.context.annotation.Import;
|
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.ActiveProfiles;
|
||||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -40,13 +47,20 @@ class JourneyItemIntegrationTest {
|
|||||||
|
|
||||||
@Autowired GeschichteRepository geschichteRepository;
|
@Autowired GeschichteRepository geschichteRepository;
|
||||||
@Autowired JourneyItemRepository journeyItemRepository;
|
@Autowired JourneyItemRepository journeyItemRepository;
|
||||||
|
@Autowired JourneyItemService journeyItemService;
|
||||||
@Autowired DocumentRepository documentRepository;
|
@Autowired DocumentRepository documentRepository;
|
||||||
|
@Autowired AppUserRepository appUserRepository;
|
||||||
|
|
||||||
Geschichte journey;
|
Geschichte journey;
|
||||||
Document doc;
|
Document doc;
|
||||||
|
AppUser writer;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void seed() {
|
void seed() {
|
||||||
|
writer = appUserRepository.save(AppUser.builder()
|
||||||
|
.email("journey-writer@test")
|
||||||
|
.password("hash")
|
||||||
|
.build());
|
||||||
doc = documentRepository.save(Document.builder()
|
doc = documentRepository.save(Document.builder()
|
||||||
.title("Testbrief")
|
.title("Testbrief")
|
||||||
.originalFilename("testbrief.pdf")
|
.originalFilename("testbrief.pdf")
|
||||||
@@ -61,6 +75,19 @@ class JourneyItemIntegrationTest {
|
|||||||
em.clear();
|
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 ─────────────────────────────────────────────────────────────
|
// ─── @OrderBy ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -206,4 +233,62 @@ class JourneyItemIntegrationTest {
|
|||||||
journeyItemRepository.flush();
|
journeyItemRepository.flush();
|
||||||
}).isInstanceOf(Exception.class);
|
}).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<JourneyItem> 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<JourneyItemView> reordered = journeyItemService.reorder(journey.getId(), reorderDto);
|
||||||
|
em.flush();
|
||||||
|
em.clear();
|
||||||
|
|
||||||
|
// Assert: item2 is now at position 10, item1 is at position 20
|
||||||
|
List<JourneyItem> 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user