A music player that connects to your cloud/distributed storage. diffuse.sh
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270import { describe, it, beforeAll, afterAll } from "@std/testing/bdd";import { expect } from "@std/expect";
import { mockServer } from "@tests/common/server.ts";import type { Track } from "~/definitions/types.d.ts";import * as Worker from "~/components/input/dropbox/worker.js";import { buildURI, parseURI } from "~/components/input/dropbox/common.js";
// Mock Dropbox API server.// The dropbox worker hardcodes `https://api.dropboxapi.com/...` URLs, so we// monkey-patch `globalThis.fetch` before each test to redirect those calls// to our in-process mock server.//// With the refresh-token flow, every API call first exchanges the refresh// token for a short-lived access token via POST /oauth2/token. The mock// server returns `access-token` for `valid-refresh-token` and 401 for// everything else, then validates `Bearer access-token` on the API calls.
const FILES = [ { ".tag": "file", name: "song1.mp3", path_lower: "/music/song1.mp3" }, { ".tag": "file", name: "song2.flac", path_lower: "/music/song2.flac" }, { ".tag": "file", name: "readme.txt", path_lower: "/readme.txt" }, { ".tag": "folder", name: "music", path_lower: "/music" },];
let server: Deno.HttpServer;let port: number;let originalFetch: typeof globalThis.fetch;
beforeAll(async () => { const started = await mockServer((req, url) => { const p = url.pathname;
// POST /oauth2/token — exchange refresh token for access token if (p === "/oauth2/token" && req.method === "POST") { return req.text().then((text) => { const params = new URLSearchParams(text); const grantType = params.get("grant_type"); const refreshToken = params.get("refresh_token") ?? params.get("code");
if (grantType === "refresh_token" && refreshToken === "valid-refresh-token") { return new Response(JSON.stringify({ access_token: "access-token", expires_in: 14400, token_type: "bearer", }), { status: 200, headers: { "content-type": "application/json" } }); }
// Code exchange (used by exchangeCode, not directly by the worker) if (grantType === "authorization_code" && refreshToken === "valid-code") { return new Response(JSON.stringify({ access_token: "access-token", refresh_token: "valid-refresh-token", expires_in: 14400, token_type: "bearer", }), { status: 200, headers: { "content-type": "application/json" } }); }
return new Response(JSON.stringify({ error: "invalid_grant" }), { status: 400, headers: { "content-type": "application/json" }, }); }); }
// GET /2/users/get_current_account — validates the access token if (p === "/2/users/get_current_account") { const auth = req.headers.get("authorization"); if (auth === "Bearer access-token") { return new Response(JSON.stringify({ account_id: "dbid:123" }), { status: 200, headers: { "content-type": "application/json" }, }); } return new Response("", { status: 401 }); }
// POST /2/files/list_folder — list files in a directory if (p === "/2/files/list_folder") { const auth = req.headers.get("authorization"); if (auth !== "Bearer access-token") { return new Response("", { status: 401 }); } return new Response( JSON.stringify({ entries: FILES, has_more: false, cursor: null, }), { status: 200, headers: { "content-type": "application/json" } }, ); }
// POST /2/files/list_folder/continue — paginate if (p === "/2/files/list_folder/continue") { return new Response( JSON.stringify({ entries: [], has_more: false, cursor: null }), { status: 200, headers: { "content-type": "application/json" } }, ); }
// POST /2/files/get_temporary_link — get a temporary download link if (p === "/2/files/get_temporary_link") { const auth = req.headers.get("authorization"); if (auth !== "Bearer access-token") { return new Response("", { status: 401 }); } return new Response( JSON.stringify({ link: `http://127.0.0.1:${port}/dl/temp-link`, metadata: { name: "song1.mp3" }, }), { status: 200, headers: { "content-type": "application/json" } }, ); }
// The temporary download link itself if (p === "/dl/temp-link") { return new Response(new Uint8Array(64), { status: 200, headers: { "content-type": "audio/mpeg" }, }); }
return new Response("", { status: 404 }); }); server = started.server; port = started.port;
// Monkey-patch fetch to redirect Dropbox API calls to our mock. originalFetch = globalThis.fetch; globalThis.fetch = ((input: string | URL | Request, init?: RequestInit) => { const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; const redirected = url .replace("https://api.dropboxapi.com", `http://127.0.0.1:${port}`) .replace("https://content.dropboxapi.com", `http://127.0.0.1:${port}`); return originalFetch(redirected, init); }) as typeof globalThis.fetch;});
afterAll(async () => { globalThis.fetch = originalFetch; await server.shutdown();});
describe("components/input/dropbox (integration)", () => { it("consult returns true for a valid refresh token", async () => { const uri = buildURI({ refreshToken: "valid-refresh-token", directoryPath: "/" }, "/"); const result = await Worker.consult(uri); expect(result.supported).toBe(true); if (result.supported) { expect(result.consult).toBe("yes"); } });
it("consult returns false for an invalid refresh token", async () => { const uri = buildURI({ refreshToken: "invalid-refresh-token", directoryPath: "/" }, "/"); const result = await Worker.consult(uri); expect(result.supported).toBe(true); if (result.supported) { expect(result.consult).toBe("no"); } });
it("list returns audio files from Dropbox", async () => { const account = { refreshToken: "valid-refresh-token", directoryPath: "/" }; const uri = buildURI(account, "/"); const tracks = await Worker.list([{ $type: "sh.diffuse.output.track", id: "p1", kind: "placeholder", uri, }]);
// Should find song1.mp3 and song2.flac; readme.txt is not audio expect(tracks.length).toBe(2); const paths = tracks.map((t) => { const parsed = parseURI(t.uri); return parsed?.path; }); expect(paths).toContain("/music/song1.mp3"); expect(paths).toContain("/music/song2.flac"); });
it("list returns placeholder when API returns error", async () => { const account = { refreshToken: "bad-refresh-token", directoryPath: "/" }; const uri = buildURI(account, "/"); const tracks = await Worker.list([{ $type: "sh.diffuse.output.track", id: "p1", kind: "placeholder", uri, }]);
// listFiles returns null on error, so worker returns a placeholder expect(tracks.length).toBe(1); expect(tracks[0].kind).toBe("placeholder"); });
it("resolve returns a temporary link URL", async () => { const uri = buildURI( { refreshToken: "valid-refresh-token", directoryPath: "/" }, "/music/song1.mp3", ); const result = await Worker.resolve({ uri }); expect(result).not.toBe(undefined); if (result && "url" in result) { expect(result.url).toContain("temp-link"); // Dropbox temporary links expire after 4 hours const fourHours = 4 * 60 * 60; const now = Math.round(Date.now() / 1000); expect(result.expiresAt).toBeGreaterThan(now); expect(result.expiresAt).toBeLessThanOrEqual(now + fourHours + 10); } });
it("resolve returns undefined for root path", async () => { const uri = buildURI( { refreshToken: "valid-refresh-token", directoryPath: "/" }, "/", ); const result = await Worker.resolve({ uri }); expect(result).toBe(undefined); });
it("groupConsult reports available for a valid token", async () => { const uri = buildURI( { refreshToken: "valid-refresh-token", directoryPath: "/" }, "/music/song1.mp3", ); const result = await Worker.groupConsult([uri]); const keys = Object.keys(result); expect(keys.length).toBe(1); expect(result[keys[0]].available).toBe("yes"); });
it("groupConsult reports unavailable for an invalid token", async () => { const uri = buildURI( { refreshToken: "invalid-refresh-token", directoryPath: "/" }, "/music/song1.mp3", ); const result = await Worker.groupConsult([uri]); const keys = Object.keys(result); expect(keys.length).toBe(1); expect(result[keys[0]].available).toBe("no"); });
it("detach with scheme removes all dropbox tracks", async () => { const tracks: Track[] = [ { $type: "sh.diffuse.output.track", id: "1", uri: buildURI({ refreshToken: "t1", directoryPath: "/" }, "/a.mp3") }, { $type: "sh.diffuse.output.track", id: "2", uri: buildURI({ refreshToken: "t1", directoryPath: "/" }, "/b.mp3") }, ]; const remaining = await Worker.detach({ fileUriOrScheme: "dropbox", tracks }); expect(remaining.length).toBe(0); });
it("detach with a specific account URI removes only that account's tracks", async () => { const tracks: Track[] = [ { $type: "sh.diffuse.output.track", id: "1", uri: buildURI({ refreshToken: "token-a", directoryPath: "/" }, "/a.mp3") }, { $type: "sh.diffuse.output.track", id: "2", uri: buildURI({ refreshToken: "token-b", directoryPath: "/" }, "/b.mp3") }, ]; const remaining = await Worker.detach({ fileUriOrScheme: buildURI({ refreshToken: "token-a", directoryPath: "/" }, "/a.mp3"), tracks, }); expect(remaining.length).toBe(1); expect(remaining[0].id).toBe("2"); });});