import { mkdtempSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { describe, expect, it, vi } from "vitest"; import { AppError } from "../../src/errors"; import { createMediaResolver, resolveMediaSource, } from "../../src/media/mediaResolver"; describe("resolveMediaSource", () => { it("accepts http URLs", async () => { await expect( resolveMediaSource("https://example.com/music.mp3"), ).resolves.toEqual({ source: "https://example.com/music.mp3", title: "music.mp3", kind: "url", }); }); it("accepts existing local files", async () => { const dir = mkdtempSync(path.join(tmpdir(), "media-resolver-")); const file = path.join(dir, "song.ogg"); writeFileSync(file, "audio"); await expect(resolveMediaSource(file)).resolves.toEqual({ source: file, title: "song.ogg", kind: "local", }); }); it("rejects empty sources", async () => { await expect(resolveMediaSource(" ")).rejects.toMatchObject({ code: "MISSING_MEDIA_SOURCE", statusCode: 400, } satisfies Partial); }); it("sanitizes URL titles", async () => { await expect( resolveMediaSource("https://example.com/%2e%2e%2fsecret.mp3"), ).resolves.toMatchObject({ title: "secret.mp3", kind: "url", }); }); it("resolves search queries to YouTube results", async () => { const resolver = createMediaResolver({ ytdlp: { getMetadata: vi.fn(), getDirectAudioUrl: vi.fn( async () => "https://audio.example.com/search", ), }, playDlResolver: { searchYouTube: vi.fn(async () => ({ title: "Search Result", url: "https://youtube.com/watch?v=search", })), resolveSpotifyTrack: vi.fn(), }, }); await expect(resolver("artist song")).resolves.toEqual({ source: "https://audio.example.com/search", title: "Search Result", kind: "search", }); }); it("rejects non-http URL sources", async () => { await expect( resolveMediaSource("file:///tmp/song.mp3"), ).rejects.toMatchObject({ code: "UNSUPPORTED_MEDIA_SOURCE", statusCode: 400, } satisfies Partial); }); it("rejects malformed http URLs as unsupported sources", async () => { await expect(resolveMediaSource("https://[invalid")).rejects.toMatchObject({ code: "UNSUPPORTED_MEDIA_SOURCE", statusCode: 400, } satisfies Partial); }); it("keeps direct URLs as generic URL sources", async () => { await expect( resolveMediaSource("https://cdn.example.com/song.mp3"), ).resolves.toMatchObject({ kind: "url", source: "https://cdn.example.com/song.mp3", }); }); it("resolves YouTube URLs with yt-dlp metadata", async () => { const resolver = createMediaResolver({ ytdlp: { getMetadata: vi.fn(async () => ({ title: "YouTube Song", webpageUrl: "https://youtube.com/watch?v=abc", })), getDirectAudioUrl: vi.fn(async () => "https://audio.example.com/abc"), }, playDlResolver: { searchYouTube: vi.fn(), resolveSpotifyTrack: vi.fn(), }, }); await expect(resolver("https://youtu.be/abc")).resolves.toEqual({ source: "https://audio.example.com/abc", title: "YouTube Song", kind: "youtube", }); }); it("resolves Spotify track URLs through YouTube search", async () => { const resolver = createMediaResolver({ ytdlp: { getMetadata: vi.fn(), getDirectAudioUrl: vi.fn( async () => "https://audio.example.com/spotify", ), }, playDlResolver: { searchYouTube: vi.fn(), resolveSpotifyTrack: vi.fn(async () => ({ title: "Spotify Match", url: "https://youtube.com/watch?v=spotify", })), }, }); await expect( resolver("https://open.spotify.com/track/123"), ).resolves.toEqual({ source: "https://audio.example.com/spotify", title: "Spotify Match", kind: "spotify", }); }); });