test(shopping): add HTTP-level role guard test and blank customName validation test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 19:52:09 +02:00
parent eb5ee1ab5a
commit e3afe1b4f2

View File

@@ -3,8 +3,10 @@ package com.recipeapp.shopping;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.recipeapp.common.GlobalExceptionHandler; import com.recipeapp.common.GlobalExceptionHandler;
import com.recipeapp.common.HouseholdRoleInterceptor;
import com.recipeapp.recipe.HouseholdResolver; import com.recipeapp.recipe.HouseholdResolver;
import com.recipeapp.shopping.dto.*; import com.recipeapp.shopping.dto.*;
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.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -12,6 +14,8 @@ import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@@ -49,6 +53,11 @@ class ShoppingListControllerTest {
.build(); .build();
} }
@AfterEach
void clearSecurityContext() {
SecurityContextHolder.clearContext();
}
@Test @Test
void getByWeekStartShouldReturn200() throws Exception { void getByWeekStartShouldReturn200() throws Exception {
var response = new ShoppingListResponse(LIST_ID, PLAN_ID, Instant.now(), 3, List.of()); var response = new ShoppingListResponse(LIST_ID, PLAN_ID, Instant.now(), 3, List.of());
@@ -169,4 +178,20 @@ class ShoppingListControllerTest {
new AddItemRequest(null, " ", new BigDecimal("1"), "")))) new AddItemRequest(null, " ", new BigDecimal("1"), ""))))
.andExpect(status().isBadRequest()); .andExpect(status().isBadRequest());
} }
@Test
void generateFromPlanShouldReturn403ForNonPlanner() throws Exception {
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("member@example.com", null));
when(householdResolver.resolveRole("member@example.com")).thenReturn("member");
MockMvc mockMvcWithInterceptor = MockMvcBuilders.standaloneSetup(shoppingListController)
.setControllerAdvice(new GlobalExceptionHandler())
.addInterceptors(new HouseholdRoleInterceptor(householdResolver))
.build();
mockMvcWithInterceptor.perform(post("/v1/week-plans/{id}/shopping-list", PLAN_ID)
.principal(() -> "member@example.com"))
.andExpect(status().isForbidden());
}
} }