Files
Lyra/web/src/app/api/library/duplicates/route.test.ts
T
Jonathan 6453cfd27b feat(library): surface possible duplicate albums
GET /api/library/duplicates groups library items that look like the same
release held more than once — sharing a MusicBrainz release-group, or the
same artist + edition-normalized title (stripEditionQualifiers), unioned so
transitive matches collapse into one group. The (artist,album) unique
constraint rules out exact dupes, so these near-dupes are what's worth
flagging.

/library shows a "Possible duplicates" section; each entry opens the album
modal to edit or delete. web 175 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:16:03 +02:00

36 lines
1.4 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { prisma } from "@/lib/db";
import { GET } from "./route";
async function li(artist: string, album: string, rgMbid: string | null, path = "/m") {
return prisma.libraryItem.create({
data: { artist, album, path, source: "scan", format: "FLAC", qualityClass: 2, rgMbid },
});
}
describe("library duplicates API", () => {
it("groups items sharing a release-group", async () => {
await li("Adele", "21", "rg-21");
await li("Adele", "21 (Deluxe)", "rg-21"); // same rg → duplicate
await li("Adele", "25", "rg-25"); // unrelated
const { duplicates } = await (await GET()).json();
expect(duplicates).toHaveLength(1);
expect(duplicates[0].map((d: { album: string }) => d.album).sort()).toEqual(["21", "21 (Deluxe)"]);
});
it("groups items with the same edition-normalized title even without a shared rgMbid", async () => {
await li("Nirvana", "Nevermind", null);
await li("Nirvana", "Nevermind (Deluxe Edition)", "rg-x");
const { duplicates } = await (await GET()).json();
expect(duplicates).toHaveLength(1);
expect(duplicates[0]).toHaveLength(2);
});
it("returns nothing when there are no duplicates", async () => {
await li("A", "One", "rg-1");
await li("B", "Two", "rg-2");
const { duplicates } = await (await GET()).json();
expect(duplicates).toEqual([]);
});
});