feat(p3.5): oapi 增 GET /tasks/done 与 /cc——已办/抄送按签名方应用过滤(P2-02 接缝)
build / build-push (push) Successful in 1m11s

- 两端点复用 FlowQueryService#pageDone/pageCc;过滤键=验签身份 appKey(请求属性
  yh.app.auth → d.appKey),不收调用方 bizType——天然覆盖消费方新旧 bizType 存量、
  防共享定义跨应用泄漏
- 契约测试 5 例(standalone 无 DB):appKey 接缝下传/bizType 不参与过滤/必参缺失 400 零查询
- 全量 262/262 绿(本地含 DB;Stage S5/P3.5)
This commit is contained in:
chenxing
2026-10-20 00:16:06 +08:00
parent 52c27d88bb
commit 93ecc79839
2 changed files with 142 additions and 0 deletions
@@ -0,0 +1,108 @@
package com.yunhai.flowcenter.oapi;
import com.yunhai.flowcenter.controller.oapi.OapiFlowController;
import com.yunhai.flowcenter.model.FlowModels;
import com.yunhai.flowcenter.service.FlowQueryService;
import com.yunhai.flowcenter.service.FlowRouterAdminService;
import com.yunhai.flowcenter.service.OapiFlowService;
import com.yunhai.platform.utils.model.BasePageResult;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import java.util.Collections;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
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.result.MockMvcResultMatchers.status;
/**
* P3.5 新增 oapi 端点(/oapi/flow/tasks/done、/oapi/flow/cc)的控制器契约测试(standalone,无 DB)。
* 核心断言=<b>过滤接缝</b>(P2-02 裁决):过滤键是验签身份 appKey(请求属性 yh.app.auth →
* queryService 第三参,落 d.appKey 过滤),<b>不收调用方 bizType</b>——bizType 参数即使传了
* 也不参与过滤(防裸透传/跨应用探查)。数据正确性由既有 pageDone/pageCc 的 IT 覆盖,
* 本类只钉 HTTP 契约与接缝。
*/
@DisplayName("P3.5 oapi done/cc - 签名 appKey 过滤接缝契约")
class OapiFlowP35DoneCcContractTest {
private FlowQueryService queryService;
private MockMvc mockMvc;
@BeforeEach
void setUp() {
OapiFlowService flowService = mock(OapiFlowService.class);
FlowRouterAdminService routerAdminService = mock(FlowRouterAdminService.class);
queryService = mock(FlowQueryService.class);
OapiFlowController controller = new OapiFlowController(flowService, routerAdminService, queryService);
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
when(queryService.pageDone(any(), any(), any(), any(), any()))
.thenReturn(emptyPage());
when(queryService.pageCc(any(), any(), any(), any(), any()))
.thenReturn(new BasePageResult<>(Collections.emptyList(), 0, 1, 10, 0, false));
}
private static BasePageResult<FlowModels.TaskItem> emptyPage() {
return new BasePageResult<>(Collections.emptyList(), 0, 1, 10, 0, false);
}
@Test
@DisplayName("done:验签 appKey 作为过滤键下传(d.appKey 接缝),分页参数透传")
void donePassesSigningAppKeyAsFilter() throws Exception {
mockMvc.perform(get("/oapi/flow/tasks/done")
.param("schId", "3").param("assigneeId", "10006")
.param("page", "2").param("size", "10")
.requestAttr("yh.app.auth", "100008"))
.andExpect(status().isOk());
verify(queryService).pageDone(eq(3L), eq(10006), eq("100008"), eq(2), eq(10));
}
@Test
@DisplayName("done:调用方带 bizType 也不参与过滤(防裸透传)")
void doneIgnoresCallerBizType() throws Exception {
mockMvc.perform(get("/oapi/flow/tasks/done")
.param("schId", "3").param("assigneeId", "10006")
.param("bizType", "svc-hall")
.requestAttr("yh.app.auth", "100008"))
.andExpect(status().isOk());
// 第三参(过滤键)必须是签名 appKey,不是 bizType
verify(queryService).pageDone(eq(3L), eq(10006), eq("100008"), Mockito.isNull(), Mockito.isNull());
}
@Test
@DisplayName("done:缺 schId/assigneeId → 400,零查询")
void doneMissingRequiredParams() throws Exception {
mockMvc.perform(get("/oapi/flow/tasks/done").param("schId", "3"))
.andExpect(status().is4xxClientError());
verify(queryService, never()).pageDone(any(), any(), any(), any(), any());
}
@Test
@DisplayName("cc:验签 appKey 过滤键 + userId 维度")
void ccPassesSigningAppKeyAsFilter() throws Exception {
mockMvc.perform(get("/oapi/flow/cc")
.param("schId", "3").param("userId", "10006")
.param("page", "1").param("size", "20")
.requestAttr("yh.app.auth", "100008"))
.andExpect(status().isOk());
verify(queryService).pageCc(eq(3L), eq(10006), eq("100008"), eq(1), eq(20));
}
@Test
@DisplayName("cc:缺 userId → 400,零查询")
void ccMissingUserId() throws Exception {
mockMvc.perform(get("/oapi/flow/cc").param("schId", "3"))
.andExpect(status().is4xxClientError());
verify(queryService, never()).pageCc(any(), any(), any(), any(), any());
}
}