task-202: Python-contract 夹具准备(heartbeat/items/result/parsed_payload schema-fixture,字段取自真实 DTO、全脱敏、README 说明)+ 9 条测试
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
package com.nanri.aiimage.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* task-202:Python-contract 夹具准备契约。
|
||||
* 校验 python-contract/*.json 存在、为合法 JSON、顶层字段与真实 DTO 对齐、已脱敏(无真实凭据)、
|
||||
* 可复现;字段来源与脱敏口径见同目录 README.md。
|
||||
*/
|
||||
class PythonContractFixtureTest {
|
||||
|
||||
private static final Path DIR = Path.of(System.getProperty("user.dir"),
|
||||
"src/test/resources/python-contract");
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
private JsonNode json(String name) throws IOException {
|
||||
return MAPPER.readTree(Files.readAllBytes(DIR.resolve(name)));
|
||||
}
|
||||
|
||||
private static Set<String> keys(JsonNode node) {
|
||||
List<String> names = new ArrayList<>();
|
||||
node.fieldNames().forEachRemaining(names::add);
|
||||
return Set.copyOf(names);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fixtureResultRequestExists() throws IOException {
|
||||
JsonNode n = json("result.request.json");
|
||||
assertEquals("sim-20260905-0001", n.path("submissionId").asText());
|
||||
}
|
||||
|
||||
@Test
|
||||
void fixtureItemsExists() throws IOException {
|
||||
JsonNode n = json("items.response.json");
|
||||
assertTrue(n.path("items").isArray() && n.path("items").size() >= 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fixtureParsedPayloadExists() throws IOException {
|
||||
JsonNode n = json("parsed_payload.response.json");
|
||||
assertTrue(n.has("allItems") && n.has("groups") && n.has("items"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fixtureHeartbeatExists() throws IOException {
|
||||
JsonNode req = json("heartbeat.request.json");
|
||||
JsonNode resp = json("heartbeat.response.json");
|
||||
assertTrue(req.has("moduleType") && req.has("phase"));
|
||||
assertTrue(resp.has("alive") && resp.has("status"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fixtureSanitizedNoRealCredentials() throws IOException {
|
||||
String all = String.join("\n", List.of(
|
||||
Files.readString(DIR.resolve("parsed_payload.response.json")),
|
||||
Files.readString(DIR.resolve("result.request.json"))));
|
||||
assertFalse(all.contains("WTFrb"), "不得出现真实 DB/内部凭据");
|
||||
assertFalse(all.toLowerCase().contains("bearer "), "不得出现真实 token");
|
||||
JsonNode parsed = json("parsed_payload.response.json");
|
||||
assertEquals("<redacted>", parsed.path("apiKey").asText(), "apiKey 必须占位");
|
||||
assertFalse(parsed.path("aiPrompt").asText().toLowerCase().contains("真实"),
|
||||
"aiPrompt 不得含真实内容");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fixtureShapeValidJson() throws IOException {
|
||||
for (String name : List.of("heartbeat.request.json", "heartbeat.response.json",
|
||||
"items.response.json", "result.request.json", "parsed_payload.response.json")) {
|
||||
JsonNode n = json(name);
|
||||
assertTrue(n.isContainerNode(), name + " 应为 JSON 对象/数组");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void fixtureFieldsDocumentedByReadme() throws IOException {
|
||||
String readme = Files.readString(DIR.resolve("README.md"));
|
||||
assertTrue(readme.contains("TaskHeartbeatRequest") && readme.contains("CollectDataItemsPageVo")
|
||||
&& readme.contains("SimilarAsinSubmitResultRequest")
|
||||
&& readme.contains("SimilarAsinParsedPayloadDto"), "README 应说明字段来源");
|
||||
assertTrue(readme.contains("脱敏"), "README 应说明脱敏");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fixtureHeartbeatTopKeysMatchDto() throws IOException {
|
||||
assertEquals(Set.of("moduleType", "phase", "current", "total", "collectStage",
|
||||
"currentKeyword", "searchCurrentPage", "searchTotalPages",
|
||||
"detailProcessedAsins", "detailTotalAsins"),
|
||||
keys(json("heartbeat.request.json")), "心跳请求顶层字段应与 TaskHeartbeatRequest 对齐");
|
||||
assertEquals(Set.of("alive", "status", "moduleType", "message"),
|
||||
keys(json("heartbeat.response.json")), "心跳响应应与 TaskHeartbeatVo 对齐");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fixtureReproducibleStable() throws IOException {
|
||||
for (String name : List.of("heartbeat.request.json", "items.response.json",
|
||||
"result.request.json", "parsed_payload.response.json")) {
|
||||
String a = Files.readString(DIR.resolve(name));
|
||||
String b = Files.readString(DIR.resolve(name));
|
||||
assertEquals(a, b, name + " 应可复现");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# Python-contract 夹具(task-202)
|
||||
|
||||
> 目录:`src/test/resources/python-contract/`。用途:模块 13 的 /result、/items、parsed-payload、heartbeat 契约测试固定输入。
|
||||
|
||||
## 内容与字段来源
|
||||
|
||||
| 文件 | 对应 Worker 接口 | 字段来源(服务 DTO,非臆造) |
|
||||
|---|---|---|
|
||||
| heartbeat.request.json | POST /api/tasks/{taskId}/heartbeat | `TaskHeartbeatRequest`(moduleType/phase/current/total/collectStage/currentKeyword/search*Page*/detail*Asins) |
|
||||
| heartbeat.response.json | 同上响应 | `TaskHeartbeatVo`(alive/status/moduleType/message) |
|
||||
| items.response.json | GET /tasks/{taskId}/items(collectdata) | `CollectDataItemsPageVo`+`CollectDataItemVo`(分页字段 + items 行结构) |
|
||||
| result.request.json | 模块 submit-result(similarasin 示例) | `SimilarAsinSubmitResultRequest`(submissionId/chunkIndex/chunkTotal/done/error/groups) |
|
||||
| parsed_payload.response.json | GET /tasks/{taskId}/parsed-payload(similarasin) | `SimilarAsinParsedPayloadDto`(aiPrompt/apiKey/imgSwitch/…/items/allItems/groups) |
|
||||
|
||||
## 说明
|
||||
|
||||
- **脱敏**:所有值均为伪造(taskId/ASIN/文件名/关键词等);敏感字段已置占位——`parsed_payload.response.json` 的 `apiKey="<redacted>"`、`aiPrompt="<redacted-prompt>"`,不含任何真实凭据。
|
||||
- **来源口径**:schema-fixture(字段取自本服务 DTO);非录自 app_client/amazon 真实抓包。若后续在具备桌面端/爬虫运行环境时录制真实报文,可另存 `*.recorded.json` 增补,不替换本 schema-fixture。
|
||||
- **可复现**:纯静态 JSON,可重复用于契约测试。
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"moduleType": "similarasin",
|
||||
"phase": "search",
|
||||
"current": 3,
|
||||
"total": 10,
|
||||
"collectStage": "keyword-search",
|
||||
"currentKeyword": "ring",
|
||||
"searchCurrentPage": 1,
|
||||
"searchTotalPages": 4,
|
||||
"detailProcessedAsins": 12,
|
||||
"detailTotalAsins": 120
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"alive": true,
|
||||
"status": "RUNNING",
|
||||
"moduleType": "similarasin",
|
||||
"message": "ok"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"taskId": 7004,
|
||||
"taskNo": "SA-20260905-7004",
|
||||
"taskType": "collect-data",
|
||||
"taskStatus": "RUNNING",
|
||||
"page": 1,
|
||||
"pageSize": 50,
|
||||
"count": 1,
|
||||
"total": 1,
|
||||
"totalPages": 1,
|
||||
"filters": {
|
||||
"country": "US"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"rowIndex": 1,
|
||||
"sourceFileKey": "in/example.xlsx",
|
||||
"sourceFilename": "example.xlsx",
|
||||
"keyword": "ring",
|
||||
"statusValue": "PENDING",
|
||||
"extra": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"aiPrompt": "<redacted-prompt>",
|
||||
"apiKey": "<redacted>",
|
||||
"imgSwitch": false,
|
||||
"categorySwitch": false,
|
||||
"sourceFiles": [],
|
||||
"headers": [
|
||||
"asin",
|
||||
"keyword",
|
||||
"title"
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
"rowIndex": 1,
|
||||
"asin": "B0FAKETEST123",
|
||||
"keyword": "ring",
|
||||
"title": "example"
|
||||
}
|
||||
],
|
||||
"allItems": [
|
||||
{
|
||||
"rowIndex": 1,
|
||||
"asin": "B0FAKETEST123",
|
||||
"keyword": "ring",
|
||||
"title": "example"
|
||||
}
|
||||
],
|
||||
"groups": []
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"submissionId": "sim-20260905-0001",
|
||||
"chunkIndex": 0,
|
||||
"chunkTotal": 1,
|
||||
"done": true,
|
||||
"error": null,
|
||||
"groups": [
|
||||
{
|
||||
"name": "group-1",
|
||||
"items": [
|
||||
{
|
||||
"rowIndex": 1,
|
||||
"keyword": "ring",
|
||||
"asin": "B0FAKETEST123",
|
||||
"status": "SUCCESS"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user