import { describe, expect, it } from "vitest";
import { fetchSitemapUrls, isSitemapIndex, parseSitemap } from "./sitemap.js";
const URLSET = `
http://x/a
http://x/b
http://x/q?x=1&y=2
`;
const INDEX = `
http://x/sub1.xml
http://x/sub2.xml
`;
describe("parseSitemap", () => {
it("extracts URLs from entries and trims whitespace", () => {
expect(parseSitemap(URLSET)).toEqual(["http://x/a", "http://x/b", "http://x/q?x=1&y=2"]);
});
it("returns an empty list when there are no locs", () => {
expect(parseSitemap("")).toEqual([]);
});
});
describe("isSitemapIndex", () => {
it("is true for a document", () => {
expect(isSitemapIndex(INDEX)).toBe(true);
});
it("is false for a plain ", () => {
expect(isSitemapIndex(URLSET)).toBe(false);
});
});
describe("fetchSitemapUrls", () => {
it("returns the URLs from a plain sitemap", async () => {
const urls = await fetchSitemapUrls("http://x/sitemap.xml", {
fetcher: async () => URLSET,
});
expect(urls).toEqual(["http://x/a", "http://x/b", "http://x/q?x=1&y=2"]);
});
it("flattens a sitemap index, fetching each sub-sitemap once", async () => {
const fetched: string[] = [];
const urls = await fetchSitemapUrls("http://x/index.xml", {
fetcher: async (src) => {
fetched.push(src);
if (src === "http://x/index.xml") return INDEX;
if (src === "http://x/sub1.xml") {
return `http://x/p1`;
}
return `http://x/p2`;
},
});
expect(urls).toEqual(["http://x/p1", "http://x/p2"]);
expect(fetched).toEqual(["http://x/index.xml", "http://x/sub1.xml", "http://x/sub2.xml"]);
});
it("deduplicates URLs that appear in multiple sub-sitemaps", async () => {
const urls = await fetchSitemapUrls("http://x/index.xml", {
fetcher: async (src) => {
if (src === "http://x/index.xml") return INDEX;
return `http://x/same`;
},
});
expect(urls).toEqual(["http://x/same"]);
});
it("throws when expansion exceeds maxSitemaps", async () => {
// A sitemap that references itself would cycle without this guard; we
// simulate blow-up with a chain of 3 indexes and a limit of 2.
const chain: Record = {
"http://x/i0.xml": `http://x/i1.xml`,
"http://x/i1.xml": `http://x/i2.xml`,
"http://x/i2.xml": `http://x/end`,
};
await expect(
fetchSitemapUrls("http://x/i0.xml", {
fetcher: async (src) => chain[src]!,
maxSitemaps: 2,
})
).rejects.toThrow(/maxSitemaps=2/);
});
});