diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/softwareversion/service/support/PublicVersionLookup.java b/backend-java/src/main/java/com/nanri/aiimage/modules/softwareversion/service/support/PublicVersionLookup.java index 83da6cd6..5c0b20ec 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/softwareversion/service/support/PublicVersionLookup.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/softwareversion/service/support/PublicVersionLookup.java @@ -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 rows) { if (rows == null || rows.isEmpty()) { diff --git a/backend-java/src/test/java/com/nanri/aiimage/modules/softwareversion/service/support/Task162Test.java b/backend-java/src/test/java/com/nanri/aiimage/modules/softwareversion/service/support/Task162Test.java new file mode 100644 index 00000000..d8738c2c --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/modules/softwareversion/service/support/Task162Test.java @@ -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 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 empty = PublicVersionLookup.latestVersionResponse(null); + assertNull(empty.get("version")); + assertNull(empty.get("file_url")); + } + + @Test + void test_task_162_invalid_input_rejected() { + Map 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)); + } +}