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
76 lines
2.5 KiB
TypeScript
76 lines
2.5 KiB
TypeScript
/**
|
|
* Manifest assertions for the AT Protocol plugin.
|
|
*
|
|
* The redesigned sandboxed-plugin layout puts identity, trust contract,
|
|
* and admin surface in `emdash-plugin.jsonc` (the source of truth) and
|
|
* leaves `src/plugin.ts` for runtime code only. This test snapshots the
|
|
* manifest's structural shape so a refactor can't silently change the
|
|
* published trust contract or admin surface.
|
|
*/
|
|
|
|
import { readFile } from "node:fs/promises";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { version } from "../package.json";
|
|
|
|
const MANIFEST_PATH = fileURLToPath(new URL("../emdash-plugin.jsonc", import.meta.url));
|
|
|
|
interface Manifest {
|
|
slug: string;
|
|
version: string;
|
|
publisher: string;
|
|
capabilities: string[];
|
|
allowedHosts: string[];
|
|
storage: Record<string, { indexes: string[] }>;
|
|
admin: {
|
|
pages: Array<{ path: string; label: string; icon?: string }>;
|
|
widgets: Array<{ id: string; title?: string; size?: string }>;
|
|
};
|
|
}
|
|
|
|
async function loadManifest(): Promise<Manifest> {
|
|
const source = await readFile(MANIFEST_PATH, "utf8");
|
|
const errors: import("jsonc-parser").ParseError[] = [];
|
|
const value: unknown = parseJsonc(source, errors, {
|
|
allowTrailingComma: true,
|
|
disallowComments: false,
|
|
});
|
|
if (errors.length > 0) {
|
|
throw new Error(`Manifest parse failed: ${JSON.stringify(errors)}`);
|
|
}
|
|
return value as Manifest;
|
|
}
|
|
|
|
describe("atproto plugin manifest", () => {
|
|
it("declares the expected identity", async () => {
|
|
const manifest = await loadManifest();
|
|
expect(manifest.slug).toBe("atproto");
|
|
expect(manifest.version).toBe(version);
|
|
});
|
|
|
|
it("declares the required capabilities", async () => {
|
|
const manifest = await loadManifest();
|
|
expect(manifest.capabilities).toContain("content:read");
|
|
expect(manifest.capabilities).toContain("network:request:unrestricted");
|
|
});
|
|
|
|
it("declares the storage used by the runtime", async () => {
|
|
const manifest = await loadManifest();
|
|
expect(manifest.storage).toHaveProperty("records");
|
|
expect(manifest.storage.records.indexes).toContain("contentId");
|
|
expect(manifest.storage.records.indexes).toContain("status");
|
|
expect(manifest.storage.records.indexes).toContain("lastSyncedAt");
|
|
});
|
|
|
|
it("declares the admin pages and widgets", async () => {
|
|
const manifest = await loadManifest();
|
|
expect(manifest.admin.pages).toHaveLength(1);
|
|
expect(manifest.admin.pages[0]?.path).toBe("/status");
|
|
expect(manifest.admin.widgets).toHaveLength(1);
|
|
expect(manifest.admin.widgets[0]?.id).toBe("sync-status");
|
|
});
|
|
});
|