fix(geschichte): restore getView() on GeschichteService with @Transactional(readOnly=true) — fixes two-call transaction gap
Re-inject JourneyItemService into GeschichteService (no cycle: JourneyItemService → GeschichteQueryService, not GeschichteService). Add getView(UUID) that loads the Geschichte and its items in a single @Transactional(readOnly=true) session. Controller now delegates to getView() instead of making two separate service calls. Tests updated to stub getView() and cover the new method. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -46,9 +46,7 @@ public class GeschichteController {
|
|||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
public GeschichteView getById(@PathVariable UUID id) {
|
public GeschichteView getById(@PathVariable UUID id) {
|
||||||
Geschichte g = geschichteService.getById(id);
|
return geschichteService.getView(id);
|
||||||
List<JourneyItemView> items = journeyItemService.getItems(g.getId());
|
|
||||||
return geschichteService.toView(g, items);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.owasp.html.HtmlPolicyBuilder;
|
|||||||
import org.owasp.html.PolicyFactory;
|
import org.owasp.html.PolicyFactory;
|
||||||
import org.raddatz.familienarchiv.exception.DomainException;
|
import org.raddatz.familienarchiv.exception.DomainException;
|
||||||
import org.raddatz.familienarchiv.exception.ErrorCode;
|
import org.raddatz.familienarchiv.exception.ErrorCode;
|
||||||
|
import org.raddatz.familienarchiv.geschichte.journeyitem.JourneyItemService;
|
||||||
import org.raddatz.familienarchiv.geschichte.journeyitem.JourneyItemView;
|
import org.raddatz.familienarchiv.geschichte.journeyitem.JourneyItemView;
|
||||||
import org.raddatz.familienarchiv.user.AppUser;
|
import org.raddatz.familienarchiv.user.AppUser;
|
||||||
import org.raddatz.familienarchiv.person.Person;
|
import org.raddatz.familienarchiv.person.Person;
|
||||||
@@ -35,6 +36,7 @@ public class GeschichteService {
|
|||||||
private final PersonService personService;
|
private final PersonService personService;
|
||||||
private final DocumentService documentService;
|
private final DocumentService documentService;
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
private final JourneyItemService journeyItemService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow-list policy for Geschichte body HTML. Tiptap on the writer side
|
* Allow-list policy for Geschichte body HTML. Tiptap on the writer side
|
||||||
@@ -67,6 +69,13 @@ public class GeschichteService {
|
|||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public GeschichteView getView(UUID id) {
|
||||||
|
Geschichte g = getById(id);
|
||||||
|
List<JourneyItemView> items = journeyItemService.getItems(id);
|
||||||
|
return toView(g, items);
|
||||||
|
}
|
||||||
|
|
||||||
GeschichteView toView(Geschichte g, List<JourneyItemView> items) {
|
GeschichteView toView(Geschichte g, List<JourneyItemView> items) {
|
||||||
AppUser author = g.getAuthor();
|
AppUser author = g.getAuthor();
|
||||||
GeschichteView.AuthorView authorView = null;
|
GeschichteView.AuthorView authorView = null;
|
||||||
|
|||||||
@@ -106,10 +106,7 @@ class GeschichteControllerTest {
|
|||||||
@WithMockUser(authorities = "READ_ALL")
|
@WithMockUser(authorities = "READ_ALL")
|
||||||
void getById_returns200_whenFound() throws Exception {
|
void getById_returns200_whenFound() throws Exception {
|
||||||
UUID id = UUID.randomUUID();
|
UUID id = UUID.randomUUID();
|
||||||
Geschichte g = published(id, "Hello");
|
when(geschichteService.getView(id)).thenReturn(viewStub(id, "Hello"));
|
||||||
when(geschichteService.getById(id)).thenReturn(g);
|
|
||||||
when(journeyItemService.getItems(id)).thenReturn(List.of());
|
|
||||||
when(geschichteService.toView(g, List.of())).thenReturn(viewStub(id, "Hello"));
|
|
||||||
|
|
||||||
mockMvc.perform(get("/api/geschichten/{id}", id))
|
mockMvc.perform(get("/api/geschichten/{id}", id))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
@@ -121,7 +118,7 @@ class GeschichteControllerTest {
|
|||||||
@WithMockUser(authorities = "READ_ALL")
|
@WithMockUser(authorities = "READ_ALL")
|
||||||
void getById_returns404_whenServiceThrowsNotFound() throws Exception {
|
void getById_returns404_whenServiceThrowsNotFound() throws Exception {
|
||||||
UUID id = UUID.randomUUID();
|
UUID id = UUID.randomUUID();
|
||||||
when(geschichteService.getById(id))
|
when(geschichteService.getView(id))
|
||||||
.thenThrow(DomainException.notFound(ErrorCode.GESCHICHTE_NOT_FOUND, "x"));
|
.thenThrow(DomainException.notFound(ErrorCode.GESCHICHTE_NOT_FOUND, "x"));
|
||||||
|
|
||||||
mockMvc.perform(get("/api/geschichten/{id}", id))
|
mockMvc.perform(get("/api/geschichten/{id}", id))
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.raddatz.familienarchiv.exception.DomainException;
|
import org.raddatz.familienarchiv.exception.DomainException;
|
||||||
import org.raddatz.familienarchiv.exception.ErrorCode;
|
import org.raddatz.familienarchiv.exception.ErrorCode;
|
||||||
|
import org.raddatz.familienarchiv.geschichte.journeyitem.JourneyItemService;
|
||||||
|
import org.raddatz.familienarchiv.geschichte.journeyitem.JourneyItemView;
|
||||||
import org.raddatz.familienarchiv.user.AppUser;
|
import org.raddatz.familienarchiv.user.AppUser;
|
||||||
import org.raddatz.familienarchiv.person.Person;
|
import org.raddatz.familienarchiv.person.Person;
|
||||||
import org.raddatz.familienarchiv.security.Permission;
|
import org.raddatz.familienarchiv.security.Permission;
|
||||||
@@ -45,6 +47,7 @@ class GeschichteServiceTest {
|
|||||||
@Mock PersonService personService;
|
@Mock PersonService personService;
|
||||||
@Mock DocumentService documentService;
|
@Mock DocumentService documentService;
|
||||||
@Mock UserService userService;
|
@Mock UserService userService;
|
||||||
|
@Mock JourneyItemService journeyItemService;
|
||||||
|
|
||||||
@InjectMocks GeschichteService geschichteService;
|
@InjectMocks GeschichteService geschichteService;
|
||||||
|
|
||||||
@@ -116,6 +119,36 @@ class GeschichteServiceTest {
|
|||||||
.isEqualTo(ErrorCode.GESCHICHTE_NOT_FOUND);
|
.isEqualTo(ErrorCode.GESCHICHTE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── getView ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getView_returns_assembled_view_and_delegates_to_journeyItemService() {
|
||||||
|
authenticateAs(reader, Permission.READ_ALL);
|
||||||
|
UUID id = UUID.randomUUID();
|
||||||
|
Geschichte published = published(id);
|
||||||
|
JourneyItemView item = new JourneyItemView(UUID.randomUUID(), 10, null, "Note");
|
||||||
|
when(geschichteRepository.findById(id)).thenReturn(Optional.of(published));
|
||||||
|
when(journeyItemService.getItems(id)).thenReturn(List.of(item));
|
||||||
|
|
||||||
|
GeschichteView view = geschichteService.getView(id);
|
||||||
|
|
||||||
|
assertThat(view.id()).isEqualTo(id);
|
||||||
|
assertThat(view.items()).containsExactly(item);
|
||||||
|
verify(journeyItemService).getItems(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getView_throws_NOT_FOUND_when_id_unknown() {
|
||||||
|
authenticateAs(reader, Permission.READ_ALL);
|
||||||
|
UUID id = UUID.randomUUID();
|
||||||
|
when(geschichteRepository.findById(id)).thenReturn(Optional.empty());
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> geschichteService.getView(id))
|
||||||
|
.isInstanceOf(DomainException.class)
|
||||||
|
.extracting("code")
|
||||||
|
.isEqualTo(ErrorCode.GESCHICHTE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void toView_author_displayName_uses_firstName_lastName() {
|
void toView_author_displayName_uses_firstName_lastName() {
|
||||||
UUID id = UUID.randomUUID();
|
UUID id = UUID.randomUUID();
|
||||||
|
|||||||
Reference in New Issue
Block a user