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