/** * WorthDoing.ai * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * File: tests/canonical-url.test.ts * Description: URL canonicalization + content hashing tests (deduplication layer). */ import { describe, it, expect } from "vitest"; import { canonicalizeUrl, sha256 } from "@/lib/firecrawl/url"; describe("canonicalizeUrl", () => { it("strips www, fragments, and trailing slashes", () => { expect(canonicalizeUrl("https://www.Example.com/path/#section")).toBe("https://example.com/path"); }); it("removes tracking parameters but keeps meaningful ones", () => { expect(canonicalizeUrl("https://example.com/a?utm_source=x&page=2&fbclid=abc")).toBe( "https://example.com/a?page=2", ); }); it("sorts query parameters for stable comparison", () => { expect(canonicalizeUrl("https://example.com/a?b=2&a=1")).toBe(canonicalizeUrl("https://example.com/a?a=1&b=2")); }); it("normalizes equivalent URLs to the same canonical form", () => { const variants = [ "https://www.example.com/post/", "https://example.com/post", "https://example.com/post#comments", "https://example.com/post?utm_campaign=news", ]; const canon = new Set(variants.map(canonicalizeUrl)); expect(canon.size).toBe(1); }); it("keeps the root path as /", () => { expect(canonicalizeUrl("https://example.com")).toBe("https://example.com/"); }); }); describe("sha256", () => { it("is deterministic and collision-distinct for different content", () => { expect(sha256("hello")).toBe(sha256("hello")); expect(sha256("hello")).not.toBe(sha256("hello!")); expect(sha256("hello")).toHaveLength(64); }); });