|
| 1 | +import { initTestEnv } from "@Tests/utils"; |
| 2 | +import { ResourceService } from "./resource"; |
| 3 | +import { vi, describe, it, expect, beforeEach } from "vitest"; |
| 4 | +import type { Group } from "@Packages/message/server"; |
| 5 | +import type { IMessageQueue } from "@Packages/message/message_queue"; |
| 6 | + |
| 7 | +initTestEnv(); |
| 8 | + |
| 9 | +// mock fetch |
| 10 | +const mockFetch = vi.fn(); |
| 11 | +vi.stubGlobal("fetch", mockFetch); |
| 12 | + |
| 13 | +// 创建文本 blob 和二进制 blob 的辅助函数 |
| 14 | +function textBlob(content: string, contentType = "text/plain") { |
| 15 | + return new Blob([content], { type: contentType }); |
| 16 | +} |
| 17 | + |
| 18 | +function binaryBlob(bytes: number[]) { |
| 19 | + return new Blob([new Uint8Array(bytes)], { type: "application/octet-stream" }); |
| 20 | +} |
| 21 | + |
| 22 | +function mockResponse(blob: Blob, status = 200, contentType?: string) { |
| 23 | + return { |
| 24 | + status, |
| 25 | + blob: () => Promise.resolve(blob), |
| 26 | + headers: new Headers(contentType ? { "content-type": contentType } : {}), |
| 27 | + } as unknown as Response; |
| 28 | +} |
| 29 | + |
| 30 | +describe("ResourceService - loadByUrl", () => { |
| 31 | + let service: ResourceService; |
| 32 | + |
| 33 | + beforeEach(() => { |
| 34 | + vi.clearAllMocks(); |
| 35 | + const mockGroup = {} as Group; |
| 36 | + const mockMQ = {} as IMessageQueue; |
| 37 | + service = new ResourceService(mockGroup, mockMQ); |
| 38 | + // calculateHash 不影响核心逻辑,直接 mock |
| 39 | + vi.spyOn(service, "calculateHash").mockResolvedValue({ |
| 40 | + md5: "mock-md5", |
| 41 | + sha1: "", |
| 42 | + sha256: "", |
| 43 | + sha384: "", |
| 44 | + sha512: "", |
| 45 | + }); |
| 46 | + }); |
| 47 | + |
| 48 | + it("加载文本资源(require)时应设置 content", async () => { |
| 49 | + const jsCode = "console.log('hello');"; |
| 50 | + mockFetch.mockResolvedValue(mockResponse(textBlob(jsCode), 200, "application/javascript; charset=utf-8")); |
| 51 | + |
| 52 | + const res = await service.loadByUrl("https://example.com/lib.js", "require"); |
| 53 | + |
| 54 | + expect(res.url).toBe("https://example.com/lib.js"); |
| 55 | + expect(res.content).toBeTruthy(); |
| 56 | + expect(res.contentType).toBe("application/javascript"); |
| 57 | + expect(res.base64).toBeTruthy(); |
| 58 | + expect(res.type).toBe("require"); |
| 59 | + }); |
| 60 | + |
| 61 | + it("加载文本资源(resource)时应通过 blob.text() 设置 content", async () => { |
| 62 | + const text = "plain text content"; |
| 63 | + mockFetch.mockResolvedValue(mockResponse(textBlob(text), 200, "text/plain")); |
| 64 | + |
| 65 | + const res = await service.loadByUrl("https://example.com/data.txt", "resource"); |
| 66 | + |
| 67 | + expect(res.content).toBe(text); |
| 68 | + expect(res.type).toBe("resource"); |
| 69 | + }); |
| 70 | + |
| 71 | + it("加载二进制资源时 content 应为空", async () => { |
| 72 | + // 包含 null 字节的二进制数据,isText 会返回 false |
| 73 | + const bytes = [0x89, 0x50, 0x4e, 0x47, 0x00, 0x00, 0x00, 0x00]; |
| 74 | + mockFetch.mockResolvedValue(mockResponse(binaryBlob(bytes), 200, "image/png")); |
| 75 | + |
| 76 | + const res = await service.loadByUrl("https://example.com/img.png", "resource"); |
| 77 | + |
| 78 | + expect(res.content).toBe(""); |
| 79 | + expect(res.base64).toBeTruthy(); |
| 80 | + expect(res.contentType).toBe("image/png"); |
| 81 | + }); |
| 82 | + |
| 83 | + it("响应非200时应抛出异常", async () => { |
| 84 | + mockFetch.mockResolvedValue(mockResponse(textBlob(""), 404)); |
| 85 | + |
| 86 | + await expect(service.loadByUrl("https://example.com/404", "require")).rejects.toThrow( |
| 87 | + "resource response status not 200: 404" |
| 88 | + ); |
| 89 | + }); |
| 90 | + |
| 91 | + it("没有 content-type 时应默认为 application/octet-stream", async () => { |
| 92 | + mockFetch.mockResolvedValue(mockResponse(textBlob("data"), 200)); |
| 93 | + |
| 94 | + const res = await service.loadByUrl("https://example.com/noct", "resource"); |
| 95 | + |
| 96 | + expect(res.contentType).toBe("application/octet-stream"); |
| 97 | + }); |
| 98 | +}); |
0 commit comments