import { describe, expect, test } from "bun:test"; import worker, { normalizeRepositories, parseManifestSummary, refreshPlugins, type Env, } from "./index"; const HEAD_COMMIT = "a".repeat(40); const SECOND_COMMIT = "b".repeat(40); type TreeFixture = { path: string; content?: string; mode?: string; type?: string; size?: number; }; class MemoryR2 { objects = new Map(); putCount = 0; failPuts = false; async put(key: string, value: string, options?: unknown): Promise { if (this.failPuts) throw new Error("simulated R2 put failure"); this.putCount += 1; this.objects.set(key, { value, options }); } async get(key: string): Promise<{ text(): Promise } | null> { const object = this.objects.get(key); return object ? { async text() { return object.value; }, } : null; } } class MemoryKV { constructor(private readonly keyNames: string[]) {} async list(options?: { prefix?: string }): Promise<{ keys: Array<{ name: string }> }> { return { keys: this.keyNames .filter((name) => !options?.prefix || name.startsWith(options.prefix)) .map((name) => ({ name })), }; } } function repo(overrides: Record = {}): Record { return { id: 1, full_name: "ogulcancelik/herdr-plugin-example", owner: { login: "ogulcancelik" }, name: "herdr-plugin-example", description: "Example plugin repository", html_url: "https://github.com/ogulcancelik/herdr-plugin-example", default_branch: "main", stargazers_count: 5, forks_count: 1, open_issues_count: 0, language: "TypeScript", topics: ["herdr-plugin"], created_at: "2026-06-01T00:00:00Z", updated_at: "2026-06-02T00:00:00Z", pushed_at: "2026-06-03T00:00:00Z", archived: false, fork: false, disabled: false, private: false, visibility: "public", ...overrides, }; } function manifest(overrides = ""): string { return ` id = "example.plugin" name = "Example Plugin" version = "0.2.0" min_herdr_version = "0.7.0" description = "Example manifest" platforms = ["linux", "macos"] ${overrides}`; } function env(bucket = new MemoryR2(), blacklist?: MemoryKV, backupBucket = new MemoryR2()): Env { return { PLUGIN_MARKETPLACE_BUCKET: bucket, PLUGIN_MARKETPLACE_BACKUP_BUCKET: backupBucket, PLUGIN_MARKETPLACE_BLACKLIST: blacklist, GITHUB_TOKEN: "token", }; } function repositoryFetch(options: { repositories: Record[]; trees?: Record; commits?: Record; totalCount?: number; incompleteResults?: boolean; searchStatus?: number; treeStatus?: Record; truncatedTrees?: Set; onRequest?: (kind: "search" | "head" | "tree" | "manifest", detail: string) => void; }): typeof fetch { return (async (input: RequestInfo | URL, init?: RequestInit): Promise => { const url = new URL(input.toString()); if (url.pathname === "/search/repositories") { options.onRequest?.("search", url.toString()); if (options.searchStatus) return new Response("search failed", { status: options.searchStatus }); return Response.json({ total_count: options.totalCount ?? options.repositories.length, incomplete_results: options.incompleteResults ?? false, items: options.repositories, }); } const treeMatch = url.pathname.match(/^\/repos\/([^/]+)\/([^/]+)\/git\/trees\/([^/]+)$/); if (treeMatch) { const fullName = `${decodeURIComponent(treeMatch[1])}/${decodeURIComponent(treeMatch[2])}`; options.onRequest?.("tree", fullName); const status = options.treeStatus?.[fullName]; if (status) return new Response("tree failed", { status }); const fixtures = options.trees?.[fullName] ?? [ { path: "herdr-plugin.toml", content: manifest() }, ]; return Response.json({ sha: "tree-sha", truncated: options.truncatedTrees?.has(fullName) ?? false, tree: fixtures.map((fixture) => ({ path: fixture.path, mode: fixture.mode ?? "100644", type: fixture.type ?? "blob", size: fixture.size ?? new TextEncoder().encode(fixture.content ?? "").byteLength, })), }); } if (url.pathname === "/graphql") { const request = JSON.parse(String(init?.body ?? "{}")); const query = String(request.query ?? ""); const data: Record = {}; if (query.includes("PluginMarketplaceHeads")) { options.onRequest?.("head", query); for (const match of query.matchAll( /repo(\d+): repository\(owner: "([^"]+)", name: "([^"]+)"\)/g, )) { const [, alias, owner, name] = match; const fullName = `${owner}/${name}`; const repository = options.repositories.find( (candidate) => candidate.full_name === fullName, ); data[`repo${alias}`] = repository ? { defaultBranchRef: { name: repository.default_branch ?? "main", target: { oid: options.commits?.[fullName] ?? HEAD_COMMIT }, }, } : null; } } else if (query.includes("PluginMarketplaceManifests")) { options.onRequest?.("manifest", query); for (const match of query.matchAll( /item(\d+): repository\(owner: "([^"]+)", name: "([^"]+)"\) \{\s+manifest: object\(expression: "[a-f0-9]+:([^"]+)"\)/g, )) { const [, alias, owner, name, path] = match; const fullName = `${owner}/${name}`; const fixture = (options.trees?.[fullName] ?? [ { path: "herdr-plugin.toml", content: manifest() }, ]).find((entry) => entry.path === path); data[`item${alias}`] = fixture?.content === undefined ? { manifest: null } : { manifest: { text: fixture.content } }; } } else { throw new Error(`unexpected GraphQL query: ${query}`); } return Response.json({ data }); } throw new Error(`unexpected request: ${url}`); }) as typeof fetch; } describe("normalizeRepositories", () => { test("normalizes fields while preserving repository-card ordering", () => { const plugins = normalizeRepositories([ repo({ id: 2, full_name: "other/newer", owner: { login: "other" }, name: "newer", html_url: "https://github.com/other/newer", stargazers_count: 5, pushed_at: "2026-06-04T00:00:00Z", }), repo(), ]); expect(plugins.map((plugin) => plugin.fullName)).toEqual([ "other/newer", "ogulcancelik/herdr-plugin-example", ]); expect(plugins[1]).toMatchObject({ id: 1, owner: "ogulcancelik", name: "herdr-plugin-example", defaultBranch: "main", stars: 5, }); }); test("deduplicates repositories by immutable GitHub id", () => { const plugins = normalizeRepositories([ repo(), repo({ full_name: "duplicate/name", owner: { login: "duplicate" }, name: "name", html_url: "https://github.com/duplicate/name" }), ]); expect(plugins).toHaveLength(1); expect(plugins[0].fullName).toBe("ogulcancelik/herdr-plugin-example"); }); test("drops unsafe, unavailable, and default-branch-less repositories", () => { const plugins = normalizeRepositories([ repo({ html_url: "https://example.com/ogulcancelik/herdr-plugin-example" }), repo({ archived: true }), repo({ fork: true }), repo({ disabled: true }), repo({ private: true }), repo({ visibility: "private" }), repo({ default_branch: undefined }), repo({ id: 5 }), ]); expect(plugins.map((plugin) => plugin.id)).toEqual([5]); }); }); describe("parseManifestSummary", () => { test("extracts metadata and accepts the UTF-8 BOM accepted by Herdr", () => { expect(parseManifestSummary(`\uFEFF${manifest()}`)).toEqual({ id: "example.plugin", name: "Example Plugin", version: "0.2.0", minHerdrVersion: "0.7.0", description: "Example manifest", platforms: ["linux", "macos"], }); }); test("rejects malformed TOML and invalid required metadata", () => { expect(parseManifestSummary("not = [valid")).toBeNull(); expect(parseManifestSummary(manifest().replace("example.plugin", "bad/plugin"))).toBeNull(); expect(parseManifestSummary(manifest().replace("0.7.0", "next"))).toBeNull(); expect(parseManifestSummary(manifest().replace('["linux", "macos"]', "[]"))).toBeNull(); expect( parseManifestSummary(manifest().replace("Example Plugin", "n".repeat(121))), ).toBeNull(); }); }); describe("refreshPlugins", () => { test("publishes one backward-compatible repository card with multiple manifests", async () => { const bucket = new MemoryR2(); const fullName = "ogulcancelik/herdr-plugin-example"; const fetch = repositoryFetch({ repositories: [repo()], trees: { [fullName]: [ { path: "herdr-plugin.toml", content: manifest() }, { path: "plugins/second/herdr-plugin.toml", content: manifest().replace("example.plugin", "example.second").replace("Example Plugin", "Second Plugin"), }, ], }, }); const result = await refreshPlugins(env(bucket), { fetch, now: new Date("2026-06-20T12:00:00.000Z"), logger: { error() {} }, }); expect(result.ok).toBe(true); const snapshotObject = bucket.objects.get("plugins/index.json"); const snapshot = JSON.parse(snapshotObject?.value ?? ""); expect(snapshotObject?.options).toEqual({ httpMetadata: { contentType: "application/json; charset=utf-8", cacheControl: "public, max-age=300, s-maxage=1800, stale-while-revalidate=3600", }, }); expect(snapshot).toMatchObject({ schemaVersion: 1, generatedAt: "2026-06-20T12:00:00.000Z", pluginCount: 2, repositoryCount: 1, source: { missingManifestCount: 0, invalidManifestCount: 0, }, }); expect(snapshot.plugins[0]).toMatchObject({ id: 1, fullName, name: "herdr-plugin-example", headCommit: HEAD_COMMIT, manifests: [ { path: "herdr-plugin.toml", id: "example.plugin" }, { path: "plugins/second/herdr-plugin.toml", id: "example.second" }, ], }); expect(snapshot.plugins[0]).not.toHaveProperty("defaultBranch"); expect(snapshot.plugins[0]).toMatchObject({ firstSeenAt: "2026-06-20T12:00:00.000Z", starsDelta7d: null, starsDelta30d: null, }); expect(bucket.objects.has("plugins/scan-cache.json")).toBe(true); const history = JSON.parse(bucket.objects.get("plugins/star-history.json")?.value ?? ""); expect(history).toEqual({ schemaVersion: 1, entries: [ { repositoryId: 1, fullName, firstSeenAt: "2026-06-20T12:00:00.000Z", samples: [{ date: "2026-06-20", stars: 5 }], }, ], }); }); test("reuses cached manifests without resolving or rescanning an unchanged repository", async () => { const bucket = new MemoryR2(); const requests: string[] = []; const fetch = repositoryFetch({ repositories: [repo()], onRequest(kind) { requests.push(kind); }, }); expect((await refreshPlugins(env(bucket), { fetch, logger: { error() {} } })).ok).toBe(true); requests.length = 0; expect((await refreshPlugins(env(bucket), { fetch, logger: { error() {} } })).ok).toBe(true); expect(requests).toEqual(["search"]); }); test("rescans a repository when its pushed timestamp and head change", async () => { const bucket = new MemoryR2(); const repository = repo(); const fullName = String(repository.full_name); const commits = { [fullName]: HEAD_COMMIT }; const trees = { [fullName]: [{ path: "herdr-plugin.toml", content: manifest() }] }; const fetch = repositoryFetch({ repositories: [repository], commits, trees }); expect((await refreshPlugins(env(bucket), { fetch, logger: { error() {} } })).ok).toBe(true); commits[fullName] = SECOND_COMMIT; repository.pushed_at = "2026-06-04T00:00:00Z"; trees[fullName][0].content = manifest().replace("0.2.0", "0.3.0"); const result = await refreshPlugins(env(bucket), { fetch, logger: { error() {} } }); expect(result.ok).toBe(true); if (!result.ok) return; expect(result.snapshot.plugins[0].headCommit).toBe(SECOND_COMMIT); expect(result.snapshot.plugins[0].manifests[0].version).toBe("0.3.0"); }); test("counts manifests separately from repository cards and omits empty cards", async () => { const repositories = [ repo(), repo({ id: 2, full_name: "example/empty", owner: { login: "example" }, name: "empty", html_url: "https://github.com/example/empty", }), repo({ id: 3, full_name: "example/invalid", owner: { login: "example" }, name: "invalid", html_url: "https://github.com/example/invalid", }), ]; const result = await refreshPlugins(env(), { fetch: repositoryFetch({ repositories, trees: { "ogulcancelik/herdr-plugin-example": [ { path: "one/herdr-plugin.toml", content: manifest() }, { path: "two/herdr-plugin.toml", content: manifest().replace("example.plugin", "example.two") }, ], "example/empty": [{ path: "README.md", content: "empty" }], "example/invalid": [{ path: "herdr-plugin.toml", content: "id = [broken" }], }, }), logger: { error() {} }, }); expect(result.ok).toBe(true); if (!result.ok) return; expect(result.snapshot.pluginCount).toBe(2); expect(result.snapshot.repositoryCount).toBe(1); expect(result.snapshot.source.missingManifestCount).toBe(1); expect(result.snapshot.source.invalidManifestCount).toBe(1); }); test("ignores manifest symlinks and indexes their regular target", async () => { const fullName = "ogulcancelik/herdr-plugin-example"; const result = await refreshPlugins(env(), { fetch: repositoryFetch({ repositories: [repo()], trees: { [fullName]: [ { path: "herdr-plugin.toml", content: "plugin/herdr-plugin.toml", mode: "120000" }, { path: "plugin/herdr-plugin.toml", content: manifest() }, ], }, }), logger: { error() {} }, }); expect(result.ok).toBe(true); if (!result.ok) return; expect(result.snapshot.plugins[0].manifests.map((item) => item.path)).toEqual([ "plugin/herdr-plugin.toml", ]); expect(result.snapshot.source.invalidManifestCount).toBe(0); }); test("ignores test fixtures and deduplicates repeated plugin ids", async () => { const fullName = "ogulcancelik/herdr-plugin-example"; const result = await refreshPlugins(env(), { fetch: repositoryFetch({ repositories: [repo()], trees: { [fullName]: [ { path: "herdr-plugin.toml", content: manifest() }, { path: "platform/herdr-plugin.toml", content: manifest() }, { path: "tests/fixtures/helper/herdr-plugin.toml", content: manifest().replace("example.plugin", "example.fixture"), }, ], }, }), logger: { error() {} }, }); expect(result.ok).toBe(true); if (!result.ok) return; expect(result.snapshot.plugins[0].manifests.map((item) => item.path)).toEqual([ "herdr-plugin.toml", ]); expect(result.snapshot.source.duplicateManifestCount).toBe(1); }); test("skips a truncated tree without blocking the marketplace", async () => { const fullName = "ogulcancelik/herdr-plugin-example"; const result = await refreshPlugins(env(), { fetch: repositoryFetch({ repositories: [repo()], truncatedTrees: new Set([fullName]), }), logger: { error() {} }, }); expect(result.ok).toBe(true); if (!result.ok) return; expect(result.snapshot.plugins).toEqual([]); expect(result.snapshot.source.skippedRepositoryCount).toBe(1); expect(result.snapshot.source.warnings?.[0]).toContain(fullName); }); test("writes an empty snapshot when every repository is blacklisted", async () => { const bucket = new MemoryR2(); const result = await refreshPlugins( env(bucket, new MemoryKV(["repo:ogulcancelik/herdr-plugin-example"])), { fetch: repositoryFetch({ repositories: [repo()] }), logger: { error() {} } }, ); expect(result.ok).toBe(true); if (!result.ok) return; expect(result.snapshot.plugins).toEqual([]); expect(result.snapshot.source.blacklistedCount).toBe(1); }); test("pins tree and manifest reads to the resolved commit", async () => { const requests: Array<[string, string]> = []; const result = await refreshPlugins(env(), { fetch: repositoryFetch({ repositories: [repo()], onRequest(kind, detail) { requests.push([kind, detail]); }, }), logger: { error() {} }, }); expect(result.ok).toBe(true); expect(requests.find(([kind]) => kind === "tree")?.[1]).toBe( "ogulcancelik/herdr-plugin-example", ); const manifestQuery = requests.find(([kind]) => kind === "manifest")?.[1] ?? ""; expect(manifestQuery).toContain(`${HEAD_COMMIT}:herdr-plugin.toml`); expect(manifestQuery).not.toContain("main:herdr-plugin.toml"); }); test("does not overwrite the public snapshot when scanning fails", async () => { const bucket = new MemoryR2(); await bucket.put("plugins/index.json", '{"schemaVersion":1,"plugins":[{"id":1}]}'); const result = await refreshPlugins(env(bucket), { fetch: repositoryFetch({ repositories: [repo()], treeStatus: { "ogulcancelik/herdr-plugin-example": 429 }, }), logger: { error() {} }, }); expect(result.ok).toBe(false); expect(bucket.objects.get("plugins/index.json")?.value).toBe( '{"schemaVersion":1,"plugins":[{"id":1}]}', ); }); test("discards malformed cache state and rebuilds it", async () => { const bucket = new MemoryR2(); await bucket.put("plugins/index.json", '{"schemaVersion":1,"plugins":[{"id":1}]}'); await bucket.put("plugins/scan-cache.json", "broken"); const errors: string[] = []; const result = await refreshPlugins(env(bucket), { fetch: repositoryFetch({ repositories: [repo()] }), logger: { error(message) { errors.push(String(message)); } }, }); expect(result.ok).toBe(true); expect(errors[0]).toContain("discarding invalid plugin marketplace scan cache"); expect(JSON.parse(bucket.objects.get("plugins/scan-cache.json")?.value ?? "").entries).toHaveLength(1); }); test("rejects an implausible empty search when a healthy cache exists", async () => { const bucket = new MemoryR2(); const normalFetch = repositoryFetch({ repositories: [repo()] }); expect((await refreshPlugins(env(bucket), { fetch: normalFetch, logger: { error() {} } })).ok).toBe(true); const previous = bucket.objects.get("plugins/index.json")?.value; const result = await refreshPlugins(env(bucket), { fetch: repositoryFetch({ repositories: [] }), logger: { error() {} }, }); expect(result.ok).toBe(false); expect(bucket.objects.get("plugins/index.json")?.value).toBe(previous); }); test("publishes an empty snapshot when the initial complete search is empty", async () => { const bucket = new MemoryR2(); const result = await refreshPlugins(env(bucket), { fetch: repositoryFetch({ repositories: [] }), logger: { error() {} }, }); expect(result.ok).toBe(true); if (!result.ok) return; expect(result.snapshot.plugins).toEqual([]); expect(result.snapshot.pluginCount).toBe(0); }); test("preserves the snapshot when head resolution omits a cached repository", async () => { const bucket = new MemoryR2(); const repository = repo(); const baseFetch = repositoryFetch({ repositories: [repository] }); expect((await refreshPlugins(env(bucket), { fetch: baseFetch, logger: { error() {} } })).ok).toBe(true); const previous = bucket.objects.get("plugins/index.json")?.value; repository.pushed_at = "2026-06-04T00:00:00Z"; const missingHeadFetch = async (input: RequestInfo | URL, init?: RequestInit): Promise => { const request = String(init?.body ?? ""); if (new URL(input.toString()).pathname === "/graphql" && request.includes("PluginMarketplaceHeads")) { return Response.json({ data: { repo0: null }, errors: [{ type: "NOT_FOUND", path: ["repo0"], message: "not found" }], }); } return baseFetch(input, init); }; const result = await refreshPlugins(env(bucket), { fetch: missingHeadFetch as typeof fetch, logger: { error() {} }, }); expect(result.ok).toBe(false); expect(bucket.objects.get("plugins/index.json")?.value).toBe(previous); }); test("does not cache a transiently missing manifest response", async () => { const bucket = new MemoryR2(); const repository = repo(); const fullName = String(repository.full_name); const commits = { [fullName]: HEAD_COMMIT }; const baseFetch = repositoryFetch({ repositories: [repository], commits }); expect((await refreshPlugins(env(bucket), { fetch: baseFetch, logger: { error() {} } })).ok).toBe(true); const previous = bucket.objects.get("plugins/index.json")?.value; repository.pushed_at = "2026-06-04T00:00:00Z"; commits[fullName] = SECOND_COMMIT; const missingFetch = async (input: RequestInfo | URL, init?: RequestInit): Promise => { const request = String(init?.body ?? ""); if (new URL(input.toString()).pathname === "/graphql" && request.includes("PluginMarketplaceManifests")) { return Response.json({ data: { item0: { manifest: null } }, errors: [{ type: "NOT_FOUND", path: ["item0"], message: "not found" }], }); } return baseFetch(input, init); }; const result = await refreshPlugins(env(bucket), { fetch: missingFetch as typeof fetch, logger: { error() {} }, }); expect(result.ok).toBe(false); expect(bucket.objects.get("plugins/index.json")?.value).toBe(previous); }); test("retries budget-skipped repositories when capacity becomes available", async () => { const repositories = Array.from({ length: 51 }, (_, index) => repo({ id: index + 1, full_name: `owner/repo-${index}`, owner: { login: "owner" }, name: `repo-${index}`, html_url: `https://github.com/owner/repo-${index}`, }), ); const trees = Object.fromEntries( repositories.map((repository) => [ repository.full_name, Array.from({ length: 100 }, (_, index) => ({ path: `plugins/${index}/herdr-plugin.toml`, content: manifest(), })), ]), ); const bucket = new MemoryR2(); const fetch = repositoryFetch({ repositories, trees }); const result = await refreshPlugins(env(bucket), { fetch, logger: { error() {} } }); expect(result.ok).toBe(true); if (!result.ok) return; expect(result.snapshot.source.skippedRepositoryCount).toBe(1); expect(result.snapshot.source.warnings?.[0]).toContain("scan budget was exhausted"); repositories.shift(); const recovered = await refreshPlugins(env(bucket), { fetch, logger: { error() {} } }); expect(recovered.ok).toBe(true); if (!recovered.ok) return; expect(recovered.snapshot.source.skippedRepositoryCount).toBe(0); expect(recovered.snapshot.repositoryCount).toBe(50); }); test("keeps one star sample per UTC day with the first observation winning", async () => { const bucket = new MemoryR2(); const backupBucket = new MemoryR2(); const repository = repo({ stargazers_count: 5 }); const logger = { error() {} }; const run = (stars: number, now: string) => { repository.stargazers_count = stars; return refreshPlugins(env(bucket, undefined, backupBucket), { fetch: repositoryFetch({ repositories: [repository] }), now: new Date(now), logger, }); }; expect((await run(5, "2026-06-20T00:10:00.000Z")).ok).toBe(true); expect((await run(9, "2026-06-20T23:50:00.000Z")).ok).toBe(true); const sameDay = JSON.parse(bucket.objects.get("plugins/star-history.json")?.value ?? ""); expect(sameDay.entries[0].samples).toEqual([{ date: "2026-06-20", stars: 5 }]); const nextDay = await run(12, "2026-06-21T00:10:00.000Z"); expect(nextDay.ok).toBe(true); if (!nextDay.ok) return; expect(nextDay.snapshot.plugins[0].firstSeenAt).toBe("2026-06-20T00:10:00.000Z"); const history = JSON.parse(bucket.objects.get("plugins/star-history.json")?.value ?? ""); expect(history.entries[0].samples).toEqual([ { date: "2026-06-20", stars: 5 }, { date: "2026-06-21", stars: 12 }, ]); // One dated backup per UTC day, written on the first run of that day and // never touched by later intraday runs. expect(backupBucket.putCount).toBe(2); expect([...backupBucket.objects.keys()]).toEqual([ "backups/star-history/2026-06-20.json", "backups/star-history/2026-06-21.json", ]); const firstBackup = JSON.parse( backupBucket.objects.get("backups/star-history/2026-06-20.json")?.value ?? "", ); expect(firstBackup.entries[0].samples).toEqual([{ date: "2026-06-20", stars: 5 }]); }); test("fails the refresh without touching the primary bucket when the backup put fails", async () => { const bucket = new MemoryR2(); await bucket.put("plugins/index.json", '{"schemaVersion":1,"plugins":[{"id":1}]}'); const backupBucket = new MemoryR2(); backupBucket.failPuts = true; const errors: string[] = []; const options = { fetch: repositoryFetch({ repositories: [repo()] }), now: new Date("2026-06-20T12:00:00.000Z"), logger: { error(message: unknown) { errors.push(String(message)); } }, }; const failed = await refreshPlugins(env(bucket, undefined, backupBucket), options); expect(failed.ok).toBe(false); expect(errors[0]).toContain("simulated R2 put failure"); expect(bucket.objects.get("plugins/index.json")?.value).toBe( '{"schemaVersion":1,"plugins":[{"id":1}]}', ); expect(bucket.objects.has("plugins/star-history.json")).toBe(false); expect(bucket.objects.has("plugins/scan-cache.json")).toBe(false); backupBucket.failPuts = false; const retried = await refreshPlugins(env(bucket, undefined, backupBucket), options); expect(retried.ok).toBe(true); expect(backupBucket.objects.has("backups/star-history/2026-06-20.json")).toBe(true); expect(bucket.objects.has("plugins/star-history.json")).toBe(true); }); test("computes star deltas from near-boundary baselines and rejects stale ones", async () => { const bucket = new MemoryR2(); await bucket.put( "plugins/star-history.json", JSON.stringify({ schemaVersion: 1, entries: [ { repositoryId: 1, fullName: "ogulcancelik/herdr-plugin-example", firstSeenAt: "2026-05-01T00:00:00.000Z", samples: [ { date: "2026-05-01", stars: 10 }, { date: "2026-05-20", stars: 30 }, { date: "2026-06-13", stars: 40 }, { date: "2026-06-19", stars: 90 }, ], }, { repositoryId: 2, fullName: "example/rejoined", firstSeenAt: "2026-05-01T00:00:00.000Z", samples: [{ date: "2026-05-01", stars: 3 }], }, ], }), ); const result = await refreshPlugins(env(bucket), { fetch: repositoryFetch({ repositories: [ repo({ stargazers_count: 100 }), repo({ id: 2, full_name: "example/rejoined", owner: { login: "example" }, name: "rejoined", html_url: "https://github.com/example/rejoined", stargazers_count: 50, }), ], }), now: new Date("2026-06-20T12:00:00.000Z"), logger: { error() {} }, }); expect(result.ok).toBe(true); if (!result.ok) return; expect(result.snapshot.plugins[0]).toMatchObject({ firstSeenAt: "2026-05-01T00:00:00.000Z", starsDelta7d: 60, starsDelta30d: 70, }); // The rejoined repository only has a six-week-old sample: far past both // window boundaries, so no delta is reported instead of an inflated one. expect(result.snapshot.plugins[1]).toMatchObject({ fullName: "example/rejoined", starsDelta7d: null, starsDelta30d: null, }); }); test("prunes aged samples and keeps history for delisted repositories", async () => { const bucket = new MemoryR2(); await bucket.put( "plugins/star-history.json", JSON.stringify({ schemaVersion: 1, entries: [ { repositoryId: 1, fullName: "ogulcancelik/herdr-plugin-example", firstSeenAt: "2026-01-01T00:00:00.000Z", samples: [ { date: "2026-01-01", stars: 1 }, { date: "2026-06-19", stars: 4 }, ], }, { repositoryId: 2, fullName: "example/delisted-recently", firstSeenAt: "2026-01-01T00:00:00.000Z", samples: [ { date: "2026-01-05", stars: 5 }, { date: "2026-06-01", stars: 7 }, ], }, { repositoryId: 3, fullName: "example/delisted-long-ago", firstSeenAt: "2026-01-01T00:00:00.000Z", samples: [{ date: "2026-01-05", stars: 3 }], }, ], }), ); const result = await refreshPlugins(env(bucket), { fetch: repositoryFetch({ repositories: [repo()] }), now: new Date("2026-06-20T12:00:00.000Z"), logger: { error() {} }, }); expect(result.ok).toBe(true); const history = JSON.parse(bucket.objects.get("plugins/star-history.json")?.value ?? ""); expect(history.entries.map((entry: { repositoryId: number }) => entry.repositoryId)).toEqual([ 1, 2, ]); expect(history.entries[0].samples.map((sample: { date: string }) => sample.date)).toEqual([ "2026-06-19", "2026-06-20", ]); expect(history.entries[1].samples).toEqual([{ date: "2026-06-01", stars: 7 }]); }); for (const { name, record } of [ { name: "malformed JSON", record: "broken" }, { name: "an unsupported shape", record: '{"schemaVersion":2,"entries":[]}' }, { name: "an invalid entry", record: JSON.stringify({ schemaVersion: 1, entries: [{ repositoryId: -5, samples: "broken" }], }), }, ]) { test(`fails the refresh without overwriting anything when star history has ${name}`, async () => { const bucket = new MemoryR2(); await bucket.put("plugins/index.json", '{"schemaVersion":1,"plugins":[{"id":1}]}'); await bucket.put("plugins/star-history.json", record); const backupBucket = new MemoryR2(); const errors: string[] = []; const result = await refreshPlugins(env(bucket, undefined, backupBucket), { fetch: repositoryFetch({ repositories: [repo()] }), now: new Date("2026-06-20T12:00:00.000Z"), logger: { error(message: unknown) { errors.push(String(message)); } }, }); expect(result.ok).toBe(false); expect(errors[0]).toContain("invalid plugin marketplace star history"); expect(bucket.objects.get("plugins/star-history.json")?.value).toBe(record); expect(bucket.objects.get("plugins/index.json")?.value).toBe( '{"schemaVersion":1,"plugins":[{"id":1}]}', ); expect(bucket.objects.has("plugins/scan-cache.json")).toBe(false); // Broken history must never be preserved as a "backup". expect(backupBucket.objects.size).toBe(0); }); } for (const { name, fetch } of [ { name: "GitHub failure", fetch: repositoryFetch({ repositories: [], searchStatus: 429 }), }, { name: "incomplete search results", fetch: repositoryFetch({ repositories: [repo()], incompleteResults: true }), }, ]) { test(`preserves the current snapshot on ${name}`, async () => { const bucket = new MemoryR2(); await bucket.put("plugins/index.json", '{"schemaVersion":1,"plugins":[{"id":1}]}'); const result = await refreshPlugins(env(bucket), { fetch, logger: { error() {} } }); expect(result.ok).toBe(false); expect(bucket.objects.get("plugins/index.json")?.value).toBe( '{"schemaVersion":1,"plugins":[{"id":1}]}', ); }); } }); describe("fetch handler", () => { test("does not expose a public Worker API", async () => { const response = await worker.fetch(new Request("https://herdr.dev/api/plugins"), env()); expect(response.status).toBe(404); expect(response.headers.get("Cache-Control")).toBe("no-store"); }); });