From 0b83c9a3c18efeb68e863f48a9daee1116c8a431 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 08:59:52 +0800 Subject: [PATCH] =?UTF-8?q?task-204:=20/items=20=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E8=AF=AD=E4=B9=89=E5=A5=91=E7=BA=A6=E5=9B=9E=E6=94=BE=EF=BC=88?= =?UTF-8?q?env-gated=EF=BC=8C=E7=9C=9F=E5=AE=9E=E6=9C=8D=E5=8A=A1=EF=BC=9A?= =?UTF-8?q?=E9=80=A0=20COLLECT=5FDATA=20=E4=BB=BB=E5=8A=A1+5=20=E8=A1=8C?= =?UTF-8?q?=20item=20=E2=86=92=20page/pageSize/count/total/totalPages/item?= =?UTF-8?q?s=E3=80=81=E8=B6=8A=E7=95=8C=E7=A9=BA=E9=A1=B5=E3=80=81?= =?UTF-8?q?=E7=BC=BA=E7=9C=81=2050=E3=80=81rowIndex=20=E5=8D=87=E5=BA=8F?= =?UTF-8?q?=EF=BC=89+=20=E5=B7=B2=E6=9C=AC=E5=9C=B0=E7=9C=9F=E8=B7=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CollectDataItemsContractReplayTest.java | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 backend-java/src/test/java/com/nanri/aiimage/modules/collectdata/service/CollectDataItemsContractReplayTest.java diff --git a/backend-java/src/test/java/com/nanri/aiimage/modules/collectdata/service/CollectDataItemsContractReplayTest.java b/backend-java/src/test/java/com/nanri/aiimage/modules/collectdata/service/CollectDataItemsContractReplayTest.java new file mode 100644 index 00000000..481b04d1 --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/modules/collectdata/service/CollectDataItemsContractReplayTest.java @@ -0,0 +1,93 @@ +package com.nanri.aiimage.modules.collectdata.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.Statement; +import java.time.Duration; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * task-204:/items 分页语义契约回放(真实服务,env-gated)。 + * + * 依赖运行应用与可写副本库:CONTRACT_BASE_URL + CONTRACT_DB_*。未设则跳过(CI 绿)。 + * 流程:造 COLLECT_DATA RUNNING 任务 + 5 行 biz_collect_data_item → GET items 各分页参数, + * 断言 page/pageSize/count/total/totalPages/items 与缺省/越界/空页语义(与 Python Worker 拉取约定一致)。 + */ +class CollectDataItemsContractReplayTest { + + private static final long TASK_ID = 9000002L; + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Test + void itemsPaginationSemantics() 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, 5); + HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build(); + + JsonNode page1 = getPage(client, base, 1, 2); + assertTrue(page1.path("success").asBoolean(), page1.toString()); + JsonNode data = page1.path("data"); + assertEquals(1, data.path("page").asInt()); + assertEquals(2, data.path("pageSize").asInt()); + assertEquals(5, data.path("total").asLong()); + assertEquals(3, data.path("totalPages").asInt()); + assertEquals(2, data.path("count").asInt()); + assertEquals(2, data.path("items").size()); + assertEquals(1, data.path("items").get(0).path("rowIndex").asInt(), "应按 rowIndex 升序"); + + JsonNode page3 = getPage(client, base, 3, 2); + assertEquals(1, page3.path("data").path("items").size(), "page3 应剩 1 行"); + assertEquals(5, page3.path("data").path("items").get(0).path("rowIndex").asInt()); + + JsonNode beyond = getPage(client, base, 99, 2); + assertEquals(0, beyond.path("data").path("items").size(), "越界页应空且成功"); + assertEquals(5, beyond.path("data").path("total").asLong()); + + JsonNode defaults = getPage(client, base, 1, 50); + JsonNode d = defaults.path("data"); + assertEquals(1, d.path("page").asInt()); + assertEquals(50, d.path("pageSize").asInt()); + assertEquals(1, d.path("totalPages").asInt(), "5 行 50 一页应 1 页"); + assertEquals(5, d.path("count").asInt()); + } + + private static JsonNode getPage(HttpClient client, String base, int page, int size) throws Exception { + String url = base + "/api/collect-data/tasks/" + TASK_ID + "/items?user_id=1&page=" + page + "&page_size=" + size; + HttpResponse resp = client.send(HttpRequest.newBuilder(URI.create(url)).GET() + .timeout(Duration.ofSeconds(15)).build(), + HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + assertEquals(200, resp.statusCode(), url); + return MAPPER.readTree(resp.body()); + } + + private static void seed(String dbUrl, String user, String pass, int rows) throws Exception { + try (Connection c = DriverManager.getConnection(dbUrl, user, pass); + Statement s = c.createStatement()) { + s.executeUpdate("INSERT INTO biz_file_task (id,task_no,module_type,task_mode,status,created_at,updated_at,user_id,owner_instance_id) " + + "VALUES (" + TASK_ID + ",'CD-REPLAY-" + TASK_ID + "','COLLECT_DATA','collect','RUNNING',NOW(),NOW(),1,'local-smoke') " + + "ON DUPLICATE KEY UPDATE module_type='COLLECT_DATA',status='RUNNING',user_id=1"); + s.executeUpdate("DELETE FROM biz_collect_data_item WHERE task_id=" + TASK_ID); + for (int i = 1; i <= rows; i++) { + s.executeUpdate("INSERT INTO biz_collect_data_item (task_id,row_index,keyword,status_value,created_at) VALUES (" + + TASK_ID + "," + i + ",'keyword-" + i + "','PENDING',NOW())"); + } + } + } +}