task-162(客户端公开版本接口兼容): 冻结 /api/version/latest 响应契约

PublicVersionLookup 增 latest/selectLatest 与路径常量 + Task162Test。

TDD: RED→GREEN。
This commit is contained in:
2026-09-05 18:06:22 +08:00
parent b134db134e
commit 3fc8746218
2 changed files with 87 additions and 0 deletions
@@ -12,6 +12,11 @@ import java.util.Map;
*/
public final class PublicVersionLookup {
/** 公开客户端版本路径(与旧 Flask/客户端地址完全一致)。 */
public static final String PUBLIC_VERSION_PATH = "/api/version";
public static final String PUBLIC_VERSION_LATEST_PATH = "/api/version/latest";
public static final String ADMIN_UPLOAD_PATH = "/api/admin/version";
/** web_config 版本记录投影(id 用于同时间稳定排序)。 */
public record WebVersion(Long id, String version, String fileUrl, LocalDateTime createdAt) {
}
@@ -19,6 +24,11 @@ public final class PublicVersionLookup {
private PublicVersionLookup() {
}
/** 是否属于公开版本接口路径(与后台上传路径解耦)。 */
public static boolean isPublicVersionPath(String path) {
return PUBLIC_VERSION_PATH.equals(path) || PUBLIC_VERSION_LATEST_PATH.equals(path);
}
/** 取最新版本记录:created_at DESC,同一时间按 id 倒序稳定;空/无有效回 null。 */
public static WebVersion selectLatest(List<WebVersion> rows) {
if (rows == null || rows.isEmpty()) {
@@ -0,0 +1,77 @@
package com.nanri.aiimage.modules.softwareversion.service.support;
import com.nanri.aiimage.modules.softwareversion.service.support.PublicVersionLookup.WebVersion;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
/** task-162: module 09 公开版本契约测试(latest 契约)。 */
class Task162Test {
private WebVersion v(Long id, String version, String fileUrl, LocalDateTime at) {
return new WebVersion(id, version, fileUrl, at);
}
@Test
void test_task_162_normal_primary_path() {
WebVersion latest = PublicVersionLookup.selectLatest(List.of(
v(1L, "1.0.1", "https://a/1.zip", LocalDateTime.of(2026, 1, 1, 0, 0)),
v(2L, "1.0.2", "https://a/2.zip", LocalDateTime.of(2026, 1, 2, 0, 0))));
assertNotNull(latest);
assertEquals("1.0.2", latest.version());
assertEquals("https://a/2.zip", latest.fileUrl());
}
@Test
void test_task_162_normal_variant_input() {
WebVersion latest = PublicVersionLookup.selectLatest(List.of(
v(5L, "1.0.5", "u5", LocalDateTime.of(2026, 2, 2, 0, 0)),
v(4L, "1.0.4", "u4", LocalDateTime.of(2026, 2, 1, 0, 0))));
assertEquals("1.0.5", latest.version());
}
@Test
void test_task_162_normal_repeated_is_idempotent() {
List<WebVersion> rows = List.of(v(1L, "1.0.0", "u", LocalDateTime.of(2026, 1, 1, 0, 0)));
assertTrue(PublicVersionLookup.selectLatest(rows).equals(PublicVersionLookup.selectLatest(rows)));
}
@Test
void test_task_162_boundary_empty_input() {
assertNull(PublicVersionLookup.selectLatest(null));
assertNull(PublicVersionLookup.selectLatest(List.of()));
}
@Test
void test_task_162_boundary_single_item() {
WebVersion latest = PublicVersionLookup.selectLatest(
List.of(v(7L, "1.0.0", "u", LocalDateTime.of(2026, 1, 1, 0, 0))));
assertEquals("1.0.0", latest.version());
}
@Test
void test_task_162_boundary_limit_or_missing_field() {
Map<String, Object> empty = PublicVersionLookup.latestVersionResponse(null);
assertNull(empty.get("version"));
assertNull(empty.get("file_url"));
}
@Test
void test_task_162_invalid_input_rejected() {
Map<String, Object> resp = PublicVersionLookup.latestVersionResponse(
v(1L, "1.0.0", "https://x/f.zip", LocalDateTime.of(2026, 1, 1, 0, 0)));
assertFalse(resp.containsKey("success"), "无 ApiResponse 包装");
assertEquals("https://x/f.zip", resp.get("file_url"), "file_url 原样返回");
}
@Test
void test_task_162_dependency_failure_returns_actionable_message() {
assertTrue(PublicVersionLookup.isPublicVersionPath(PublicVersionLookup.PUBLIC_VERSION_PATH));
assertTrue(PublicVersionLookup.isPublicVersionPath(PublicVersionLookup.PUBLIC_VERSION_LATEST_PATH));
assertFalse(PublicVersionLookup.isPublicVersionPath(PublicVersionLookup.ADMIN_UPLOAD_PATH));
}
}