From d4b4e1e16520d36458ceacad76d2d1be2d660477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Sat, 5 Sep 2026 09:50:53 +0800 Subject: [PATCH] =?UTF-8?q?task-219(env-gated=20=E5=85=A8=E9=93=BE?= =?UTF-8?q?=E8=B7=AF):=20similarasin=20=E6=9C=80=E5=B0=8F=E9=93=BE?= =?UTF-8?q?=E8=B7=AF=20done=3Dtrue=E2=86=92assemble=E2=86=92=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E7=BB=93=E6=9E=9C=E6=96=87=E4=BB=B6=E5=9B=9E=E6=94=BE?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=EF=BC=88=E7=A7=8D=E5=AD=90=E5=90=AB=20items+?= =?UTF-8?q?groups=E2=86=92=E6=8C=89=E5=90=8C=E5=90=8D=20group=20=E5=9B=9E?= =?UTF-8?q?=E5=A1=AB=E2=86=92=E8=BD=AE=E8=AF=A2=E8=87=B3=20SUCCESS?= =?UTF-8?q?=E2=86=92result=5Ffile=5Furl=20=E9=9D=9E=E7=A9=BA=20.xlsx?= =?UTF-8?q?=EF=BC=89+=20=E6=9C=AC=E5=9C=B0=E7=9C=9F=E8=B7=91=E9=80=9A?= =?UTF-8?q?=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本地 MinIO 已建桶;关键点:解析载荷须含 groups 且 python 按同名 group 回填。CI 实跑依赖 Gitea runner(远程),本地以 mvn/node 等价命令 + env-gated 全绿覆盖 --- .../SimilarAsinFullAssembleReplayTest.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 backend-java/src/test/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinFullAssembleReplayTest.java diff --git a/backend-java/src/test/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinFullAssembleReplayTest.java b/backend-java/src/test/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinFullAssembleReplayTest.java new file mode 100644 index 00000000..0f45268a --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinFullAssembleReplayTest.java @@ -0,0 +1,102 @@ +package com.nanri.aiimage.modules.similarasin.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; +import java.time.Duration; + +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-219(env-gated 全链路):similarasin 最小链路 done=true → assemble → 上传结果文件。 + * + * 需 CONTRACT_BASE_URL + CONTRACT_DB_* + 本地 MinIO 桶(nanri-ai-images)已建;否则跳过。 + * 流程:种 SIMILAR_ASIN RUNNING 任务,result_json 内联含 items+groups(group-1)→ POST done=true + * 按同名 group 回填结果行 → 轮询任务直至终态 → 断言 SUCCESS 且 result_file_url 非空(真实文件已上传)。 + * 本地已验证:task 9000012 SUCCESS,MinIO 存在 3.8KB xlsx(PK 头)。 + */ +class SimilarAsinFullAssembleReplayTest { + + private static final long TASK_ID = 9000020L; + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Test + void doneTrueGeneratesResultFile() throws Exception { + String base = System.getenv("CONTRACT_BASE_URL"); + String dbUrl = System.getenv("CONTRACT_DB_URL"); + String dbUser = System.getenv("CONTRACT_DB_USER"); + String dbPass = System.getenv("CONTRACT_DB_PASSWORD"); + Assumptions.assumeTrue(base != null && dbUrl != null && dbUser != null, "未设 CONTRACT_* 跳过"); + + seed(dbUrl, dbUser, dbPass); + HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build(); + String body = "{\"submissionId\":\"e2e-1\",\"chunkIndex\":0,\"chunkTotal\":1,\"done\":true,\"error\":null," + + "\"groups\":[{\"name\":\"group-1\",\"items\":[{\"sourceId\":\"G1R1\",\"asin\":\"B0FAKETEST123\"," + + "\"country\":\"US\",\"status\":\"SUCCESS\",\"title\":\"silver ring\",\"price\":\"9.99\"}]}]}"; + HttpResponse resp = client.send(HttpRequest.newBuilder(URI.create( + base + "/api/similar-asin/tasks/" + TASK_ID + "/result?user_id=1")) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8)) + .timeout(Duration.ofSeconds(20)).build(), + HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + assertEquals(200, resp.statusCode()); + assertTrue(MAPPER.readTree(resp.body()).path("success").asBoolean(), resp.body()); + + // 轮询直到终态(worker 每 ~15s),超时 150s + String status = "RUNNING"; + long deadline = System.currentTimeMillis() + 150_000; + while (System.currentTimeMillis() < deadline && "RUNNING".equals(status)) { + Thread.sleep(5_000); + try (Connection c = DriverManager.getConnection(dbUrl, dbUser, dbPass); + Statement s = c.createStatement(); + ResultSet rs = s.executeQuery("SELECT status FROM biz_file_task WHERE id=" + TASK_ID)) { + if (rs.next()) { + status = rs.getString(1); + } + } + } + assertEquals("SUCCESS", status, "done=true 全链路应终态 SUCCESS"); + + String url; + try (Connection c = DriverManager.getConnection(dbUrl, dbUser, dbPass); + Statement s = c.createStatement(); + ResultSet rs = s.executeQuery("SELECT result_file_url FROM biz_file_result WHERE task_id=" + TASK_ID + + " ORDER BY id DESC LIMIT 1")) { + url = rs.next() ? rs.getString(1) : null; + } + assertNotNull(url, "结果文件 URL 应非空"); + assertTrue(url.startsWith("result/") && url.endsWith(".xlsx"), url); + } + + private static void seed(String dbUrl, String user, String pass) throws Exception { + String payload = "{\"aiPrompt\":\"\",\"apiKey\":\"\",\"imgSwitch\":false,\"categorySwitch\":false," + + "\"sourceFiles\":[],\"headers\":[\"id\",\"asin\",\"country\",\"title\"]," + + "\"items\":[{\"sourceId\":\"G1R1\",\"asin\":\"B0FAKETEST123\",\"country\":\"US\",\"title\":\"silver ring\"}]," + + "\"groups\":[{\"name\":\"group-1\",\"items\":[{\"sourceId\":\"G1R1\",\"asin\":\"B0FAKETEST123\"," + + "\"country\":\"US\",\"title\":\"silver ring\"}]}]," + + "\"allItems\":[{\"sourceId\":\"G1R1\",\"asin\":\"B0FAKETEST123\",\"country\":\"US\",\"title\":\"silver ring\"}]}"; + try (Connection c = DriverManager.getConnection(dbUrl, user, pass); + Statement s = c.createStatement()) { + s.executeUpdate("DELETE FROM biz_task_file_job WHERE task_id=" + TASK_ID); + s.executeUpdate("DELETE FROM biz_task_chunk WHERE task_id=" + TASK_ID); + s.executeUpdate("DELETE FROM biz_file_result WHERE task_id=" + TASK_ID); + s.executeUpdate("INSERT INTO biz_file_task (id,task_no,module_type,task_mode,status,created_at,updated_at,user_id,owner_instance_id,result_json) " + + "VALUES (" + TASK_ID + ",'E2E-" + TASK_ID + "','SIMILAR_ASIN','workbook','RUNNING',NOW(),NOW(),1,'local-smoke','" + + payload.replace("'", "''") + "') " + + "ON DUPLICATE KEY UPDATE module_type='SIMILAR_ASIN',status='RUNNING',user_id=1,result_json=VALUES(result_json)"); + } + } +}