+
{(() => {
const renderInboxIssue = ({
issue,
From deba60ebb23483c7316db95dccc1ea4218986578 Mon Sep 17 00:00:00 2001
From: Dotta <34892728+cryppadotta@users.noreply.github.com>
Date: Fri, 24 Apr 2026 19:27:00 -0500
Subject: [PATCH 5/5] Stabilize serialized server route tests (#4448)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The server route suite is a core confidence layer for auth, issue
context, and workspace runtime behavior
> - Some route tests were doing extra module/server isolation work that
made local runs slower and more fragile
> - The stable Vitest runner also needs to pass server-relative exclude
paths to avoid accidentally re-including serialized suites
> - This pull request tightens route test isolation and runner
serialization behavior
> - The benefit is more reliable targeted and stable-route test
execution without product behavior changes
## What Changed
- Updated `run-vitest-stable.mjs` to exclude serialized server tests
using server-relative paths.
- Forced the server Vitest config to use a single worker in addition to
isolated forks.
- Simplified agent permission route tests to create per-request test
servers without shared server lifecycle state.
- Stabilized issue goal context route mocks by using static mocked
services and a sequential suite.
- Re-registered workspace runtime route mocks before cache-busted route
imports.
## Verification
- `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/agent-permissions-routes.test.ts
server/src/__tests__/issues-goal-context-routes.test.ts
server/src/__tests__/workspace-runtime-routes-authz.test.ts --pool=forks
--poolOptions.forks.isolate=true`
- `node --check scripts/run-vitest-stable.mjs`
## Risks
- Low risk. This is test infrastructure only.
- The stable runner path fix changes which tests are excluded from the
non-serialized server batch, matching the server project root that
Vitest applies internally.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5 coding agent, tool-enabled with
shell/GitHub/Paperclip API access. Context window was not reported by
the runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip
---
scripts/run-vitest-stable.mjs | 12 +-
.../agent-permissions-routes.test.ts | 702 ++++++------------
.../issues-goal-context-routes.test.ts | 165 ++--
.../workspace-runtime-routes-authz.test.ts | 29 +
server/vitest.config.ts | 2 +
5 files changed, 335 insertions(+), 575 deletions(-)
diff --git a/scripts/run-vitest-stable.mjs b/scripts/run-vitest-stable.mjs
index 307a9c070e9..b9473434a64 100644
--- a/scripts/run-vitest-stable.mjs
+++ b/scripts/run-vitest-stable.mjs
@@ -5,6 +5,7 @@ import os from "node:os";
import path from "node:path";
const repoRoot = process.cwd();
+const serverRoot = path.join(repoRoot, "server");
const serverTestsDir = path.join(repoRoot, "server", "src", "__tests__");
const nonServerProjects = [
"@paperclipai/shared",
@@ -63,6 +64,10 @@ function toRepoPath(file) {
return path.relative(repoRoot, file).split(path.sep).join("/");
}
+function toServerPath(file) {
+ return path.relative(serverRoot, file).split(path.sep).join("/");
+}
+
function isRouteOrAuthzTest(file) {
if (routeTestPattern.test(file)) {
return true;
@@ -99,10 +104,13 @@ function runVitest(args, label) {
const routeTests = walk(serverTestsDir)
.filter((file) => isRouteOrAuthzTest(toRepoPath(file)))
- .map((file) => ({ repoPath: toRepoPath(file) }))
+ .map((file) => ({
+ repoPath: toRepoPath(file),
+ serverPath: toServerPath(file),
+ }))
.sort((a, b) => a.repoPath.localeCompare(b.repoPath));
-const excludeRouteArgs = routeTests.flatMap((file) => ["--exclude", file.repoPath]);
+const excludeRouteArgs = routeTests.flatMap((file) => ["--exclude", file.serverPath]);
for (const project of nonServerProjects) {
runVitest(["--project", project], `non-server project ${project}`);
}
diff --git a/server/src/__tests__/agent-permissions-routes.test.ts b/server/src/__tests__/agent-permissions-routes.test.ts
index 2e2014ee1b2..e5afd65fb57 100644
--- a/server/src/__tests__/agent-permissions-routes.test.ts
+++ b/server/src/__tests__/agent-permissions-routes.test.ts
@@ -1,7 +1,6 @@
-import type { Server } from "node:http";
import express from "express";
import request from "supertest";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { beforeEach, describe, expect, it, vi } from "vitest";
const agentId = "11111111-1111-4111-8111-111111111111";
const companyId = "22222222-2222-4222-8222-222222222222";
@@ -61,10 +60,6 @@ const mockBudgetService = vi.hoisted(() => ({
upsertPolicy: vi.fn(),
}));
-const mockEnvironmentService = vi.hoisted(() => ({
- getById: vi.fn(),
-}));
-
const mockHeartbeatService = vi.hoisted(() => ({
listTaskSessions: vi.fn(),
resetRuntimeSession: vi.fn(),
@@ -98,16 +93,15 @@ const mockTrackAgentCreated = vi.hoisted(() => vi.fn());
const mockGetTelemetryClient = vi.hoisted(() => vi.fn());
const mockSyncInstructionsBundleConfigFromFilePath = vi.hoisted(() => vi.fn());
const mockEnsureOpenCodeModelConfiguredAndAvailable = vi.hoisted(() => vi.fn());
+const mockEnvironmentService = vi.hoisted(() => ({
+ getById: vi.fn(),
+}));
const mockInstanceSettingsService = vi.hoisted(() => ({
getGeneral: vi.fn(),
}));
function registerModuleMocks() {
- vi.doMock("../routes/agents.js", async () => vi.importActual("../routes/agents.js"));
- vi.doMock("../routes/authz.js", async () => vi.importActual("../routes/authz.js"));
- vi.doMock("../adapters/index.js", async () => vi.importActual("../adapters/index.js"));
- vi.doMock("../middleware/index.js", async () => vi.importActual("../middleware/index.js"));
vi.doMock("@paperclipai/adapter-opencode-local/server", async () => {
const actual = await vi.importActual("@paperclipai/adapter-opencode-local/server");
return {
@@ -219,44 +213,53 @@ function createDbStub(options: { requireBoardApprovalForNewAgents?: boolean } =
};
}
-let sharedServer: Server | null = null;
-
-async function closeSharedServer() {
- if (!sharedServer) return;
- const server = sharedServer;
- sharedServer = null;
- await new Promise((resolve, reject) => {
- server.close((err) => {
- if (err) reject(err);
- else resolve();
- });
- });
-}
-
async function createApp(actor: Record, dbOptions: { requireBoardApprovalForNewAgents?: boolean } = {}) {
- await closeSharedServer();
const [{ errorHandler }, { agentRoutes }] = await Promise.all([
- import("../middleware/index.js"),
- import("../routes/agents.js"),
+ import("../middleware/index.js") as Promise,
+ import("../routes/agents.js") as Promise,
]);
const app = express();
app.use(express.json());
app.use((req, _res, next) => {
- (req as any).actor = actor;
+ (req as any).actor = {
+ ...actor,
+ companyIds: Array.isArray(actor.companyIds) ? [...actor.companyIds] : actor.companyIds,
+ };
next();
});
app.use("/api", agentRoutes(createDbStub(dbOptions) as any));
app.use(errorHandler);
- sharedServer = app.listen(0, "127.0.0.1");
- await new Promise((resolve) => {
- sharedServer?.once("listening", resolve);
- });
- return sharedServer;
+ return app;
+}
+
+async function requestApp(
+ app: express.Express,
+ buildRequest: (baseUrl: string) => request.Test,
+) {
+ const { createServer } = await vi.importActual("node:http");
+ const server = createServer(app);
+ try {
+ await new Promise((resolve) => {
+ server.listen(0, "127.0.0.1", resolve);
+ });
+ const address = server.address();
+ if (!address || typeof address === "string") {
+ throw new Error("Expected HTTP server to listen on a TCP port");
+ }
+ return await buildRequest(`http://127.0.0.1:${address.port}`);
+ } finally {
+ if (server.listening) {
+ await new Promise((resolve, reject) => {
+ server.close((error) => {
+ if (error) reject(error);
+ else resolve();
+ });
+ });
+ }
+ }
}
describe.sequential("agent permission routes", () => {
- afterEach(closeSharedServer);
-
beforeEach(() => {
vi.resetModules();
vi.doUnmock("@paperclipai/shared/telemetry");
@@ -316,6 +319,7 @@ describe.sequential("agent permission routes", () => {
mockGetTelemetryClient.mockReset();
mockSyncInstructionsBundleConfigFromFilePath.mockReset();
mockInstanceSettingsService.getGeneral.mockReset();
+ mockEnvironmentService.getById.mockReset();
mockEnsureOpenCodeModelConfiguredAndAvailable.mockReset();
mockSyncInstructionsBundleConfigFromFilePath.mockImplementation((_agent, config) => config);
mockGetTelemetryClient.mockReturnValue({ track: vi.fn() });
@@ -388,7 +392,7 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app).get(`/api/agents/${agentId}`);
+ const res = await requestApp(app, (baseUrl) => request(baseUrl).get(`/api/agents/${agentId}`));
expect(res.status).toBe(200);
expect(res.body.adapterConfig).toEqual({});
@@ -406,7 +410,7 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app).get(`/api/companies/${companyId}/agents`);
+ const res = await requestApp(app, (baseUrl) => request(baseUrl).get(`/api/companies/${companyId}/agents`));
expect(res.status).toBe(200);
expect(res.body).toEqual([
@@ -429,9 +433,9 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.patch(`/api/agents/${agentId}`)
- .send({ title: "Compromised" });
+ .send({ title: "Compromised" }));
expect(res.status).toBe(403);
});
@@ -447,9 +451,9 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.post(`/api/agents/${agentId}/keys`)
- .send({ name: "backdoor" });
+ .send({ name: "backdoor" }));
expect(res.status).toBe(403);
});
@@ -465,9 +469,9 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.post(`/api/agents/${agentId}/wakeup`)
- .send({});
+ .send({}));
expect(res.status).toBe(403);
});
@@ -481,7 +485,7 @@ describe.sequential("agent permission routes", () => {
runId: "run-1",
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.patch(`/api/agents/${agentId}`)
.send({
adapterConfig: {
@@ -490,7 +494,7 @@ describe.sequential("agent permission routes", () => {
provisionCommand: "touch /tmp/paperclip-rce",
},
},
- });
+ }));
expect(res.status).toBe(403);
expect(res.body.error).toContain("host-executed workspace commands");
@@ -506,14 +510,14 @@ describe.sequential("agent permission routes", () => {
runId: "run-1",
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.patch(`/api/agents/${agentId}`)
.send({
adapterConfig: {
instructionsRootPath: "/etc",
instructionsEntryFile: "passwd",
},
- });
+ }));
expect(res.status).toBe(403);
expect(res.body.error).toContain("instructions path or bundle configuration");
@@ -529,9 +533,9 @@ describe.sequential("agent permission routes", () => {
runId: "run-1",
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.patch(`/api/agents/${agentId}/instructions-path`)
- .send({ path: "/etc/passwd" });
+ .send({ path: "/etc/passwd" }));
expect(res.status).toBe(403);
expect(res.body.error).toContain("instructions path or bundle configuration");
@@ -549,7 +553,7 @@ describe.sequential("agent permission routes", () => {
runId: "run-1",
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.post(`/api/companies/${companyId}/agent-hires`)
.send({
name: "Injected",
@@ -559,7 +563,7 @@ describe.sequential("agent permission routes", () => {
instructionsRootPath: "/etc",
instructionsEntryFile: "passwd",
},
- });
+ }));
expect(res.status).toBe(403);
expect(res.body.error).toContain("instructions path or bundle configuration");
@@ -578,14 +582,14 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.post(`/api/companies/${companyId}/agents`)
.send({
name: "Backdoor",
role: "engineer",
adapterType: "process",
adapterConfig: {},
- });
+ }));
expect(res.status).toBe(403);
expect(res.body.error).toContain("agents:create");
@@ -604,16 +608,16 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.post(`/api/companies/${companyId}/agents`)
.send({
name: "Builder",
role: "engineer",
adapterType: "process",
adapterConfig: {},
- });
+ }));
- expect([200, 201]).toContain(res.status);
+ expect(res.status, JSON.stringify(res.body)).toBe(201);
expect(mockAgentService.create).toHaveBeenCalledWith(
companyId,
expect.objectContaining({
@@ -642,14 +646,14 @@ describe.sequential("agent permission routes", () => {
{ requireBoardApprovalForNewAgents: true },
);
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.post(`/api/companies/${companyId}/agents`)
.send({
name: "Builder",
role: "engineer",
adapterType: "process",
adapterConfig: {},
- });
+ }));
expect(res.status).toBe(409);
expect(res.body.error).toContain("/agent-hires");
@@ -667,14 +671,14 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.post(`/api/companies/${companyId}/agents`)
.send({
name: "Builder",
role: "engineer",
adapterType: "process",
adapterConfig: {},
- });
+ }));
expect([200, 201]).toContain(res.status);
expect(mockAccessService.ensureMembership).toHaveBeenCalledWith(
@@ -703,9 +707,9 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.get(`/api/companies/${companyId}/agents`)
- .query({ urlKey: "builder" });
+ .query({ urlKey: "builder" }));
expect(res.status).toBe(400);
expect(res.body.error).toContain("urlKey");
@@ -721,7 +725,7 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.post(`/api/companies/${companyId}/agents`)
.send({
name: "Builder",
@@ -733,7 +737,7 @@ describe.sequential("agent permission routes", () => {
intervalSec: 3600,
},
},
- });
+ }));
expect([200, 201]).toContain(res.status);
expect(mockAgentService.create).toHaveBeenCalledWith(
@@ -759,7 +763,7 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.post(`/api/companies/${companyId}/agent-hires`)
.send({
name: "Builder",
@@ -771,7 +775,7 @@ describe.sequential("agent permission routes", () => {
intervalSec: 3600,
},
},
- });
+ }));
expect(res.status).toBe(201);
expect(mockAgentService.create).toHaveBeenCalledWith(
@@ -811,9 +815,9 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.post(`/api/agents/${agentId}/approve`)
- .send({});
+ .send({}));
expect(res.status).toBe(200);
expect(mockAgentService.activatePendingApproval).toHaveBeenCalledWith(agentId);
@@ -837,9 +841,9 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.post(`/api/agents/${agentId}/approve`)
- .send({});
+ .send({}));
expect(res.status).toBe(409);
expect(mockAgentService.activatePendingApproval).not.toHaveBeenCalled();
@@ -849,9 +853,12 @@ describe.sequential("agent permission routes", () => {
});
it("rejects creating an agent with an environment from another company", async () => {
+ const environmentId = "33333333-3333-4333-8333-333333333333";
mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
+ id: environmentId,
companyId: "other-company",
+ driver: "local",
+ config: {},
});
const app = await createApp({
@@ -862,25 +869,28 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.post(`/api/companies/${companyId}/agents`)
.send({
name: "Builder",
role: "engineer",
adapterType: "process",
adapterConfig: {},
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
+ defaultEnvironmentId: environmentId,
+ }));
expect(res.status).toBe(422);
expect(res.body.error).toContain("Environment not found");
+ expect(mockAgentService.create).not.toHaveBeenCalled();
});
- it("rejects creating an agent with an unsupported non-local default environment", async () => {
+ it("rejects creating an agent with an unsupported default environment driver", async () => {
+ const environmentId = "33333333-3333-4333-8333-333333333333";
mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
+ id: environmentId,
companyId,
driver: "ssh",
+ config: {},
});
const app = await createApp({
@@ -891,96 +901,82 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.post(`/api/companies/${companyId}/agents`)
.send({
name: "Builder",
role: "engineer",
adapterType: "process",
adapterConfig: {},
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
+ defaultEnvironmentId: environmentId,
+ }));
expect(res.status).toBe(422);
expect(res.body.error).toContain('Environment driver "ssh" is not allowed here');
+ expect(mockAgentService.create).not.toHaveBeenCalled();
});
- it("allows creating a codex agent with an SSH default environment", async () => {
- mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
- companyId,
- driver: "ssh",
- });
- mockAgentService.create.mockResolvedValue({
- ...baseAgent,
- adapterType: "codex_local",
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
+ const sshCapableAdapterCases = [
+ { adapterType: "codex_local", name: "Codex Builder", adapterConfig: {} },
+ { adapterType: "claude_local", name: "Claude Builder", adapterConfig: {} },
+ { adapterType: "gemini_local", name: "Gemini Builder", adapterConfig: {} },
+ { adapterType: "opencode_local", name: "OpenCode Builder", adapterConfig: { model: "opencode/gpt-5-nano" } },
+ { adapterType: "cursor", name: "Cursor Builder", adapterConfig: {} },
+ { adapterType: "pi_local", name: "Pi Builder", adapterConfig: { model: "openai/gpt-5.4-mini" } },
+ ];
- const app = await createApp({
- type: "board",
- userId: "board-user",
- source: "local_implicit",
- isInstanceAdmin: true,
- companyIds: [companyId],
- });
-
- const res = await request(app)
- .post(`/api/companies/${companyId}/agents`)
- .send({
- name: "Codex Builder",
- role: "engineer",
- adapterType: "codex_local",
- adapterConfig: {},
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
+ for (const adapterCase of sshCapableAdapterCases) {
+ it(`allows creating a ${adapterCase.adapterType} agent with an SSH default environment`, async () => {
+ const environmentId = "33333333-3333-4333-8333-333333333333";
+ mockEnvironmentService.getById.mockResolvedValue({
+ id: environmentId,
+ companyId,
+ driver: "ssh",
+ config: {},
+ });
+ mockAgentService.create.mockResolvedValue({
+ ...baseAgent,
+ name: adapterCase.name,
+ adapterType: adapterCase.adapterType,
+ defaultEnvironmentId: environmentId,
});
- expect([200, 201]).toContain(res.status);
- });
-
- it("allows creating a claude agent with an SSH default environment", async () => {
- mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
- companyId,
- driver: "ssh",
- });
- mockAgentService.create.mockResolvedValue({
- ...baseAgent,
- adapterType: "claude_local",
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- const app = await createApp({
- type: "board",
- userId: "board-user",
- source: "local_implicit",
- isInstanceAdmin: true,
- companyIds: [companyId],
- });
-
- const res = await request(app)
- .post(`/api/companies/${companyId}/agents`)
- .send({
- name: "Claude Builder",
- role: "engineer",
- adapterType: "claude_local",
- adapterConfig: {},
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
+ const app = await createApp({
+ type: "board",
+ userId: "board-user",
+ source: "local_implicit",
+ isInstanceAdmin: true,
+ companyIds: [companyId],
});
- expect(res.status).toBe(201);
- });
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
+ .post(`/api/companies/${companyId}/agents`)
+ .send({
+ name: adapterCase.name,
+ role: "engineer",
+ adapterType: adapterCase.adapterType,
+ adapterConfig: adapterCase.adapterConfig,
+ defaultEnvironmentId: environmentId,
+ }));
- it("allows creating a gemini agent with an SSH default environment", async () => {
+ expect(res.status, JSON.stringify(res.body)).toBe(201);
+ expect(mockAgentService.create).toHaveBeenCalledWith(
+ companyId,
+ expect.objectContaining({
+ adapterType: adapterCase.adapterType,
+ defaultEnvironmentId: environmentId,
+ }),
+ );
+ });
+ }
+
+ it("rejects updating an agent with an unsupported default environment driver", async () => {
+ const environmentId = "33333333-3333-4333-8333-333333333333";
mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
+ id: environmentId,
companyId,
driver: "ssh",
- });
- mockAgentService.create.mockResolvedValue({
- ...baseAgent,
- adapterType: "gemini_local",
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
+ config: {},
});
const app = await createApp({
@@ -991,162 +987,76 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
- .post(`/api/companies/${companyId}/agents`)
- .send({
- name: "Gemini Builder",
- role: "engineer",
- adapterType: "gemini_local",
- adapterConfig: {},
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- expect(res.status).toBe(201);
- });
-
- it("allows creating an opencode agent with an SSH default environment", async () => {
- mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
- companyId,
- driver: "ssh",
- });
- mockAgentService.create.mockResolvedValue({
- ...baseAgent,
- adapterType: "opencode_local",
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- const app = await createApp({
- type: "board",
- userId: "board-user",
- source: "local_implicit",
- isInstanceAdmin: true,
- companyIds: [companyId],
- });
-
- const res = await request(app)
- .post(`/api/companies/${companyId}/agents`)
- .send({
- name: "OpenCode Builder",
- role: "engineer",
- adapterType: "opencode_local",
- adapterConfig: {
- model: "opencode/gpt-5-nano",
- },
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- expect(res.status).toBe(201);
- });
-
- it("allows creating a cursor agent with an SSH default environment", async () => {
- mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
- companyId,
- driver: "ssh",
- });
- mockAgentService.create.mockResolvedValue({
- ...baseAgent,
- adapterType: "cursor",
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- const app = await createApp({
- type: "board",
- userId: "board-user",
- source: "local_implicit",
- isInstanceAdmin: true,
- companyIds: [companyId],
- });
-
- const res = await request(app)
- .post(`/api/companies/${companyId}/agents`)
- .send({
- name: "Cursor Builder",
- role: "engineer",
- adapterType: "cursor",
- adapterConfig: {},
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- expect(res.status).toBe(201);
- });
-
- it("allows creating a pi agent with an SSH default environment", async () => {
- mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
- companyId,
- driver: "ssh",
- });
- mockAgentService.create.mockResolvedValue({
- ...baseAgent,
- adapterType: "pi_local",
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- const app = await createApp({
- type: "board",
- userId: "board-user",
- source: "local_implicit",
- isInstanceAdmin: true,
- companyIds: [companyId],
- });
-
- const res = await request(app)
- .post(`/api/companies/${companyId}/agents`)
- .send({
- name: "Pi Builder",
- role: "engineer",
- adapterType: "pi_local",
- adapterConfig: {
- model: "openai/gpt-5.4-mini",
- },
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- expect([200, 201]).toContain(res.status);
- });
-
- it("rejects updating an agent with an unsupported non-local default environment", async () => {
- mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
- companyId,
- driver: "ssh",
- });
-
- const app = await createApp({
- type: "board",
- userId: "board-user",
- source: "local_implicit",
- isInstanceAdmin: true,
- companyIds: [companyId],
- });
-
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.patch(`/api/agents/${agentId}`)
.send({
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
+ defaultEnvironmentId: environmentId,
+ }));
expect(res.status).toBe(422);
expect(res.body.error).toContain('Environment driver "ssh" is not allowed here');
+ expect(mockAgentService.update).not.toHaveBeenCalled();
});
- it("allows updating a codex agent with an SSH default environment", async () => {
+ for (const adapterCase of sshCapableAdapterCases) {
+ it(`allows updating a ${adapterCase.adapterType} agent with an SSH default environment`, async () => {
+ const environmentId = "33333333-3333-4333-8333-333333333333";
+ mockEnvironmentService.getById.mockResolvedValue({
+ id: environmentId,
+ companyId,
+ driver: "ssh",
+ config: {},
+ });
+ mockAgentService.getById.mockResolvedValue({
+ ...baseAgent,
+ adapterType: adapterCase.adapterType,
+ adapterConfig: adapterCase.adapterConfig,
+ defaultEnvironmentId: null,
+ });
+ mockAgentService.update.mockResolvedValue({
+ ...baseAgent,
+ adapterType: adapterCase.adapterType,
+ adapterConfig: adapterCase.adapterConfig,
+ defaultEnvironmentId: environmentId,
+ });
+
+ const app = await createApp({
+ type: "board",
+ userId: "board-user",
+ source: "local_implicit",
+ isInstanceAdmin: true,
+ companyIds: [companyId],
+ });
+
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
+ .patch(`/api/agents/${agentId}`)
+ .send({
+ defaultEnvironmentId: environmentId,
+ }));
+
+ expect(res.status, JSON.stringify(res.body)).toBe(200);
+ expect(mockAgentService.update).toHaveBeenCalledWith(
+ agentId,
+ expect.objectContaining({
+ defaultEnvironmentId: environmentId,
+ }),
+ expect.anything(),
+ );
+ });
+ }
+
+ it("rejects switching an agent away from an SSH-capable runtime without clearing its SSH default", async () => {
+ const environmentId = "33333333-3333-4333-8333-333333333333";
mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
+ id: environmentId,
companyId,
driver: "ssh",
+ config: {},
});
mockAgentService.getById.mockResolvedValue({
...baseAgent,
adapterType: "codex_local",
- defaultEnvironmentId: null,
- });
- mockAgentService.update.mockResolvedValue({
- ...baseAgent,
- adapterType: "codex_local",
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
+ defaultEnvironmentId: environmentId,
});
const app = await createApp({
@@ -1157,213 +1067,15 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
- .patch(`/api/agents/${agentId}`)
- .send({
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- expect(res.status).toBe(200);
- });
-
- it("allows updating a claude agent with an SSH default environment", async () => {
- mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
- companyId,
- driver: "ssh",
- });
- mockAgentService.getById.mockResolvedValue({
- ...baseAgent,
- adapterType: "claude_local",
- defaultEnvironmentId: null,
- });
- mockAgentService.update.mockResolvedValue({
- ...baseAgent,
- adapterType: "claude_local",
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- const app = await createApp({
- type: "board",
- userId: "board-user",
- source: "local_implicit",
- isInstanceAdmin: true,
- companyIds: [companyId],
- });
-
- const res = await request(app)
- .patch(`/api/agents/${agentId}`)
- .send({
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- expect(res.status).toBe(200);
- });
-
- it("allows updating a gemini agent with an SSH default environment", async () => {
- mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
- companyId,
- driver: "ssh",
- });
- mockAgentService.getById.mockResolvedValue({
- ...baseAgent,
- adapterType: "gemini_local",
- defaultEnvironmentId: null,
- });
- mockAgentService.update.mockResolvedValue({
- ...baseAgent,
- adapterType: "gemini_local",
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- const app = await createApp({
- type: "board",
- userId: "board-user",
- source: "local_implicit",
- isInstanceAdmin: true,
- companyIds: [companyId],
- });
-
- const res = await request(app)
- .patch(`/api/agents/${agentId}`)
- .send({
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- expect(res.status).toBe(200);
- });
-
- it("allows updating an opencode agent with an SSH default environment", async () => {
- mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
- companyId,
- driver: "ssh",
- });
- mockAgentService.getById.mockResolvedValue({
- ...baseAgent,
- adapterType: "opencode_local",
- defaultEnvironmentId: null,
- });
- mockAgentService.update.mockResolvedValue({
- ...baseAgent,
- adapterType: "opencode_local",
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- const app = await createApp({
- type: "board",
- userId: "board-user",
- source: "local_implicit",
- isInstanceAdmin: true,
- companyIds: [companyId],
- });
-
- const res = await request(app)
- .patch(`/api/agents/${agentId}`)
- .send({
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- expect(res.status).toBe(200);
- });
-
- it("allows updating a cursor agent with an SSH default environment", async () => {
- mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
- companyId,
- driver: "ssh",
- });
- mockAgentService.getById.mockResolvedValue({
- ...baseAgent,
- adapterType: "cursor",
- defaultEnvironmentId: null,
- });
- mockAgentService.update.mockResolvedValue({
- ...baseAgent,
- adapterType: "cursor",
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- const app = await createApp({
- type: "board",
- userId: "board-user",
- source: "local_implicit",
- isInstanceAdmin: true,
- companyIds: [companyId],
- });
-
- const res = await request(app)
- .patch(`/api/agents/${agentId}`)
- .send({
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- expect(res.status).toBe(200);
- });
-
- it("allows updating a pi agent with an SSH default environment", async () => {
- mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
- companyId,
- driver: "ssh",
- });
- mockAgentService.getById.mockResolvedValue({
- ...baseAgent,
- adapterType: "pi_local",
- defaultEnvironmentId: null,
- });
- mockAgentService.update.mockResolvedValue({
- ...baseAgent,
- adapterType: "pi_local",
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- const app = await createApp({
- type: "board",
- userId: "board-user",
- source: "local_implicit",
- isInstanceAdmin: true,
- companyIds: [companyId],
- });
-
- const res = await request(app)
- .patch(`/api/agents/${agentId}`)
- .send({
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- expect(res.status).toBe(200);
- });
-
- it("rejects switching a codex agent away from SSH-capable runtime without clearing its SSH default", async () => {
- mockEnvironmentService.getById.mockResolvedValue({
- id: "33333333-3333-4333-8333-333333333333",
- companyId,
- driver: "ssh",
- });
- mockAgentService.getById.mockResolvedValue({
- ...baseAgent,
- adapterType: "codex_local",
- defaultEnvironmentId: "33333333-3333-4333-8333-333333333333",
- });
-
- const app = await createApp({
- type: "board",
- userId: "board-user",
- source: "local_implicit",
- isInstanceAdmin: true,
- companyIds: [companyId],
- });
-
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.patch(`/api/agents/${agentId}`)
.send({
adapterType: "process",
- });
+ }));
expect(res.status).toBe(422);
expect(res.body.error).toContain('Environment driver "ssh" is not allowed here');
+ expect(mockAgentService.update).not.toHaveBeenCalled();
});
it("exposes explicit task assignment access on agent detail", async () => {
@@ -1389,7 +1101,7 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app).get(`/api/agents/${agentId}`);
+ const res = await requestApp(app, (baseUrl) => request(baseUrl).get(`/api/agents/${agentId}`));
expect(res.status).toBe(200);
expect(res.body.access.canAssignTasks).toBe(true);
@@ -1410,9 +1122,9 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.patch(`/api/agents/${agentId}/permissions`)
- .send({ canCreateAgents: true, canAssignTasks: false });
+ .send({ canCreateAgents: true, canAssignTasks: false }));
expect(res.status).toBe(200);
expect(mockAccessService.setPrincipalPermission).toHaveBeenCalledWith(
@@ -1445,9 +1157,9 @@ describe.sequential("agent permission routes", () => {
source: "agent_key",
});
- const res = await request(app)
+ const res = await requestApp(app, (baseUrl) => request(baseUrl)
.get("/api/agents/me/inbox/mine")
- .query({ userId: "board-user" });
+ .query({ userId: "board-user" }));
expect(res.status).toBe(200);
expect(res.body).toEqual([
@@ -1482,7 +1194,7 @@ describe.sequential("agent permission routes", () => {
companyIds: [companyId],
});
- const res = await request(app).post("/api/heartbeat-runs/run-1/cancel").send({});
+ const res = await requestApp(app, (baseUrl) => request(baseUrl).post("/api/heartbeat-runs/run-1/cancel").send({}));
expect(res.status).toBe(403);
expect(mockHeartbeatService.cancelRun).not.toHaveBeenCalled();
diff --git a/server/src/__tests__/issues-goal-context-routes.test.ts b/server/src/__tests__/issues-goal-context-routes.test.ts
index 5247ec74121..ddafeb9fc7f 100644
--- a/server/src/__tests__/issues-goal-context-routes.test.ts
+++ b/server/src/__tests__/issues-goal-context-routes.test.ts
@@ -1,6 +1,8 @@
import express from "express";
import request from "supertest";
import { beforeEach, describe, expect, it, vi } from "vitest";
+import { errorHandler } from "../middleware/index.js";
+import { issueRoutes } from "../routes/issues.js";
const mockIssueService = vi.hoisted(() => ({
getById: vi.fn(),
@@ -32,72 +34,86 @@ const mockExecutionWorkspaceService = vi.hoisted(() => ({
getById: vi.fn(),
}));
-function registerModuleMocks() {
- vi.doMock("../services/index.js", () => ({
- accessService: () => ({
- canUser: vi.fn(),
- hasPermission: vi.fn(),
- }),
- agentService: () => ({
- getById: vi.fn(),
- }),
- documentService: () => mockDocumentsService,
- environmentService: () => ({}),
- executionWorkspaceService: () => mockExecutionWorkspaceService,
- feedbackService: () => ({
- listIssueVotesForUser: vi.fn(async () => []),
- saveIssueVote: vi.fn(async () => ({ vote: null, consentEnabledNow: false, sharingEnabled: false })),
- }),
- goalService: () => mockGoalService,
- heartbeatService: () => ({
- wakeup: vi.fn(async () => undefined),
- reportRunActivity: vi.fn(async () => undefined),
- }),
- instanceSettingsService: () => ({
- get: vi.fn(async () => ({
- id: "instance-settings-1",
- general: {
- censorUsernameInLogs: false,
- feedbackDataSharingPreference: "prompt",
- },
- })),
- listCompanyIds: vi.fn(async () => ["company-1"]),
- }),
- issueApprovalService: () => ({}),
- issueReferenceService: () => ({
- deleteDocumentSource: async () => undefined,
- diffIssueReferenceSummary: () => ({
- addedReferencedIssues: [],
- removedReferencedIssues: [],
- currentReferencedIssues: [],
- }),
- emptySummary: () => ({ outbound: [], inbound: [] }),
- listIssueReferenceSummary: async () => ({ outbound: [], inbound: [] }),
- syncComment: async () => undefined,
- syncDocument: async () => undefined,
- syncIssue: async () => undefined,
- }),
- issueService: () => mockIssueService,
- logActivity: vi.fn(async () => undefined),
- projectService: () => mockProjectService,
- routineService: () => ({
- syncRunStatusForIssue: vi.fn(async () => undefined),
- }),
- workProductService: () => ({
- listForIssue: vi.fn(async () => []),
- }),
- }));
+const mockAccessService = vi.hoisted(() => ({
+ canUser: vi.fn(),
+ hasPermission: vi.fn(),
+}));
- vi.doMock("../services/execution-workspaces.js", () => ({
- executionWorkspaceService: () => mockExecutionWorkspaceService,
- }));
-}
+const mockAgentService = vi.hoisted(() => ({
+ getById: vi.fn(),
+}));
-async function createApp() {
- const [{ issueRoutes }, { errorHandler }] = await Promise.all([
- vi.importActual("../routes/issues.js"),
- vi.importActual("../middleware/index.js"),
- ]);
+const mockFeedbackService = vi.hoisted(() => ({
+ listIssueVotesForUser: vi.fn(async () => []),
+ saveIssueVote: vi.fn(async () => ({ vote: null, consentEnabledNow: false, sharingEnabled: false })),
+}));
+
+const mockHeartbeatService = vi.hoisted(() => ({
+ wakeup: vi.fn(async () => undefined),
+ reportRunActivity: vi.fn(async () => undefined),
+}));
+
+const mockInstanceSettingsService = vi.hoisted(() => ({
+ get: vi.fn(async () => ({
+ id: "instance-settings-1",
+ general: {
+ censorUsernameInLogs: false,
+ feedbackDataSharingPreference: "prompt",
+ },
+ })),
+ listCompanyIds: vi.fn(async () => ["company-1"]),
+}));
+
+const mockIssueReferenceService = vi.hoisted(() => ({
+ deleteDocumentSource: vi.fn(async () => undefined),
+ diffIssueReferenceSummary: vi.fn(() => ({
+ addedReferencedIssues: [],
+ removedReferencedIssues: [],
+ currentReferencedIssues: [],
+ })),
+ emptySummary: vi.fn(() => ({ outbound: [], inbound: [] })),
+ listIssueReferenceSummary: vi.fn(async () => ({ outbound: [], inbound: [] })),
+ syncComment: vi.fn(async () => undefined),
+ syncDocument: vi.fn(async () => undefined),
+ syncIssue: vi.fn(async () => undefined),
+}));
+
+const mockLogActivity = vi.hoisted(() => vi.fn(async () => undefined));
+
+const mockRoutineService = vi.hoisted(() => ({
+ syncRunStatusForIssue: vi.fn(async () => undefined),
+}));
+
+const mockWorkProductService = vi.hoisted(() => ({
+ listForIssue: vi.fn(async () => []),
+}));
+
+const mockEnvironmentService = vi.hoisted(() => ({}));
+
+vi.mock("../services/index.js", () => ({
+ accessService: () => mockAccessService,
+ agentService: () => mockAgentService,
+ documentService: () => mockDocumentsService,
+ environmentService: () => mockEnvironmentService,
+ executionWorkspaceService: () => mockExecutionWorkspaceService,
+ feedbackService: () => mockFeedbackService,
+ goalService: () => mockGoalService,
+ heartbeatService: () => mockHeartbeatService,
+ instanceSettingsService: () => mockInstanceSettingsService,
+ issueApprovalService: () => ({}),
+ issueReferenceService: () => mockIssueReferenceService,
+ issueService: () => mockIssueService,
+ logActivity: mockLogActivity,
+ projectService: () => mockProjectService,
+ routineService: () => mockRoutineService,
+ workProductService: () => mockWorkProductService,
+}));
+
+vi.mock("../services/execution-workspaces.js", () => ({
+ executionWorkspaceService: () => mockExecutionWorkspaceService,
+}));
+
+function createApp() {
const app = express();
app.use(express.json());
app.use((req, _res, next) => {
@@ -147,16 +163,9 @@ const projectGoal = {
updatedAt: new Date("2026-03-20T00:00:00Z"),
};
-describe("issue goal context routes", () => {
+describe.sequential("issue goal context routes", () => {
beforeEach(() => {
- vi.resetModules();
- vi.doUnmock("../services/index.js");
- vi.doUnmock("../services/execution-workspaces.js");
- vi.doUnmock("../routes/issues.js");
- vi.doUnmock("../routes/authz.js");
- vi.doUnmock("../middleware/index.js");
- registerModuleMocks();
- vi.resetAllMocks();
+ vi.clearAllMocks();
mockIssueService.getById.mockResolvedValue(legacyProjectLinkedIssue);
mockIssueService.getAncestors.mockResolvedValue([]);
mockIssueService.getRelationSummaries.mockResolvedValue({ blockedBy: [], blocks: [] });
@@ -213,7 +222,7 @@ describe("issue goal context routes", () => {
});
it("surfaces the project goal from GET /issues/:id when the issue has no direct goal", async () => {
- const res = await request(await createApp()).get("/api/issues/11111111-1111-4111-8111-111111111111");
+ const res = await request(createApp()).get("/api/issues/11111111-1111-4111-8111-111111111111");
expect(res.status).toBe(200);
expect(res.body.goalId).toBe(projectGoal.id);
@@ -231,7 +240,7 @@ describe("issue goal context routes", () => {
});
it("surfaces the project goal from GET /issues/:id/heartbeat-context", async () => {
- const res = await request(await createApp()).get(
+ const res = await request(createApp()).get(
"/api/issues/11111111-1111-4111-8111-111111111111/heartbeat-context",
);
@@ -257,7 +266,7 @@ describe("issue goal context routes", () => {
updatedAt: new Date("2026-04-19T12:00:00.000Z"),
});
- const res = await request(await createApp()).get(
+ const res = await request(createApp()).get(
"/api/issues/11111111-1111-4111-8111-111111111111/heartbeat-context",
);
@@ -288,7 +297,7 @@ describe("issue goal context routes", () => {
blocks: [],
});
- const res = await request(await createApp()).get(
+ const res = await request(createApp()).get(
"/api/issues/11111111-1111-4111-8111-111111111111/heartbeat-context",
);
@@ -323,7 +332,7 @@ describe("issue goal context routes", () => {
],
});
- const res = await request(await createApp()).get(
+ const res = await request(createApp()).get(
"/api/issues/11111111-1111-4111-8111-111111111111/heartbeat-context",
);
diff --git a/server/src/__tests__/workspace-runtime-routes-authz.test.ts b/server/src/__tests__/workspace-runtime-routes-authz.test.ts
index 0791d10dccc..34aaf872f74 100644
--- a/server/src/__tests__/workspace-runtime-routes-authz.test.ts
+++ b/server/src/__tests__/workspace-runtime-routes-authz.test.ts
@@ -56,9 +56,37 @@ vi.mock("../routes/workspace-runtime-service-authz.js", () => ({
assertCanManageExecutionWorkspaceRuntimeServices: mockAssertCanManageExecutionWorkspaceRuntimeServices,
}));
+function registerWorkspaceRouteMocks() {
+ vi.doMock("../telemetry.js", () => ({
+ getTelemetryClient: mockGetTelemetryClient,
+ }));
+
+ vi.doMock("../services/index.js", () => ({
+ environmentService: () => mockEnvironmentService,
+ executionWorkspaceService: () => mockExecutionWorkspaceService,
+ logActivity: mockLogActivity,
+ projectService: () => mockProjectService,
+ secretService: () => mockSecretService,
+ workspaceOperationService: () => mockWorkspaceOperationService,
+ }));
+
+ vi.doMock("../services/workspace-runtime.js", () => ({
+ cleanupExecutionWorkspaceArtifacts: vi.fn(),
+ startRuntimeServicesForWorkspaceControl: vi.fn(),
+ stopRuntimeServicesForExecutionWorkspace: vi.fn(),
+ stopRuntimeServicesForProjectWorkspace: vi.fn(),
+ }));
+
+ vi.doMock("../routes/workspace-runtime-service-authz.js", () => ({
+ assertCanManageProjectWorkspaceRuntimeServices: mockAssertCanManageProjectWorkspaceRuntimeServices,
+ assertCanManageExecutionWorkspaceRuntimeServices: mockAssertCanManageExecutionWorkspaceRuntimeServices,
+ }));
+}
+
let appImportCounter = 0;
async function createProjectApp(actor: Record) {
+ registerWorkspaceRouteMocks();
appImportCounter += 1;
const routeModulePath = `../routes/projects.js?workspace-runtime-routes-authz-${appImportCounter}`;
const middlewareModulePath = `../middleware/index.js?workspace-runtime-routes-authz-${appImportCounter}`;
@@ -78,6 +106,7 @@ async function createProjectApp(actor: Record) {
}
async function createExecutionWorkspaceApp(actor: Record) {
+ registerWorkspaceRouteMocks();
appImportCounter += 1;
const routeModulePath = `../routes/execution-workspaces.js?workspace-runtime-routes-authz-${appImportCounter}`;
const middlewareModulePath = `../middleware/index.js?workspace-runtime-routes-authz-${appImportCounter}`;
diff --git a/server/vitest.config.ts b/server/vitest.config.ts
index 4a3639bc140..4047b72340d 100644
--- a/server/vitest.config.ts
+++ b/server/vitest.config.ts
@@ -5,6 +5,8 @@ export default defineConfig({
environment: "node",
isolate: true,
maxConcurrency: 1,
+ maxWorkers: 1,
+ minWorkers: 1,
pool: "forks",
poolOptions: {
forks: {