From 3fc87462181f734462c2d04142b4d0f68c37621d 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 18:06:22 +0800 Subject: [PATCH] =?UTF-8?q?task-162(=E5=AE=A2=E6=88=B7=E7=AB=AF=E5=85=AC?= =?UTF-8?q?=E5=BC=80=E7=89=88=E6=9C=AC=E6=8E=A5=E5=8F=A3=E5=85=BC=E5=AE=B9?= =?UTF-8?q?):=20=E5=86=BB=E7=BB=93=20/api/version/latest=20=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PublicVersionLookup 增 latest/selectLatest 与路径常量 + Task162Test。 TDD: RED→GREEN。 --- .../service/support/PublicVersionLookup.java | 10 +++ .../service/support/Task162Test.java | 77 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 backend-java/src/test/java/com/nanri/aiimage/modules/softwareversion/service/support/Task162Test.java 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)); + } +}