many changes
Traceability Validation / Check Requirement Traces (push) Failing after 1m18s
🏗️ Build and Test JellyTau / Build APK and Run Tests (push) Has been cancelled

This commit is contained in:
2026-02-14 00:09:47 +01:00
parent 6d1c618a3a
commit e3797f32ca
74 changed files with 6718 additions and 771 deletions
+428
View File
@@ -0,0 +1,428 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { invoke } from "@tauri-apps/api/core";
import { RepositoryClient } from "./repository-client";
vi.mock("@tauri-apps/api/core");
describe("RepositoryClient", () => {
let client: RepositoryClient;
beforeEach(() => {
client = new RepositoryClient();
vi.clearAllMocks();
});
afterEach(() => {
vi.resetAllMocks();
});
describe("Initialization", () => {
it("should initialize with no handle", () => {
expect(() => client.getHandle()).toThrow("Repository not initialized");
});
it("should create repository with invoke command", async () => {
const mockHandle = "test-handle-123";
(invoke as any).mockResolvedValueOnce(mockHandle);
const handle = await client.create("https://server.com", "user1", "token123", "server1");
expect(handle).toBe(mockHandle);
expect(invoke).toHaveBeenCalledWith("repository_create", {
serverUrl: "https://server.com",
userId: "user1",
accessToken: "token123",
serverId: "server1",
});
});
it("should store handle after creation", async () => {
const mockHandle = "test-handle-456";
(invoke as any).mockResolvedValueOnce(mockHandle);
await client.create("https://server.com", "user1", "token123", "server1");
expect(client.getHandle()).toBe(mockHandle);
});
it("should destroy repository and clear handle", async () => {
const mockHandle = "test-handle-789";
(invoke as any).mockResolvedValueOnce(mockHandle);
await client.create("https://server.com", "user1", "token123", "server1");
(invoke as any).mockResolvedValueOnce(undefined);
await client.destroy();
expect(() => client.getHandle()).toThrow("Repository not initialized");
expect(invoke).toHaveBeenCalledWith("repository_destroy", { handle: mockHandle });
});
});
describe("Image URL Methods", () => {
beforeEach(async () => {
(invoke as any).mockResolvedValueOnce("test-handle-123");
await client.create("https://server.com", "user1", "token123", "server1");
});
it("should get image URL from backend", async () => {
const mockUrl = "https://server.com/Items/item123/Images/Primary?maxWidth=300&api_key=token";
(invoke as any).mockResolvedValueOnce(mockUrl);
const imageUrl = await client.getImageUrl("item123", "Primary", { maxWidth: 300 });
expect(imageUrl).toBe(mockUrl);
expect(invoke).toHaveBeenCalledWith("repository_get_image_url", {
handle: "test-handle-123",
itemId: "item123",
imageType: "Primary",
options: { maxWidth: 300 },
});
});
it("should use default image type if not provided", async () => {
const mockUrl = "https://server.com/Items/item123/Images/Primary?api_key=token";
(invoke as any).mockResolvedValueOnce(mockUrl);
await client.getImageUrl("item123");
expect(invoke).toHaveBeenCalledWith("repository_get_image_url", {
handle: "test-handle-123",
itemId: "item123",
imageType: "Primary",
options: null,
});
});
it("should pass multiple image options to backend", async () => {
const mockUrl = "https://server.com/Items/item123/Images/Backdrop?maxWidth=1920&maxHeight=1080&quality=90&api_key=token";
(invoke as any).mockResolvedValueOnce(mockUrl);
const options = {
maxWidth: 1920,
maxHeight: 1080,
quality: 90,
tag: "abc123",
};
await client.getImageUrl("item123", "Backdrop", options);
expect(invoke).toHaveBeenCalledWith("repository_get_image_url", {
handle: "test-handle-123",
itemId: "item123",
imageType: "Backdrop",
options,
});
});
it("should handle different image types", async () => {
const mockUrl = "https://server.com/Items/item123/Images/Logo?api_key=token";
(invoke as any).mockResolvedValueOnce(mockUrl);
await client.getImageUrl("item123", "Logo");
expect(invoke).toHaveBeenCalledWith("repository_get_image_url", {
handle: expect.any(String),
itemId: "item123",
imageType: "Logo",
options: null,
});
});
it("should throw error if not initialized before getImageUrl", async () => {
const newClient = new RepositoryClient();
await expect(newClient.getImageUrl("item123")).rejects.toThrow(
"Repository not initialized"
);
});
});
describe("Subtitle URL Methods", () => {
beforeEach(async () => {
(invoke as any).mockResolvedValueOnce("test-handle-123");
await client.create("https://server.com", "user1", "token123", "server1");
});
it("should get subtitle URL from backend", async () => {
const mockUrl = "https://server.com/Videos/item123/Subtitles/1/subtitles.vtt?api_key=token";
(invoke as any).mockResolvedValueOnce(mockUrl);
const subtitleUrl = await client.getSubtitleUrl("item123", "source456", 0);
expect(subtitleUrl).toBe(mockUrl);
expect(invoke).toHaveBeenCalledWith("repository_get_subtitle_url", {
handle: "test-handle-123",
itemId: "item123",
mediaSourceId: "source456",
streamIndex: 0,
format: "vtt",
});
});
it("should use default format if not provided", async () => {
const mockUrl = "https://server.com/Videos/item123/Subtitles/0/subtitles.vtt?api_key=token";
(invoke as any).mockResolvedValueOnce(mockUrl);
await client.getSubtitleUrl("item123", "source456", 0);
expect(invoke).toHaveBeenCalledWith("repository_get_subtitle_url", {
handle: expect.any(String),
itemId: "item123",
mediaSourceId: "source456",
streamIndex: 0,
format: "vtt",
});
});
it("should support custom subtitle formats", async () => {
const mockUrl = "https://server.com/Videos/item123/Subtitles/0/subtitles.srt?api_key=token";
(invoke as any).mockResolvedValueOnce(mockUrl);
await client.getSubtitleUrl("item123", "source456", 1, "srt");
expect(invoke).toHaveBeenCalledWith("repository_get_subtitle_url", {
handle: expect.any(String),
itemId: "item123",
mediaSourceId: "source456",
streamIndex: 1,
format: "srt",
});
});
});
describe("Video Download URL Methods", () => {
beforeEach(async () => {
(invoke as any).mockResolvedValueOnce("test-handle-123");
await client.create("https://server.com", "user1", "token123", "server1");
});
it("should get video download URL from backend", async () => {
const mockUrl = "https://server.com/Videos/item123/stream.mp4?maxWidth=1920&api_key=token";
(invoke as any).mockResolvedValueOnce(mockUrl);
const downloadUrl = await client.getVideoDownloadUrl("item123", "1080p");
expect(downloadUrl).toBe(mockUrl);
expect(invoke).toHaveBeenCalledWith("repository_get_video_download_url", {
handle: "test-handle-123",
itemId: "item123",
quality: "1080p",
mediaSourceId: null,
});
});
it("should use original quality by default", async () => {
const mockUrl = "https://server.com/Videos/item123/stream.mp4?api_key=token";
(invoke as any).mockResolvedValueOnce(mockUrl);
await client.getVideoDownloadUrl("item123");
expect(invoke).toHaveBeenCalledWith("repository_get_video_download_url", {
handle: expect.any(String),
itemId: "item123",
quality: "original",
mediaSourceId: null,
});
});
it("should support quality presets", async () => {
const qualities = ["original", "1080p", "720p", "480p"];
for (const quality of qualities) {
vi.clearAllMocks();
(invoke as any).mockResolvedValueOnce("test-handle-123");
await client.create("https://server.com", "user1", "token123", "server1");
(invoke as any).mockResolvedValueOnce(`https://server.com/stream.mp4?quality=${quality}`);
await client.getVideoDownloadUrl("item123", quality as any);
expect(invoke).toHaveBeenCalledWith(
"repository_get_video_download_url",
expect.objectContaining({
quality,
})
);
}
});
it("should support optional media source ID", async () => {
const mockUrl = "https://server.com/Videos/item123/stream.mp4?api_key=token";
(invoke as any).mockResolvedValueOnce(mockUrl);
await client.getVideoDownloadUrl("item123", "720p", "source789");
expect(invoke).toHaveBeenCalledWith("repository_get_video_download_url", {
handle: expect.any(String),
itemId: "item123",
quality: "720p",
mediaSourceId: "source789",
});
});
});
describe("Library Methods", () => {
beforeEach(async () => {
(invoke as any).mockResolvedValueOnce("test-handle-123");
await client.create("https://server.com", "user1", "token123", "server1");
});
it("should get libraries from backend", async () => {
const mockLibraries = [
{ id: "lib1", name: "Music", collectionType: "music" },
{ id: "lib2", name: "Movies", collectionType: "movies" },
];
(invoke as any).mockResolvedValueOnce(mockLibraries);
const libraries = await client.getLibraries();
expect(libraries).toEqual(mockLibraries);
expect(invoke).toHaveBeenCalledWith("repository_get_libraries", {
handle: "test-handle-123",
});
});
it("should get items with sorting parameters", async () => {
const mockResult = {
items: [
{ id: "item1", name: "Track 1", type: "Audio" },
{ id: "item2", name: "Track 2", type: "Audio" },
],
totalRecordCount: 2,
};
(invoke as any).mockResolvedValueOnce(mockResult);
const result = await client.getItems("library123", {
sortBy: "SortName",
sortOrder: "Ascending",
limit: 50,
});
expect(result).toEqual(mockResult);
expect(invoke).toHaveBeenCalledWith("repository_get_items", {
handle: "test-handle-123",
parentId: "library123",
options: {
sortBy: "SortName",
sortOrder: "Ascending",
limit: 50,
},
});
});
it("should search with backend search command", async () => {
const mockResult = {
items: [
{ id: "item1", name: "Search Result 1", type: "Audio" },
],
totalRecordCount: 1,
};
(invoke as any).mockResolvedValueOnce(mockResult);
const result = await client.search("query", {
includeItemTypes: ["Audio"],
limit: 100,
});
expect(result).toEqual(mockResult);
expect(invoke).toHaveBeenCalledWith("repository_search", {
handle: "test-handle-123",
query: "query",
options: {
includeItemTypes: ["Audio"],
limit: 100,
},
});
});
});
describe("Playback Methods", () => {
beforeEach(async () => {
(invoke as any).mockResolvedValueOnce("test-handle-123");
await client.create("https://server.com", "user1", "token123", "server1");
});
it("should get audio stream URL", async () => {
const mockUrl = "https://server.com/Audio/item123/stream.mp3?api_key=token";
(invoke as any).mockResolvedValueOnce(mockUrl);
const url = await client.getAudioStreamUrl("item123");
expect(url).toBe(mockUrl);
expect(invoke).toHaveBeenCalledWith("repository_get_audio_stream_url", {
handle: "test-handle-123",
itemId: "item123",
});
});
it("should get video stream URL", async () => {
const mockUrl = "https://server.com/Videos/item123/stream.mp4?api_key=token";
(invoke as any).mockResolvedValueOnce(mockUrl);
const url = await client.getVideoStreamUrl("item123");
expect(url).toBe(mockUrl);
expect(invoke).toHaveBeenCalledWith("repository_get_video_stream_url", {
handle: "test-handle-123",
itemId: "item123",
mediaSourceId: null,
startTimeSeconds: null,
audioStreamIndex: null,
});
});
it("should get video stream URL with options", async () => {
const mockUrl = "https://server.com/Videos/item123/stream.mp4?start=300&api_key=token";
(invoke as any).mockResolvedValueOnce(mockUrl);
const url = await client.getVideoStreamUrl("item123", "source456", 300, 0);
expect(url).toBe(mockUrl);
expect(invoke).toHaveBeenCalledWith("repository_get_video_stream_url", {
handle: "test-handle-123",
itemId: "item123",
mediaSourceId: "source456",
startTimeSeconds: 300,
audioStreamIndex: 0,
});
});
it("should report playback progress", async () => {
(invoke as any).mockResolvedValueOnce(undefined);
await client.reportPlaybackProgress("item123", 5000000);
expect(invoke).toHaveBeenCalledWith("repository_report_playback_progress", {
handle: "test-handle-123",
itemId: "item123",
positionTicks: 5000000,
});
});
});
describe("Error Handling", () => {
it("should throw error if invoke fails", async () => {
(invoke as any).mockRejectedValueOnce(new Error("Network error"));
await expect(client.create("https://server.com", "user1", "token", "server1")).rejects.toThrow(
"Network error"
);
});
it("should handle missing optional parameters", async () => {
(invoke as any).mockResolvedValueOnce("test-handle-123");
await client.create("https://server.com", "user1", "token123", "server1");
(invoke as any).mockResolvedValueOnce("");
await client.getImageUrl("item123");
expect(invoke).toHaveBeenCalledWith(
"repository_get_image_url",
expect.objectContaining({
options: null,
})
);
});
});
});