import { afterAll, afterEach, beforeAll, describe, expect, it, vi, } from "vitest"; let prisma: typeof import("@linkwarden/prisma").prisma; let importFromHTMLFile: typeof import("./importFromHTMLFile").default; let removeFolder: typeof import("@linkwarden/filesystem").removeFolder; const createdUserIds: number[] = []; const ensureTestEnv = async () => { await import("dotenv/config"); if (!process.env.DATABASE_URL) { throw new Error( "DATABASE_URL must be set to run integration tests for importFromHTMLFile." ); } vi.stubEnv("NODE_ENV", "test"); process.env.STRIPE_SECRET_KEY = ""; process.env.NEXT_PUBLIC_STRIPE = "false"; process.env.NEXT_PUBLIC_REQUIRE_CC = "false"; process.env.MAX_LINKS_PER_USER = process.env.MAX_LINKS_PER_USER || "5"; process.env.STORAGE_FOLDER = process.env.STORAGE_FOLDER || "data-test"; delete process.env.SPACES_ENDPOINT; delete process.env.SPACES_REGION; delete process.env.SPACES_KEY; delete process.env.SPACES_SECRET; }; const createTestUser = async () => { const suffix = `${Date.now()}_${Math.random().toString(16).slice(2)}`; const user = await prisma.user.create({ data: { username: `import_test_${suffix}`, email: `import_test_${suffix}@example.com`, }, }); createdUserIds.push(user.id); return user; }; const cleanupUser = async (userId: number) => { const collections = await prisma.collection.findMany({ where: { ownerId: userId }, select: { id: true }, }); try { await prisma.user.delete({ where: { id: userId } }); } catch (error) { return; } for (const { id } of collections) { await removeFolder({ filePath: `archives/${id}` }); } }; beforeAll(async () => { await ensureTestEnv(); const prismaModule = await import("@linkwarden/prisma"); prisma = prismaModule.prisma; const filesystemModule = await import("@linkwarden/filesystem"); removeFolder = filesystemModule.removeFolder; importFromHTMLFile = (await import("./importFromHTMLFile")).default; await prisma.$connect(); }); afterEach(async () => { const users = createdUserIds.splice(0, createdUserIds.length); for (const userId of users) { await cleanupUser(userId); } }); afterAll(async () => { await prisma.$disconnect(); }); describe.sequential("importFromHTMLFile integration", () => { it("returns an error when the link limit is exceeded", async () => { const user = await createTestUser(); const collection = await prisma.collection.create({ data: { name: "Existing", owner: { connect: { id: user.id } }, createdBy: { connect: { id: user.id } }, }, }); for (let i = 0; i < 5; i += 1) { await prisma.link.create({ data: { name: `Existing ${i}`, url: `https://example.com/existing-${i}`, collectionId: collection.id, createdById: user.id, }, }); } const html = `
`; const beforeCount = await prisma.link.count({ where: { createdById: user.id }, }); const result = await importFromHTMLFile(user.id, html); const afterCount = await prisma.link.count({ where: { createdById: user.id }, }); expect(result).toEqual({ response: "Your subscription has reached the maximum number of links allowed.", status: 400, }); expect(afterCount).toBe(beforeCount); }); it("imports root links into the Imports collection with tags, date, and description", async () => { const user = await createTestUser(); const html = `
Bookmarks
`; const result = await importFromHTMLFile(user.id, html); expect(result).toEqual({ response: "Success.", status: 200 }); const importsCollection = await prisma.collection.findFirst({ where: { ownerId: user.id, name: "Imports" }, }); expect(importsCollection).toBeTruthy(); const link = await prisma.link.findFirst({ where: { collectionId: importsCollection?.id, url: "https://example.com/path?q=fish&chips", }, include: { tags: true }, }); expect(link).toBeTruthy(); expect(link?.description).toBe("Example description"); expect(link?.importDate?.toISOString()).toBe( new Date(1700000000 * 1000).toISOString() ); expect(link?.tags.map((tag) => tag.name).sort()).toEqual(["news", "tech"]); }); it("creates nested collections and assigns links to the correct parent", async () => { const user = await createTestUser(); const html = `
`; await importFromHTMLFile(user.id, html); const recipesCollection = await prisma.collection.findFirst({ where: { ownerId: user.id, name: "Recipes" }, }); expect(recipesCollection).toBeTruthy(); const dessertsCollection = await prisma.collection.findFirst({ where: { ownerId: user.id, name: "Desserts", parentId: recipesCollection?.id, }, }); expect(dessertsCollection).toBeTruthy(); const soupLink = await prisma.link.findFirst({ where: { url: "https://example.com/soup" }, }); const cakeLink = await prisma.link.findFirst({ where: { url: "https://example.com/cake" }, }); expect(soupLink?.collectionId).toBe(recipesCollection?.id); expect(cakeLink?.collectionId).toBe(dessertsCollection?.id); }); it("reuses an existing Imports collection instead of creating a duplicate", async () => { const user = await createTestUser(); const importsCollection = await prisma.collection.create({ data: { name: "Imports", owner: { connect: { id: user.id } }, createdBy: { connect: { id: user.id } }, }, }); const html = `
`; await importFromHTMLFile(user.id, html); const importsCollections = await prisma.collection.findMany({ where: { ownerId: user.id, name: "Imports" }, }); expect(importsCollections).toHaveLength(1); const importedLinks = await prisma.link.findMany({ where: { createdById: user.id }, }); expect(importedLinks).toHaveLength(2); importedLinks.forEach((link) => { expect(link.collectionId).toBe(importsCollection.id); }); }); it("falls back to an Untitled Collection when a folder name is empty", async () => { const user = await createTestUser(); const html = `
`; await importFromHTMLFile(user.id, html); const untitledCollection = await prisma.collection.findFirst({ where: { ownerId: user.id, name: "Untitled Collection" }, }); expect(untitledCollection).toBeTruthy(); const link = await prisma.link.findFirst({ where: { url: "https://example.com/blank" }, }); expect(link?.collectionId).toBe(untitledCollection?.id); }); it("skips invalid URLs and only creates links for valid URLs", async () => { const user = await createTestUser(); const html = `
`; await importFromHTMLFile(user.id, html); const links = await prisma.link.findMany({ where: { createdById: user.id }, }); expect(links).toHaveLength(1); expect(links[0]?.url).toBe("https://valid.example.com"); }); // it("keeps link ids in the same chronological order as importDate (createdAt fallback)", async () => { // const user = await createTestUser(); // const nowSeconds = Math.floor(Date.now() / 1000); // const olderSeconds = nowSeconds - 86400; // const newerSeconds = nowSeconds + 86400; // const html = ` // //
////
//
//
//
//
// // `; // await importFromHTMLFile(user.id, html); // const linksById = await prisma.link.findMany({ // where: { createdById: user.id }, // orderBy: { id: "asc" }, // select: { id: true, importDate: true, createdAt: true, url: true }, // }); // console.log(linksById); // expect(linksById).toHaveLength(3); // const idsByIdOrder = linksById.map((link) => link.id); // const idsByEffectiveDateOrder = [...linksById] // .sort( // (a, b) => // (a.importDate ?? a.createdAt).getTime() - // (b.importDate ?? b.createdAt).getTime() // ) // .map((link) => link.id); // expect(idsByIdOrder).toEqual(idsByEffectiveDateOrder); // }); });