import { describe, it, expect, vi, afterEach } from "vitest"; vi.mock("@/lib/musicbrainz", () => ({ browseReleaseGroups: vi.fn() })); import { browseReleaseGroups } from "@/lib/musicbrainz"; import { prisma } from "@/lib/db"; import { POST } from "./route"; const mockBrowse = vi.mocked(browseReleaseGroups); afterEach(() => mockBrowse.mockReset()); function post(id: string, body: unknown) { return POST( new Request(`http://localhost/api/discover/${id}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), }), { params: Promise.resolve({ id }) }, ); } async function artist(mbid = "a1") { return prisma.discoverySuggestion.create({ data: { kind: "artist", artistMbid: mbid, artistName: "Cand", score: 1, seedCount: 1, sources: ["listenbrainz"], secondaryTypes: [], dedupeKey: `artist:${mbid}:` }, }); } async function album(rg = "rg1") { return prisma.discoverySuggestion.create({ data: { kind: "album", artistMbid: "a9", artistName: "Band", rgMbid: rg, album: "Rec", primaryType: "Album", score: 1, seedCount: 1, sources: ["listenbrainz"], secondaryTypes: [], firstReleaseDate: "2023", dedupeKey: `album:a9:${rg}` }, }); } describe("discover action API", () => { it("dismiss marks the suggestion dismissed", async () => { const s = await artist("d1"); const res = await post(s.id, { action: "dismiss" }); expect(res.status).toBe(200); expect((await prisma.discoverySuggestion.findUnique({ where: { id: s.id } }))!.status).toBe("dismissed"); }); it("follow creates a WatchedArtist + unmonitored discography and marks followed", async () => { mockBrowse.mockResolvedValue([ { rgMbid: "rg1", title: "Debut", primaryType: "Album", secondaryTypes: [], firstReleaseDate: "2019" }, ]); const s = await artist("f1"); const res = await post(s.id, { action: "follow" }); expect(res.status).toBe(200); expect(await prisma.watchedArtist.findUnique({ where: { mbid: "f1" } })).not.toBeNull(); const rel = await prisma.monitoredRelease.findUnique({ where: { rgMbid: "rg1" } }); expect(rel).toMatchObject({ monitored: false, artistName: "Cand" }); expect((await prisma.discoverySuggestion.findUnique({ where: { id: s.id } }))!.status).toBe("followed"); }); it("follow is idempotent when already following (no MB call, still marks followed)", async () => { await prisma.watchedArtist.create({ data: { mbid: "f2", name: "Cand" } }); const s = await artist("f2"); const res = await post(s.id, { action: "follow" }); expect(res.status).toBe(200); expect(mockBrowse).not.toHaveBeenCalled(); expect((await prisma.discoverySuggestion.findUnique({ where: { id: s.id } }))!.status).toBe("followed"); }); it("dismissing an artist also dismisses its pending album suggestions", async () => { const a = await prisma.discoverySuggestion.create({ data: { kind: "artist", artistMbid: "shared", artistName: "Cand", score: 1, seedCount: 1, sources: ["listenbrainz"], secondaryTypes: [], dedupeKey: "artist:shared:" }, }); const alb = await prisma.discoverySuggestion.create({ data: { kind: "album", artistMbid: "shared", artistName: "Cand", rgMbid: "rgS", album: "Rec", albumReason: "newest", score: 1, seedCount: 1, sources: ["listenbrainz"], secondaryTypes: [], dedupeKey: "album:shared:rgS" }, }); await post(a.id, { action: "dismiss" }); expect((await prisma.discoverySuggestion.findUnique({ where: { id: alb.id } }))!.status).toBe("dismissed"); }); it("want upserts a monitored release from the suggestion and marks wanted", async () => { const s = await album("rgW"); const res = await post(s.id, { action: "want" }); expect(res.status).toBe(200); const rel = await prisma.monitoredRelease.findUnique({ where: { rgMbid: "rgW" } }); expect(rel).toMatchObject({ album: "Rec", monitored: true, artistName: "Band" }); expect((await prisma.discoverySuggestion.findUnique({ where: { id: s.id } }))!.status).toBe("wanted"); }); it("rejects bad action, mismatched kind, and unknown id", async () => { const a = await artist("m1"); expect((await post(a.id, { action: "nope" })).status).toBe(400); expect((await post(a.id, { action: "want" })).status).toBe(400); // want on artist const al = await album("rgM"); expect((await post(al.id, { action: "follow" })).status).toBe(400); // follow on album expect((await post("missing", { action: "dismiss" })).status).toBe(404); }); });