/** * Image cache service tests * * TRACES: UR-007 | DR-016 */ import { describe, it, expect, beforeEach, vi } from "vitest"; import { getCachedImageUrl, getCacheStats, setCacheLimit, clearCache, deleteItemCache, formatBytes, gbToBytes, bytesToGb, } from "./imageCache"; vi.mock("@tauri-apps/api/core", () => ({ invoke: vi.fn(async (command: string, args?: any) => { if (command === "thumbnail_get_cached") { return null; // No cached image } if (command === "thumbnail_get_stats") { return { totalSizeBytes: 1024 * 1024, itemCount: 10, limitBytes: 1024 * 1024 * 1024, }; } if (command === "thumbnail_set_limit") { return undefined; } if (command === "thumbnail_clear_cache") { return undefined; } if (command === "thumbnail_delete_item") { return undefined; } if (command === "thumbnail_save") { return undefined; } return null; }), convertFileSrc: vi.fn((path: string) => `asset://path/${path}`), })); describe("image cache service", () => { beforeEach(() => { vi.clearAllMocks(); }); describe("getCachedImageUrl", () => { it("should build server URL with default image type", async () => { const url = await getCachedImageUrl( "http://server.local:8096", "item-123" ); expect(url).toContain("http://server.local:8096/Items/item-123/Images/Primary"); }); it("should build server URL with custom image type", async () => { const url = await getCachedImageUrl( "http://server.local:8096", "item-123", "Backdrop" ); expect(url).toContain("Backdrop"); }); it("should include image options in URL", async () => { const url = await getCachedImageUrl( "http://server.local:8096", "item-123", "Primary", { maxWidth: 300, maxHeight: 400, quality: 90, tag: "abc123", } ); expect(url).toContain("maxWidth=300"); expect(url).toContain("maxHeight=400"); expect(url).toContain("quality=90"); expect(url).toContain("tag=abc123"); }); it("should trigger background caching", async () => { const { invoke } = await import("@tauri-apps/api/core"); const invokeSpy = vi.mocked(invoke); await getCachedImageUrl("http://server.local:8096", "item-123"); const saveCall = invokeSpy.mock.calls.find( (call) => call[0] === "thumbnail_save" ); expect(saveCall).toBeDefined(); expect(saveCall![1]).toHaveProperty("itemId", "item-123"); expect(saveCall![1]).toHaveProperty("imageType", "Primary"); }); }); describe("cache statistics", () => { it("should get cache statistics", async () => { const stats = await getCacheStats(); expect(stats).toHaveProperty("totalSizeBytes"); expect(stats).toHaveProperty("itemCount"); expect(stats).toHaveProperty("limitBytes"); expect(typeof stats.totalSizeBytes).toBe("number"); expect(typeof stats.itemCount).toBe("number"); }); it("should set cache limit", async () => { const limit = 1024 * 1024 * 1024 * 5; // 5GB await setCacheLimit(limit); const { invoke } = await import("@tauri-apps/api/core"); const invokeSpy = vi.mocked(invoke); const setLimitCall = invokeSpy.mock.calls.find( (call) => call[0] === "thumbnail_set_limit" ); expect(setLimitCall).toBeDefined(); expect(setLimitCall![1]).toHaveProperty("limitBytes", limit); }); }); describe("cache clearing", () => { it("should clear all cached thumbnails", async () => { await clearCache(); const { invoke } = await import("@tauri-apps/api/core"); const invokeSpy = vi.mocked(invoke); expect(invokeSpy).toHaveBeenCalledWith("thumbnail_clear_cache"); }); it("should delete cache for specific item", async () => { await deleteItemCache("item-456"); const { invoke } = await import("@tauri-apps/api/core"); const invokeSpy = vi.mocked(invoke); const deleteCall = invokeSpy.mock.calls.find( (call) => call[0] === "thumbnail_delete_item" ); expect(deleteCall).toBeDefined(); expect(deleteCall![1]).toHaveProperty("itemId", "item-456"); }); }); describe("byte formatting", () => { it("should format bytes", () => { expect(formatBytes(512)).toContain("B"); expect(formatBytes(512)).not.toContain("KB"); }); it("should format kilobytes", () => { expect(formatBytes(1024)).toContain("KB"); }); it("should format megabytes", () => { expect(formatBytes(1024 * 1024)).toContain("MB"); }); it("should format gigabytes", () => { expect(formatBytes(1024 * 1024 * 1024)).toContain("GB"); }); it("should format with correct precision", () => { const result = formatBytes(1024 * 1.5); expect(result).toMatch(/\d+\.\d+ KB/); }); }); describe("unit conversion", () => { it("should convert gigabytes to bytes", () => { const bytes = gbToBytes(1); expect(bytes).toBe(1024 * 1024 * 1024); }); it("should convert bytes to gigabytes", () => { const gb = bytesToGb(1024 * 1024 * 1024); expect(gb).toBe(1); }); it("should handle fractional conversions", () => { const bytes = gbToBytes(0.5); expect(bytes).toBe(512 * 1024 * 1024); const gb = bytesToGb(512 * 1024 * 1024); expect(gb).toBe(0.5); }); }); });