/** * Preload service tests * * TRACES: UR-004, UR-011 | DR-006, DR-015 */ import { describe, it, expect, beforeEach, vi } from "vitest"; import { preloadUpcomingTracks, updateCacheConfig, getCacheConfig } from "./preload"; import type { CacheConfig } from "$lib/api/bindings"; // updateCacheConfig now takes a full CacheConfig (matches the backend command) function makeConfig(overrides: Partial = {}): CacheConfig { return { queuePrecacheEnabled: true, queuePrecacheCount: 5, albumAffinityEnabled: false, albumAffinityThreshold: 0.75, storageLimit: 2 * 1024 * 1024 * 1024, wifiOnly: false, temporaryTtlHours: 24 * 7, ...overrides, }; } vi.mock("@tauri-apps/api/core", () => ({ invoke: vi.fn(async (command: string, args?: any) => { if (command === "player_preload_upcoming") { return { queuedCount: 3, alreadyDownloaded: 2, skipped: 1, }; } if (command === "player_set_cache_config") { return undefined; } if (command === "player_get_cache_config") { return { queuePrecacheEnabled: true, queuePrecacheCount: 5, albumAffinityEnabled: true, albumAffinityThreshold: 0.8, storageLimit: 1024 * 1024 * 1024, wifiOnly: false, }; } return null; }), })); vi.mock("$lib/stores/auth", () => ({ auth: { getUserId: vi.fn(() => "user-123"), }, })); describe("preload service", () => { beforeEach(() => { vi.clearAllMocks(); }); describe("preloadUpcomingTracks", () => { it("should preload tracks without options", async () => { await expect(preloadUpcomingTracks()).resolves.toBeUndefined(); }); it("should invoke correct backend command", async () => { const { invoke } = await import("@tauri-apps/api/core"); const invokeSpy = vi.mocked(invoke); await preloadUpcomingTracks(); const call = invokeSpy.mock.calls.find((c) => c[0] === "player_preload_upcoming"); expect(call).toBeDefined(); }); it("should include userId in command", async () => { const { invoke } = await import("@tauri-apps/api/core"); const invokeSpy = vi.mocked(invoke); await preloadUpcomingTracks(); const call = invokeSpy.mock.calls.find((c) => c[0] === "player_preload_upcoming"); expect(call![1]).toHaveProperty("userId", "user-123"); }); it("should use override userId if provided", async () => { const { invoke } = await import("@tauri-apps/api/core"); const invokeSpy = vi.mocked(invoke); await preloadUpcomingTracks({ userId: "user-456" }); const call = invokeSpy.mock.calls.find((c) => c[0] === "player_preload_upcoming"); expect(call![1]).toHaveProperty("userId", "user-456"); }); it("should skip if no active user", async () => { const { auth } = await import("$lib/stores/auth"); const authModule = vi.mocked(auth); authModule.getUserId = vi.fn(() => null); const { invoke } = await import("@tauri-apps/api/core"); const invokeSpy = vi.mocked(invoke); await preloadUpcomingTracks(); const call = invokeSpy.mock.calls.find((c) => c[0] === "player_preload_upcoming"); expect(call).toBeUndefined(); }); it("should handle preload result", async () => { // Should not throw even with result await expect(preloadUpcomingTracks()).resolves.toBeUndefined(); }); it("should handle errors gracefully", async () => { const { invoke } = await import("@tauri-apps/api/core"); const invokeSpy = vi.mocked(invoke); invokeSpy.mockRejectedValueOnce(new Error("Backend error")); // Should not throw await expect(preloadUpcomingTracks()).resolves.toBeUndefined(); }); it("should support debug option", async () => { await expect(preloadUpcomingTracks({ debug: true })).resolves.toBeUndefined(); }); it("should support both debug and userId options", async () => { const { invoke } = await import("@tauri-apps/api/core"); const invokeSpy = vi.mocked(invoke); await preloadUpcomingTracks({ debug: true, userId: "user-789" }); const call = invokeSpy.mock.calls.find((c) => c[0] === "player_preload_upcoming"); expect(call![1]).toHaveProperty("userId", "user-789"); }); }); describe("updateCacheConfig", () => { it("should update cache config", async () => { const config = makeConfig({ queuePrecacheEnabled: false, queuePrecacheCount: 10, }); await expect(updateCacheConfig(config)).resolves.toBeUndefined(); }); it("should invoke correct backend command", async () => { const { invoke } = await import("@tauri-apps/api/core"); const invokeSpy = vi.mocked(invoke); const config = makeConfig({ queuePrecacheEnabled: true }); await updateCacheConfig(config); const call = invokeSpy.mock.calls.find((c) => c[0] === "player_set_cache_config"); expect(call).toBeDefined(); expect(call![1]).toHaveProperty("config", config); }); it("should support overriding individual config options", async () => { const config = makeConfig({ wifiOnly: true }); await expect(updateCacheConfig(config)).resolves.toBeUndefined(); }); it("should support all config options", async () => { const config = makeConfig({ wifiOnly: true, albumAffinityEnabled: false }); await expect(updateCacheConfig(config)).resolves.toBeUndefined(); }); }); describe("getCacheConfig", () => { it("should get cache config", async () => { const config = await getCacheConfig(); expect(config).toBeDefined(); expect(typeof config.queuePrecacheEnabled).toBe("boolean"); expect(typeof config.queuePrecacheCount).toBe("number"); expect(typeof config.albumAffinityEnabled).toBe("boolean"); expect(typeof config.albumAffinityThreshold).toBe("number"); expect(typeof config.storageLimit).toBe("number"); expect(typeof config.wifiOnly).toBe("boolean"); }); it("should invoke correct backend command", async () => { const { invoke } = await import("@tauri-apps/api/core"); const invokeSpy = vi.mocked(invoke); await getCacheConfig(); const call = invokeSpy.mock.calls.find((c) => c[0] === "player_get_cache_config"); expect(call).toBeDefined(); }); it("should return valid config structure", async () => { const config = await getCacheConfig(); expect(config.queuePrecacheEnabled).toBe(true); expect(config.queuePrecacheCount).toBe(5); expect(config.albumAffinityEnabled).toBe(true); expect(config.albumAffinityThreshold).toBe(0.8); expect(config.storageLimit).toBe(1024 * 1024 * 1024); expect(config.wifiOnly).toBe(false); }); }); });