feat(api): add GET/POST/DELETE /api/persons/{id}/aliases endpoints

GET returns aliases (no permission required), POST requires
WRITE_ALL, DELETE requires WRITE_ALL. 5 new controller tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-07 13:09:58 +02:00
parent 0fc568dd9f
commit a1d63bbc42
2 changed files with 87 additions and 0 deletions

View File

@@ -5,10 +5,12 @@ import java.util.Map;
import java.util.UUID;
import org.raddatz.familienarchiv.dto.PersonNameAliasDTO;
import org.raddatz.familienarchiv.dto.PersonSummaryDTO;
import org.raddatz.familienarchiv.dto.PersonUpdateDTO;
import org.raddatz.familienarchiv.model.Document;
import org.raddatz.familienarchiv.model.Person;
import org.raddatz.familienarchiv.model.PersonNameAlias;
import org.raddatz.familienarchiv.security.Permission;
import org.raddatz.familienarchiv.security.RequirePermission;
import org.raddatz.familienarchiv.service.DocumentService;
@@ -92,4 +94,24 @@ public class PersonController {
}
personService.mergePersons(id, UUID.fromString(targetIdStr));
}
// ─── Alias endpoints ────────────────────────────────────────────────────
@GetMapping("/{id}/aliases")
public List<PersonNameAlias> getAliases(@PathVariable UUID id) {
return personService.getAliases(id);
}
@PostMapping("/{id}/aliases")
@RequirePermission(Permission.WRITE_ALL)
public PersonNameAlias addAlias(@PathVariable UUID id, @RequestBody PersonNameAliasDTO dto) {
return personService.addAlias(id, dto);
}
@DeleteMapping("/{id}/aliases/{aliasId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@RequirePermission(Permission.WRITE_ALL)
public void removeAlias(@PathVariable UUID id, @PathVariable UUID aliasId) {
personService.removeAlias(id, aliasId);
}
}

View File

@@ -3,6 +3,8 @@ package org.raddatz.familienarchiv.controller;
import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.model.Document;
import org.raddatz.familienarchiv.model.Person;
import org.raddatz.familienarchiv.model.PersonNameAlias;
import org.raddatz.familienarchiv.model.PersonNameAliasType;
import org.raddatz.familienarchiv.security.PermissionAspect;
import org.raddatz.familienarchiv.service.CustomUserDetailsService;
import org.raddatz.familienarchiv.service.DocumentService;
@@ -25,6 +27,7 @@ import org.raddatz.familienarchiv.dto.PersonSummaryDTO;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@@ -393,4 +396,66 @@ class PersonControllerTest {
.content("{\"targetPersonId\":\"" + UUID.randomUUID() + "\"}"))
.andExpect(status().isForbidden());
}
// ─── GET /api/persons/{id}/aliases ────────────────────────────────────────
@Test
@WithMockUser
void getAliases_returns200_withList() throws Exception {
UUID personId = UUID.randomUUID();
PersonNameAlias alias = PersonNameAlias.builder()
.id(UUID.randomUUID()).lastName("de Gruyter").type(PersonNameAliasType.BIRTH).sortOrder(0).build();
when(personService.getAliases(personId)).thenReturn(List.of(alias));
mockMvc.perform(get("/api/persons/{id}/aliases", personId))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].lastName").value("de Gruyter"));
}
// ─── POST /api/persons/{id}/aliases ──────────────────────────────────────
@Test
@WithMockUser(authorities = "WRITE_ALL")
void addAlias_returns200_whenValid() throws Exception {
UUID personId = UUID.randomUUID();
PersonNameAlias saved = PersonNameAlias.builder()
.id(UUID.randomUUID()).lastName("de Gruyter").type(PersonNameAliasType.BIRTH).sortOrder(0).build();
when(personService.addAlias(eq(personId), any())).thenReturn(saved);
mockMvc.perform(post("/api/persons/{id}/aliases", personId)
.contentType(MediaType.APPLICATION_JSON)
.content("{\"lastName\":\"de Gruyter\",\"type\":\"BIRTH\"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.lastName").value("de Gruyter"));
}
@Test
@WithMockUser(authorities = "READ_ALL")
void addAlias_returns403_withoutWritePermission() throws Exception {
mockMvc.perform(post("/api/persons/{id}/aliases", UUID.randomUUID())
.contentType(MediaType.APPLICATION_JSON)
.content("{\"lastName\":\"de Gruyter\",\"type\":\"BIRTH\"}"))
.andExpect(status().isForbidden());
}
// ─── DELETE /api/persons/{id}/aliases/{aliasId} ──────────────────────────
@Test
@WithMockUser(authorities = "WRITE_ALL")
void removeAlias_returns204_whenValid() throws Exception {
UUID personId = UUID.randomUUID();
UUID aliasId = UUID.randomUUID();
mockMvc.perform(delete("/api/persons/{id}/aliases/{aliasId}", personId, aliasId))
.andExpect(status().isNoContent());
verify(personService).removeAlias(personId, aliasId);
}
@Test
@WithMockUser(authorities = "READ_ALL")
void removeAlias_returns403_withoutWritePermission() throws Exception {
mockMvc.perform(delete("/api/persons/{id}/aliases/{aliasId}", UUID.randomUUID(), UUID.randomUUID()))
.andExpect(status().isForbidden());
}
}