diff options
Diffstat (limited to 'packages/tool-youtube-transcript/src/client.test.ts')
| -rw-r--r-- | packages/tool-youtube-transcript/src/client.test.ts | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/packages/tool-youtube-transcript/src/client.test.ts b/packages/tool-youtube-transcript/src/client.test.ts new file mode 100644 index 0000000..a33f44a --- /dev/null +++ b/packages/tool-youtube-transcript/src/client.test.ts @@ -0,0 +1,134 @@ +import { describe, expect, it } from "vitest"; +import { createTranscriptClient, type FetchLike } from "./client.js"; + +function jsonResponse(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +interface CapturedCall { + url: string; + method?: string | undefined; +} + +/** Builds a fake fetch that returns scripted responses in order, capturing each call. */ +function makeFetch(responses: Response[]): { fetchFn: FetchLike; calls: CapturedCall[] } { + const calls: CapturedCall[] = []; + let i = 0; + const fetchFn: FetchLike = (async (input: string | URL | Request, init?: RequestInit) => { + const url = + typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; + calls.push({ url, method: init?.method }); + return responses[i++] ?? jsonResponse({}); + }) as unknown as FetchLike; + return { fetchFn, calls }; +} + +const BASE = "http://test-transcriber.local"; +const signal = (): AbortSignal => new AbortController().signal; + +describe("createTranscriptClient.getTranscript", () => { + it("sends GET /api/transcript?url=...", async () => { + const { fetchFn, calls } = makeFetch([ + jsonResponse({ + status: "completed", + video_id: "v1", + full_text: "", + segments: [], + }), + ]); + const client = createTranscriptClient({ baseUrl: BASE, fetchFn }); + await client.getTranscript("https://youtu.be/v1", signal()); + + const call = calls[0]; + if (!call) throw new Error("no call captured"); + expect(call.url).toBe( + `${BASE}/api/transcript?url=${encodeURIComponent("https://youtu.be/v1")}`, + ); + expect(call.method).toBe("GET"); + }); + + it("returns completed response", async () => { + const body = { + status: "completed" as const, + video_id: "v1", + full_text: "hi", + segments: [{ text: "hi", start: 0, duration: 1 }], + }; + const { fetchFn } = makeFetch([jsonResponse(body)]); + const client = createTranscriptClient({ baseUrl: BASE, fetchFn }); + const result = await client.getTranscript("https://youtu.be/v1", signal()); + expect(result).toEqual(body); + }); + + it("returns queued response", async () => { + const body = { + status: "queued" as const, + video_id: "v1", + position: 2, + estimated_seconds: 60, + }; + const { fetchFn } = makeFetch([jsonResponse(body)]); + const client = createTranscriptClient({ baseUrl: BASE, fetchFn }); + const result = await client.getTranscript("https://youtu.be/v1", signal()); + expect(result).toEqual(body); + }); + + it("returns failed response", async () => { + const body = { + status: "failed" as const, + video_id: "v1", + error: "boom", + error_type: "DownloadError", + }; + const { fetchFn } = makeFetch([jsonResponse(body)]); + const client = createTranscriptClient({ baseUrl: BASE, fetchFn }); + const result = await client.getTranscript("https://youtu.be/v1", signal()); + expect(result).toEqual(body); + }); + + it("throws on HTTP error", async () => { + const { fetchFn } = makeFetch([ + new Response("not found", { status: 404, statusText: "Not Found" }), + ]); + const client = createTranscriptClient({ baseUrl: BASE, fetchFn }); + await expect(client.getTranscript("https://youtu.be/v1", signal())).rejects.toThrow("HTTP 404"); + }); + + it("throws on timeout", async () => { + const fetchFn: FetchLike = ((_input: string | URL | Request, init?: RequestInit) => + new Promise<Response>((_resolve, reject) => { + const sig = init?.signal; + if (!sig) return; + sig.addEventListener("abort", () => { + const err = new Error("aborted"); + err.name = "AbortError"; + reject(err); + }); + })) as unknown as FetchLike; + const client = createTranscriptClient({ baseUrl: BASE, fetchFn, timeoutMs: 10 }); + await expect(client.getTranscript("https://youtu.be/v1", signal())).rejects.toThrow( + "timed out", + ); + }); + + it("respects abort signal", async () => { + const controller = new AbortController(); + const fetchFn: FetchLike = ((_input: string | URL | Request, init?: RequestInit) => + new Promise<Response>((_resolve, reject) => { + const sig = init?.signal; + if (!sig) return; + sig.addEventListener("abort", () => { + const err = new Error("aborted"); + err.name = "AbortError"; + reject(err); + }); + })) as unknown as FetchLike; + const client = createTranscriptClient({ baseUrl: BASE, fetchFn, timeoutMs: 30_000 }); + const promise = client.getTranscript("https://youtu.be/v1", controller.signal); + controller.abort(); + await expect(promise).rejects.toThrow("aborted"); + }); +}); |
