test(e2e): fingerprint executed plugin CLI source and loader

This commit is contained in:
Dotta committed 2026-10-28 08:15:28 -05:00
1 parent 250ba62791
commit efa3382b21
7 files changed
+50 -11

No files matched your search

@@ -3770,7 +3770,7 @@
"chat-catalog-domains/external-support.ts",
"chat-catalog-domains/internal-support.ts",
"chat-catalog-domains/identity-support.ts",
"../../cli/dist/index.js",
"../../cli/node_modules/tsx/dist/loader.mjs",
"../../cli/src/index.ts",
"../../packages/plugins/create-paperclip-plugin/src/index.ts",
"../../packages/plugins/sdk/src/index.ts",
@@ -3792,7 +3792,7 @@
"chat-catalog-domains/external-support.ts",
"chat-catalog-domains/internal-support.ts",
"chat-catalog-domains/identity-support.ts",
"../../cli/dist/index.js",
"../../cli/node_modules/tsx/dist/loader.mjs",
"../../cli/src/index.ts",
"../../packages/plugins/create-paperclip-plugin/src/index.ts",
"../../packages/plugins/sdk/src/index.ts",
@@ -3814,7 +3814,7 @@
"chat-catalog-domains/external-support.ts",
"chat-catalog-domains/internal-support.ts",
"chat-catalog-domains/identity-support.ts",
"../../cli/dist/index.js",
"../../cli/node_modules/tsx/dist/loader.mjs",
"../../cli/src/index.ts",
"../../packages/plugins/create-paperclip-plugin/src/index.ts",
"../../packages/plugins/sdk/src/index.ts",
@@ -3837,7 +3837,7 @@
"chat-catalog-domains/internal-support.ts",
"chat-catalog-domains/identity-support.ts",
"chat-flow.ts",
"../../cli/dist/index.js",
"../../cli/node_modules/tsx/dist/loader.mjs",
"../../cli/src/index.ts",
"../../packages/plugins/create-paperclip-plugin/src/index.ts",
"../../packages/plugins/sdk/src/index.ts",
@@ -4611,7 +4611,7 @@
"chat-catalog-domains/internal-definition.ts",
"chat-catalog-domains/internal-support.ts",
"chat-catalog-domains/integrator-support.ts",
"../../cli/dist/index.js",
"../../cli/node_modules/tsx/dist/loader.mjs",
"../../cli/src/index.ts",
"../../packages/plugins/create-paperclip-plugin/src/index.ts",
"../../packages/plugins/sdk/src/index.ts",
@@ -4890,7 +4890,6 @@
"../../server/src/services/agent-instance-secrets.ts",
"../../server/src/services/agent-lifecycle-driver.ts",
"../../server/src/services/heartbeat/run-preparation.ts",
"../../cli/dist/index.js",
"../../cli/src/index.ts",
"../../cli/node_modules/tsx/dist/loader.mjs",
"chat-catalog-domains/external-plugin-sdk-mirror.ts",
@@ -33,7 +33,7 @@ export const pluginInstallExternalCases = [definition("plugins.install-plugin",
return { pluginKey: fixture.key, syntheticPackage: true, agentInstallStatus };
} catch (error) { fixtures.delete(ctx); await fixture.close(); throw error; }
}, gradePluginInstall, {
sourceFiles: ["chat-catalog-domains/external-plugin-install.ts", "chat-catalog-domains/external-plugin-fixture.ts", "chat-catalog-domains/external-support.ts", "chat-catalog-domains/internal-support.ts", "chat-catalog-domains/identity-support.ts", "../../cli/dist/index.js", "../../cli/src/index.ts", "../../packages/plugins/create-paperclip-plugin/src/index.ts", "../../packages/plugins/sdk/src/index.ts", "../../packages/plugins/sdk/dist/index.js", "chat-scenarios/plugins.yaml"],
sourceFiles: ["chat-catalog-domains/external-plugin-install.ts", "chat-catalog-domains/external-plugin-fixture.ts", "chat-catalog-domains/external-support.ts", "chat-catalog-domains/internal-support.ts", "chat-catalog-domains/identity-support.ts", "../../cli/node_modules/tsx/dist/loader.mjs", "../../cli/src/index.ts", "../../packages/plugins/create-paperclip-plugin/src/index.ts", "../../packages/plugins/sdk/src/index.ts", "../../packages/plugins/sdk/dist/index.js", "chat-scenarios/plugins.yaml"],
async act(ctx, state) {
await waitForReply(ctx);
const fixture = fixtures.get(ctx); if (!fixture) throw new Error("Vetted package fixture is missing");
@@ -57,7 +57,7 @@ export const pluginToolExternalCases=[definition("plugins.use-plugin-tool",async
}catch(error){fixtures.delete(ctx);try{if(profileId)await ctx.input.api.post(`${root(ctx)}/tools/profiles/${profileId}/unbind`,{targetType:"agent",targetId:ctx.input.fixtures.agent.id});if(installedId)await ctx.input.api.delete(`/api/plugins/${installedId}`,{allowNotFound:true});}finally{await scaffold.close();}throw error;}
},gradePluginTool,{
expectedRunCount:2,expectedHumanMessageCount:2,attemptTimeoutMs:480000,
sourceFiles:["chat-catalog-domains/external-plugin-tool.ts","chat-catalog-domains/external-plugin-fixture.ts","chat-catalog-domains/external-support.ts","chat-catalog-domains/internal-support.ts","chat-catalog-domains/identity-support.ts","chat-flow.ts","../../cli/dist/index.js","../../cli/src/index.ts","../../packages/plugins/create-paperclip-plugin/src/index.ts","../../packages/plugins/sdk/src/index.ts","../../packages/plugins/sdk/dist/index.js","chat-scenarios/plugins.yaml"],
sourceFiles:["chat-catalog-domains/external-plugin-tool.ts","chat-catalog-domains/external-plugin-fixture.ts","chat-catalog-domains/external-support.ts","chat-catalog-domains/internal-support.ts","chat-catalog-domains/identity-support.ts","chat-flow.ts","../../cli/node_modules/tsx/dist/loader.mjs","../../cli/src/index.ts","../../packages/plugins/create-paperclip-plugin/src/index.ts","../../packages/plugins/sdk/src/index.ts","../../packages/plugins/sdk/dist/index.js","chat-scenarios/plugins.yaml"],
async act(ctx,state){
await waitForReply(ctx);const first=await observeExternal(ctx,state),reply=first.phaseReplies.at(-1);if(!reply)throw new Error("Initial source preview reply missing");state.initialRunId=reply.runId;
const href=previewLink(first.links,first.origin,state.companyPrefix,state.route);state.initialPreviewLinkMatched=!!href;if(!href)throw new Error("Actual plugin preview link missing from model reply");const source=ctx.input.page.url();await ctx.input.page.goto(new URL(href,first.origin).href,{waitUntil:"domcontentloaded"});await expect(ctx.input.page.getByRole("heading",{name:"Synthetic website preview",exact:true})).toBeVisible();await expect(ctx.input.page.frameLocator('iframe[title="Website preview"]').getByRole("heading",{name:"Synthetic website",exact:true})).toBeVisible();state.initialPreviewVisible=true;await ctx.input.page.goto(source,{waitUntil:"domcontentloaded"});
@@ -58,7 +58,7 @@ export const pluginExternalCases = ["configure-plugin", "disable-plugin"].map(id
resultPath: `/${ctx.input.fixtures.company.issuePrefix}/company/settings/instance/plugins/${plugin.id}`, resultLabel: "Website Preview Eval" };
} catch (error) { fixtures.delete(ctx); await fixture.close(); throw error; }
}, gradePluginManagement, {
sourceFiles: ["chat-catalog-domains/external-plugins.ts", "chat-catalog-domains/external-plugin-fixture.ts", "chat-catalog-domains/external-support.ts", "chat-catalog-domains/internal-support.ts", "chat-catalog-domains/identity-support.ts", "../../cli/dist/index.js", "../../cli/src/index.ts", "../../packages/plugins/create-paperclip-plugin/src/index.ts", "../../packages/plugins/sdk/src/index.ts", "../../packages/plugins/sdk/dist/index.js", "chat-scenarios/plugins.yaml"],
sourceFiles: ["chat-catalog-domains/external-plugins.ts", "chat-catalog-domains/external-plugin-fixture.ts", "chat-catalog-domains/external-support.ts", "chat-catalog-domains/internal-support.ts", "chat-catalog-domains/identity-support.ts", "../../cli/node_modules/tsx/dist/loader.mjs", "../../cli/src/index.ts", "../../packages/plugins/create-paperclip-plugin/src/index.ts", "../../packages/plugins/sdk/src/index.ts", "../../packages/plugins/sdk/dist/index.js", "chat-scenarios/plugins.yaml"],
async act(ctx, state) {
await waitForReply(ctx);
const before = await ctx.input.api.get<PluginRecord>(`/api/plugins/${state.pluginId}`);
@@ -322,7 +322,6 @@ export const secretDeliveryExternalCases = [
"../../server/src/services/agent-instance-secrets.ts",
"../../server/src/services/agent-lifecycle-driver.ts",
"../../server/src/services/heartbeat/run-preparation.ts",
"../../cli/dist/index.js",
"../../cli/src/index.ts",
"../../cli/node_modules/tsx/dist/loader.mjs",
"chat-catalog-domains/external-plugin-sdk-mirror.ts",
@@ -73,5 +73,5 @@ export const integratorPluginRoutineCases = [integratorDefinition("routines", "r
} catch (error) { await cleanup(ctx); throw error; }
}, observeManaged, gradeIntegratorPluginRoutine, async (ctx, state) => {
const routine = state.routine as RoutineDetail; await inspectResult(ctx, `/${ctx.input.fixtures.company.issuePrefix}/routines/${routine.id}/history`, routine.title);
}, { sourceFiles: ["chat-catalog-domains/integrator-plugin-routine.ts", "chat-catalog-domains/external-plugin-fixture.ts", "chat-catalog-domains/identity-support.ts", "chat-catalog-domains/internal-definition.ts", "chat-catalog-domains/internal-support.ts", "chat-catalog-domains/integrator-support.ts", "../../cli/dist/index.js", "../../cli/src/index.ts", "../../packages/plugins/create-paperclip-plugin/src/index.ts", "../../packages/plugins/sdk/src/index.ts", "../../packages/plugins/sdk/dist/index.js", "chat-scenarios/routines.yaml"], cleanup,
}, { sourceFiles: ["chat-catalog-domains/integrator-plugin-routine.ts", "chat-catalog-domains/external-plugin-fixture.ts", "chat-catalog-domains/identity-support.ts", "chat-catalog-domains/internal-definition.ts", "chat-catalog-domains/internal-support.ts", "chat-catalog-domains/integrator-support.ts", "../../cli/node_modules/tsx/dist/loader.mjs", "../../cli/src/index.ts", "../../packages/plugins/create-paperclip-plugin/src/index.ts", "../../packages/plugins/sdk/src/index.ts", "../../packages/plugins/sdk/dist/index.js", "chat-scenarios/routines.yaml"], cleanup,
})];
@@ -0,0 +1,41 @@
import { createHash } from "node:crypto";
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { afterEach, expect, it } from "vitest";
import { fingerprintChatDependencyClosure } from "./chat-catalog-fingerprint.js";
import { findChatCatalogCase, loadChatCatalogImplementation } from "./chat-catalog-registry.js";
const selected = ["plugins.configure-plugin", "plugins.disable-plugin", "plugins.install-plugin", "plugins.use-plugin-tool", "routines.plugin-managed-routine", "secrets.least-scope-delivery"];
const directories: string[] = [];
afterEach(() => { for (const directory of directories.splice(0)) rmSync(directory, { recursive: true, force: true }); });
it.each(selected)("%s binds its actual source CLI, loader and required built SDK without unused CLI dist", async id => {
const descriptor = findChatCatalogCase(id)!;
const implementation = await loadChatCatalogImplementation(descriptor);
expect(implementation.sourceFiles).toEqual(descriptor.sourceFiles);
expect(descriptor.sourceFiles).toEqual(expect.arrayContaining(["../../cli/src/index.ts", "../../cli/node_modules/tsx/dist/loader.mjs", "../../packages/plugins/sdk/dist/index.js"]));
expect(descriptor.sourceFiles).not.toContain("../../cli/dist/index.js");
});
it("resolves and hashes the actual executed source CLI and loader dependency closure without running either", () => {
const root = fileURLToPath(new URL("../../", import.meta.url));
const entries = ["cli/src/index.ts", "cli/node_modules/tsx/dist/loader.mjs"];
const fingerprint = fingerprintChatDependencyClosure({ root, entries, selected: { fixture: "source-cli" } });
for (const file of [...entries, "cli/src/commands/client/plugin.ts"]) {
expect(fingerprint.sources).toContainEqual({ file, sha256: createHash("sha256").update(readFileSync(path.join(root, file))).digest("hex") });
}
});
it("still rejects missing actual source imports, the executed loader and required SDK artifact", () => {
const root = mkdtempSync(path.join(tmpdir(), "plugin-source-closure-")); directories.push(root);
mkdirSync(path.join(root, "cli/src"), { recursive: true });
writeFileSync(path.join(root, "cli/src/index.ts"), 'export { command } from "./command.js";');
const fingerprint = (entries: string[]) => fingerprintChatDependencyClosure({ root, entries, selected: {} });
expect(() => fingerprint(["cli/src/index.ts"])).toThrow("Unresolved fingerprint dependency: ./command.js");
writeFileSync(path.join(root, "cli/src/command.ts"), "export const command = 1;");
expect(fingerprint(["cli/src/index.ts"]).sources.map(row => row.file)).toContain("cli/src/command.ts");
expect(() => fingerprint(["cli/src/index.ts", "cli/node_modules/tsx/dist/loader.mjs"])).toThrow("Unresolved fingerprint dependency");
expect(() => fingerprint(["cli/src/index.ts", "packages/plugins/sdk/dist/index.js"])).toThrow("Unresolved fingerprint dependency");
});