Files
YschcmsService/src/test/java/com/yunhai/cms/controller/oapi/SiteOapiIT.java
T
chenxing 1cb95225a6
build / build-push (push) Successful in 3m59s
周末修订
2026-09-15 23:41:34 +08:00

122 lines
5.7 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.yunhai.cms.controller.oapi;
import com.yunhai.cms.entity.CmsSite;
import com.yunhai.cms.support.CmsIntegrationTestBase;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import java.nio.charset.StandardCharsets;
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.result.MockMvcResultMatchers.status;
/**
* 站点目录开放接口 IT(P0-1 解堵的 CMS 半边):
* 咬合契约=svc-hall CmsSiteDirectoryImpl(appKey=100018 签名调用;resolveSite 不传 schId 拉全量、
* listSites(schId) 按校过滤)。本类冻结三件事:
* <ul>
* <li>鉴权:/oapi/** 无签名 401(RequireAppSignFilter)、错密钥 401;</li>
* <li>契约:schId 可选——不传=全部启用站点(含跨校),传了=按校过滤;</li>
* <li>出参形态:data[] 元素键=siteCode/name/schId(消费方 mapToVos 按这三键解析)。</li>
* </ul>
* 签名 canonical 与平台 AppSignInterceptor 一致:METHOD + "\n" + path[?sortedQuery] + "\n" + ts,
* HmacSHA256 输出 Base64(见 SignUtil.signRequest / AppAuthFilter.buildSignedTarget)。
*/
@DisplayName("SiteOapi /oapi/site/list - P0-1 解堵:签名鉴权 + schId 可选契约")
class SiteOapiIT extends CmsIntegrationTestBase {
/** 与 svc-hall application.yml yh.app-auth.app-secret 逐字一致(联调态密钥) */
private static final String SVC_HALL_APP_KEY = "100018";
private static final String SVC_HALL_APP_SECRET = "svc-hall-dev-secret-100018";
@Autowired
private MockMvc mockMvc;
@BeforeEach
void seed() {
purgeTables();
persistSite("demo1", 1, "演示站点一");
persistSite("schx", 1, "主站X子站");
persistSite("demo2", 2, "演示站点二");
// 停用站点:任何形态(全量/按校)都不得下发
CmsSite disabled = persistSite("off1", 1, "停用站点");
disabled.setStatus(0);
siteRepository.saveAndFlush(disabled);
}
@AfterEach
void clean() {
purgeTables();
}
@Test
@DisplayName("无签名 → 401(RequireAppSignFilter 拦截)")
void noSignature_yields401() throws Exception {
mockMvc.perform(get("/oapi/site/list")).andExpect(status().isUnauthorized());
mockMvc.perform(get("/oapi/site/list?schId=1")).andExpect(status().isUnauthorized());
}
@Test
@DisplayName("错误密钥签名 → 401")
void wrongSecret_yields401() throws Exception {
mockMvc.perform(get("/oapi/site/list")
.header("X-App-Key", SVC_HALL_APP_KEY)
.header("X-Timestamp", String.valueOf(System.currentTimeMillis()))
.header("X-Signature", "bad-signature"))
.andExpect(status().isUnauthorized());
}
@Test
@DisplayName("带签名不传 schId → 全部启用站点(跨校全量,供 resolveSite 反解 siteCode)")
void signed_withoutSchId_returnsAllEnabledSites() throws Exception {
MvcResult result = mockMvc.perform(get("/oapi/site/list").headers(signHeaders("/oapi/site/list")))
.andExpect(status().isOk())
.andReturn();
String body = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
assertThat(body).contains("\"siteCode\":\"demo1\"").contains("\"siteCode\":\"demo2\"");
assertThat(body).doesNotContain("off1").doesNotContain("停用站点");
}
@Test
@DisplayName("带签名传 schId → 仅该校启用站点")
void signed_withSchId_filtersBySchool() throws Exception {
MvcResult result = mockMvc.perform(get("/oapi/site/list?schId=1")
.headers(signHeaders("/oapi/site/list?schId=1")))
.andExpect(status().isOk())
.andReturn();
String body = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
assertThat(body).contains("\"siteCode\":\"demo1\"").contains("\"siteCode\":\"schx\"");
assertThat(body).doesNotContain("demo2");
}
@Test
@DisplayName("出参形态冻结:{code,msg,data[]}, 元素键=siteCode/name/schId(schId 为数字)")
void responseShape_frozenForSvcHallConsumer() throws Exception {
MvcResult result = mockMvc.perform(get("/oapi/site/list").headers(signHeaders("/oapi/site/list")))
.andExpect(status().isOk())
.andReturn();
String body = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
// 信封(IDHttp 按code/data 解包)+ 消费方三键(CmsSiteDirectoryImpl.mapToVos)
assertThat(body).startsWith("{\"code\":0").contains("\"data\":[");
assertThat(body).contains("\"name\":\"演示站点一\"").contains("\"schId\":1");
}
/** 按平台 AppSignInterceptor 规则生成签名头(path + 排序 query,与 AppAuthFilter 服务端验签对齐) */
private static org.springframework.http.HttpHeaders signHeaders(String target) {
String ts = String.valueOf(System.currentTimeMillis());
String sign = com.yunhai.platform.utils.crypto.SignUtil.signRequest(
"GET", target, ts, SVC_HALL_APP_SECRET);
org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
headers.add("X-App-Key", SVC_HALL_APP_KEY);
headers.add("X-Timestamp", ts);
headers.add("X-Signature", sign);
return headers;
}
}