From d03bb998752727a4534db732c9bc54e5fec57371 Mon Sep 17 00:00:00 2001 From: Simon Forsberg Date: Thu, 23 Apr 2026 12:41:59 +0200 Subject: [PATCH 01/10] Add integration test suite for `TicketController` --- .../alfs/controllers/TicketControllerIT.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/test/java/org/example/alfs/controllers/TicketControllerIT.java diff --git a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java new file mode 100644 index 0000000..eadddd4 --- /dev/null +++ b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java @@ -0,0 +1,17 @@ +package org.example.alfs.controllers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.test.web.servlet.MockMvc; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@AutoConfigureMockMvc +class TicketControllerIT { + + @Autowired + private MockMvc mockMvc; + +} \ No newline at end of file From 9d2920faff9377385e71fbe35ef830f5d406a618 Mon Sep 17 00:00:00 2001 From: Simon Forsberg Date: Thu, 23 Apr 2026 13:19:30 +0200 Subject: [PATCH 02/10] Add nested integration tests for anonymous reporter endpoints in `TicketControllerIT` --- .../alfs/controllers/TicketControllerIT.java | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java index eadddd4..1a50ce4 100644 --- a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java +++ b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java @@ -1,17 +1,70 @@ package org.example.alfs.controllers; +import org.example.alfs.dto.ticket.TicketCreateDTO; +import org.example.alfs.services.TicketService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; -import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SpringBootTest @AutoConfigureMockMvc +@Transactional +@DisplayName("TicketController Integration Tests") class TicketControllerIT { @Autowired private MockMvc mockMvc; + @Autowired + private TicketService ticketService; + + @Nested + @DisplayName("Anonymous Reporter") + class AnonymousReporter { + + @Test + @DisplayName("Anonymous reporter can access create form") + void anonymousReporter_canAccessCreateForm() throws Exception { + mockMvc.perform(get("/tickets/create")) + .andExpect(status().isOk()) + .andExpect(view().name("create")) + .andExpect(model().attributeExists("ticket")); + } + + @Test + @DisplayName("Anonymous reporter can view a created ticket with valid token") + void anonymousReporter_validToken_returnsView() throws Exception { + var dto = new TicketCreateDTO(); + dto.setTitle("Test"); + dto.setDescription("Test"); + + var ticket = ticketService.createNewTicket(dto); + + mockMvc.perform(get("/tickets/token/" + ticket.getToken())) + .andExpect(status().isOk()) + .andExpect(view().name("view")) + .andExpect(model().attributeExists("ticket")) + .andExpect(model().attributeExists("comments")) + .andExpect(model().attributeExists("attachments")) + .andExpect(model().attributeExists("auditLogs")) + .andExpect(model().attributeExists("investigators")) + .andExpect(model().attributeExists("accessToken")); + } + + @Test + @DisplayName("Anonymous reporter is redirected when token is invalid") + void anonymousReporter_invalidToken_redirectsToLogin() throws Exception { + mockMvc.perform(get("/tickets/token/invalid-token")) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/login?tokenError=true")); + } + } } \ No newline at end of file From 5fbd2215f2e7512ab7b857e41b474cdc8b14fc04 Mon Sep 17 00:00:00 2001 From: Simon Forsberg Date: Thu, 23 Apr 2026 14:39:28 +0200 Subject: [PATCH 03/10] Add additional integration tests for anonymous reporter ticket creation in `TicketControllerIT` --- .../alfs/controllers/TicketControllerIT.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java index 1a50ce4..bf3aba5 100644 --- a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java +++ b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java @@ -8,10 +8,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SpringBootTest @@ -66,5 +69,28 @@ void anonymousReporter_invalidToken_redirectsToLogin() throws Exception { .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/login?tokenError=true")); } + + @Test + @DisplayName("Anonymous reporter submits valid ticket and is redirected to ticket-created") + void anonymousReporter_validPost_redirectsToTicketCreated() throws Exception { + mockMvc.perform(post("/tickets/create") + .param("title", "Test title") + .param("description", "Test description") + .with(csrf())) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrlPattern("/tickets/ticket-created?token=*")); + } + + @Test + @DisplayName("Anonymous reporter submits blank form and sees create page again") + void anonymousReporter_blankPost_returnsCreateForm() throws Exception { + mockMvc.perform(post("/tickets/create") + .param("title", "") + .param("description", "") + .with(csrf())) + .andExpect(status().isOk()) + .andExpect(view().name("create")) + .andExpect(model().attributeHasFieldErrors("ticket", "title", "description")); + } } } \ No newline at end of file From b5dace3078e7558251a7993a73d941ecfd0dac0d Mon Sep 17 00:00:00 2001 From: Simon Forsberg Date: Thu, 23 Apr 2026 15:48:47 +0200 Subject: [PATCH 04/10] Add integration tests for authenticated reporter and investigator endpoints in `TicketControllerIT` --- .../alfs/controllers/TicketControllerIT.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java index bf3aba5..28d2708 100644 --- a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java +++ b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java @@ -1,7 +1,11 @@ package org.example.alfs.controllers; import org.example.alfs.dto.ticket.TicketCreateDTO; +import org.example.alfs.entities.User; +import org.example.alfs.enums.Role; +import org.example.alfs.repositories.UserRepository; import org.example.alfs.services.TicketService; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -28,6 +32,26 @@ class TicketControllerIT { @Autowired private TicketService ticketService; + @Autowired + private UserRepository userRepository; + + private User investigator; + private User reporter; + + @BeforeEach + void setUp() { + investigator = new User(); + investigator.setRole(Role.INVESTIGATOR); + investigator.setUsername("investigator"); + investigator.setPasswordHash("hashed-password"); + investigator = userRepository.save(investigator); + + reporter = new User(); + reporter.setRole(Role.REPORTER); + reporter.setUsername("reporter"); + reporter.setPasswordHash("hashed-password"); + reporter = userRepository.save(reporter); + } @Nested @DisplayName("Anonymous Reporter") @@ -93,4 +117,34 @@ void anonymousReporter_blankPost_returnsCreateForm() throws Exception { .andExpect(model().attributeHasFieldErrors("ticket", "title", "description")); } } + + @Nested + @DisplayName("Authenticated Reporter") + class AuthenticatedReporter { + + @Test + @WithMockUser(username = "reporter", roles = "REPORTER") + @DisplayName("Authenticated reporter can view their own tickets") + void authenticatedReporter_canViewOwnTickets() throws Exception { + mockMvc.perform(get("/tickets/my")) + .andExpect(status().isOk()) + .andExpect(view().name("my-tickets")) + .andExpect(model().attributeExists("tickets")); + } + } + + @Nested + @DisplayName("Authenticated Investigator") + class AuthenticatedInvestigator { + + @Test + @WithMockUser(username = "investigator", roles = "INVESTIGATOR") + @DisplayName("Authenticated investigator can view their assigned tickets") + void authenticatedInvestigator_canViewAssignedTickets() throws Exception { + mockMvc.perform(get("/tickets/assigned")) + .andExpect(status().isOk()) + .andExpect(view().name("assigned-tickets")) + .andExpect(model().attributeExists("tickets")); + } + } } \ No newline at end of file From d2299f0ab492b6b648566b6c0cca66661c183e60 Mon Sep 17 00:00:00 2001 From: Simon Forsberg Date: Thu, 23 Apr 2026 16:38:08 +0200 Subject: [PATCH 05/10] Add integration tests for admin endpoints in `TicketControllerIT` --- .../alfs/controllers/TicketControllerIT.java | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java index 28d2708..7ba9a0b 100644 --- a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java +++ b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java @@ -35,11 +35,19 @@ class TicketControllerIT { @Autowired private UserRepository userRepository; + private Long ticketId; + private User admin; private User investigator; private User reporter; @BeforeEach void setUp() { + admin = new User(); + admin.setRole(Role.ADMIN); + admin.setUsername("admin"); + admin.setPasswordHash("hashed-password"); + admin = userRepository.save(admin); + investigator = new User(); investigator.setRole(Role.INVESTIGATOR); investigator.setUsername("investigator"); @@ -51,6 +59,11 @@ void setUp() { reporter.setUsername("reporter"); reporter.setPasswordHash("hashed-password"); reporter = userRepository.save(reporter); + + TicketCreateDTO dto = new TicketCreateDTO(); + dto.setTitle("Test"); + dto.setDescription("Test"); + ticketId = ticketService.createNewTicket(dto).getId(); } @Nested @@ -119,7 +132,7 @@ void anonymousReporter_blankPost_returnsCreateForm() throws Exception { } @Nested - @DisplayName("Authenticated Reporter") + @DisplayName("Reporter") class AuthenticatedReporter { @Test @@ -134,7 +147,7 @@ void authenticatedReporter_canViewOwnTickets() throws Exception { } @Nested - @DisplayName("Authenticated Investigator") + @DisplayName("Investigator") class AuthenticatedInvestigator { @Test @@ -147,4 +160,23 @@ void authenticatedInvestigator_canViewAssignedTickets() throws Exception { .andExpect(model().attributeExists("tickets")); } } + + @Nested + @DisplayName("Admin") + class Admin { + + @Test + @WithMockUser(username = "admin", roles = "ADMIN") + @DisplayName("Admin can view ticket by id") + void admin_canViewTicketById() throws Exception { + mockMvc.perform(get("/tickets/{id}", ticketId)) + .andExpect(status().isOk()) + .andExpect(view().name("view")) + .andExpect(model().attributeExists("ticket")) + .andExpect(model().attributeExists("comments")) + .andExpect(model().attributeExists("attachments")) + .andExpect(model().attributeExists("auditLogs")) + .andExpect(model().attributeExists("investigators")); + } + } } \ No newline at end of file From 7937776b6636907e97af994e4ca1c1ac8151a6a1 Mon Sep 17 00:00:00 2001 From: Simon Forsberg Date: Thu, 23 Apr 2026 16:59:47 +0200 Subject: [PATCH 06/10] Add integration test for admin assigning investigators in `TicketControllerIT` --- .../example/alfs/controllers/TicketControllerIT.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java index 7ba9a0b..d202252 100644 --- a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java +++ b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java @@ -178,5 +178,16 @@ void admin_canViewTicketById() throws Exception { .andExpect(model().attributeExists("auditLogs")) .andExpect(model().attributeExists("investigators")); } + + @Test + @WithMockUser(username = "admin", roles = "ADMIN") + @DisplayName("Admin can assign an investigator to a ticket") + void admin_canAssignInvestigator() throws Exception { + mockMvc.perform(post("/tickets/{id}/assign", ticketId) + .param("investigatorId", investigator.getId().toString()) + .with(csrf())) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/tickets/" + ticketId)); + } } } \ No newline at end of file From 08804bf65757f195f85dd82aa64ab355c14fc1df Mon Sep 17 00:00:00 2001 From: Simon Forsberg Date: Thu, 23 Apr 2026 17:06:06 +0200 Subject: [PATCH 07/10] Add integration test for admin updating ticket status in `TicketControllerIT` --- .../alfs/controllers/TicketControllerIT.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java index d202252..09c1bdc 100644 --- a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java +++ b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java @@ -189,5 +189,18 @@ void admin_canAssignInvestigator() throws Exception { .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/tickets/" + ticketId)); } + + @Test + @WithMockUser(username = "admin", roles = "ADMIN") + @DisplayName("Admin can update status on a ticket") + void admin_canUpdateTicketStatus() throws Exception { + ticketService.assignInvestigator(ticketId, investigator.getId()); + + mockMvc.perform(post("/tickets/{id}/status", ticketId) + .param("status", "RESOLVED") + .with(csrf())) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/tickets/" + ticketId)); + } } } \ No newline at end of file From 6e31813e6e887d1af31aada7f5a10b3cb106f4f0 Mon Sep 17 00:00:00 2001 From: Simon Forsberg Date: Thu, 23 Apr 2026 17:27:45 +0200 Subject: [PATCH 08/10] Refactor test descriptions and add validation for admin status transitions in `TicketControllerIT` --- .../alfs/controllers/TicketControllerIT.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java index 09c1bdc..ac5b7b4 100644 --- a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java +++ b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java @@ -132,13 +132,13 @@ void anonymousReporter_blankPost_returnsCreateForm() throws Exception { } @Nested - @DisplayName("Reporter") + @DisplayName("Authenticated reporter") class AuthenticatedReporter { @Test @WithMockUser(username = "reporter", roles = "REPORTER") - @DisplayName("Authenticated reporter can view their own tickets") - void authenticatedReporter_canViewOwnTickets() throws Exception { + @DisplayName("Reporter can view their own tickets") + void reporter_canViewOwnTickets() throws Exception { mockMvc.perform(get("/tickets/my")) .andExpect(status().isOk()) .andExpect(view().name("my-tickets")) @@ -147,13 +147,13 @@ void authenticatedReporter_canViewOwnTickets() throws Exception { } @Nested - @DisplayName("Investigator") + @DisplayName("Authenticated investigator") class AuthenticatedInvestigator { @Test @WithMockUser(username = "investigator", roles = "INVESTIGATOR") - @DisplayName("Authenticated investigator can view their assigned tickets") - void authenticatedInvestigator_canViewAssignedTickets() throws Exception { + @DisplayName("Investigator can view their assigned tickets") + void investigator_canViewAssignedTickets() throws Exception { mockMvc.perform(get("/tickets/assigned")) .andExpect(status().isOk()) .andExpect(view().name("assigned-tickets")) @@ -202,5 +202,15 @@ void admin_canUpdateTicketStatus() throws Exception { .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/tickets/" + ticketId)); } + + @Test + @WithMockUser(username = "admin", roles = "ADMIN") + @DisplayName("Admin cannot perform invalid status transition") + void admin_cannotPerformInvalidStatusTransition() throws Exception { + mockMvc.perform(post("/tickets/{id}/status", ticketId) + .param("status", "RESOLVED") + .with(csrf())) + .andExpect(status().isBadRequest()); + } } } \ No newline at end of file From ab5a2d1a904ebe661768f422c8128740f2497350 Mon Sep 17 00:00:00 2001 From: Simon Forsberg Date: Thu, 23 Apr 2026 17:38:30 +0200 Subject: [PATCH 09/10] Refactor test to reuse existing ticket and update repository initialization in `TicketControllerIT` --- .../example/alfs/controllers/TicketControllerIT.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java index ac5b7b4..5dbeed1 100644 --- a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java +++ b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java @@ -3,6 +3,7 @@ import org.example.alfs.dto.ticket.TicketCreateDTO; import org.example.alfs.entities.User; import org.example.alfs.enums.Role; +import org.example.alfs.repositories.TicketRepository; import org.example.alfs.repositories.UserRepository; import org.example.alfs.services.TicketService; import org.junit.jupiter.api.BeforeEach; @@ -34,6 +35,8 @@ class TicketControllerIT { private TicketService ticketService; @Autowired private UserRepository userRepository; + @Autowired + private TicketRepository ticketRepository; private Long ticketId; private User admin; @@ -82,13 +85,9 @@ void anonymousReporter_canAccessCreateForm() throws Exception { @Test @DisplayName("Anonymous reporter can view a created ticket with valid token") void anonymousReporter_validToken_returnsView() throws Exception { - var dto = new TicketCreateDTO(); - dto.setTitle("Test"); - dto.setDescription("Test"); - - var ticket = ticketService.createNewTicket(dto); + var ticket = ticketRepository.findById(ticketId).orElseThrow(); - mockMvc.perform(get("/tickets/token/" + ticket.getToken())) + mockMvc.perform(get("/tickets/token/" + ticket.getReporterToken())) .andExpect(status().isOk()) .andExpect(view().name("view")) .andExpect(model().attributeExists("ticket")) From 47a3b866c2803f7d6282ebe8bd7de5491ec903b3 Mon Sep 17 00:00:00 2001 From: Simon Forsberg Date: Thu, 23 Apr 2026 17:49:34 +0200 Subject: [PATCH 10/10] Add assertion for initial ticket status in admin invalid status transition test in `TicketControllerIT` --- .../org/example/alfs/controllers/TicketControllerIT.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java index 5dbeed1..83cfdda 100644 --- a/src/test/java/org/example/alfs/controllers/TicketControllerIT.java +++ b/src/test/java/org/example/alfs/controllers/TicketControllerIT.java @@ -17,6 +17,7 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -206,8 +207,11 @@ void admin_canUpdateTicketStatus() throws Exception { @WithMockUser(username = "admin", roles = "ADMIN") @DisplayName("Admin cannot perform invalid status transition") void admin_cannotPerformInvalidStatusTransition() throws Exception { + var ticket = ticketRepository.findById(ticketId).orElseThrow(); + assertEquals(org.example.alfs.enums.TicketStatus.OPEN, ticket.getStatus()); + mockMvc.perform(post("/tickets/{id}/status", ticketId) - .param("status", "RESOLVED") + .param("status", "RESOLVED") // Transition OPEN -> RESOLVED is invalid .with(csrf())) .andExpect(status().isBadRequest()); }