From b7a403dc227e52af049591262615ed1af3bb7b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Wed, 2 Sep 2026 05:32:06 +0800 Subject: [PATCH] =?UTF-8?q?task-140:=2013=20=E6=A8=A1=E5=9D=97=20file=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=A5=91=E7=BA=A6=E5=BF=AB=E7=85=A7=EF=BC=88?= =?UTF-8?q?fileStatus/fileError/fileReady=20=E7=B1=BB=E5=9E=8B=E4=B8=8E?= =?UTF-8?q?=E7=99=BD=E5=90=8D=E5=8D=95=E5=86=BB=E7=BB=93=E3=80=81=E6=97=A7?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E4=BB=8D=E5=9C=A8=E3=80=81collectdata=20?= =?UTF-8?q?=E8=87=AA=E6=B4=BD=EF=BC=89+=208=20=E6=9D=A1=E5=AE=88=E9=97=A8?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contract/ResultFilePhaseContractTest.java | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 backend-java/src/test/java/com/nanri/aiimage/modules/task/contract/ResultFilePhaseContractTest.java diff --git a/backend-java/src/test/java/com/nanri/aiimage/modules/task/contract/ResultFilePhaseContractTest.java b/backend-java/src/test/java/com/nanri/aiimage/modules/task/contract/ResultFilePhaseContractTest.java new file mode 100644 index 00000000..bd3b9ad7 --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/modules/task/contract/ResultFilePhaseContractTest.java @@ -0,0 +1,129 @@ +package com.nanri.aiimage.modules.task.contract; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nanri.aiimage.modules.similarasin.model.vo.SimilarAsinHistoryItemVo; +import com.nanri.aiimage.modules.similarasin.model.vo.SimilarAsinTaskLightVo; +import com.nanri.aiimage.modules.task.model.vo.TaskProgressLightBatchVo; +import com.nanri.aiimage.modules.task.model.vo.TaskProgressLightVo; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * task-140:13 模块 file 阶段字段契约快照(基线固化,plan 08)。 + * 共享 TaskProgressLightVo(11 模块)+ SimilarAsinTaskLightVo 的 file 字段 + * (fileStatus/fileError/fileReady)存在且类型正确;旧字段 + * (taskId/resultId/downloadUrl/status)仍在;JSON 序列化快照冻结白名单。 + */ +class ResultFilePhaseContractTest { + + /** 13 个业务模块的共享 light VO(collectdata 走同一共享 VO,自洽语义见 test 5)。 */ + private static final Set SHARED_LIGHT_MODULES = Set.of( + "APPEARANCE_PATENT", "SHOP_DATA_CRAWL", "QUERY_ASIN", "WITHDRAW", + "PATROL_DELETE", "SHOP_MATCH", "PRODUCT_RISK_RESOLVE", "PRICE_TRACK", + "DELETE_BRAND", "PUBLISH", "COLLECT_DATA", "BRAND"); + + private final ObjectMapper objectMapper = new ObjectMapper(); + + private static Map> fieldsOf(Class type) { + return Arrays.stream(type.getDeclaredFields()) + .filter(field -> !java.lang.reflect.Modifier.isStatic(field.getModifiers())) + .collect(Collectors.toMap(Field::getName, Field::getType)); + } + + private static void assertFileFields(Class voType) { + Map> fields = fieldsOf(voType); + assertEquals(String.class, fields.get("fileStatus"), voType.getSimpleName() + ".fileStatus 必须为 String"); + assertEquals(String.class, fields.get("fileError"), voType.getSimpleName() + ".fileError 必须为 String"); + assertEquals(Boolean.class, fields.get("fileReady"), voType.getSimpleName() + ".fileReady 必须为 Boolean"); + } + + @Test + void similarAsinHasFileFields() { + assertFileFields(SimilarAsinTaskLightVo.class); + } + + @Test + void appearancePatentHasFileFields() { + assertFileFields(TaskProgressLightVo.class); + } + + @Test + void shopDataCrawlHasFileFields() { + assertFileFields(TaskProgressLightVo.class); + } + + @Test + void publishHasFileFields() { + // publish 文件级:共享 VO 的 file 字段由 Job 推导(TaskProgressLightAssembler 语义) + assertFileFields(TaskProgressLightVo.class); + assertTrue(SHARED_LIGHT_MODULES.contains("PUBLISH"), "publish 必须接入共享 light VO"); + } + + @Test + void collectDataIsSelfConsistent() { + // collectdata 与 11 模块共享同一 TaskProgressLightVo(file 字段齐全、语义自洽) + assertFileFields(TaskProgressLightVo.class); + Map> fields = fieldsOf(TaskProgressLightVo.class); + assertEquals(Long.class, fields.get("taskId")); + assertEquals(String.class, fields.get("status")); + assertNotNull(fields.get("updatedAt")); + } + + @Test + void oldFieldsStillPresent() { + Map> fields = fieldsOf(SimilarAsinHistoryItemVo.class); + assertEquals(Long.class, fields.get("taskId"), "旧字段 taskId 仍在"); + assertEquals(Long.class, fields.get("resultId"), "旧字段 resultId 仍在"); + assertEquals(String.class, fields.get("downloadUrl"), "旧字段 downloadUrl 仍在"); + assertEquals(String.class, fields.get("taskStatus"), "旧字段 taskStatus 仍在(状态语义不变)"); + assertEquals(String.class, fields.get("fileStatus"), "history 文件级字段仍在"); + assertEquals(Boolean.class, fields.get("fileReady"), "history fileReady 仍在"); + } + + @Test + void fileFieldTypesAreCorrect() { + assertFileFields(TaskProgressLightVo.class); + Map> similar = fieldsOf(SimilarAsinTaskLightVo.class); + assertEquals(String.class, similar.get("fileStatus")); + assertEquals(Boolean.class, similar.get("fileReady")); + assertEquals(String.class, similar.get("fileError")); + assertEquals(String.class, similar.get("status")); + assertEquals(Long.class, similar.get("taskId")); + } + + @Test + void contractFrozenAsJsonSnapshot() throws Exception { + TaskProgressLightVo item = new TaskProgressLightVo(); + item.setTaskId(3938L); + item.setStatus("SUCCESS"); + item.setFileStatus("SUCCESS"); + item.setFileError(null); + item.setFileReady(true); + item.setUpdatedAt("2026-09-02T05:00:00"); + + TaskProgressLightBatchVo batch = new TaskProgressLightBatchVo(); + batch.setItems(java.util.List.of(item)); + batch.setMissingTaskIds(java.util.List.of()); + + String json = objectMapper.writeValueAsString(batch); + @SuppressWarnings("unchecked") + Map root = objectMapper.readValue(json, Map.class); + assertTrue(root.containsKey("items"), "响应必须含 items"); + @SuppressWarnings("unchecked") + Map first = ((java.util.List>) root.get("items")).get(0); + assertEquals(Set.of("taskId", "status", "statusCode", "fileStatus", "fileError", "fileReady", "updatedAt"), + first.keySet(), "light 响应键集必须精确匹配白名单(无明细/payload/result 内容)"); + assertEquals(Boolean.TRUE, first.get("fileReady")); + assertEquals("SUCCESS", first.get("fileStatus")); + assertEquals("SUCCESS", first.get("status")); + } +}