Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,160 @@
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import type { WasmSandboxHandle } from "@suaveplan/api-sandbox-wasm";
import { createSandboxedWasmPluginModule } from "@suaveplan/api-sandbox-wasm-host-bridge";
import { describe, expect, it } from "@suaveplan/testing/runner";
import { compilePluginToWasm } from "../../src/index.js";
const HERE = dirname(fileURLToPath(import.meta.url));
const EXAMPLE_ENTRY = resolve(
HERE,
"../../../api-plugin-example/src/wasm/index.ts"
);
const MANIFEST = {
name: "@suaveplan/api-plugin-example",
version: "0.1.0",
apiHost: "@suaveplan/api-host@0" as const,
isolation: "sandboxed" as const,
trustLevel: "first-party" as const,
requiredCapabilities: ["example:read", "example:write"],
};
function createSandboxStub(): WasmSandboxHandle {
let disposed = false;
return {
pluginName: MANIFEST.name,
instance: { exports: {} } as unknown as WebAssembly.Instance,
preopens: [],
outbound: { allow: () => false } as never,
dispose: () => {
disposed = true;
},
get disposed() {
return disposed;
},
} as unknown as WasmSandboxHandle;
}
describe("api-plugin-example (KEYSTONE Stage-8 gate 2.8)", () => {
it("compiled WASM deploys via wasi-stdio bridge", {
timeout: 120000,
}, async () => {
const outDir = mkdtempSync(join(tmpdir(), "example-wasm-"));
const result = await compilePluginToWasm({
pluginEntry: EXAMPLE_ENTRY,
outDir,
pluginName: "@suaveplan/api-plugin-example",
});
// Source-declared routes (see api-plugin-example/src/wasm/routes.ts)
const expectedPaths = [
"/example/public/ping",
"/example/health",
"/example/invoices",
"/example/invoices/:id",
"/example/invoices",
"/example/invoices/:id/finalise",
"/example/rate-limited",
"/example/subscription-required",
"/_/test/fail-response",
];
const havePaths = result.manifest.routes.map((r) => r.path);
for (const p of expectedPaths) {
expect(havePaths).toContain(p);
}
expect(result.manifest.routes.length).toBeGreaterThanOrEqual(9);
expect(result.manifest.abiVariant).toBe("wasi-stdio");
const wasmBytes = new Uint8Array(readFileSync(result.wasmPath));
const wasmModule = await WebAssembly.compile(wasmBytes);
const sandbox = createSandboxStub();
const install = createSandboxedWasmPluginModule({
abiVariant: "wasi-stdio",
sandbox,
manifest: MANIFEST,
wasmModule,
});
expect(install.routes.length).toBeGreaterThanOrEqual(9);
install.lifecycle.start();
// 1. Public ping — no auth required
const pingRoute = install.routes.find(
(r) => r.path === "/example/public/ping"
);
expect(pingRoute).toBeDefined();
const pingResp = await (
pingRoute as never as {
handler: (c: unknown) => Promise<Response>;
}
).handler(buildCtx("https://example.com/example/public/ping", "GET"));
expect(pingResp.status).toBe(200);
const pingBody = await pingResp.json();
expect(pingBody.ok).toBe(true);
// 2. Health
const healthRoute = install.routes.find(
(r) => r.path === "/example/health"
);
const healthResp = await (
healthRoute as never as {
handler: (c: unknown) => Promise<Response>;
}
).handler(buildCtx("https://example.com/example/health", "GET"));
expect(healthResp.status).toBe(200);
// 3. RBAC-protected list (missing scope → 403)
const listRoute = install.routes.find(
(r) => r.path === "/example/invoices" && r.method === "GET"
);
const denyResp = await (
listRoute as never as {
handler: (c: unknown) => Promise<Response>;
}
).handler(buildCtx("https://example.com/example/invoices", "GET"));
expect(denyResp.status).toBe(403);
// 4. RBAC-protected list with scope → 200 with empty array
const grantResp = await (
listRoute as never as {
handler: (c: unknown) => Promise<Response>;
}
).handler(
buildCtx("https://example.com/example/invoices", "GET", undefined, [
"example:read",
])
);
expect(grantResp.status).toBe(200);
const grantBody = await grantResp.json();
expect(Array.isArray(grantBody.invoices)).toBe(true);
install.lifecycle.dispose();
rmSync(outDir, { recursive: true, force: true });
});
});
function buildCtx(
url: string,
method: string,
body?: string,
capabilities: readonly string[] = []
): unknown {
const init: RequestInit = { method };
if (body !== undefined) init.body = body;
// The bridge's marshal reads `Object.keys(ctx.capabilities)` to
// produce the serialized array — model the host's
// RequestContext shape (capability map) accordingly.
const capMap: Record<string, true> = {};
for (const c of capabilities) capMap[c] = true;
return {
request: new Request(url, init),
params: {},
plugin: { name: MANIFEST.name },
principal: null,
tenant: null,
capabilities: capMap,
requestId: "ex-req-1",
timestamp: Date.now(),
};
}