mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-30 13:20:54 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The server test suite checks budget and cost routes > - The costs-service test rebuilt the full route module graph before every test > - Synchronous graph rebuilds caused long stalls under CPU load > - This pull request loads the mocked graph once for each describe block and keeps per-test mock setup > - The benefit is a stable 17-test file without production code changes ## Linked Issues or Issue Description **What happened?** The costs-service route test rebuilt its full mocked module graph before every test. Under CPU load, a rebuild sometimes stalled a test past the 15-second timeout. **Expected behavior** The test file should load its mocked route graph once for each describe block while each test keeps isolated mock behavior. **Steps to reproduce** 1. Run the costs-service route test under synthetic CPU load. 2. Repeat the file test 30 times. 3. Observe intermittent test timeouts before this change. **Paperclip version or commit** The test used the current master branch at the time of this change. **Deployment mode** Built from source. ## What Changed - Add `hoistModuleGraph` to load the mocked route graph once for each describe block. - Keep per-test mock setup in `beforeEach` so test isolation stays unchanged. - Keep all 17 tests and their assertions. - Remove the module graph rebuild from the per-test path. ## Verification - Run `npx vitest run src/__tests__/costs-service.test.ts` from `server/`. - Confirm that the file reports 17 tests and zero skipped tests. - Confirm that 30 runs under the same synthetic CPU load report 0.0% failure after the change, compared with 10.0% before the change. - Confirm that mutation checks still fail when each authorization guard is broken. ## Risks This change affects test setup only. The main risk is weaker test isolation if a mock keeps state between tests. Each test still re-arms its mock behavior in `beforeEach`, and the full assertion set remains. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. This bug fix does not add a core feature. ## Model Used OpenAI GPT-5. The model used tool calls and code execution. The exact context window and reasoning configuration were not exposed 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 searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes: #` / `Refs: #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass (the submitting engineer ran the file before handoff) - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>
41 lines
1.3 KiB
TypeScript
41 lines
1.3 KiB
TypeScript
import { beforeAll } from "vitest";
|
|
|
|
/**
|
|
* Loads a mocked module graph one time for the enclosing `describe` block,
|
|
* not one time for each test.
|
|
*
|
|
* A per-test `vi.resetModules()` plus a per-test `vi.importActual` (or a
|
|
* dynamic `import()`) forces Node to transform and evaluate the module
|
|
* graph again on every test. This work is synchronous and CPU-bound. Under
|
|
* CPU load, one re-import can take seconds instead of milliseconds and
|
|
* stall a test past its timeout.
|
|
*
|
|
* A route module graph does not need a fresh import for test isolation.
|
|
* `vi.hoisted` mocks keep a stable identity across tests. Each test can
|
|
* still re-arm mock behavior in its own `beforeEach`. Register the module
|
|
* mocks one time. Import the graph one time in `beforeAll`. Reuse the
|
|
* result for every test in the block.
|
|
*
|
|
* Read `.value` only inside a test body. It throws before `beforeAll` runs.
|
|
*/
|
|
export function hoistModuleGraph<T>(
|
|
registerMocks: () => void,
|
|
loadGraph: () => Promise<T>,
|
|
): { readonly value: T } {
|
|
let graph: T | undefined;
|
|
|
|
beforeAll(async () => {
|
|
registerMocks();
|
|
graph = await loadGraph();
|
|
});
|
|
|
|
return {
|
|
get value(): T {
|
|
if (graph === undefined) {
|
|
throw new Error("module graph is not loaded yet — read .value inside a test");
|
|
}
|
|
return graph;
|
|
},
|
|
};
|
|
}
|