package com.xly.erp.common.exception; import com.xly.erp.common.response.ErrorCode; import jakarta.validation.constraints.NotBlank; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; 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.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; class GlobalExceptionHandlerTest { private MockMvc mockMvc; @BeforeEach void setUp() { mockMvc = MockMvcBuilders.standaloneSetup(new DummyController()) .setControllerAdvice(new GlobalExceptionHandler()) .build(); } @RestController @RequestMapping("/api/__dummy") static class DummyController { @GetMapping("/biz") public Object biz() { throw new BizException(ErrorCode.MOD_PARENT_NOT_FOUND); } @GetMapping("/runtime") public Object runtime() { throw new RuntimeException("internal boom\nstack-line-1\nstack-line-2"); } @PostMapping(value = "/validation", consumes = MediaType.APPLICATION_JSON_VALUE) public Object validation(@jakarta.validation.Valid @RequestBody Payload p) { return "ok"; } } static class Payload { @NotBlank String name; public String getName() { return name; } public void setName(String n) { this.name = n; } } @Test void bizException_returns200WithBizCode() throws Exception { mockMvc.perform(get("/api/__dummy/biz")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(40411)) .andExpect(jsonPath("$.message").value("父模块不存在或已删除")); } @Test void validationException_returns200WithParamInvalidCode() throws Exception { mockMvc.perform(post("/api/__dummy/validation") .contentType(MediaType.APPLICATION_JSON) .content("{\"name\":\"\"}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(40010)); } @Test void uncaughtException_returns200WithInternalErrorCode() throws Exception { mockMvc.perform(get("/api/__dummy/runtime")) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(50000)); } @Test void response_doesNotContainStackTrace() throws Exception { String body = mockMvc.perform(get("/api/__dummy/runtime")) .andReturn().getResponse().getContentAsString(); assertThat(body) .doesNotContain("stack-line-1") .doesNotContain("internal boom") .doesNotContain("at java.") .doesNotContain("Caused by"); } }