task-206: heartbeat 旧字段契约回放(env-gated,真实服务:RUNNING→alive:true、非运行→alive:false、未知任务 alive:false+message、旧/多余字段容忍)+ 已本地真跑

This commit is contained in:
2026-09-05 09:01:55 +08:00
parent f0f9ba29f0
commit dacdab7adb
@@ -0,0 +1,87 @@
package com.nanri.aiimage.modules.task.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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* task-206heartbeat 旧字段契约回放(真实服务,env-gated)。
* CONTRACT_BASE_URL + CONTRACT_DB_* 设则跑、否则跳过(CI 绿)。
* 覆盖:RUNNING 任务 heartbeat alive:true(路径/响应字段)、非运行任务 alive:false、未知任务
* alive:false+message、缺省/多余(旧)字段容忍。
*/
class TaskHeartbeatContractReplayTest {
private static final long TASK_ID = 9000004L;
private static final ObjectMapper MAPPER = new ObjectMapper();
@Test
void heartbeatContract() 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, "RUNNING");
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
// RUNNING + 旧/额外字段 → alive:true(兼容多余字段)
String body = "{\"moduleType\":\"SIMILAR_ASIN\",\"phase\":\"search\",\"current\":1,\"total\":10,"
+ "\"collectStage\":\"keyword-search\",\"currentKeyword\":\"ring\",\"searchCurrentPage\":1,"
+ "\"searchTotalPages\":4,\"detailProcessedAsins\":1,\"detailTotalAsins\":50,"
+ "\"extraLegacyField\":\"ignored\"}";
HttpResponse<String> ok = post(client, base + "/api/tasks/" + TASK_ID + "/heartbeat", body);
assertEquals(200, ok.statusCode());
JsonNode okBody = MAPPER.readTree(ok.body());
assertTrue(okBody.path("success").asBoolean(), ok.body());
assertTrue(okBody.path("data").path("alive").asBoolean(), "RUNNING 任务应 alive:true: " + ok.body());
assertEquals("SIMILAR_ASIN", okBody.path("data").path("moduleType").asText());
// 非运行 → alive:false
seed(dbUrl, dbUser, dbPass, "FINISHED");
HttpResponse<String> finished = post(client, base + "/api/tasks/" + TASK_ID + "/heartbeat",
"{\"moduleType\":\"SIMILAR_ASIN\",\"phase\":\"search\",\"current\":1,\"total\":1}");
JsonNode f = MAPPER.readTree(finished.body());
assertFalse(f.path("data").path("alive").asBoolean(), "非运行任务应 alive:false: " + finished.body());
// 未知任务 → alive:false + message
HttpResponse<String> unknown = post(client, base + "/api/tasks/999999999/heartbeat",
"{\"moduleType\":\"SIMILAR_ASIN\"}");
JsonNode u = MAPPER.readTree(unknown.body());
assertFalse(u.path("data").path("alive").asBoolean(), "未知任务应 alive:false");
assertTrue(u.path("data").has("message"), "应带 message");
}
private static void seed(String dbUrl, String user, String pass, String status) 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 + ",'HB-REPLAY-" + TASK_ID + "','SIMILAR_ASIN','workbook','" + status + "',NOW(),NOW(),1,'local-smoke') "
+ "ON DUPLICATE KEY UPDATE module_type='SIMILAR_ASIN',status='" + status + "',user_id=1");
}
}
private static HttpResponse<String> post(HttpClient client, String url, String body) throws Exception {
return client.send(HttpRequest.newBuilder(URI.create(url))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.timeout(Duration.ofSeconds(15)).build(),
HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
}
}