import { getScansCollection, getScansByClientAndDateRange, getScanFieldsByClientAndDateRange, findScansByQuery, countScans, createScanDoc, updateScanDoc, findInProgressScansByClient, getScansByWebsiteUrl, getLatestScanByWebsiteUrl, getInProgressScanByWebsiteUrl, getLatestCompletedScanByWebsiteUrl, deleteScansByWebsiteUrl, deleteScanById, } from "../scans.getters"; import { FINISHED_SCAN_STATUSES, SCAN_STATUSES } from "../scans.constants"; import { createScanFixture } from "../../../test-utils/factories"; import { ObjectId } from "mongodb"; describe("db.websitalk.scans", () => { describe("getScansByClientAndDateRange()", () => { it("should return full scan documents within the date range", async () => { const clientId = "dateRangeClient"; const startDate = new Date("2023-05-01"); const endDate = new Date("2023-05-31"); const scanInside = createScanFixture({ clientId, status: "COMPLETED" }); const scanBefore = createScanFixture({ clientId }); const scanAfter = createScanFixture({ clientId }); await getScansCollection().insertMany([ { ...scanInside, createdAt: new Date("2023-05-15") }, { ...scanBefore, createdAt: new Date("2023-04-30") }, { ...scanAfter, createdAt: new Date("2023-06-01") }, ]); const result = await getScansByClientAndDateRange( clientId, startDate, endDate, ); expect(result.length).toBe(1); expect(result[0]).toMatchObject({ clientId, websiteUrlId: scanInside.websiteUrlId, status: "COMPLETED", }); expect(result[0]).toHaveProperty("siteChrome"); expect(result[0]).toHaveProperty("skipped"); }); it("should respect the date range boundaries (inclusive)", async () => { const clientId = "boundaryClient"; const onStart = createScanFixture({ clientId }); const onEnd = createScanFixture({ clientId }); await getScansCollection().insertMany([ { ...onStart, createdAt: new Date("2023-05-01") }, { ...onEnd, createdAt: new Date("2023-05-31") }, ]); const result = await getScansByClientAndDateRange( clientId, new Date("2023-05-01"), new Date("2023-05-31"), ); expect(result.length).toBe(2); }); }); describe("getScanFieldsByClientAndDateRange()", () => { it("should return only the requested fields (plus _id)", async () => { const clientId = "projectionClient"; const scan = createScanFixture({ clientId, status: "COMPLETED", siteChrome: { url: "https://example.com", sourceType: "site_chrome" }, skipped: [{ path: "/broken", reason: "HTTP 500" }], }); await getScansCollection().insertOne({ ...scan, createdAt: new Date("2023-05-15"), }); const [result] = await getScanFieldsByClientAndDateRange( clientId, new Date("2023-05-01"), new Date("2023-05-31"), ["clientId", "websiteUrlId", "status"], ); expect(result).toMatchObject({ clientId, websiteUrlId: scan.websiteUrlId, status: "COMPLETED", }); expect(result).toHaveProperty("_id"); expect(result).not.toHaveProperty("siteChrome"); expect(result).not.toHaveProperty("skipped"); expect(result).not.toHaveProperty("createdAt"); }); }); describe("findScansByQuery() / countScans()", () => { it("should find scans matching a raw filter and count them", async () => { const clientId = "queryClient"; const completed = createScanFixture({ clientId, status: "COMPLETED" }); const scanning = createScanFixture({ clientId, status: "SCANNING" }); await getScansCollection().insertMany([completed, scanning]); const result = await findScansByQuery({ clientId, status: "COMPLETED" }); const count = await countScans({ clientId }); expect(result.length).toBe(1); expect(result[0].websiteUrlId).toBe(completed.websiteUrlId); expect(count).toBe(2); }); }); describe("createScanDoc()", () => { it("should insert a scan with default values for the rest of the fields", async () => { const clientId = "createClient"; const websiteUrlId = new ObjectId().toString(); const { insertedId } = await createScanDoc({ clientId, websiteUrlId }); const result = await getScansCollection().findOne({ _id: insertedId }); expect(result).toMatchObject({ clientId, websiteUrlId, status: "CHECKING", activeTasksCount: 0, pagesScanned: 0, pagesSkipped: 0, skipped: [], siteChrome: {}, pausedAt: null, }); expect(result?.createdAt).toBeInstanceOf(Date); expect(result?.updatedAt).toBeInstanceOf(Date); }); }); describe("pausedAt", () => { it("is set on pause and cleared back to null on resume, via updateScanDoc", async () => { const scan = createScanFixture({ status: "SCANNING", pausedAt: null }); const { insertedId } = await getScansCollection().insertOne(scan); const pausedAt = new Date(); const paused = await updateScanDoc(insertedId, { status: "PAUSED", pausedAt, }); expect(paused?.pausedAt).toEqual(pausedAt); const resumed = await updateScanDoc(insertedId, { status: "SCANNING", pausedAt: null, }); expect(resumed?.pausedAt).toBeNull(); }); }); describe("updateScanDoc()", () => { it("should update the given fields and bump updatedAt", async () => { const scan = createScanFixture({ status: "SCANNING", pagesScanned: 0 }); const { insertedId } = await getScansCollection().insertOne(scan); const result = await updateScanDoc(insertedId, { status: "COMPLETED", pagesScanned: 10, }); expect(result).toMatchObject({ _id: insertedId, status: "COMPLETED", pagesScanned: 10, }); expect(result?.updatedAt.getTime()).toBeGreaterThan( scan.updatedAt.getTime(), ); }); it("should return null when the scan does not exist", async () => { const result = await updateScanDoc(new ObjectId(), { status: "FAILED" }); expect(result).toBeNull(); }); }); describe("in-progress scan helpers", () => { const websiteUrlId = "website-123"; it("should return website scan history newest first", async () => { await getScansCollection().insertMany([ createScanFixture({ websiteUrlId, status: "COMPLETED", createdAt: new Date("2023-05-01"), }), createScanFixture({ websiteUrlId, status: "COMPLETED", createdAt: new Date("2023-05-15"), }), ]); const scans = await getScansByWebsiteUrl(websiteUrlId); expect(scans).toHaveLength(2); expect(scans[0].createdAt.getTime()).toBeGreaterThan( scans[1].createdAt.getTime(), ); }); it("should return the newest scan for a website", async () => { await getScansCollection().insertMany([ createScanFixture({ websiteUrlId, status: "COMPLETED", createdAt: new Date("2023-05-01"), }), createScanFixture({ websiteUrlId, status: "FAILED", createdAt: new Date("2023-05-15"), }), ]); const latest = await getLatestScanByWebsiteUrl(websiteUrlId); expect(latest?.status).toBe("FAILED"); }); it("should return in-progress scan and return null when only completed exists", async () => { await getScansCollection().insertMany([ createScanFixture({ websiteUrlId, status: "COMPLETED", createdAt: new Date("2023-05-01"), }), createScanFixture({ websiteUrlId, status: "SCANNING", createdAt: new Date("2023-05-15"), }), ]); const inProgressScan = await getInProgressScanByWebsiteUrl(websiteUrlId); expect(inProgressScan?.status).toBe("SCANNING"); await getScansCollection().deleteMany({ websiteUrlId }); await getScansCollection().insertOne( createScanFixture({ websiteUrlId, status: "COMPLETED", createdAt: new Date("2023-05-20"), }), ); expect(await getInProgressScanByWebsiteUrl(websiteUrlId)).toBeNull(); }); it("should return all in-progress scans across websites for a client", async () => { const clientId = "multiScanClient"; await getScansCollection().insertMany([ createScanFixture({ clientId, websiteUrlId: "website-a", status: "SCANNING", }), createScanFixture({ clientId, websiteUrlId: "website-b", status: "PROCESSING", }), createScanFixture({ clientId, websiteUrlId: "website-c", status: "COMPLETED", }), ]); const scans = await findInProgressScansByClient(clientId); expect(scans).toHaveLength(2); expect(scans.map((s) => s.websiteUrlId).sort()).toEqual([ "website-a", "website-b", ]); }); }); describe("website delete helpers", () => { it("should return the newest completed scan only", async () => { const websiteUrlId = new ObjectId().toString(); await getScansCollection().insertMany([ createScanFixture({ websiteUrlId, status: "FAILED", createdAt: new Date("2023-05-25"), }), createScanFixture({ websiteUrlId, status: "COMPLETED", createdAt: new Date("2023-05-20"), }), createScanFixture({ websiteUrlId, status: "COMPLETED", createdAt: new Date("2023-05-22"), }), ]); const latest = await getLatestCompletedScanByWebsiteUrl(websiteUrlId); expect(latest?.status).toBe("COMPLETED"); expect(latest?.createdAt).toEqual(new Date("2023-05-22")); }); it("should delete all scans for the website and return count", async () => { const websiteUrlId = new ObjectId().toString(); await getScansCollection().insertMany([ createScanFixture({ websiteUrlId }), createScanFixture({ websiteUrlId }), createScanFixture({ websiteUrlId: new ObjectId().toString() }), ]); const deleted = await deleteScansByWebsiteUrl(websiteUrlId); expect(deleted).toBe(2); expect(await countScans({ websiteUrlId })).toBe(0); }); }); describe("deleteScanById()", () => { it("should delete the scan and return true", async () => { const { insertedId } = await getScansCollection().insertOne(createScanFixture()); expect(await deleteScanById(insertedId)).toBe(true); expect( await getScansCollection().findOne({ _id: insertedId }), ).toBeNull(); }); it("should return false when the scan does not exist", async () => { expect(await deleteScanById(new ObjectId())).toBe(false); }); }); describe("FINISHED_SCAN_STATUSES", () => { it("should include CANCELED — canceling is final, not resumable", () => { expect(FINISHED_SCAN_STATUSES).toContain(SCAN_STATUSES.CANCELED); }); it("should NOT include PAUSED — pausing is reversible", () => { expect(FINISHED_SCAN_STATUSES).not.toContain(SCAN_STATUSES.PAUSED); }); }); });