import { beforeEach, expect, test, vi } from "vitest"; import { GET, POST } from "../../../app/api/libraries/[libraryType]/route"; import { RouteError } from "@/lib/http/response"; const { backendRequestMock } = vi.hoisted(() => ({ backendRequestMock: vi.fn(), })); vi.mock("@/lib/http/backend-client", () => ({ backendRequest: backendRequestMock, })); beforeEach(() => { backendRequestMock.mockReset(); }); test("proxies backend library resources into the existing frontend view-model shape", async () => { backendRequestMock.mockResolvedValue({ status: 200, data: { total: 1, items: [ { id: 12, resource_type: "model", name: "Ava Studio", description: "棚拍女模特", tags: ["女装", "棚拍"], status: "active", gender: "female", age_group: "adult", environment: null, category: null, files: [ { id: 1, file_role: "thumbnail", storage_key: "library/models/ava/thumb.png", public_url: "https://images.marcusd.me/library/models/ava/thumb.png", bucket: "images", mime_type: "image/png", size_bytes: 2345, sort_order: 0, width: 480, height: 600, created_at: "2026-03-28T10:00:00Z", }, ], cover_url: "https://images.marcusd.me/library/models/ava/thumb.png", original_url: "https://images.marcusd.me/library/models/ava/original.png", created_at: "2026-03-28T10:00:00Z", updated_at: "2026-03-28T10:00:00Z", }, ], }, }); const response = await GET(new Request("http://localhost/api/libraries/models"), { params: Promise.resolve({ libraryType: "models" }), }); const payload = await response.json(); expect(response.status).toBe(200); expect(payload).toMatchObject({ mode: "proxy", data: { items: expect.arrayContaining([ expect.objectContaining({ id: "12", libraryType: "models", name: "Ava Studio", previewUri: "https://images.marcusd.me/library/models/ava/thumb.png", isMock: false, }), ]), }, message: "资源库当前显示真实后端数据。", }); expect(backendRequestMock).toHaveBeenCalledWith( "/library/resources?resource_type=model&limit=100", ); }); test("rejects unsupported placeholder library types with a normalized error", async () => { const response = await GET(new Request("http://localhost/api/libraries/unknown"), { params: Promise.resolve({ libraryType: "unknown" }), }); const payload = await response.json(); expect(response.status).toBe(404); expect(payload).toEqual({ error: "NOT_FOUND", message: "不支持的资源库类型。", }); }); test("rejects inherited object keys instead of treating them as valid library types", async () => { const response = await GET(new Request("http://localhost/api/libraries/toString"), { params: Promise.resolve({ libraryType: "toString" }), }); const payload = await response.json(); expect(response.status).toBe(404); expect(payload).toEqual({ error: "NOT_FOUND", message: "不支持的资源库类型。", }); }); test("normalizes backend errors while proxying library resources", async () => { backendRequestMock.mockRejectedValue( new RouteError(502, "BACKEND_UNAVAILABLE", "后端暂时不可用,请稍后重试。"), ); const response = await GET(new Request("http://localhost/api/libraries/models"), { params: Promise.resolve({ libraryType: "models" }), }); const payload = await response.json(); expect(response.status).toBe(502); expect(payload).toEqual({ error: "BACKEND_UNAVAILABLE", message: "后端暂时不可用,请稍后重试。", }); }); test("proxies library resource creation and adapts the created item into the existing view-model shape", async () => { backendRequestMock.mockResolvedValue({ status: 201, data: { id: 12, resource_type: "model", name: "Ava Studio", description: "棚拍女模特", tags: ["女装", "棚拍"], status: "active", gender: "female", age_group: "adult", pose_id: null, environment: null, category: null, files: [ { id: 1, file_role: "thumbnail", storage_key: "library/models/ava/thumb.png", public_url: "https://images.marcusd.me/library/models/ava/thumb.png", bucket: "images", mime_type: "image/png", size_bytes: 2345, sort_order: 0, width: 480, height: 600, created_at: "2026-03-28T10:00:00Z", }, ], cover_url: "https://images.marcusd.me/library/models/ava/thumb.png", original_url: "https://images.marcusd.me/library/models/ava/original.png", created_at: "2026-03-28T10:00:00Z", updated_at: "2026-03-28T10:00:00Z", }, }); const response = await POST( new Request("http://localhost/api/libraries/models", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify({ name: "Ava Studio", description: "棚拍女模特", tags: ["女装", "棚拍"], gender: "female", ageGroup: "adult", files: [ { fileRole: "original", storageKey: "library/models/2026/ava-original.png", publicUrl: "https://images.marcusd.me/library/models/ava/original.png", mimeType: "image/png", sizeBytes: 12345, sortOrder: 0, }, { fileRole: "thumbnail", storageKey: "library/models/2026/ava-thumb.png", publicUrl: "https://images.marcusd.me/library/models/ava/thumb.png", mimeType: "image/png", sizeBytes: 2345, sortOrder: 0, }, ], }), }), { params: Promise.resolve({ libraryType: "models" }), }, ); const payload = await response.json(); expect(response.status).toBe(201); expect(payload).toEqual({ mode: "proxy", data: { item: { id: "12", libraryType: "models", name: "Ava Studio", description: "棚拍女模特", previewUri: "https://images.marcusd.me/library/models/ava/thumb.png", originalUri: "https://images.marcusd.me/library/models/ava/original.png", tags: ["女装", "棚拍"], files: [ { id: 1, role: "thumbnail", url: "https://images.marcusd.me/library/models/ava/thumb.png", }, ], isMock: false, backendId: 12, poseId: null, }, }, message: "资源创建成功。", }); expect(backendRequestMock).toHaveBeenCalledWith("/library/resources", { method: "POST", body: JSON.stringify({ resource_type: "model", name: "Ava Studio", description: "棚拍女模特", tags: ["女装", "棚拍"], gender: "female", age_group: "adult", files: [ { file_role: "original", storage_key: "library/models/2026/ava-original.png", public_url: "https://images.marcusd.me/library/models/ava/original.png", mime_type: "image/png", size_bytes: 12345, sort_order: 0, }, { file_role: "thumbnail", storage_key: "library/models/2026/ava-thumb.png", public_url: "https://images.marcusd.me/library/models/ava/thumb.png", mime_type: "image/png", size_bytes: 2345, sort_order: 0, }, ], }), }); });