feat: expand API coverage with new tools, params, and 90% test threshold

This commit is contained in:
Deivid Soto 2026-02-12 15:45:08 +01:00
parent 8bb8e5507e
commit fa913d1561
21 changed files with 1573 additions and 88 deletions

View file

@ -82,7 +82,10 @@ describe("TorrentClawClient", () => {
await expect(client.search({ query: "test" })).rejects.toThrow(ApiError);
});
it("throws ApiError with rate limit message on 429", async () => {
it("throws ApiError with rate limit message on 429 after retries", async () => {
// With retry logic (MAX_RETRIES=2), need 3 consecutive 429 responses
mockFetchError("Too many requests", 429);
mockFetchError("Too many requests", 429);
mockFetchError("Too many requests", 429);
const client = new TorrentClawClient();
@ -119,6 +122,28 @@ describe("TorrentClawClient", () => {
expect(calledUrl).toContain("page=2");
});
it("includes locale param for popular", async () => {
mockFetch({ items: [], total: 0, page: 1, pageSize: 10 });
const client = new TorrentClawClient();
await client.getPopular(10, 1, "es");
const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
const calledUrl = fetchMock.mock.calls[0][0] as string;
expect(calledUrl).toContain("locale=es");
});
it("includes locale param for recent", async () => {
mockFetch({ items: [], total: 0, page: 1, pageSize: 10 });
const client = new TorrentClawClient();
await client.getRecent(10, 1, "fr");
const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
const calledUrl = fetchMock.mock.calls[0][0] as string;
expect(calledUrl).toContain("locale=fr");
});
it("calls correct endpoint for watch providers", async () => {
mockFetch({
contentId: 42,
@ -216,4 +241,101 @@ describe("TorrentClawClient", () => {
expect((e as ApiError).body).toBe("");
}
});
it("calls correct endpoint for autocomplete", async () => {
mockFetch({ suggestions: [{ id: 1, title: "Test", year: 2024, contentType: "movie", posterUrl: null }] });
const client = new TorrentClawClient();
const result = await client.autocomplete("test");
const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
const calledUrl = fetchMock.mock.calls[0][0] as string;
expect(calledUrl).toContain("/api/v1/autocomplete");
expect(calledUrl).toContain("q=test");
expect(result.suggestions).toHaveLength(1);
});
it("calls correct endpoint for track (POST)", async () => {
mockFetch({ ok: true });
const client = new TorrentClawClient();
const result = await client.track("abc123def456abc123def456abc123def456abc1", "magnet");
const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
const calledUrl = fetchMock.mock.calls[0][0] as string;
const options = fetchMock.mock.calls[0][1] as RequestInit;
expect(calledUrl).toContain("/api/v1/track");
expect(options.method).toBe("POST");
expect(JSON.parse(options.body as string)).toEqual({
infoHash: "abc123def456abc123def456abc123def456abc1",
action: "magnet",
});
expect(result.ok).toBe(true);
});
it("calls correct endpoint for submitScanRequest (POST)", async () => {
mockFetch({ status: "pending" });
const client = new TorrentClawClient();
const result = await client.submitScanRequest("abc123def456abc123def456abc123def456abc1", "test@example.com");
const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
const calledUrl = fetchMock.mock.calls[0][0] as string;
const options = fetchMock.mock.calls[0][1] as RequestInit;
expect(calledUrl).toContain("/api/v1/scan-request");
expect(options.method).toBe("POST");
expect(JSON.parse(options.body as string)).toEqual({
infoHash: "abc123def456abc123def456abc123def456abc1",
email: "test@example.com",
website: "",
});
expect(result.status).toBe("pending");
});
it("calls correct endpoint for getScanStatus", async () => {
mockFetch({ status: "completed", source: "scan_request" });
const client = new TorrentClawClient();
const result = await client.getScanStatus("abc123def456abc123def456abc123def456abc1");
const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
const calledUrl = fetchMock.mock.calls[0][0] as string;
expect(calledUrl).toContain("/api/v1/scan-request/abc123def456abc123def456abc123def456abc1");
expect(result.status).toBe("completed");
});
it("retries on 429 and succeeds", async () => {
mockFetchError("Too many requests", 429);
mockFetch({ total: 1, page: 1, pageSize: 10, results: [] });
const client = new TorrentClawClient();
const result = await client.search({ query: "test" });
expect(result.total).toBe(1);
const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
expect(fetchMock).toHaveBeenCalledTimes(2);
});
it("includes Authorization header when apiKey is set", async () => {
mockFetch({ total: 0, page: 1, pageSize: 10, results: [] });
const client = new TorrentClawClient();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(client as any).apiKey = "test-api-key-123";
await client.search({ query: "test" });
const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
const options = fetchMock.mock.calls[0][1] as RequestInit;
const headers = options.headers as Record<string, string>;
expect(headers["Authorization"]).toBe("Bearer test-api-key-123");
});
it("throws ApiError on POST 400 response", async () => {
mockFetchError("Invalid body", 400);
const client = new TorrentClawClient();
await expect(
client.track("abc123def456abc123def456abc123def456abc1", "magnet"),
).rejects.toThrow(ApiError);
});
});