test: cherry-pick agent JWT plugin-tool regression tests from #5549

Adds focused regression tests for the agent JWT plugin tool access
fix in this PR:

- agent JWT can list available plugin tools (GET /api/plugins/tools)
- agent JWT can execute a tool within its company scope
- agent JWT cannot call execute with runContext.companyId outside its
  authenticated company
- agent JWT cannot call execute with a runContext.agentId that does
  not belong to runContext.companyId

Cherry-picked from #5549 and adapted to this PR's symbol names
(assertBoardOrAgent) and three-row validateToolRunContextScope queue.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin FoleyandPaperclip committed 2026-06-03 19:02:30 -07:00
1 parent 2e34a77980
commit 8582881777
1 file changed
+118
@@ -912,4 +912,122 @@ describe.sequential("plugin tool and bridge authz", () => {
expect(res.body).toEqual({ runId: "run-1", jobId: "job-1" });
expect(scheduler.triggerJob).toHaveBeenCalledWith("job-1", "manual");
});
// ─── Agent JWT tool execution (cherry-picked from #5549) ─────────────────────
it("allows agent JWT to list available plugin tools", async () => {
const listToolsForAgent = vi.fn(() => []);
const { app } = await createApp(agentActor(), {}, {
toolDeps: {
toolDispatcher: {
listToolsForAgent,
getTool: vi.fn(),
executeTool: vi.fn(),
},
},
});
const res = await request(app).get("/api/plugins/tools");
expect(res.status).toBe(200);
expect(listToolsForAgent).toHaveBeenCalled();
});
it("allows agent JWT to execute a tool within its company scope", async () => {
const executeTool = vi.fn().mockResolvedValue({ content: "ok" });
const { app } = await createApp(
agentActor(),
{},
{
db: createSelectQueueDb([
[{ companyId: companyA }],
[{ companyId: companyA, agentId: agentA }],
[{ companyId: companyA }],
]),
toolDeps: {
toolDispatcher: {
listToolsForAgent: vi.fn(),
getTool: vi.fn(() => ({ name: "paperclip.example:search", pluginDbId: pluginId })),
executeTool,
},
},
},
);
const res = await request(app)
.post("/api/plugins/tools/execute")
.send({
tool: "paperclip.example:search",
parameters: { q: "test" },
runContext: { agentId: agentA, runId: runA, companyId: companyA, projectId: projectA },
});
expect(res.status).toBe(200);
expect(executeTool).toHaveBeenCalledWith(
"paperclip.example:search",
{ q: "test" },
{ agentId: agentA, runId: runA, companyId: companyA, projectId: projectA },
);
});
it("rejects agent JWT when runContext.companyId is outside the agent's company scope", async () => {
const executeTool = vi.fn();
const { app } = await createApp(
agentActor(),
{},
{
db: createSelectQueueDb([]),
toolDeps: {
toolDispatcher: {
listToolsForAgent: vi.fn(),
getTool: vi.fn(() => ({ name: "paperclip.example:search", pluginDbId: pluginId })),
executeTool,
},
},
},
);
const res = await request(app)
.post("/api/plugins/tools/execute")
.send({
tool: "paperclip.example:search",
parameters: {},
runContext: { agentId: agentA, runId: runA, companyId: companyB, projectId: projectA },
});
expect(res.status).toBe(403);
expect(executeTool).not.toHaveBeenCalled();
});
it("rejects agent JWT when runContext.agentId does not belong to runContext.companyId", async () => {
const otherAgent = "77777777-7777-4777-8777-777777777777";
const executeTool = vi.fn();
const { app } = await createApp(
agentActor(),
{},
{
db: createSelectQueueDb([
[{ companyId: companyB }],
]),
toolDeps: {
toolDispatcher: {
listToolsForAgent: vi.fn(),
getTool: vi.fn(() => ({ name: "paperclip.example:search", pluginDbId: pluginId })),
executeTool,
},
},
},
);
const res = await request(app)
.post("/api/plugins/tools/execute")
.send({
tool: "paperclip.example:search",
parameters: {},
runContext: { agentId: otherAgent, runId: runA, companyId: companyA, projectId: projectA },
});
expect(res.status).toBe(403);
expect(executeTool).not.toHaveBeenCalled();
});
});