package com.company.agentfoundation.assistant.api; import com.company.agentfoundation.integration.coze.service.CozeWorkflowService; import com.company.agentfoundation.semantic.SemanticDispatchResult; import com.company.agentfoundation.semantic.SemanticDispatchService; import com.company.agentfoundation.semantic.SemanticReplyAttachment; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; 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 AssistantApiControllerTest { @Test void generalApiCallsOriginalCozeWorkflowWithoutAuthorizationHeader() throws Exception { CozeWorkflowService coze = mock(CozeWorkflowService.class); when(coze.chatDefault("测试表")).thenReturn("扣子回答"); MockMvc mvc = mvc(coze, mock(SemanticDispatchService.class), new AssistantFileStore()); mvc.perform(post("/api/assistant/general/query") .contentType(MediaType.APPLICATION_JSON) .content("{\"query\":\"测试表\"}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.success").value(true)) .andExpect(jsonPath("$.content").value("扣子回答")); verify(coze).chatDefault("测试表"); } @Test void sportsVenueApiRunsSemanticRoutingAndReturnsDownloadableExcel() throws Exception { SemanticDispatchService semantic = mock(SemanticDispatchService.class); byte[] excel = {80, 75, 1, 2}; when(semantic.dispatchWithAttachments("YiNianYiQingJing", "查看今日EC日报")) .thenReturn(new SemanticDispatchResult( "EC日报内容", List.of(SemanticReplyAttachment.excel("EC日报.xlsx", excel)))); MockMvc mvc = mvc(mock(CozeWorkflowService.class), semantic, new AssistantFileStore()); MvcResult result = mvc.perform(post("/api/assistant/sports-venue/query") .contentType(MediaType.APPLICATION_JSON) .content(""" {"content":"查看今日EC日报","wecomUserId":"YiNianYiQingJing"} """)) .andExpect(status().isOk()) .andExpect(jsonPath("$.success").value(true)) .andExpect(jsonPath("$.content").value(org.hamcrest.Matchers.containsString("EC日报内容"))) .andExpect(jsonPath("$.content").value(org.hamcrest.Matchers.containsString("EC日报.xlsx"))) .andExpect(jsonPath("$.attachments[0].name").value("EC日报.xlsx")) .andReturn(); String response = result.getResponse().getContentAsString(); String url = new com.fasterxml.jackson.databind.ObjectMapper().readTree(response) .path("attachments").get(0).path("downloadUrl").asText(); String path = java.net.URI.create(url).getPath(); MvcResult download = mvc.perform(get(path)) .andExpect(status().isOk()) .andReturn(); assertThat(download.getResponse().getContentAsByteArray()).isEqualTo(excel); } @Test void sportsVenueApiReadsUserIdFromWecomContextHeader() throws Exception { SemanticDispatchService semantic = mock(SemanticDispatchService.class); when(semantic.dispatchWithAttachments("YiNianYiQingJing", "查看今日客资日报")) .thenReturn(new SemanticDispatchResult("客资日报", List.of())); MockMvc mvc = mvc( mock(CozeWorkflowService.class), semantic, new AssistantFileStore()); mvc.perform(post("/api/assistant/sports-venue/query") .header("X-WeCom-UserId", "YiNianYiQingJing") .contentType(MediaType.APPLICATION_JSON) .content("{\"query\":\"查看今日客资日报\"}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.success").value(true)); verify(semantic).dispatchWithAttachments("YiNianYiQingJing", "查看今日客资日报"); } @Test void sportsVenueApiRejectsRequestWhenWecomContextHasNoUserId() throws Exception { MockMvc mvc = mvc( mock(CozeWorkflowService.class), mock(SemanticDispatchService.class), new AssistantFileStore()); mvc.perform(post("/api/assistant/sports-venue/query") .contentType(MediaType.APPLICATION_JSON) .content("{\"query\":\"查看今日客资日报\"}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.success").value(false)) .andExpect(jsonPath("$.content").value(org.hamcrest.Matchers.containsString("UserID"))); } private MockMvc mvc( CozeWorkflowService coze, SemanticDispatchService semantic, AssistantFileStore files ) { return MockMvcBuilders.standaloneSetup(new AssistantApiController(coze, semantic, files)) .setMessageConverters( new MappingJackson2HttpMessageConverter(), new ByteArrayHttpMessageConverter()) .build(); } }