diff --git a/scripts/__tests__/run-vitest-stable-shard.test.mjs b/scripts/__tests__/run-vitest-stable-shard.test.mjs index ffe7fb2d0b7..cad60a6cfcf 100644 --- a/scripts/__tests__/run-vitest-stable-shard.test.mjs +++ b/scripts/__tests__/run-vitest-stable-shard.test.mjs @@ -92,17 +92,37 @@ test("a route/authz suite never leaks into the general-server shards", () => { const shard = dryRunJson(["--mode", "general", "--group", "general-server", "--shard-index", "0", "--shard-count", SHARD_COUNT.toString()]); for (const file of shard.selectedGeneralServerSuites) { assert.ok( - !/[^/]*(?:route|routes|authz)[^/]*\.test\.ts$/.test(file), + !(file.startsWith("server/src/__tests__/") && /[^/]*(?:route|routes|authz)[^/]*\.test\.ts$/.test(file)), `route/authz suite must stay in the serialized lane, not general-server: ${file}`, ); } }); +test("general shards retain scripts and route-named suites outside the serialized directory", () => { + const general = dryRunJson(["--mode", "general", "--group", "general-server", "--shard-index", "0", "--shard-count", "1"]); + for (const file of [ + "server/scripts/verify-runner-vendor-dependencies.test.mjs", + "server/src/services/openrouter-models.test.ts", + "server/src/routes/setup-token-route.test.ts", + ]) assert.ok(general.selectedGeneralServerSuites.includes(file), `missing configured server suite: ${file}`); +}); + test("shard flags are rejected for the workspaces-b group", () => { const result = dryRun(["--mode", "general", "--group", "general-workspaces-b", "--shard-index", "0", "--shard-count", "3"]); assert.notEqual(result.status, 0, "workspaces-b must not accept shard flags"); }); +test("workspace lanes cover every non-server project in the root Vitest configuration", () => { + const config = readFileSync(path.join(repoRoot, "vitest.config.ts"), "utf8"); + const roots = [...config.matchAll(/^\s+"([^"]+)",?\s*$/gm)].map(match => match[1]); + assert.ok(roots.includes("server"), "expected the explicit root Vitest project list"); + const expected = roots.filter(root => root !== "server") + .map(root => JSON.parse(readFileSync(path.join(repoRoot, root, "package.json"), "utf8")).name).sort(); + const actual = ["general-workspaces-a", "general-workspaces-b"] + .flatMap(group => dryRunJson(["--mode", "general", "--group", group]).workspaceProjects).sort(); + assert.deepEqual(actual, expected, "no configured project may be silently omitted or run twice"); +}); + test("workspaces-a shards map to Vitest native --shard slices over a stable project list", () => { const shards = [0, 1].map((index) => dryRunJson([ diff --git a/scripts/run-vitest-stable.mjs b/scripts/run-vitest-stable.mjs index d98a814a12d..3abc0ad62e3 100644 --- a/scripts/run-vitest-stable.mjs +++ b/scripts/run-vitest-stable.mjs @@ -19,6 +19,7 @@ const serializedShardDurations = loadShardDurations( const serverRoot = path.join(repoRoot, "server"); const serverSrcDir = path.join(repoRoot, "server", "src"); const serverTestsDir = path.join(repoRoot, "server", "src", "__tests__"); +const serverScriptsDir = path.join(repoRoot, "server", "scripts"); const nonServerProjects = [ "@paperclipai/shared", "@paperclipai/skills-catalog", @@ -26,9 +27,14 @@ const nonServerProjects = [ "@paperclipai/adapter-utils", "@paperclipai/adapter-claude-local", "@paperclipai/adapter-codex-local", + "@paperclipai/adapter-cursor-cloud", + "@paperclipai/adapter-cursor-local", + "@paperclipai/adapter-gemini-local", "@paperclipai/adapter-grok-local", + "@paperclipai/adapter-kimi-local", "@paperclipai/adapter-openclaw-gateway", "@paperclipai/adapter-opencode-local", + "@paperclipai/adapter-pi-local", "@paperclipai/plugin-daytona", "@paperclipai/plugin-sdk", "@paperclipai/create-paperclip-plugin", @@ -492,10 +498,15 @@ const routeTests = walk(serverTestsDir) // config pins maxWorkers to 1, so the only way to parallelize is across jobs. // Suites are partitioned by recorded duration (scripts/general-server-shard.mjs) // rather than round-robin, so one slow suite cluster can't stretch a single shard. -const generalServerTestFiles = walk(serverSrcDir) +const serializedRepoPaths = new Set(routeTests.map(test => test.repoPath)); +const generalServerTestFiles = [ + ...walk(serverSrcDir).filter(file => file.endsWith(".test.ts")), + ...walk(serverScriptsDir).filter(file => file.endsWith(".test.mjs")), +] .map((file) => toRepoPath(file)) - .filter((repoPath) => repoPath.endsWith(".test.ts")) - .filter((repoPath) => !isRouteOrAuthzTest(repoPath)) + // Only exclude suites actually assigned to the serialized lane. A name + // such as services/openrouter-models.test.ts is not a serialized route. + .filter((repoPath) => !serializedRepoPaths.has(repoPath)) .sort((a, b) => a.localeCompare(b)); const options = parseCliOptions(process.argv.slice(2)); diff --git a/server/src/__tests__/cli-invocation-safety.test.ts b/server/src/__tests__/cli-invocation-safety.test.ts index 4fde7a8e135..abd8a68fe3b 100644 --- a/server/src/__tests__/cli-invocation-safety.test.ts +++ b/server/src/__tests__/cli-invocation-safety.test.ts @@ -1,3 +1,4 @@ +import { execFileSync } from "node:child_process"; import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; @@ -367,6 +368,9 @@ function isTestFile(relPath: string): boolean { function listGuidanceFiles(rootDir = repoRoot): string[] { const found: string[] = []; + // Include tracked files and new authored files, while respecting gitignored + // local settings. A tracked file remains scanned even if an ignore matches. + const authored = rootDir === repoRoot ? new Set(execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], { cwd: rootDir, encoding: "utf8" }).split("\0")) : null; function walk(absDir: string, relDir: string): void { for (const entry of readdirSync(absDir, { withFileTypes: true })) { @@ -382,7 +386,7 @@ function listGuidanceFiles(rootDir = repoRoot): string[] { if (!SCAN_EXTENSIONS.has(path.extname(entry.name))) continue; if (isTestFile(relPath)) continue; if (SKIP_PATH_PREFIXES.some((prefix) => relPath.startsWith(prefix))) continue; - found.push(relPath); + if (!authored || authored.has(relPath)) found.push(relPath); } } diff --git a/server/src/__tests__/environment-runtime.test.ts b/server/src/__tests__/environment-runtime.test.ts index 7886155e222..14a0327f114 100644 --- a/server/src/__tests__/environment-runtime.test.ts +++ b/server/src/__tests__/environment-runtime.test.ts @@ -11,6 +11,7 @@ import { stopSshEnvLabFixture, } from "@paperclipai/adapter-utils/ssh"; import { + costEvents, agents, builtInManagedResources, companies, @@ -219,6 +220,7 @@ describeEmbeddedPostgres("environmentRuntimeService", () => { } await db.delete(environmentLeases); await db.delete(issues); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agents); await db.delete(environments); diff --git a/server/src/__tests__/execution-lock-orphan-cleanup.test.ts b/server/src/__tests__/execution-lock-orphan-cleanup.test.ts index b742862466e..ae42a1e4458 100644 --- a/server/src/__tests__/execution-lock-orphan-cleanup.test.ts +++ b/server/src/__tests__/execution-lock-orphan-cleanup.test.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; import { + costEvents, activityLog, agents, agentWakeupRequests, @@ -40,6 +41,7 @@ describeEmbeddedPostgres("execution lock orphan cleanup", () => { await db.delete(activityLog); await db.delete(heartbeatRunEvents); await db.delete(issues); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agents); await db.delete(companies); diff --git a/server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts b/server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts index 211c9e662cd..7fe868ba1e9 100644 --- a/server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts +++ b/server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts @@ -7,6 +7,7 @@ import { promisify } from "node:util"; import { eq, ne } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, activityLog, agentRuntimeState, agentTaskSessions, @@ -136,6 +137,7 @@ describeEmbeddedPostgres("accepted plan workspace refresh", () => { await db.delete(activityLog); await db.delete(heartbeatRunEvents); try { + await db.delete(costEvents); await db.delete(heartbeatRuns); break; } catch (error) { diff --git a/server/src/__tests__/heartbeat-archived-company-guard.test.ts b/server/src/__tests__/heartbeat-archived-company-guard.test.ts index 085f8bfd21b..bc9bdb40acd 100644 --- a/server/src/__tests__/heartbeat-archived-company-guard.test.ts +++ b/server/src/__tests__/heartbeat-archived-company-guard.test.ts @@ -1,6 +1,7 @@ import { randomUUID } from "node:crypto"; import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; import { + costEvents, agents, agentWakeupRequests, companies, @@ -35,6 +36,7 @@ describeEmbeddedPostgres("heartbeat archived-company guard", () => { afterEach(async () => { await db.delete(heartbeatRunEvents); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agentWakeupRequests); await db.delete(issues); diff --git a/server/src/__tests__/heartbeat-dependency-scheduling.test.ts b/server/src/__tests__/heartbeat-dependency-scheduling.test.ts index 15563d1e777..f0194c88176 100644 --- a/server/src/__tests__/heartbeat-dependency-scheduling.test.ts +++ b/server/src/__tests__/heartbeat-dependency-scheduling.test.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { and, eq, inArray, sql } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, activityLog, agents, agentRuntimeState, @@ -143,6 +144,7 @@ describeEmbeddedPostgres("heartbeat dependency-aware queued run selection", () = await db.delete(issues); await db.delete(heartbeatRunEvents); await db.delete(activityLog); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agentWakeupRequests); await db.delete(agentRuntimeState); diff --git a/server/src/__tests__/heartbeat-issue-rewake-throttle.test.ts b/server/src/__tests__/heartbeat-issue-rewake-throttle.test.ts index 38af962b6ad..f7a7ccd0bc5 100644 --- a/server/src/__tests__/heartbeat-issue-rewake-throttle.test.ts +++ b/server/src/__tests__/heartbeat-issue-rewake-throttle.test.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { and, desc, eq, sql } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, activityLog, agentRuntimeState, agentWakeupRequests, @@ -90,6 +91,7 @@ describeEmbeddedPostgres("heartbeat issue rewake throttle", () => { await db.delete(issues); await db.delete(heartbeatRunEvents); await db.delete(activityLog); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agentWakeupRequests); await db.delete(agentRuntimeState); diff --git a/server/src/__tests__/heartbeat-lock-release-on-reassignment.test.ts b/server/src/__tests__/heartbeat-lock-release-on-reassignment.test.ts index 80dbae68226..c8b9cf500c6 100644 --- a/server/src/__tests__/heartbeat-lock-release-on-reassignment.test.ts +++ b/server/src/__tests__/heartbeat-lock-release-on-reassignment.test.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { and, eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, agents, agentRuntimeState, agentWakeupRequests, @@ -59,6 +60,7 @@ describeEmbeddedPostgres("heartbeat lock release on cross-agent reassignment", ( afterEach(async () => { await drainHeartbeatRunsToQuiescence(db, heartbeat); await db.delete(heartbeatRunEvents); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agentWakeupRequests); await db.delete(issues); diff --git a/server/src/__tests__/heartbeat-orphaned-active-lease-sweep.test.ts b/server/src/__tests__/heartbeat-orphaned-active-lease-sweep.test.ts index 967f228a0a0..6ff69f16f0a 100644 --- a/server/src/__tests__/heartbeat-orphaned-active-lease-sweep.test.ts +++ b/server/src/__tests__/heartbeat-orphaned-active-lease-sweep.test.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { + costEvents, agents, companies, createDb, @@ -62,6 +63,7 @@ describeEmbeddedPostgres("heartbeat sweepOrphanedActiveLeases", () => { afterEach(async () => { await db.delete(environmentLeases); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(environments); await db.delete(agents); diff --git a/server/src/__tests__/heartbeat-pending-cleanup-sweep.test.ts b/server/src/__tests__/heartbeat-pending-cleanup-sweep.test.ts index aee3ccb40ae..a118b6945c1 100644 --- a/server/src/__tests__/heartbeat-pending-cleanup-sweep.test.ts +++ b/server/src/__tests__/heartbeat-pending-cleanup-sweep.test.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { eq, sql } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { + costEvents, activityLog, agents, heartbeatRuns, @@ -71,6 +72,7 @@ describeEmbeddedPostgres("heartbeat sweepPendingCleanupLeases", () => { afterEach(async () => { await db.delete(activityLog); await db.delete(environmentLeases); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(environments); await db.delete(agents); diff --git a/server/src/__tests__/heartbeat-responsible-user-invariant.test.ts b/server/src/__tests__/heartbeat-responsible-user-invariant.test.ts index 933468ad93c..59c3cb2c419 100644 --- a/server/src/__tests__/heartbeat-responsible-user-invariant.test.ts +++ b/server/src/__tests__/heartbeat-responsible-user-invariant.test.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { and, eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, activityLog, agents, agentRuntimeState, @@ -62,6 +63,7 @@ async function deleteHeartbeatRunsAfterEvents(db: ReturnType) { for (let attempt = 0; attempt < 5; attempt += 1) { await db.delete(heartbeatRunEvents); try { + await db.delete(costEvents); await db.delete(heartbeatRuns); return; } catch (error) { diff --git a/server/src/__tests__/heartbeat-retry-scheduling.test.ts b/server/src/__tests__/heartbeat-retry-scheduling.test.ts index 697b6e7fb58..a63113c2c08 100644 --- a/server/src/__tests__/heartbeat-retry-scheduling.test.ts +++ b/server/src/__tests__/heartbeat-retry-scheduling.test.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { and, eq, inArray, sql } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, agents, approvals, issueApprovals, @@ -164,6 +165,7 @@ describeEmbeddedPostgres("heartbeat bounded retry scheduling", () => { } async function cleanupRetryFixtureOnce() { + await db.delete(costEvents); await db.delete(activityLog); await db.delete(environmentLeases); await db.delete(issueRelations); diff --git a/server/src/__tests__/heartbeat-run-lease-release-terminalization.test.ts b/server/src/__tests__/heartbeat-run-lease-release-terminalization.test.ts index 3b015f5f3c7..872da4596e4 100644 --- a/server/src/__tests__/heartbeat-run-lease-release-terminalization.test.ts +++ b/server/src/__tests__/heartbeat-run-lease-release-terminalization.test.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, agents, companies, createDb, @@ -42,6 +43,7 @@ describeEmbeddedPostgres("heartbeat terminalizeRunOnLeaseRelease", () => { afterEach(async () => { await db.delete(heartbeatRunEvents); await db.delete(issues); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agents); await db.delete(companies); diff --git a/server/src/__tests__/heartbeat-run-terminalize-before-release.test.ts b/server/src/__tests__/heartbeat-run-terminalize-before-release.test.ts index 62555a27371..a291f5b9bce 100644 --- a/server/src/__tests__/heartbeat-run-terminalize-before-release.test.ts +++ b/server/src/__tests__/heartbeat-run-terminalize-before-release.test.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, agents, companies, createDb, @@ -71,6 +72,7 @@ describeEmbeddedPostgres("heartbeat teardown terminalizes the run before releasi afterEach(async () => { await db.delete(heartbeatRunEvents); await db.delete(issues); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agents); await db.delete(companies); diff --git a/server/src/__tests__/heartbeat-runtime-state.test.ts b/server/src/__tests__/heartbeat-runtime-state.test.ts index d66d54a094b..fbc9c24f6a6 100644 --- a/server/src/__tests__/heartbeat-runtime-state.test.ts +++ b/server/src/__tests__/heartbeat-runtime-state.test.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { eq } from "drizzle-orm"; import { + costEvents, agents, agentRuntimeState, agentWakeupRequests, @@ -52,6 +53,7 @@ describeEmbeddedPostgres("heartbeat runtime state deduplication", () => { afterEach(async () => { clearAllHeartbeatRunRuntimeStatuses(); await db.delete(heartbeatRunEvents); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agentWakeupRequests); await db.delete(agentRuntimeState); diff --git a/server/src/__tests__/heartbeat-task-drain-admission-release.test.ts b/server/src/__tests__/heartbeat-task-drain-admission-release.test.ts index 8de38150f94..5f93b1ca9d0 100644 --- a/server/src/__tests__/heartbeat-task-drain-admission-release.test.ts +++ b/server/src/__tests__/heartbeat-task-drain-admission-release.test.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { eq, sql } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; import { + costEvents, activityLog, agents, agentRuntimeState, @@ -51,6 +52,7 @@ describeEmbeddedPostgres("heartbeat task-drain admission release", () => { await db.delete(heartbeatRunEvents); await db.delete(activityLog); try { + await db.delete(costEvents); await db.delete(heartbeatRuns); return; } catch (error) { diff --git a/server/src/__tests__/heartbeat-workspace-branch-containment.test.ts b/server/src/__tests__/heartbeat-workspace-branch-containment.test.ts index ef2ac9670ff..6ccec0f02e3 100644 --- a/server/src/__tests__/heartbeat-workspace-branch-containment.test.ts +++ b/server/src/__tests__/heartbeat-workspace-branch-containment.test.ts @@ -7,6 +7,7 @@ import { promisify } from "node:util"; import { and, eq, inArray } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, activityLog, agentRuntimeState, agentWakeupRequests, @@ -179,6 +180,7 @@ async function deleteHeartbeatRunsForCleanup(db: Db) { await db.delete(heartbeatRunEvents); await db.delete(activityLog); try { + await db.delete(costEvents); await db.delete(heartbeatRuns); return; } catch (error) { diff --git a/server/src/__tests__/heartbeat-workspace-busy.test.ts b/server/src/__tests__/heartbeat-workspace-busy.test.ts index b62f6f061c4..0d2fc3f1be1 100644 --- a/server/src/__tests__/heartbeat-workspace-busy.test.ts +++ b/server/src/__tests__/heartbeat-workspace-busy.test.ts @@ -5,6 +5,7 @@ import path from "node:path"; import { and, eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, agents, agentRuntimeState, agentWakeupRequests, @@ -151,6 +152,7 @@ describeEmbeddedPostgres("shared-workspace run serialization", () => { } async function cleanupFixtureOnce() { + await db.delete(costEvents); await db.delete(activityLog); await db.delete(environmentLeases); await db.delete(issueComments); diff --git a/server/src/__tests__/heartbeat-workspace-finalize-branch.test.ts b/server/src/__tests__/heartbeat-workspace-finalize-branch.test.ts index ff6d564024b..545b3a16305 100644 --- a/server/src/__tests__/heartbeat-workspace-finalize-branch.test.ts +++ b/server/src/__tests__/heartbeat-workspace-finalize-branch.test.ts @@ -7,6 +7,7 @@ import { promisify } from "node:util"; import { and, asc, eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, activityLog, agentRuntimeState, agentTaskSessions, @@ -121,6 +122,7 @@ async function deleteHeartbeatRowsAfterActivityLogDrains(db: Db) { await db.delete(activityLog); await db.delete(heartbeatRunEvents); try { + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agentWakeupRequests); return; diff --git a/server/src/__tests__/heartbeat-worktree-suppression.test.ts b/server/src/__tests__/heartbeat-worktree-suppression.test.ts index b007a83a00b..ed2825711e8 100644 --- a/server/src/__tests__/heartbeat-worktree-suppression.test.ts +++ b/server/src/__tests__/heartbeat-worktree-suppression.test.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; import { eq, sql } from "drizzle-orm"; import { + costEvents, activityLog, agents, agentWakeupRequests, @@ -52,6 +53,7 @@ describeEmbeddedPostgres("heartbeat worktree suppression", () => { await db.delete(heartbeatRunEvents); await db.delete(activityLog); try { + await db.delete(costEvents); await db.delete(heartbeatRuns); return; } catch (error) { diff --git a/server/src/__tests__/issue-monitor-scheduler.test.ts b/server/src/__tests__/issue-monitor-scheduler.test.ts index 84f62bb8d6f..13389f73037 100644 --- a/server/src/__tests__/issue-monitor-scheduler.test.ts +++ b/server/src/__tests__/issue-monitor-scheduler.test.ts @@ -3,6 +3,7 @@ import { eq, sql } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; import { PROVIDER_QUOTA_MONITOR_SERVICE_NAME } from "@paperclipai/shared"; import { + costEvents, activityLog, agentRuntimeState, agentWakeupRequests, @@ -111,6 +112,7 @@ describeEmbeddedPostgres("issue monitor scheduler", () => { await db.delete(environmentLeases); await db.delete(workspaceRuntimeServices); await db.delete(issues); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agentWakeupRequests); await db.delete(agentRuntimeState); diff --git a/server/src/__tests__/issue-onboarding-first-task-routes.test.ts b/server/src/__tests__/issue-onboarding-first-task-routes.test.ts index 4e6015a802e..0ae5a48a5e3 100644 --- a/server/src/__tests__/issue-onboarding-first-task-routes.test.ts +++ b/server/src/__tests__/issue-onboarding-first-task-routes.test.ts @@ -4,6 +4,7 @@ import request from "supertest"; import { and, eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, activityLog, agentRuntimeState, agents, @@ -94,6 +95,7 @@ describeEmbeddedPostgres("issue create onboarding first-task routes", () => { await db.delete(issueThreadInteractions); await db.delete(issueComments); await db.delete(heartbeatRunEvents); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agentWakeupRequests); await db.delete(agentRuntimeState); diff --git a/server/src/__tests__/issue-recovery-actions.test.ts b/server/src/__tests__/issue-recovery-actions.test.ts index 357c3cf38f3..9807310b3c1 100644 --- a/server/src/__tests__/issue-recovery-actions.test.ts +++ b/server/src/__tests__/issue-recovery-actions.test.ts @@ -7,6 +7,7 @@ import request from "supertest"; import { and, eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, agents, agentRuntimeState, authUsers, @@ -154,6 +155,7 @@ describeEmbeddedPostgres("issue recovery actions", () => { await db.delete(issueComments); await db.delete(environmentLeases); await db.delete(activityLog); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agentWakeupRequests); await db.delete(environments); diff --git a/server/src/__tests__/issue-watchdogs-routes.test.ts b/server/src/__tests__/issue-watchdogs-routes.test.ts index dd5636d1797..1106073b8f3 100644 --- a/server/src/__tests__/issue-watchdogs-routes.test.ts +++ b/server/src/__tests__/issue-watchdogs-routes.test.ts @@ -4,6 +4,7 @@ import request from "supertest"; import { and, eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, activityLog, agentRuntimeState, agentWakeupRequests, @@ -80,6 +81,7 @@ describeEmbeddedPostgres("issue watchdog routes", () => { await db.delete(activityLog); await db.delete(issueComments); await db.delete(heartbeatRunEvents); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agentWakeupRequests); await db.delete(agentRuntimeState); diff --git a/server/src/__tests__/low-trust-red-team-routes.test.ts b/server/src/__tests__/low-trust-red-team-routes.test.ts index 5de45043682..3b13d32de8d 100644 --- a/server/src/__tests__/low-trust-red-team-routes.test.ts +++ b/server/src/__tests__/low-trust-red-team-routes.test.ts @@ -6,6 +6,7 @@ import { WebSocketServer } from "ws"; import { and, eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; import { + costEvents, activityLog, authUsers, agentWakeupRequests, @@ -103,6 +104,7 @@ async function deleteHeartbeatRunsAndWakeupsAfterActivityLogDrains(db: Db) { await db.delete(activityLog); try { await db.delete(heartbeatRunEvents); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(agentWakeupRequests); return; diff --git a/server/src/__tests__/recovery-stale-issue-lock-sweep.test.ts b/server/src/__tests__/recovery-stale-issue-lock-sweep.test.ts index 1fd20c88cd8..f2f6e9f76c3 100644 --- a/server/src/__tests__/recovery-stale-issue-lock-sweep.test.ts +++ b/server/src/__tests__/recovery-stale-issue-lock-sweep.test.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { + costEvents, activityLog, agents, companies, @@ -54,6 +55,7 @@ describeEmbeddedPostgres("recovery sweepStaleIssueLocks", () => { await db.delete(issueRelations); await db.delete(activityLog); await db.delete(heartbeatRunEvents); + await db.delete(costEvents); await db.delete(heartbeatRuns); await db.delete(completionContracts); await db.delete(issues); diff --git a/server/src/__tests__/setup-supertest.ts b/server/src/__tests__/setup-supertest.ts index 3a34850d1dd..0fa086fe29b 100644 --- a/server/src/__tests__/setup-supertest.ts +++ b/server/src/__tests__/setup-supertest.ts @@ -21,6 +21,13 @@ type SupertestTestConstructor = { }; }; +// Receipt-spool recovery must never scan a developer's instance during tests. +const paperclipTestHome = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-vitest-home-")); +process.env.PAPERCLIP_HOME = paperclipTestHome; +// Setup-file afterAll hooks run before suites drain asynchronous heartbeats. +// Cleanup at worker exit, after all suite teardown, avoids deleting live state. +process.once("exit", () => fs.rmSync(paperclipTestHome, { recursive: true, force: true, maxRetries: 8, retryDelay: 50 })); + const require = createRequire(import.meta.url); const SupertestTest = require("supertest/lib/test.js") as SupertestTestConstructor; diff --git a/server/src/__tests__/vitest-chat-shards.test.ts b/server/src/__tests__/vitest-chat-shards.test.ts index dd929f1dd8e..75de627551f 100644 --- a/server/src/__tests__/vitest-chat-shards.test.ts +++ b/server/src/__tests__/vitest-chat-shards.test.ts @@ -12,6 +12,7 @@ it("runs every active nested/parameterized fixture case exactly once through the try { const tests = path.join(root, "server/src/__tests__"); mkdirSync(tests, { recursive: true }); + mkdirSync(path.join(root, "server/scripts"), { recursive: true }); symlinkSync(path.join(repoRoot, "node_modules"), path.join(root, "node_modules"), "junction"); writeFileSync(path.join(root, "package.json"), JSON.stringify({ private: true })); writeFileSync(path.join(root, "vitest.config.mjs"), `export default {