机器人版本
This commit is contained in:
parent
32a42546f3
commit
52d5bdacf3
55
docs/assistant-api.md
Normal file
55
docs/assistant-api.md
Normal file
@ -0,0 +1,55 @@
|
||||
# 企业微信智能机器人 API 插件
|
||||
|
||||
两个接口均暂不校验 Authorization 或 API Key。
|
||||
|
||||
## 问答小助手
|
||||
|
||||
- OpenAPI 配置地址:`/api/assistant/openapi/general.json`
|
||||
- 业务接口:`POST /api/assistant/general/query`
|
||||
- 请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "测试表"
|
||||
}
|
||||
```
|
||||
|
||||
- `query` 同时兼容 `content`、`userText`、`message` 和 `input`。
|
||||
- 处理逻辑保持不变:直接调用原有扣子 `wecom-default` 工作流,不经过 DeepSeek 语义识别。
|
||||
|
||||
## 运动馆智能助手
|
||||
|
||||
- OpenAPI 配置地址:`/api/assistant/openapi/sports-venue.json`
|
||||
- 业务接口:`POST /api/assistant/sports-venue/query`
|
||||
- 请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "查看本月EC电话合规监控日报",
|
||||
"userId": "YiNianYiQingJing"
|
||||
}
|
||||
```
|
||||
|
||||
- `query` 同时兼容 `content`、`userText`、`message` 和 `input`。
|
||||
- `userId` 同时兼容 `wecomUserId`、`userid` 和 `user_id`。
|
||||
- 处理逻辑保持不变:DeepSeek 语义识别 → 日期解析 → UserID 数据权限 → 对应日报处理器。
|
||||
- 同一个 UserID 同时只能执行一条运动馆指令。
|
||||
|
||||
## 返回结构
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"code": 0,
|
||||
"content": "机器人直接回复给用户的内容",
|
||||
"attachments": [
|
||||
{
|
||||
"type": "excel",
|
||||
"name": "日报.xlsx",
|
||||
"downloadUrl": "http://agent.yuedongsports.com/api/assistant/files/文件ID"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
机器人应将 `content` 作为最终回复。Excel 下载链接会同时附加到 `content` 中,文件链接有效期为 30 分钟。
|
||||
@ -0,0 +1,124 @@
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
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 org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/assistant")
|
||||
public class AssistantApiController {
|
||||
private static final Logger log = LoggerFactory.getLogger(AssistantApiController.class);
|
||||
|
||||
private final CozeWorkflowService cozeWorkflowService;
|
||||
private final SemanticDispatchService semanticDispatchService;
|
||||
private final AssistantFileStore fileStore;
|
||||
private final Set<String> runningUsers = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public AssistantApiController(
|
||||
CozeWorkflowService cozeWorkflowService,
|
||||
SemanticDispatchService semanticDispatchService,
|
||||
AssistantFileStore fileStore
|
||||
) {
|
||||
this.cozeWorkflowService = cozeWorkflowService;
|
||||
this.semanticDispatchService = semanticDispatchService;
|
||||
this.fileStore = fileStore;
|
||||
}
|
||||
|
||||
@PostMapping("/general/query")
|
||||
public AssistantQueryResponse generalQuery(@RequestBody AssistantQueryRequest request) {
|
||||
String query = request == null ? "" : request.normalizedQuery();
|
||||
if (query.isEmpty()) return AssistantQueryResponse.failure("query不能为空");
|
||||
try {
|
||||
return AssistantQueryResponse.success(cozeWorkflowService.chatDefault(query), List.of());
|
||||
} catch (RuntimeException exception) {
|
||||
log.error("assistant general api failed err={}", exception.getMessage(), exception);
|
||||
return AssistantQueryResponse.failure("扣子问答调用失败,请稍后重试。");
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/sports-venue/query")
|
||||
public AssistantQueryResponse sportsVenueQuery(@RequestBody AssistantQueryRequest request) {
|
||||
String query = request == null ? "" : request.normalizedQuery();
|
||||
String userId = request == null ? "" : request.normalizedUserId();
|
||||
if (query.isEmpty()) return AssistantQueryResponse.failure("query不能为空");
|
||||
if (userId.isEmpty()) return AssistantQueryResponse.failure("userId不能为空,数据权限需要企业微信UserID");
|
||||
|
||||
String runningKey = userId.toLowerCase(Locale.ROOT);
|
||||
if (!runningUsers.add(runningKey)) {
|
||||
return AssistantQueryResponse.failure("正在处理上一条指令,请等待结果返回后再试。");
|
||||
}
|
||||
try {
|
||||
SemanticDispatchResult result = semanticDispatchService.dispatchWithAttachments(userId, query);
|
||||
List<AssistantQueryResponse.Attachment> attachments = store(result.attachments());
|
||||
return AssistantQueryResponse.success(appendAttachmentLinks(result.reply(), attachments), attachments);
|
||||
} catch (RuntimeException exception) {
|
||||
log.error("assistant sports venue api failed userId={} err={}",
|
||||
userId, exception.getMessage(), exception);
|
||||
return AssistantQueryResponse.failure("语义识别或业务查询失败,请稍后重试。");
|
||||
} finally {
|
||||
runningUsers.remove(runningKey);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/files/{fileId}")
|
||||
public ResponseEntity<byte[]> download(@PathVariable String fileId) {
|
||||
return fileStore.find(fileId)
|
||||
.map(file -> ResponseEntity.ok()
|
||||
.contentType(file.mediaType())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
ContentDisposition.attachment()
|
||||
.filename(file.fileName(), StandardCharsets.UTF_8)
|
||||
.build().toString())
|
||||
.body(file.content()))
|
||||
.orElseGet(() -> ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
private List<AssistantQueryResponse.Attachment> store(List<SemanticReplyAttachment> values) {
|
||||
List<AssistantQueryResponse.Attachment> result = new ArrayList<>();
|
||||
for (SemanticReplyAttachment value : values) {
|
||||
String id = fileStore.save(value);
|
||||
String url = ServletUriComponentsBuilder.fromCurrentContextPath()
|
||||
.path("/api/assistant/files/")
|
||||
.path(id)
|
||||
.build()
|
||||
.toUriString();
|
||||
result.add(new AssistantQueryResponse.Attachment(
|
||||
value.type().name().toLowerCase(Locale.ROOT), value.fileName(), url));
|
||||
}
|
||||
return List.copyOf(result);
|
||||
}
|
||||
|
||||
private String appendAttachmentLinks(
|
||||
String reply,
|
||||
List<AssistantQueryResponse.Attachment> attachments
|
||||
) {
|
||||
if (attachments.isEmpty()) return reply == null ? "" : reply;
|
||||
StringBuilder content = new StringBuilder(reply == null ? "" : reply);
|
||||
content.append("\n\n附件:");
|
||||
for (AssistantQueryResponse.Attachment attachment : attachments) {
|
||||
content.append("\n- [").append(attachment.name()).append("](")
|
||||
.append(attachment.downloadUrl()).append(")");
|
||||
}
|
||||
return content.toString();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
package com.company.agentfoundation.assistant.api;
|
||||
|
||||
import com.company.agentfoundation.semantic.SemanticReplyAttachment;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Service
|
||||
public class AssistantFileStore {
|
||||
private static final Duration FILE_TTL = Duration.ofMinutes(30);
|
||||
private static final int MAX_FILES = 200;
|
||||
private static final String EXCEL_CONTENT_TYPE =
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
|
||||
private final Map<String, StoredFile> files = new ConcurrentHashMap<>();
|
||||
|
||||
public String save(SemanticReplyAttachment attachment) {
|
||||
cleanup();
|
||||
String id = UUID.randomUUID().toString().replace("-", "");
|
||||
String contentType = switch (attachment.type()) {
|
||||
case EXCEL -> EXCEL_CONTENT_TYPE;
|
||||
};
|
||||
files.put(id, new StoredFile(
|
||||
attachment.fileName(),
|
||||
contentType,
|
||||
attachment.content() == null ? new byte[0] : attachment.content().clone(),
|
||||
Instant.now().plus(FILE_TTL)));
|
||||
trimToLimit();
|
||||
return id;
|
||||
}
|
||||
|
||||
public Optional<StoredFile> find(String id) {
|
||||
cleanup();
|
||||
StoredFile value = files.get(id);
|
||||
if (value == null || value.expiresAt().isBefore(Instant.now())) {
|
||||
if (value != null) files.remove(id);
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(value);
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
Instant now = Instant.now();
|
||||
files.entrySet().removeIf(entry -> entry.getValue().expiresAt().isBefore(now));
|
||||
}
|
||||
|
||||
private void trimToLimit() {
|
||||
int overflow = files.size() - MAX_FILES;
|
||||
if (overflow <= 0) return;
|
||||
files.entrySet().stream()
|
||||
.sorted(Comparator.comparing(entry -> entry.getValue().expiresAt()))
|
||||
.limit(overflow)
|
||||
.map(Map.Entry::getKey)
|
||||
.toList()
|
||||
.forEach(files::remove);
|
||||
}
|
||||
|
||||
public record StoredFile(
|
||||
String fileName,
|
||||
String contentType,
|
||||
byte[] content,
|
||||
Instant expiresAt
|
||||
) {
|
||||
public MediaType mediaType() {
|
||||
return MediaType.parseMediaType(contentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
package com.company.agentfoundation.assistant.api;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/assistant/openapi")
|
||||
public class AssistantOpenApiController {
|
||||
@GetMapping({"/general", "/general.json"})
|
||||
public Map<String, Object> general() {
|
||||
return specification(
|
||||
"问答小助手API",
|
||||
"/api/assistant/general/query",
|
||||
"generalAssistantQuery",
|
||||
"将用户输入发送给扣子问答工作流并返回回答",
|
||||
false);
|
||||
}
|
||||
|
||||
@GetMapping({"/sports-venue", "/sports-venue.json"})
|
||||
public Map<String, Object> sportsVenue() {
|
||||
return specification(
|
||||
"运动馆智能助手API",
|
||||
"/api/assistant/sports-venue/query",
|
||||
"sportsVenueAssistantQuery",
|
||||
"根据用户输入进行语义识别,并按企业微信UserID权限查询客资、学员、企业微信客户运营或EC日报",
|
||||
true);
|
||||
}
|
||||
|
||||
private Map<String, Object> specification(
|
||||
String title,
|
||||
String path,
|
||||
String operationId,
|
||||
String description,
|
||||
boolean requireUserId
|
||||
) {
|
||||
Map<String, Object> properties = new LinkedHashMap<>();
|
||||
properties.put("query", Map.of(
|
||||
"type", "string",
|
||||
"description", "用户在企业微信中输入的完整问题或指令",
|
||||
"example", requireUserId ? "查看今日客资管理日报" : "测试表"));
|
||||
if (requireUserId) {
|
||||
properties.put("userId", Map.of(
|
||||
"type", "string",
|
||||
"description", "当前提问人的企业微信UserID,用于数据权限判断",
|
||||
"example", "YiNianYiQingJing"));
|
||||
}
|
||||
|
||||
Map<String, Object> requestSchema = new LinkedHashMap<>();
|
||||
requestSchema.put("type", "object");
|
||||
requestSchema.put("required", requireUserId ? List.of("query", "userId") : List.of("query"));
|
||||
requestSchema.put("properties", properties);
|
||||
|
||||
Map<String, Object> attachmentProperties = Map.of(
|
||||
"type", Map.of("type", "string"),
|
||||
"name", Map.of("type", "string"),
|
||||
"downloadUrl", Map.of("type", "string"));
|
||||
Map<String, Object> attachmentSchema = Map.of(
|
||||
"type", "object",
|
||||
"properties", attachmentProperties);
|
||||
Map<String, Object> responseProperties = Map.of(
|
||||
"success", Map.of("type", "boolean"),
|
||||
"code", Map.of("type", "integer"),
|
||||
"content", Map.of("type", "string", "description", "机器人应直接回复给用户的内容"),
|
||||
"attachments", Map.of(
|
||||
"type", "array",
|
||||
"items", attachmentSchema));
|
||||
Map<String, Object> responseSchema = Map.of(
|
||||
"type", "object",
|
||||
"properties", responseProperties);
|
||||
|
||||
Map<String, Object> operation = new LinkedHashMap<>();
|
||||
operation.put("operationId", operationId);
|
||||
operation.put("summary", title);
|
||||
operation.put("description", description);
|
||||
operation.put("requestBody", Map.of(
|
||||
"required", true,
|
||||
"content", Map.of("application/json", Map.of("schema", requestSchema))));
|
||||
operation.put("responses", Map.of(
|
||||
"200", Map.of(
|
||||
"description", "处理结果",
|
||||
"content", Map.of("application/json", Map.of("schema", responseSchema)))));
|
||||
|
||||
String server = ServletUriComponentsBuilder.fromCurrentContextPath().build().toUriString();
|
||||
return Map.of(
|
||||
"openapi", "3.0.1",
|
||||
"info", Map.of("title", title, "version", "1.0.0", "description", description),
|
||||
"servers", List.of(Map.of("url", server)),
|
||||
"paths", Map.of(path, Map.of("post", operation)));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.company.agentfoundation.assistant.api;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
|
||||
public record AssistantQueryRequest(
|
||||
@JsonAlias({"content", "userText", "message", "input"}) String query,
|
||||
@JsonAlias({"wecomUserId", "userid", "user_id"}) String userId
|
||||
) {
|
||||
public String normalizedQuery() {
|
||||
return query == null ? "" : query.trim();
|
||||
}
|
||||
|
||||
public String normalizedUserId() {
|
||||
return userId == null ? "" : userId.trim();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.company.agentfoundation.assistant.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record AssistantQueryResponse(
|
||||
boolean success,
|
||||
int code,
|
||||
String content,
|
||||
List<Attachment> attachments
|
||||
) {
|
||||
public AssistantQueryResponse {
|
||||
content = content == null ? "" : content;
|
||||
attachments = attachments == null ? List.of() : List.copyOf(attachments);
|
||||
}
|
||||
|
||||
public static AssistantQueryResponse success(String content, List<Attachment> attachments) {
|
||||
return new AssistantQueryResponse(true, 0, content, attachments);
|
||||
}
|
||||
|
||||
public static AssistantQueryResponse failure(String content) {
|
||||
return new AssistantQueryResponse(false, 1, content, List.of());
|
||||
}
|
||||
|
||||
public record Attachment(
|
||||
String type,
|
||||
String name,
|
||||
String downloadUrl
|
||||
) {}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
server:
|
||||
port: 8880
|
||||
forward-headers-strategy: framework
|
||||
spring:
|
||||
application:
|
||||
name: agent-foundation
|
||||
|
||||
@ -0,0 +1,101 @@
|
||||
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 sportsVenueApiRequiresUserIdForPermissionScope() 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();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package com.company.agentfoundation.assistant.api;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
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;
|
||||
|
||||
class AssistantOpenApiControllerTest {
|
||||
private final MockMvc mvc =
|
||||
MockMvcBuilders.standaloneSetup(new AssistantOpenApiController())
|
||||
.setMessageConverters(new MappingJackson2HttpMessageConverter())
|
||||
.build();
|
||||
|
||||
@Test
|
||||
void exposesGeneralAssistantOpenApiDocument() throws Exception {
|
||||
mvc.perform(get("/api/assistant/openapi/general.json"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.openapi").value("3.0.1"))
|
||||
.andExpect(jsonPath("$.paths['/api/assistant/general/query'].post.operationId")
|
||||
.value("generalAssistantQuery"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void exposesSportsVenueOpenApiDocumentWithUserId() throws Exception {
|
||||
mvc.perform(get("/api/assistant/openapi/sports-venue.json"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.paths['/api/assistant/sports-venue/query'].post.operationId")
|
||||
.value("sportsVenueAssistantQuery"))
|
||||
.andExpect(jsonPath(
|
||||
"$.paths['/api/assistant/sports-venue/query'].post.requestBody.content['application/json'].schema.required[1]")
|
||||
.value("userId"));
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user