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)"); + } + } +}