diff --git a/backend-java/src/test/java/com/nanri/aiimage/modules/file/service/oss/DownloadUrlNonExpiringTest.java b/backend-java/src/test/java/com/nanri/aiimage/modules/file/service/oss/DownloadUrlNonExpiringTest.java new file mode 100644 index 00000000..8f27725a --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/modules/file/service/oss/DownloadUrlNonExpiringTest.java @@ -0,0 +1,97 @@ +package com.nanri.aiimage.modules.file.service.oss; + +import com.nanri.aiimage.config.OssProperties; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * task-147:downloadUrl 永不过期验证(plan 08)。 + * generateFreshDownloadUrl:result/ 前缀走下载域名、非 result/ 走公开端点、 + * URL 无签名(不含 X-Amz 系列 / Expires / Signature 等过期参数)、bucket 前缀正确、 + * null/空白安全、同输入同输出稳定。 + */ +class DownloadUrlNonExpiringTest { + + private OssProperties properties; + private OssStorageService storageService; + + @BeforeEach + void setUp() { + properties = new OssProperties(); + properties.setEndpoint("https://oss.aishufu.top"); + properties.setPublicEndpoint("https://oss.aishufu.top"); + properties.setDownloadEndpoint("https://download.aishufu.top"); + properties.setBucket("nanri-ai-images"); + properties.setImageVideoBucket("shufu-video"); + properties.setDigitalHumanBucket("nanri-ai-digital-human"); + properties.setTemplateBucket("aiimage-templates"); + properties.setAccessKeyId("test-access-key"); + properties.setAccessKeySecret("test-secret-key"); + storageService = new OssStorageService(properties); + } + + @Test + void resultPrefixUsesDownloadEndpoint() { + assertEquals( + "https://download.aishufu.top/nanri-ai-images/result/publish/1/result.xlsx", + storageService.generateFreshDownloadUrl("result/publish/1/result.xlsx")); + } + + @Test + void nonResultPrefixUsesPublicEndpoint() { + assertEquals( + "https://oss.aishufu.top/nanri-ai-images/supply_images/main.jpg", + storageService.generateFreshDownloadUrl("supply_images/main.jpg")); + } + + @Test + void urlHasNoExpiringSignatureParameters() { + String url = storageService.generateFreshDownloadUrl("result/publish/1/result.xlsx"); + assertFalse(url.contains("X-Amz-"), "不得包含 AWS 签名参数: " + url); + assertFalse(url.contains("Signature"), "不得包含签名: " + url); + assertFalse(url.contains("Expires"), "不得包含过期时间: " + url); + assertFalse(url.contains("?") || url.contains("&"), "不得携带任何查询参数: " + url); + } + + @Test + void bucketPrefixIsCorrect() { + String url = storageService.generateFreshDownloadUrl("result/collect_data/2/data.xlsx"); + assertTrue(url.contains("/nanri-ai-images/result/collect_data/2/data.xlsx"), + "主 bucket 前缀正确: " + url); + String imageUrl = storageService.generateFreshDownloadUrl("shufu-video/result/image_video/demo.mp4"); + assertTrue(imageUrl.contains("/shufu-video/result/image_video/demo.mp4"), + "image-video bucket 前缀正确: " + imageUrl); + } + + @Test + void nullUrlIsSafe() { + assertNull(storageService.generateFreshDownloadUrl(null), "null 输入返回 null 不抛错"); + } + + @Test + void blankUrlIsSafe() { + assertNull(storageService.generateFreshDownloadUrl(" "), "空白输入返回 null 不抛错"); + assertNull(storageService.generateFreshDownloadUrl(""), "空串返回 null 不抛错"); + } + + @Test + void generatedUrlIsStable() { + String first = storageService.generateFreshDownloadUrl("result/similar_asin/3/re.xlsx"); + String second = storageService.generateFreshDownloadUrl("result/similar_asin/3/re.xlsx"); + assertEquals(first, second, "同输入同输出(无随机签名)"); + } + + @Test + void contractFrozen() { + String url = storageService.generateFreshDownloadUrl("result/similar_asin/3/re.xlsx"); + assertNotNull(url); + // 快照:https://{download-endpoint}/{bucket}/{objectKey},纯路径无签名 + assertEquals("https://download.aishufu.top/nanri-ai-images/result/similar_asin/3/re.xlsx", url); + } +}