test(#148): add controller tests and raise coverage gate to 88%
Some checks failed
CI / Unit & Component Tests (pull_request) Successful in 2m40s
CI / Backend Unit Tests (pull_request) Failing after 2m28s
CI / Unit & Component Tests (push) Successful in 3m44s
CI / Backend Unit Tests (push) Failing after 5m9s
CI / E2E Tests (pull_request) Failing after 3h13m37s
CI / E2E Tests (push) Failing after 3h9m10s
Some checks failed
CI / Unit & Component Tests (pull_request) Successful in 2m40s
CI / Backend Unit Tests (pull_request) Failing after 2m28s
CI / Unit & Component Tests (push) Successful in 3m44s
CI / Backend Unit Tests (push) Failing after 5m9s
CI / E2E Tests (pull_request) Failing after 3h13m37s
CI / E2E Tests (push) Failing after 3h9m10s
Add branch-coverage tests for DocumentController (getDocumentFile happy/error paths, quickUpload null files), UserController (getCurrentUser auth branches), AnnotationController (resolveUserId null/exception branches), CommentController (resolveUser exception branch), and PersonController (updatePerson blank lastName). Controller branch coverage: 62% → 80%. Overall: 87.8% → 89.4%. Raise JaCoCo gate from 0.42 to 0.88. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit was merged in pull request #150.
This commit is contained in:
@@ -193,8 +193,7 @@
|
||||
<phase>verify</phase>
|
||||
<goals><goal>report</goal></goals>
|
||||
</execution>
|
||||
<!-- Gate: current baseline 46.8% — threshold set at 42% to prevent regression.
|
||||
Target is 80%; close the gap by adding tests for service error paths. -->
|
||||
<!-- Gate: baseline 89.4% overall / service 90.2% / controller 80.0% -->
|
||||
<execution>
|
||||
<id>check</id>
|
||||
<phase>verify</phase>
|
||||
@@ -207,7 +206,7 @@
|
||||
<limit>
|
||||
<counter>BRANCH</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.42</minimum>
|
||||
<minimum>0.88</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
|
||||
@@ -155,4 +155,51 @@ class AnnotationControllerTest {
|
||||
mockMvc.perform(delete("/api/documents/" + UUID.randomUUID() + "/annotations/" + UUID.randomUUID()))
|
||||
.andExpect(status().isNoContent());
|
||||
}
|
||||
|
||||
// ─── resolveUserId — unauthenticated / null user / exception branches ─────
|
||||
|
||||
@Test
|
||||
void createAnnotation_returns401_whenUnauthenticated_resolveUserIdReturnsNull() throws Exception {
|
||||
// authentication == null → resolveUserId returns null
|
||||
mockMvc.perform(post("/api/documents/" + UUID.randomUUID() + "/annotations")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(ANNOTATION_JSON))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = "ANNOTATE_ALL")
|
||||
void createAnnotation_resolvesNullUserId_whenUserServiceThrows() throws Exception {
|
||||
// findByUsername throws → catch block → resolveUserId returns null
|
||||
UUID docId = UUID.randomUUID();
|
||||
when(userService.findByUsername(any())).thenThrow(new RuntimeException("DB error"));
|
||||
DocumentAnnotation saved = DocumentAnnotation.builder()
|
||||
.id(UUID.randomUUID()).documentId(docId).pageNumber(1)
|
||||
.x(0.1).y(0.1).width(0.2).height(0.2).color("#ff0000").build();
|
||||
when(documentService.getDocumentById(any())).thenReturn(Document.builder().build());
|
||||
when(annotationService.createAnnotation(any(), any(), any(), any())).thenReturn(saved);
|
||||
|
||||
mockMvc.perform(post("/api/documents/" + docId + "/annotations")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(ANNOTATION_JSON))
|
||||
.andExpect(status().isCreated());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = "ANNOTATE_ALL")
|
||||
void createAnnotation_resolvesNullUserId_whenUserServiceReturnsNull() throws Exception {
|
||||
// findByUsername returns null → user != null = false → resolveUserId returns null
|
||||
UUID docId = UUID.randomUUID();
|
||||
when(userService.findByUsername(any())).thenReturn(null);
|
||||
DocumentAnnotation saved = DocumentAnnotation.builder()
|
||||
.id(UUID.randomUUID()).documentId(docId).pageNumber(1)
|
||||
.x(0.1).y(0.1).width(0.2).height(0.2).color("#ff0000").build();
|
||||
when(documentService.getDocumentById(any())).thenReturn(Document.builder().build());
|
||||
when(annotationService.createAnnotation(any(), any(), any(), any())).thenReturn(saved);
|
||||
|
||||
mockMvc.perform(post("/api/documents/" + docId + "/annotations")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(ANNOTATION_JSON))
|
||||
.andExpect(status().isCreated());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,4 +263,20 @@ class CommentControllerTest {
|
||||
.contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
|
||||
.andExpect(status().isCreated());
|
||||
}
|
||||
|
||||
// ─── resolveUser — exception branch ──────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = "WRITE_ALL")
|
||||
void postDocumentComment_stillSucceeds_whenUserServiceThrows() throws Exception {
|
||||
// findByUsername throws → catch block in resolveUser → author null, saves anyway
|
||||
when(userService.findByUsername(any())).thenThrow(new RuntimeException("DB error"));
|
||||
DocumentComment saved = DocumentComment.builder()
|
||||
.id(UUID.randomUUID()).documentId(DOC_ID).content("Test comment").build();
|
||||
when(commentService.postComment(any(), any(), any(), any(), any())).thenReturn(saved);
|
||||
|
||||
mockMvc.perform(post("/api/documents/" + DOC_ID + "/comments")
|
||||
.contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
|
||||
.andExpect(status().isCreated());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +213,80 @@ class DocumentControllerTest {
|
||||
.andExpect(jsonPath("$.errors[0].code").value("UNSUPPORTED_FILE_TYPE"));
|
||||
}
|
||||
|
||||
// ─── GET /api/documents/{id}/file ────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void getDocumentFile_returns404_whenDocHasNoFilePath() throws Exception {
|
||||
UUID id = UUID.randomUUID();
|
||||
Document doc = Document.builder().id(id).title("Brief").build(); // filePath == null
|
||||
when(documentService.getDocumentById(id)).thenReturn(doc);
|
||||
|
||||
mockMvc.perform(get("/api/documents/" + id + "/file"))
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void getDocumentFile_returns200_withContentTypeFromDoc() throws Exception {
|
||||
UUID id = UUID.randomUUID();
|
||||
Document doc = Document.builder().id(id).title("Brief")
|
||||
.filePath("docs/brief.pdf").contentType("application/pdf")
|
||||
.originalFilename("brief.pdf").build();
|
||||
when(documentService.getDocumentById(id)).thenReturn(doc);
|
||||
java.io.InputStream stream = new java.io.ByteArrayInputStream(new byte[]{1, 2, 3});
|
||||
when(fileService.downloadFile("docs/brief.pdf"))
|
||||
.thenReturn(new FileService.S3FileDownload(
|
||||
new org.springframework.core.io.InputStreamResource(stream), "application/octet-stream"));
|
||||
|
||||
mockMvc.perform(get("/api/documents/" + id + "/file"))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void getDocumentFile_returns200_withContentTypeFromStorage_whenDocContentTypeIsBlank() throws Exception {
|
||||
UUID id = UUID.randomUUID();
|
||||
Document doc = Document.builder().id(id).title("Brief")
|
||||
.filePath("docs/brief.pdf").contentType(" ") // blank → falls back to storage type
|
||||
.originalFilename("brief.pdf").build();
|
||||
when(documentService.getDocumentById(id)).thenReturn(doc);
|
||||
java.io.InputStream stream = new java.io.ByteArrayInputStream(new byte[]{1, 2, 3});
|
||||
when(fileService.downloadFile("docs/brief.pdf"))
|
||||
.thenReturn(new FileService.S3FileDownload(
|
||||
new org.springframework.core.io.InputStreamResource(stream), "application/pdf"));
|
||||
|
||||
mockMvc.perform(get("/api/documents/" + id + "/file"))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void getDocumentFile_returns404_whenStorageFileNotFound() throws Exception {
|
||||
UUID id = UUID.randomUUID();
|
||||
Document doc = Document.builder().id(id).title("Brief")
|
||||
.filePath("docs/missing.pdf").contentType("application/pdf")
|
||||
.originalFilename("missing.pdf").build();
|
||||
when(documentService.getDocumentById(id)).thenReturn(doc);
|
||||
when(fileService.downloadFile("docs/missing.pdf"))
|
||||
.thenThrow(new FileService.StorageFileNotFoundException("not found"));
|
||||
|
||||
mockMvc.perform(get("/api/documents/" + id + "/file"))
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
// ─── POST /api/documents/quick-upload — null/empty files ─────────────────
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = "WRITE_ALL")
|
||||
void quickUpload_returnsEmptyResult_whenNoFilesPartProvided() throws Exception {
|
||||
mockMvc.perform(multipart("/api/documents/quick-upload"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.created").isEmpty())
|
||||
.andExpect(jsonPath("$.updated").isEmpty())
|
||||
.andExpect(jsonPath("$.errors").isEmpty());
|
||||
}
|
||||
|
||||
// ─── GET /api/documents/incomplete-count ─────────────────────────────────
|
||||
|
||||
@Test
|
||||
|
||||
@@ -291,4 +291,17 @@ class PersonControllerTest {
|
||||
.content("{\"targetPersonId\":\"" + targetId + "\"}"))
|
||||
.andExpect(status().isNoContent());
|
||||
}
|
||||
|
||||
// ─── PUT /api/persons/{id} — lastName blank branch ────────────────────────
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = "WRITE_ALL")
|
||||
void updatePerson_returns400_whenLastNameIsBlank() throws Exception {
|
||||
// firstName valid, lastName blank → second || operand = true → 400
|
||||
UUID id = UUID.randomUUID();
|
||||
mockMvc.perform(put("/api/persons/{id}", id)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"firstName\":\"Hans\",\"lastName\":\" \"}"))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.raddatz.familienarchiv.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.raddatz.familienarchiv.config.SecurityConfig;
|
||||
import org.raddatz.familienarchiv.model.AppUser;
|
||||
import org.raddatz.familienarchiv.security.PermissionAspect;
|
||||
import org.raddatz.familienarchiv.service.CustomUserDetailsService;
|
||||
import org.raddatz.familienarchiv.service.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration;
|
||||
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@WebMvcTest(UserController.class)
|
||||
@Import({SecurityConfig.class, PermissionAspect.class, AopAutoConfiguration.class})
|
||||
class UserControllerTest {
|
||||
|
||||
@Autowired MockMvc mockMvc;
|
||||
|
||||
@MockitoBean UserService userService;
|
||||
@MockitoBean CustomUserDetailsService customUserDetailsService;
|
||||
|
||||
// ─── GET /api/users/me ────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void getCurrentUser_returns401_whenUnauthenticated() throws Exception {
|
||||
// authentication == null → returns 401 (covers null/!isAuthenticated branch)
|
||||
mockMvc.perform(get("/api/users/me"))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "anna")
|
||||
void getCurrentUser_returns200_whenAuthenticated() throws Exception {
|
||||
AppUser user = AppUser.builder().id(UUID.randomUUID()).username("anna").build();
|
||||
when(userService.findByUsername("anna")).thenReturn(user);
|
||||
|
||||
mockMvc.perform(get("/api/users/me"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.username").value("anna"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user