Files
wehub-resource-sync b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:23:53 +08:00

327 lines
12 KiB
TypeScript

import { ClientResponseError } from "@atcute/client";
import { describe, expect, it, vi } from "vitest";
import { DiscoveryClient } from "../src/discovery/index.js";
/**
* Builds a fetch stub that records every call and returns canned responses.
*/
function buildFetchStub(responses: Record<string, { status: number; body: unknown }>): {
fetch: typeof fetch;
calls: Array<{ url: string; init: RequestInit | undefined }>;
} {
const calls: Array<{ url: string; init: RequestInit | undefined }> = [];
const fetchStub: typeof fetch = vi.fn(async (input, init) => {
const url = typeof input === "string" || input instanceof URL ? input.toString() : input.url;
calls.push({ url, init });
const path = new URL(url).pathname;
const match = responses[path];
if (!match) {
return new Response(JSON.stringify({ error: "TestNotConfigured" }), {
status: 500,
headers: { "content-type": "application/json" },
});
}
return new Response(JSON.stringify(match.body), {
status: match.status,
headers: { "content-type": "application/json" },
});
});
return { fetch: fetchStub, calls };
}
describe("DiscoveryClient", () => {
const aggregator = "https://aggregator.test";
// atcute validates the response envelope; `cid` must be a real DASL CID
// (`/^baf[ky]rei[a-z2-7]{52}$/`, 59 chars). Content is irrelevant to these
// tests — only the shape is.
const CID = `bafyrei${"a".repeat(52)}`;
it("hits the searchPackages XRPC endpoint with the right query string", async () => {
const { fetch, calls } = buildFetchStub({
"/xrpc/com.emdashcms.experimental.aggregator.searchPackages": {
status: 200,
body: { packages: [], cursor: undefined },
},
});
const client = new DiscoveryClient({ aggregatorUrl: aggregator, fetch });
const result = await client.searchPackages({ q: "gallery", limit: 5 });
expect(result.packages).toEqual([]);
expect(calls).toHaveLength(1);
const callUrl = new URL(calls[0]!.url);
expect(callUrl.pathname).toBe("/xrpc/com.emdashcms.experimental.aggregator.searchPackages");
expect(callUrl.searchParams.get("q")).toBe("gallery");
expect(callUrl.searchParams.get("limit")).toBe("5");
});
it("forwards the atproto-accept-labelers header when configured", async () => {
const { fetch, calls } = buildFetchStub({
"/xrpc/com.emdashcms.experimental.aggregator.searchPackages": {
status: 200,
body: { packages: [] },
},
});
const client = new DiscoveryClient({
aggregatorUrl: aggregator,
acceptLabelers: "did:plc:labeller-a, did:plc:labeller-b",
fetch,
});
await client.searchPackages({ q: "x" });
const headers = new Headers(calls[0]!.init?.headers);
expect(headers.get("atproto-accept-labelers")).toBe("did:plc:labeller-a, did:plc:labeller-b");
});
it("does not set atproto-accept-labelers when the option is omitted", async () => {
const { fetch, calls } = buildFetchStub({
"/xrpc/com.emdashcms.experimental.aggregator.searchPackages": {
status: 200,
body: { packages: [] },
},
});
const client = new DiscoveryClient({ aggregatorUrl: aggregator, fetch });
await client.searchPackages({ q: "x" });
const headers = new Headers(calls[0]!.init?.headers);
expect(headers.get("atproto-accept-labelers")).toBeNull();
});
it("throws ClientResponseError on non-2xx responses with the structured payload", async () => {
const { fetch } = buildFetchStub({
"/xrpc/com.emdashcms.experimental.aggregator.getPackage": {
status: 404,
body: { error: "PackageNotFound", message: "no such package" },
},
});
const client = new DiscoveryClient({ aggregatorUrl: aggregator, fetch });
try {
await client.getPackage({ did: "did:plc:xyz", slug: "missing" });
expect.fail("expected ClientResponseError");
} catch (err) {
expect(err).toBeInstanceOf(ClientResponseError);
const e = err as ClientResponseError;
expect(e.status).toBe(404);
expect(e.error).toBe("PackageNotFound");
expect(e.description).toBe("no such package");
}
});
it("hits each XRPC endpoint at the right path", async () => {
const { fetch, calls } = buildFetchStub({
"/xrpc/com.emdashcms.experimental.aggregator.getPackage": {
status: 200,
body: {
uri: "at://did:plc:abc/com.emdashcms.experimental.package.profile/gallery",
cid: CID,
did: "did:plc:abc",
slug: "gallery",
indexedAt: "2026-04-01T00:00:00Z",
profile: {},
},
},
"/xrpc/com.emdashcms.experimental.aggregator.resolvePackage": {
status: 200,
body: {
uri: "at://did:plc:abc/com.emdashcms.experimental.package.profile/gallery",
cid: CID,
did: "did:plc:abc",
slug: "gallery",
indexedAt: "2026-04-01T00:00:00Z",
profile: {},
},
},
"/xrpc/com.emdashcms.experimental.aggregator.listReleases": {
status: 200,
body: { releases: [], cursor: undefined },
},
"/xrpc/com.emdashcms.experimental.aggregator.getLatestRelease": {
status: 200,
body: {
uri: "at://did:plc:abc/com.emdashcms.experimental.package.release/gallery:1.0.0",
cid: CID,
did: "did:plc:abc",
package: "gallery",
version: "1.0.0",
indexedAt: "2026-04-01T00:00:00Z",
release: {},
},
},
});
const client = new DiscoveryClient({ aggregatorUrl: aggregator, fetch });
await client.getPackage({ did: "did:plc:abc", slug: "gallery" });
await client.resolvePackage({ handle: "alice.example.com", slug: "gallery" });
await client.listReleases({ did: "did:plc:abc", package: "gallery" });
await client.getLatestRelease({ did: "did:plc:abc", package: "gallery" });
const paths = calls.map((c) => new URL(c.url).pathname);
expect(paths).toEqual([
"/xrpc/com.emdashcms.experimental.aggregator.getPackage",
"/xrpc/com.emdashcms.experimental.aggregator.resolvePackage",
"/xrpc/com.emdashcms.experimental.aggregator.listReleases",
"/xrpc/com.emdashcms.experimental.aggregator.getLatestRelease",
]);
});
describe("record validation at the trust boundary", () => {
const validProfile = {
$type: "com.emdashcms.experimental.package.profile",
id: "at://did:plc:abc/com.emdashcms.experimental.package.profile/gallery",
type: "emdash-plugin",
license: "MIT",
authors: [{ name: "Alice", url: "https://alice.example" }],
security: [{ email: "security@example.com" }],
name: "Gallery",
description: "An image gallery.",
keywords: ["images"],
};
const validRelease = {
$type: "com.emdashcms.experimental.package.release",
package: "gallery",
version: "1.0.0",
artifacts: {
package: { url: "https://cdn.example/gallery-1.0.0.tgz", checksum: "bciqtest" },
},
};
it("returns the typed record for a conforming profile; extra keys pass through (non-stripping)", async () => {
const { fetch } = buildFetchStub({
"/xrpc/com.emdashcms.experimental.aggregator.getPackage": {
status: 200,
body: {
uri: "at://did:plc:abc/com.emdashcms.experimental.package.profile/gallery",
cid: CID,
did: "did:plc:abc",
slug: "gallery",
indexedAt: "2026-04-01T00:00:00Z",
profile: { ...validProfile, somethingTheAggregatorMadeUp: "ignored" },
},
},
});
const client = new DiscoveryClient({ aggregatorUrl: aggregator, fetch });
const result = await client.getPackage({ did: "did:plc:abc", slug: "gallery" });
expect(result.profile).not.toBeNull();
expect(result.profile?.name).toBe("Gallery");
expect(result.profile?.authors?.[0]?.name).toBe("Alice");
// Contract: atcute validation is non-stripping (the lexicon objects
// are open). Unrecognised keys are NOT removed — they pass through
// inert because consumers only read the typed lexicon fields. This
// asserts the boundary's actual behaviour so a future change to it
// is a deliberate, visible decision.
expect(result.profile).toHaveProperty("somethingTheAggregatorMadeUp");
});
it("returns null for a profile that does not conform to the lexicon", async () => {
const { fetch } = buildFetchStub({
"/xrpc/com.emdashcms.experimental.aggregator.getPackage": {
status: 200,
body: {
uri: "at://did:plc:abc/com.emdashcms.experimental.package.profile/x",
cid: CID,
did: "did:plc:abc",
slug: "x",
indexedAt: "2026-04-01T00:00:00Z",
// Missing every required field (id/type/license/authors/security).
profile: { name: "Looks fine but isn't" },
},
},
});
const client = new DiscoveryClient({ aggregatorUrl: aggregator, fetch });
const result = await client.getPackage({ did: "did:plc:abc", slug: "x" });
expect(result.profile).toBeNull();
});
it("returns null for a non-conforming release record (fail closed)", async () => {
// The envelope is valid and `release` is an object (lexicon
// `unknown` ≈ open object — a non-object is rejected earlier by
// envelope validation), but the record itself is missing every
// required package-release field, so `validateRelease` → null.
const { fetch } = buildFetchStub({
"/xrpc/com.emdashcms.experimental.aggregator.getLatestRelease": {
status: 200,
body: {
uri: "at://did:plc:abc/com.emdashcms.experimental.package.release/x:1.0.0",
cid: CID,
did: "did:plc:abc",
package: "x",
version: "1.0.0",
indexedAt: "2026-04-01T00:00:00Z",
release: { not: "a valid release record" },
},
},
});
const client = new DiscoveryClient({ aggregatorUrl: aggregator, fetch });
const result = await client.getLatestRelease({ did: "did:plc:abc", package: "x" });
expect(result.release).toBeNull();
});
it("does NOT sanitise URL schemes — a javascript: author url passes lexicon validation", async () => {
// Documents the boundary's contract: it validates *structure*, not
// URL safety (atproto's `uri` format permits any scheme). Consumers
// rendering these URLs MUST apply their own scheme allow-list.
const { fetch } = buildFetchStub({
"/xrpc/com.emdashcms.experimental.aggregator.getPackage": {
status: 200,
body: {
uri: "at://did:plc:abc/com.emdashcms.experimental.package.profile/gallery",
cid: CID,
did: "did:plc:abc",
slug: "gallery",
indexedAt: "2026-04-01T00:00:00Z",
profile: {
...validProfile,
authors: [{ name: "Mallory", url: "javascript:alert(document.cookie)" }],
},
},
},
});
const client = new DiscoveryClient({ aggregatorUrl: aggregator, fetch });
const result = await client.getPackage({ did: "did:plc:abc", slug: "gallery" });
expect(result.profile).not.toBeNull();
expect(result.profile?.authors?.[0]?.url).toBe("javascript:alert(document.cookie)");
});
it("validates each release in a listReleases page", async () => {
const { fetch } = buildFetchStub({
"/xrpc/com.emdashcms.experimental.aggregator.listReleases": {
status: 200,
body: {
releases: [
{
uri: "at://did:plc:abc/com.emdashcms.experimental.package.release/gallery:1.0.0",
cid: CID,
did: "did:plc:abc",
package: "gallery",
version: "1.0.0",
indexedAt: "2026-04-01T00:00:00Z",
release: validRelease,
},
{
uri: "at://did:plc:abc/com.emdashcms.experimental.package.release/gallery:0.9.0",
cid: CID,
did: "did:plc:abc",
package: "gallery",
version: "0.9.0",
indexedAt: "2026-03-01T00:00:00Z",
release: { garbage: true },
},
],
cursor: undefined,
},
},
});
const client = new DiscoveryClient({ aggregatorUrl: aggregator, fetch });
const result = await client.listReleases({ did: "did:plc:abc", package: "gallery" });
expect(result.releases[0]?.release?.version).toBe("1.0.0");
expect(result.releases[1]?.release).toBeNull();
});
});
});