From b134db134eb4ad61e3998b87ea8835bf0e5998b0 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:04:19 +0800 Subject: [PATCH] =?UTF-8?q?task-161(=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=20=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 PublicVersionLookup.currentVersionResponse + Task161Test。 TDD: RED→GREEN。 --- .../service/support/PublicVersionLookup.java | 74 +++++++++++++++++++ .../service/support/Task161Test.java | 63 ++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 backend-java/src/main/java/com/nanri/aiimage/modules/softwareversion/service/support/PublicVersionLookup.java create mode 100644 backend-java/src/test/java/com/nanri/aiimage/modules/softwareversion/service/support/Task161Test.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 new file mode 100644 index 00000000..83da6cd6 --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/softwareversion/service/support/PublicVersionLookup.java @@ -0,0 +1,74 @@ +package com.nanri.aiimage.modules.softwareversion.service.support; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * 客户端公开版本查询纯逻辑(module 09):空语义、created_at DESC + id 稳定排序、 + * 无 ApiResponse 包装;供 PublicVersionController/契约测试复用。 + */ +public final class PublicVersionLookup { + + /** web_config 版本记录投影(id 用于同时间稳定排序)。 */ + public record WebVersion(Long id, String version, String fileUrl, LocalDateTime createdAt) { + } + + private PublicVersionLookup() { + } + + /** 取最新版本记录:created_at DESC,同一时间按 id 倒序稳定;空/无有效回 null。 */ + public static WebVersion selectLatest(List rows) { + if (rows == null || rows.isEmpty()) { + return null; + } + List list = new ArrayList<>(rows); + list.sort((a, b) -> { + int time = compareTime(b.createdAt(), a.createdAt()); + if (time != 0) { + return time; + } + long ai = a.id() == null ? 0L : a.id(); + long bi = b.id() == null ? 0L : b.id(); + return Long.compare(bi, ai); + }); + return list.get(0); + } + + private static int compareTime(LocalDateTime a, LocalDateTime b) { + if (a == null && b == null) { + return 0; + } + if (a == null) { + return -1; + } + if (b == null) { + return 1; + } + return a.compareTo(b); + } + + /** 当前服务版本响应:保留 version/desc/url 字段(可为空字符串),不包装 ApiResponse。 */ + public static Map currentVersionResponse(String version, String desc, String url) { + Map map = new LinkedHashMap<>(); + map.put("version", version == null ? "" : version); + map.put("desc", desc == null ? "" : desc); + map.put("url", url == null ? "" : url); + return map; + } + + /** 最新版本响应:无记录返回 version=null,file_url=null(客户端语义)。 */ + public static Map latestVersionResponse(WebVersion latest) { + Map map = new LinkedHashMap<>(); + if (latest == null) { + map.put("version", null); + map.put("file_url", null); + return map; + } + map.put("version", latest.version()); + map.put("file_url", latest.fileUrl()); + return map; + } +} diff --git a/backend-java/src/test/java/com/nanri/aiimage/modules/softwareversion/service/support/Task161Test.java b/backend-java/src/test/java/com/nanri/aiimage/modules/softwareversion/service/support/Task161Test.java new file mode 100644 index 00000000..438feb34 --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/modules/softwareversion/service/support/Task161Test.java @@ -0,0 +1,63 @@ +package com.nanri.aiimage.modules.softwareversion.service.support; + +import org.junit.jupiter.api.Test; + +import java.util.Map; + +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-161:冻结 /api/version 响应契约(version/desc/url 保留且无 ApiResponse 包装)。 */ +class Task161Test { + + @Test + void test_task_161_freeze_public_version_normal_primary_path() { + Map resp = PublicVersionLookup.currentVersionResponse("1.0.46", "x", "https://a"); + assertEquals("1.0.46", resp.get("version")); + assertEquals("x", resp.get("desc")); + assertEquals("https://a", resp.get("url")); + } + + @Test + void test_task_161_freeze_public_version_normal_variant_input() { + Map resp = PublicVersionLookup.currentVersionResponse(null, null, null); + assertEquals("", resp.get("version")); + assertEquals("", resp.get("desc")); + assertEquals("", resp.get("url")); + } + + @Test + void test_task_161_freeze_public_version_repeated_is_idempotent() { + assertTrue(PublicVersionLookup.currentVersionResponse("v", "d", "u") + .equals(PublicVersionLookup.currentVersionResponse("v", "d", "u"))); + } + + @Test + void test_task_161_freeze_public_version_boundary_empty_input() { + assertTrue(PublicVersionLookup.currentVersionResponse("", "", "").values().stream().allMatch(v -> "".equals(v))); + } + + @Test + void test_task_161_freeze_public_version_boundary_single_item() { + Map resp = PublicVersionLookup.currentVersionResponse("1.0.0", "", ""); + assertEquals(3, resp.size()); + } + + @Test + void test_task_161_freeze_public_version_boundary_limit_or_missing_field() { + assertFalse(PublicVersionLookup.currentVersionResponse("v", "d", "u").containsKey("success"), "公开接口不包装信封"); + } + + @Test + void test_task_161_freeze_public_version_invalid_input_rejected() { + Map resp = PublicVersionLookup.currentVersionResponse("1", "a", "b"); + assertEquals("1", resp.get("version"), "version 不被转数字"); + } + + @Test + void test_task_161_freeze_public_version_dependency_failure_returns_actionable_message() { + // 保留字段即使为空;desc/url 不能丢弃 + assertEquals(3, PublicVersionLookup.currentVersionResponse("v", "", "").keySet().size()); + } +}