feat(discovery): GET /api/discover/search interactive seed search
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import { describe, it, expect, vi, afterEach } from "vitest";
|
||||
|
||||
vi.mock("@/lib/musicbrainz", () => ({ searchArtists: vi.fn() }));
|
||||
vi.mock("@/lib/listenbrainz", () => ({ similarArtists: vi.fn() }));
|
||||
import { searchArtists } from "@/lib/musicbrainz";
|
||||
import { similarArtists } from "@/lib/listenbrainz";
|
||||
import { GET } from "./route";
|
||||
|
||||
const mockSearch = vi.mocked(searchArtists);
|
||||
const mockSimilar = vi.mocked(similarArtists);
|
||||
afterEach(() => { mockSearch.mockReset(); mockSimilar.mockReset(); });
|
||||
|
||||
function req(artist?: string) {
|
||||
const u = new URL("http://localhost/api/discover/search");
|
||||
if (artist !== undefined) u.searchParams.set("artist", artist);
|
||||
return new Request(u);
|
||||
}
|
||||
|
||||
describe("discover search API", () => {
|
||||
it("400s when artist is missing/blank", async () => {
|
||||
expect((await GET(req())).status).toBe(400);
|
||||
expect((await GET(req(" "))).status).toBe(400);
|
||||
});
|
||||
|
||||
it("resolves the top MB hit then returns its similar artists", async () => {
|
||||
mockSearch.mockResolvedValue([{ mbid: "seed", name: "Boygenius", disambiguation: "" }]);
|
||||
mockSimilar.mockResolvedValue([{ mbid: "c1", name: "Cand", score: 9 }]);
|
||||
const body = await (await GET(req("boygenius"))).json();
|
||||
expect(body.seed).toMatchObject({ mbid: "seed", name: "Boygenius" });
|
||||
expect(body.similar).toEqual([{ mbid: "c1", name: "Cand", score: 9 }]);
|
||||
expect(mockSimilar).toHaveBeenCalledWith("seed");
|
||||
});
|
||||
|
||||
it("returns empty when MB has no hit, 502 on failures", async () => {
|
||||
mockSearch.mockResolvedValue([]);
|
||||
expect(await (await GET(req("nobody"))).json()).toEqual({ seed: null, similar: [] });
|
||||
mockSearch.mockRejectedValue(new Error("mb down"));
|
||||
expect((await GET(req("x"))).status).toBe(502);
|
||||
mockSearch.mockResolvedValue([{ mbid: "seed", name: "X", disambiguation: "" }]);
|
||||
mockSimilar.mockRejectedValue(new Error("lb down"));
|
||||
expect((await GET(req("x"))).status).toBe(502);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { searchArtists } from "@/lib/musicbrainz";
|
||||
import { similarArtists } from "@/lib/listenbrainz";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const artist = new URL(request.url).searchParams.get("artist");
|
||||
if (!artist || !artist.trim()) {
|
||||
return Response.json({ error: "artist is required" }, { status: 400 });
|
||||
}
|
||||
let hits;
|
||||
try {
|
||||
hits = await searchArtists(artist.trim());
|
||||
} catch {
|
||||
return Response.json({ error: "MusicBrainz unavailable" }, { status: 502 });
|
||||
}
|
||||
if (hits.length === 0) return Response.json({ seed: null, similar: [] });
|
||||
const seed = hits[0];
|
||||
let similar;
|
||||
try {
|
||||
similar = await similarArtists(seed.mbid);
|
||||
} catch {
|
||||
return Response.json({ error: "ListenBrainz unavailable" }, { status: 502 });
|
||||
}
|
||||
return Response.json({ seed, similar });
|
||||
}
|
||||
Reference in New Issue
Block a user