@copilotkit/aimock 1.43.0 → 1.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +46 -0
- package/README.md +14 -2
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +31 -5
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.d.cts.map +1 -1
- package/dist/aimock-cli.d.ts.map +1 -1
- package/dist/aimock-cli.js +31 -5
- package/dist/aimock-cli.js.map +1 -1
- package/dist/bedrock-converse.cjs +344 -43
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +345 -44
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +195 -42
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +196 -43
- package/dist/bedrock.js.map +1 -1
- package/dist/chaos.cjs +9 -5
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +10 -7
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +238 -9
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +242 -13
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +159 -72
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts.map +1 -1
- package/dist/cohere.js +160 -73
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +63 -5
- package/dist/config-loader.cjs.map +1 -1
- package/dist/config-loader.d.cts +37 -4
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +37 -4
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +63 -5
- package/dist/config-loader.js.map +1 -1
- package/dist/constants.cjs +8 -0
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +6 -2
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +8 -1
- package/dist/constants.js.map +1 -1
- package/dist/echo-text.cjs +65 -0
- package/dist/echo-text.cjs.map +1 -0
- package/dist/echo-text.d.cts +22 -0
- package/dist/echo-text.d.cts.map +1 -0
- package/dist/echo-text.d.ts +22 -0
- package/dist/echo-text.d.ts.map +1 -0
- package/dist/echo-text.js +65 -0
- package/dist/echo-text.js.map +1 -0
- package/dist/embeddings.cjs +18 -1
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts +1 -1
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +18 -1
- package/dist/embeddings.js.map +1 -1
- package/dist/fakes-plugin.cjs +131 -0
- package/dist/fakes-plugin.cjs.map +1 -0
- package/dist/fakes-plugin.d.cts +23 -0
- package/dist/fakes-plugin.d.cts.map +1 -0
- package/dist/fakes-plugin.d.ts +23 -0
- package/dist/fakes-plugin.d.ts.map +1 -0
- package/dist/fakes-plugin.js +127 -0
- package/dist/fakes-plugin.js.map +1 -0
- package/dist/files.cjs +9 -11
- package/dist/files.cjs.map +1 -1
- package/dist/files.d.cts.map +1 -1
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +10 -13
- package/dist/files.js.map +1 -1
- package/dist/fine-tuning.cjs +1 -19
- package/dist/fine-tuning.cjs.map +1 -1
- package/dist/fine-tuning.d.cts.map +1 -1
- package/dist/fine-tuning.d.ts.map +1 -1
- package/dist/fine-tuning.js +2 -20
- package/dist/fine-tuning.js.map +1 -1
- package/dist/fixture-loader-services.cjs +116 -0
- package/dist/fixture-loader-services.cjs.map +1 -0
- package/dist/fixture-loader-services.d.cts +48 -0
- package/dist/fixture-loader-services.d.cts.map +1 -0
- package/dist/fixture-loader-services.d.ts +48 -0
- package/dist/fixture-loader-services.d.ts.map +1 -0
- package/dist/fixture-loader-services.js +114 -0
- package/dist/fixture-loader-services.js.map +1 -0
- package/dist/fixture-loader.cjs +552 -29
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +140 -2
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +140 -2
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +542 -31
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-embeddings.cjs +42 -2
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +43 -3
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +288 -52
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +289 -53
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +234 -38
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +235 -39
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +1157 -12
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +85 -4
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +85 -4
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +1113 -14
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +3 -2
- package/dist/images.cjs.map +1 -1
- package/dist/images.js +3 -2
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +39 -1
- package/dist/index.d.cts +11 -4
- package/dist/index.d.ts +11 -4
- package/dist/index.js +11 -4
- package/dist/jest.cjs +43 -14
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +47 -5
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +47 -5
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +45 -16
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +155 -13
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +54 -4
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +54 -4
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +155 -13
- package/dist/journal.js.map +1 -1
- package/dist/live-sanitize.cjs +4 -32
- package/dist/live-sanitize.cjs.map +1 -1
- package/dist/live-sanitize.js +4 -32
- package/dist/live-sanitize.js.map +1 -1
- package/dist/llmock.cjs +204 -39
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +51 -1
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +51 -1
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +206 -41
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes-mount.cjs +217 -0
- package/dist/mcp-fakes-mount.cjs.map +1 -0
- package/dist/mcp-fakes-mount.js +211 -0
- package/dist/mcp-fakes-mount.js.map +1 -0
- package/dist/mcp-fakes-report.cjs +111 -0
- package/dist/mcp-fakes-report.cjs.map +1 -0
- package/dist/mcp-fakes-report.d.cts +40 -0
- package/dist/mcp-fakes-report.d.cts.map +1 -0
- package/dist/mcp-fakes-report.d.ts +40 -0
- package/dist/mcp-fakes-report.d.ts.map +1 -0
- package/dist/mcp-fakes-report.js +107 -0
- package/dist/mcp-fakes-report.js.map +1 -0
- package/dist/mcp-fakes.cjs +1942 -0
- package/dist/mcp-fakes.cjs.map +1 -0
- package/dist/mcp-fakes.d.cts +504 -0
- package/dist/mcp-fakes.d.cts.map +1 -0
- package/dist/mcp-fakes.d.ts +504 -0
- package/dist/mcp-fakes.d.ts.map +1 -0
- package/dist/mcp-fakes.js +1937 -0
- package/dist/mcp-fakes.js.map +1 -0
- package/dist/mcp-handler.cjs +534 -27
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.d.cts +49 -0
- package/dist/mcp-handler.d.cts.map +1 -0
- package/dist/mcp-handler.d.ts +49 -0
- package/dist/mcp-handler.d.ts.map +1 -0
- package/dist/mcp-handler.js +530 -28
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-mock.cjs +326 -47
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts +97 -1
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +97 -1
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +327 -48
- package/dist/mcp-mock.js.map +1 -1
- package/dist/mcp-recorder.cjs +1224 -0
- package/dist/mcp-recorder.cjs.map +1 -0
- package/dist/mcp-recorder.d.cts +108 -0
- package/dist/mcp-recorder.d.cts.map +1 -0
- package/dist/mcp-recorder.d.ts +108 -0
- package/dist/mcp-recorder.d.ts.map +1 -0
- package/dist/mcp-recorder.js +1215 -0
- package/dist/mcp-recorder.js.map +1 -0
- package/dist/mcp-stub.cjs +5 -1
- package/dist/mcp-stub.d.cts +3 -1
- package/dist/mcp-stub.d.ts +3 -1
- package/dist/mcp-stub.js +3 -1
- package/dist/mcp-types.d.cts +6 -0
- package/dist/mcp-types.d.ts +6 -0
- package/dist/message-text.cjs +192 -0
- package/dist/message-text.cjs.map +1 -0
- package/dist/message-text.d.cts +31 -0
- package/dist/message-text.d.cts.map +1 -0
- package/dist/message-text.d.ts +31 -0
- package/dist/message-text.d.ts.map +1 -0
- package/dist/message-text.js +184 -0
- package/dist/message-text.js.map +1 -0
- package/dist/messages.cjs +400 -56
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +401 -58
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +46 -66
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts +14 -6
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts +14 -6
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +32 -40
- package/dist/metrics.js.map +1 -1
- package/dist/misbehavior.cjs +1523 -0
- package/dist/misbehavior.cjs.map +1 -0
- package/dist/misbehavior.d.cts +43 -0
- package/dist/misbehavior.d.cts.map +1 -0
- package/dist/misbehavior.d.ts +43 -0
- package/dist/misbehavior.d.ts.map +1 -0
- package/dist/misbehavior.js +1511 -0
- package/dist/misbehavior.js.map +1 -0
- package/dist/ollama.cjs +315 -113
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +316 -114
- package/dist/ollama.js.map +1 -1
- package/dist/openapi.cjs +3405 -0
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +30 -0
- package/dist/openapi.d.cts.map +1 -0
- package/dist/openapi.d.ts +30 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +3404 -0
- package/dist/openapi.js.map +1 -0
- package/dist/openrouter-video.cjs +4 -4
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.d.cts.map +1 -1
- package/dist/openrouter-video.d.ts.map +1 -1
- package/dist/openrouter-video.js +4 -4
- package/dist/openrouter-video.js.map +1 -1
- package/dist/record-sanitize.cjs +231 -0
- package/dist/record-sanitize.cjs.map +1 -0
- package/dist/record-sanitize.js +224 -0
- package/dist/record-sanitize.js.map +1 -0
- package/dist/recorder.cjs +115 -6
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts +2 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts +2 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +116 -8
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +701 -166
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +5 -13
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +5 -13
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +699 -169
- package/dist/responses.js.map +1 -1
- package/dist/route-registry.cjs +951 -0
- package/dist/route-registry.cjs.map +1 -0
- package/dist/route-registry.d.cts +33 -0
- package/dist/route-registry.d.cts.map +1 -0
- package/dist/route-registry.d.ts +33 -0
- package/dist/route-registry.d.ts.map +1 -0
- package/dist/route-registry.js +866 -0
- package/dist/route-registry.js.map +1 -0
- package/dist/router.cjs +24 -2
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +25 -4
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +1159 -264
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +4 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +4 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1067 -172
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +4 -2
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.js +4 -2
- package/dist/speech.js.map +1 -1
- package/dist/stream-collapse.cjs +236 -34
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +39 -4
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +39 -4
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +235 -35
- package/dist/stream-collapse.js.map +1 -1
- package/dist/suite.cjs +1 -1
- package/dist/suite.js +1 -1
- package/dist/transcription.cjs +2 -2
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +2 -2
- package/dist/transcription.js.map +1 -1
- package/dist/types.cjs +18 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +516 -5
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +516 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/dist/validate-cli.cjs +123 -33
- package/dist/validate-cli.cjs.map +1 -1
- package/dist/validate-cli.js +125 -35
- package/dist/validate-cli.js.map +1 -1
- package/dist/vector-stores.cjs +1180 -0
- package/dist/vector-stores.cjs.map +1 -0
- package/dist/vector-stores.d.cts +106 -0
- package/dist/vector-stores.d.cts.map +1 -0
- package/dist/vector-stores.d.ts +106 -0
- package/dist/vector-stores.d.ts.map +1 -0
- package/dist/vector-stores.js +1164 -0
- package/dist/vector-stores.js.map +1 -0
- package/dist/vector-types.d.cts.map +1 -1
- package/dist/version.cjs +45 -0
- package/dist/version.cjs.map +1 -0
- package/dist/version.js +44 -0
- package/dist/version.js.map +1 -0
- package/dist/video.cjs +19 -0
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +19 -0
- package/dist/video.js.map +1 -1
- package/dist/vitest.cjs +77 -16
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +38 -5
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +38 -5
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +78 -17
- package/dist/vitest.js.map +1 -1
- package/dist/watcher.cjs +6 -2
- package/dist/watcher.cjs.map +1 -1
- package/dist/watcher.js +6 -2
- package/dist/watcher.js.map +1 -1
- package/dist/ws-gemini-live.cjs +210 -101
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts +9 -3
- package/dist/ws-gemini-live.d.cts.map +1 -1
- package/dist/ws-gemini-live.d.ts +9 -3
- package/dist/ws-gemini-live.d.ts.map +1 -1
- package/dist/ws-gemini-live.js +211 -102
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +175 -62
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts +8 -3
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts +8 -3
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +176 -63
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +121 -16
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts +8 -12
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts +8 -12
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +123 -18
- package/dist/ws-responses.js.map +1 -1
- package/package.json +13 -2
- package/skills/write-fixtures/SKILL.md +179 -21
package/dist/vitest.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
3
3
|
const require_fixture_loader = require('./fixture-loader.cjs');
|
|
4
|
+
const require_mcp_fakes_report = require('./mcp-fakes-report.cjs');
|
|
4
5
|
const require_llmock = require('./llmock.cjs');
|
|
6
|
+
const require_fakes_plugin = require('./fakes-plugin.cjs');
|
|
5
7
|
let node_fs = require("node:fs");
|
|
6
8
|
let node_path = require("node:path");
|
|
7
9
|
let vitest = require("vitest");
|
|
@@ -19,12 +21,26 @@ let vitest = require("vitest");
|
|
|
19
21
|
* const res = await fetch(`${mock().url}/v1/chat/completions`, { ... });
|
|
20
22
|
* });
|
|
21
23
|
*/
|
|
24
|
+
/** TI1: `<file> › <name1> › … › <nameN>`; unnamed suites add no segment. */
|
|
25
|
+
function defaultVitestId(task) {
|
|
26
|
+
const names = [];
|
|
27
|
+
for (let suite = task.suite; suite !== void 0 && suite !== task.file; suite = suite.suite) if (suite.name !== "") names.unshift(suite.name);
|
|
28
|
+
return [
|
|
29
|
+
task.file.name,
|
|
30
|
+
...names,
|
|
31
|
+
task.name
|
|
32
|
+
].join(require_fakes_plugin.TEST_ID_SEPARATOR);
|
|
33
|
+
}
|
|
22
34
|
/**
|
|
23
35
|
* Start an aimock server for the duration of the test suite.
|
|
24
36
|
*
|
|
25
|
-
* - `beforeAll`: starts the server and optionally loads fixtures
|
|
26
|
-
*
|
|
27
|
-
* - `
|
|
37
|
+
* - `beforeAll`: starts the server and optionally loads fixtures (LLM fixtures and
|
|
38
|
+
* `mcpFakes` blocks; a bad `mcpFakes` block fails `beforeAll`)
|
|
39
|
+
* - `beforeEach`: closes Live sessions and resets fixture match counts and MCP fake
|
|
40
|
+
* consumption (not fixtures or fakes); under `fakesReport: "warn" | "fail"` it
|
|
41
|
+
* throws in a concurrent test, which the per-test report does not support
|
|
42
|
+
* - `afterEach`: closes Live sessions owned by this helper's server, then checks
|
|
43
|
+
* the MCP fake report under `fakesReport: "warn" | "fail"`
|
|
28
44
|
* - `afterAll`: stops the server
|
|
29
45
|
*
|
|
30
46
|
* Returns a getter function — call it inside tests to access the handle.
|
|
@@ -33,13 +49,34 @@ function useAimock(options = {}) {
|
|
|
33
49
|
let handle = null;
|
|
34
50
|
let origOpenaiUrl;
|
|
35
51
|
let origAnthropicUrl;
|
|
52
|
+
let state = new require_fakes_plugin.FakesPluginState("off");
|
|
53
|
+
/** TI5: default id -> the `task.id` that first used it; fresh per `beforeAll`. */
|
|
54
|
+
let idMap = /* @__PURE__ */ new Map();
|
|
55
|
+
let current = null;
|
|
56
|
+
function resolveTestId(testId) {
|
|
57
|
+
if (testId !== void 0) return testId;
|
|
58
|
+
if (current === null) throw new require_mcp_fakes_report.AimockTestIdCollisionError("fakesFor() needs a running test; pass an explicit testId");
|
|
59
|
+
if (current.concurrent === true) throw new require_mcp_fakes_report.AimockTestIdCollisionError("default test ids are not supported in concurrent tests; pass an explicit testId");
|
|
60
|
+
const id = defaultVitestId(current);
|
|
61
|
+
const owner = idMap.get(id);
|
|
62
|
+
if (owner !== void 0 && owner !== current.id) throw new require_mcp_fakes_report.AimockTestIdCollisionError(`two tests share the default test id ${JSON.stringify(id)}; pass an explicit testId`);
|
|
63
|
+
idMap.set(id, current.id);
|
|
64
|
+
return id;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The report keeps one state per `useAimock`, so concurrent tests would
|
|
68
|
+
* clear and read each other's identities: under `"warn"` or `"fail"`, a
|
|
69
|
+
* concurrent test is refused in `beforeEach` (and not reported on).
|
|
70
|
+
*/
|
|
71
|
+
function refusesConcurrent(task) {
|
|
72
|
+
return state.mode !== "off" && task.concurrent === true;
|
|
73
|
+
}
|
|
36
74
|
(0, vitest.beforeAll)(async () => {
|
|
37
|
-
const { fixtures: fixturePath, patchEnv, ...serverOpts } = options;
|
|
75
|
+
const { fixtures: fixturePath, patchEnv, fakesReport, ...serverOpts } = options;
|
|
76
|
+
state = new require_fakes_plugin.FakesPluginState(require_fakes_plugin.fakesReportMode(fakesReport));
|
|
77
|
+
idMap = /* @__PURE__ */ new Map();
|
|
38
78
|
const llm = new require_llmock.LLMock(serverOpts);
|
|
39
|
-
if (fixturePath)
|
|
40
|
-
const loadedFixtures = loadFixtures((0, node_path.resolve)(fixturePath), options.live);
|
|
41
|
-
for (const f of loadedFixtures) llm.addFixture(f);
|
|
42
|
-
}
|
|
79
|
+
if (fixturePath) loadFixtures(llm, (0, node_path.resolve)(fixturePath));
|
|
43
80
|
const url = await llm.start();
|
|
44
81
|
if (patchEnv !== false) {
|
|
45
82
|
origOpenaiUrl = process.env.OPENAI_BASE_URL;
|
|
@@ -49,17 +86,33 @@ function useAimock(options = {}) {
|
|
|
49
86
|
}
|
|
50
87
|
handle = {
|
|
51
88
|
llm,
|
|
52
|
-
url
|
|
89
|
+
url,
|
|
90
|
+
fakesFor(testId, opts = {}) {
|
|
91
|
+
const id = resolveTestId(testId);
|
|
92
|
+
state.register(id, opts.context ?? null);
|
|
93
|
+
return require_fakes_plugin.fakesTarget(url, id, opts);
|
|
94
|
+
},
|
|
95
|
+
fakesReport(testId, opts = {}) {
|
|
96
|
+
return require_fakes_plugin.fetchFakesReport(url, resolveTestId(testId), opts.context ?? null);
|
|
97
|
+
}
|
|
53
98
|
};
|
|
54
99
|
});
|
|
55
|
-
(0, vitest.beforeEach)(() => {
|
|
100
|
+
(0, vitest.beforeEach)((ctx) => {
|
|
101
|
+
current = ctx.task;
|
|
102
|
+
if (refusesConcurrent(ctx.task)) throw new Error("useAimock(): fakesReport is not supported in concurrent tests; set fakesReport: \"off\" for this suite or run it sequentially");
|
|
56
103
|
if (handle) {
|
|
57
104
|
handle.llm.closeLiveSessions();
|
|
58
105
|
handle.llm.resetMatchCounts();
|
|
106
|
+
state.beforeEach(handle.llm);
|
|
59
107
|
}
|
|
60
108
|
});
|
|
61
|
-
(0, vitest.afterEach)(() => {
|
|
62
|
-
|
|
109
|
+
(0, vitest.afterEach)(async (ctx) => {
|
|
110
|
+
try {
|
|
111
|
+
handle?.llm.closeLiveSessions();
|
|
112
|
+
if (handle && !refusesConcurrent(ctx.task)) await state.afterEach(handle.llm, handle.url);
|
|
113
|
+
} finally {
|
|
114
|
+
current = null;
|
|
115
|
+
}
|
|
63
116
|
});
|
|
64
117
|
(0, vitest.afterAll)(async () => {
|
|
65
118
|
if (handle) {
|
|
@@ -78,13 +131,21 @@ function useAimock(options = {}) {
|
|
|
78
131
|
return handle;
|
|
79
132
|
};
|
|
80
133
|
}
|
|
81
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Load a fixture file or directory into `llm`: its LLM fixtures and its
|
|
136
|
+
* `mcpFakes` blocks, which the LLMock buffers and auto-mounts at start (F12).
|
|
137
|
+
* A `FixtureLoadError` (a bad `mcpFakes` block) or a `MisbehaviorConfigError`
|
|
138
|
+
* (a bad fixture `misbehavior` key, thrown only with `enableMisbehavior: true`)
|
|
139
|
+
* propagates and fails `beforeAll`; any other load failure is only warned
|
|
140
|
+
* about, as before.
|
|
141
|
+
*/
|
|
142
|
+
function loadFixtures(llm, fixturePath) {
|
|
82
143
|
try {
|
|
83
|
-
if ((0, node_fs.statSync)(fixturePath).isDirectory())
|
|
84
|
-
|
|
144
|
+
if ((0, node_fs.statSync)(fixturePath).isDirectory()) llm.loadFixtureDir(fixturePath);
|
|
145
|
+
else llm.loadFixtureFile(fixturePath);
|
|
85
146
|
} catch (err) {
|
|
147
|
+
if (err instanceof require_fixture_loader.FixtureLoadError || err instanceof require_fixture_loader.MisbehaviorConfigError) throw err;
|
|
86
148
|
console.warn(`[aimock] Failed to load fixtures from ${fixturePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
87
|
-
return [];
|
|
88
149
|
}
|
|
89
150
|
}
|
|
90
151
|
|
package/dist/vitest.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest.cjs","names":["LLMock","loadFixturesFromDir","loadFixtureFile"],"sources":["../src/vitest.ts"],"sourcesContent":["/**\n * Vitest integration for aimock.\n *\n * Usage:\n * import { useAimock } from \"@copilotkit/aimock/vitest\";\n *\n * const mock = useAimock({ fixtures: \"./fixtures\" });\n *\n * it(\"responds\", async () => {\n * const res = await fetch(`${mock().url}/v1/chat/completions`, { ... });\n * });\n */\n\nimport { beforeAll, afterAll, beforeEach, afterEach } from \"vitest\";\nimport { LLMock } from \"./llmock.js\";\nimport { loadFixtureFile, loadFixturesFromDir } from \"./fixture-loader.js\";\nimport type { LiveOptions } from \"./live-types.js\";\nimport type { Fixture, MockServerOptions } from \"./types.js\";\nimport { statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport interface UseAimockOptions extends MockServerOptions {\n /** Path to fixture file or directory. Loaded automatically on start. */\n fixtures?: string;\n /** If true, sets process.env.OPENAI_BASE_URL to the mock URL + /v1. */\n patchEnv?: boolean;\n}\n\nexport interface AimockHandle {\n /** The LLMock instance. */\n readonly llm: LLMock;\n /** The server URL (e.g., http://127.0.0.1:4010). */\n readonly url: string;\n}\n\n/**\n * Start an aimock server for the duration of the test suite.\n *\n * - `beforeAll`: starts the server and optionally loads fixtures\n * - `beforeEach`: closes Live sessions and resets fixture match counts (not fixtures)\n * - `afterEach`: closes Live sessions owned by this helper's server\n * - `afterAll`: stops the server\n *\n * Returns a getter function — call it inside tests to access the handle.\n */\nexport function useAimock(options: UseAimockOptions = {}): () => AimockHandle {\n let handle: AimockHandle | null = null;\n let origOpenaiUrl: string | undefined;\n let origAnthropicUrl: string | undefined;\n\n beforeAll(async () => {\n const { fixtures: fixturePath, patchEnv, ...serverOpts } = options;\n const llm = new LLMock(serverOpts);\n\n if (fixturePath) {\n const resolved = resolve(fixturePath);\n const loadedFixtures = loadFixtures(resolved, options.live);\n for (const f of loadedFixtures) {\n llm.addFixture(f);\n }\n }\n\n const url = await llm.start();\n\n if (patchEnv !== false) {\n origOpenaiUrl = process.env.OPENAI_BASE_URL;\n origAnthropicUrl = process.env.ANTHROPIC_BASE_URL;\n process.env.OPENAI_BASE_URL = `${url}/v1`;\n process.env.ANTHROPIC_BASE_URL = `${url}/v1`;\n }\n\n handle = { llm, url };\n });\n\n beforeEach(() => {\n if (handle) {\n handle.llm.closeLiveSessions();\n handle.llm.resetMatchCounts();\n }\n });\n\n afterEach(() => {\n handle?.llm.closeLiveSessions();\n });\n\n afterAll(async () => {\n if (handle) {\n if (options.patchEnv !== false) {\n if (origOpenaiUrl !== undefined) process.env.OPENAI_BASE_URL = origOpenaiUrl;\n else delete process.env.OPENAI_BASE_URL;\n if (origAnthropicUrl !== undefined) process.env.ANTHROPIC_BASE_URL = origAnthropicUrl;\n else delete process.env.ANTHROPIC_BASE_URL;\n }\n await handle.llm.stop();\n handle = null;\n }\n });\n\n return () => {\n if (!handle) {\n throw new Error(\"useAimock(): server not started — are you calling this inside a test?\");\n }\n return handle;\n };\n}\n\nfunction loadFixtures(fixturePath: string, liveOptions?: LiveOptions): Fixture[] {\n try {\n const stat = statSync(fixturePath);\n if (stat.isDirectory()) {\n return loadFixturesFromDir(fixturePath, undefined, liveOptions);\n }\n return loadFixtureFile(fixturePath, undefined, liveOptions);\n } catch (err) {\n console.warn(\n `[aimock] Failed to load fixtures from ${fixturePath}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return [];\n }\n}\n\nexport { LLMock } from \"./llmock.js\";\nexport type { MockServerOptions, Fixture } from \"./types.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,SAAgB,UAAU,UAA4B,EAAE,EAAsB;CAC5E,IAAI,SAA8B;CAClC,IAAI;CACJ,IAAI;AAEJ,uBAAU,YAAY;EACpB,MAAM,EAAE,UAAU,aAAa,UAAU,GAAG,eAAe;EAC3D,MAAM,MAAM,IAAIA,sBAAO,WAAW;AAElC,MAAI,aAAa;GAEf,MAAM,iBAAiB,oCADE,YAAY,EACS,QAAQ,KAAK;AAC3D,QAAK,MAAM,KAAK,eACd,KAAI,WAAW,EAAE;;EAIrB,MAAM,MAAM,MAAM,IAAI,OAAO;AAE7B,MAAI,aAAa,OAAO;AACtB,mBAAgB,QAAQ,IAAI;AAC5B,sBAAmB,QAAQ,IAAI;AAC/B,WAAQ,IAAI,kBAAkB,GAAG,IAAI;AACrC,WAAQ,IAAI,qBAAqB,GAAG,IAAI;;AAG1C,WAAS;GAAE;GAAK;GAAK;GACrB;AAEF,8BAAiB;AACf,MAAI,QAAQ;AACV,UAAO,IAAI,mBAAmB;AAC9B,UAAO,IAAI,kBAAkB;;GAE/B;AAEF,6BAAgB;AACd,UAAQ,IAAI,mBAAmB;GAC/B;AAEF,sBAAS,YAAY;AACnB,MAAI,QAAQ;AACV,OAAI,QAAQ,aAAa,OAAO;AAC9B,QAAI,kBAAkB,OAAW,SAAQ,IAAI,kBAAkB;QAC1D,QAAO,QAAQ,IAAI;AACxB,QAAI,qBAAqB,OAAW,SAAQ,IAAI,qBAAqB;QAChE,QAAO,QAAQ,IAAI;;AAE1B,SAAM,OAAO,IAAI,MAAM;AACvB,YAAS;;GAEX;AAEF,cAAa;AACX,MAAI,CAAC,OACH,OAAM,IAAI,MAAM,wEAAwE;AAE1F,SAAO;;;AAIX,SAAS,aAAa,aAAqB,aAAsC;AAC/E,KAAI;AAEF,4BADsB,YAAY,CACzB,aAAa,CACpB,QAAOC,2CAAoB,aAAa,QAAW,YAAY;AAEjE,SAAOC,uCAAgB,aAAa,QAAW,YAAY;UACpD,KAAK;AACZ,UAAQ,KACN,yCAAyC,YAAY,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAC1G;AACD,SAAO,EAAE"}
|
|
1
|
+
{"version":3,"file":"vitest.cjs","names":["TEST_ID_SEPARATOR","FakesPluginState","AimockTestIdCollisionError","fakesReportMode","LLMock","fakesTarget","fetchFakesReport","FixtureLoadError","MisbehaviorConfigError"],"sources":["../src/vitest.ts"],"sourcesContent":["/**\n * Vitest integration for aimock.\n *\n * Usage:\n * import { useAimock } from \"@copilotkit/aimock/vitest\";\n *\n * const mock = useAimock({ fixtures: \"./fixtures\" });\n *\n * it(\"responds\", async () => {\n * const res = await fetch(`${mock().url}/v1/chat/completions`, { ... });\n * });\n */\n\nimport { beforeAll, afterAll, beforeEach, afterEach } from \"vitest\";\nimport type { RunnerTestCase, RunnerTestSuite } from \"vitest\";\nimport { LLMock } from \"./llmock.js\";\nimport { FixtureLoadError, MisbehaviorConfigError } from \"./fixture-loader.js\";\nimport type { MockServerOptions } from \"./types.js\";\nimport {\n FakesPluginState,\n TEST_ID_SEPARATOR,\n fakesReportMode,\n fakesTarget,\n fetchFakesReport,\n type FakesForOptions,\n type FakesReportMode,\n type FakesTarget,\n} from \"./fakes-plugin.js\";\nimport { AimockTestIdCollisionError, type McpFakesReport } from \"./mcp-fakes-report.js\";\nimport { statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport interface UseAimockOptions extends MockServerOptions {\n /** Path to fixture file or directory. Loaded automatically on start. */\n fixtures?: string;\n /** If true, sets process.env.OPENAI_BASE_URL to the mock URL + /v1. */\n patchEnv?: boolean;\n /**\n * The per-test MCP fake report, read in `afterEach` (RP7): `\"off\"` (the\n * default), `\"warn\"` (print a failing report) or `\"fail\"` (throw\n * `AimockFakesReportError`). Not supported in concurrent tests: under\n * `\"warn\"` or `\"fail\"`, a concurrent test fails in `beforeEach`.\n */\n fakesReport?: FakesReportMode;\n}\n\nexport interface AimockHandle {\n /** The LLMock instance. */\n readonly llm: LLMock;\n /** The server URL (e.g., http://127.0.0.1:4010). */\n readonly url: string;\n}\n\n/**\n * The handle `useAimock` returns: an `AimockHandle` plus the MCP fakes\n * helpers (RP7). A separate type, so that `AimockHandle` stays as it was and\n * code that builds its own `AimockHandle` keeps compiling.\n */\nexport interface AimockFakesHandle extends AimockHandle {\n /**\n * The MCP URL and headers for the fakes scoped to `testId` (RP7), and\n * registers that identity for this test's report. With no `testId`, the\n * default is `<file> › <describe…> › <test>` (TI1); it is used only here and\n * in `fakesReport`, never on LLM traffic (TI4). Throws\n * `AimockTestIdCollisionError` when two tests share a default id, in a\n * concurrent test, or outside a test.\n */\n fakesFor(testId?: string, opts?: FakesForOptions): FakesTarget;\n /** The MCP fake report for `testId` (default as in `fakesFor`). */\n fakesReport(testId?: string, opts?: { context?: string }): Promise<McpFakesReport>;\n}\n\n/** TI1: `<file> › <name1> › … › <nameN>`; unnamed suites add no segment. */\nfunction defaultVitestId(task: Readonly<RunnerTestCase>): string {\n const names: string[] = [];\n for (\n let suite: RunnerTestSuite | undefined = task.suite;\n suite !== undefined && suite !== task.file;\n suite = suite.suite\n ) {\n if (suite.name !== \"\") names.unshift(suite.name);\n }\n return [task.file.name, ...names, task.name].join(TEST_ID_SEPARATOR);\n}\n\n/**\n * Start an aimock server for the duration of the test suite.\n *\n * - `beforeAll`: starts the server and optionally loads fixtures (LLM fixtures and\n * `mcpFakes` blocks; a bad `mcpFakes` block fails `beforeAll`)\n * - `beforeEach`: closes Live sessions and resets fixture match counts and MCP fake\n * consumption (not fixtures or fakes); under `fakesReport: \"warn\" | \"fail\"` it\n * throws in a concurrent test, which the per-test report does not support\n * - `afterEach`: closes Live sessions owned by this helper's server, then checks\n * the MCP fake report under `fakesReport: \"warn\" | \"fail\"`\n * - `afterAll`: stops the server\n *\n * Returns a getter function — call it inside tests to access the handle.\n */\nexport function useAimock(options: UseAimockOptions = {}): () => AimockFakesHandle {\n let handle: AimockFakesHandle | null = null;\n let origOpenaiUrl: string | undefined;\n let origAnthropicUrl: string | undefined;\n let state = new FakesPluginState(\"off\");\n /** TI5: default id -> the `task.id` that first used it; fresh per `beforeAll`. */\n let idMap = new Map<string, string>();\n let current: Readonly<RunnerTestCase> | null = null;\n\n function resolveTestId(testId: string | undefined): string {\n if (testId !== undefined) return testId; // TI4: an explicit id wins\n if (current === null) {\n throw new AimockTestIdCollisionError(\n \"fakesFor() needs a running test; pass an explicit testId\",\n );\n }\n if (current.concurrent === true) {\n throw new AimockTestIdCollisionError(\n \"default test ids are not supported in concurrent tests; pass an explicit testId\",\n );\n }\n const id = defaultVitestId(current);\n const owner = idMap.get(id);\n if (owner !== undefined && owner !== current.id) {\n throw new AimockTestIdCollisionError(\n `two tests share the default test id ${JSON.stringify(id)}; pass an explicit testId`,\n );\n }\n idMap.set(id, current.id);\n return id;\n }\n\n /**\n * The report keeps one state per `useAimock`, so concurrent tests would\n * clear and read each other's identities: under `\"warn\"` or `\"fail\"`, a\n * concurrent test is refused in `beforeEach` (and not reported on).\n */\n function refusesConcurrent(task: Readonly<RunnerTestCase>): boolean {\n return state.mode !== \"off\" && task.concurrent === true;\n }\n\n beforeAll(async () => {\n const { fixtures: fixturePath, patchEnv, fakesReport, ...serverOpts } = options;\n state = new FakesPluginState(fakesReportMode(fakesReport));\n idMap = new Map();\n const llm = new LLMock(serverOpts);\n\n if (fixturePath) {\n loadFixtures(llm, resolve(fixturePath));\n }\n\n const url = await llm.start();\n\n if (patchEnv !== false) {\n origOpenaiUrl = process.env.OPENAI_BASE_URL;\n origAnthropicUrl = process.env.ANTHROPIC_BASE_URL;\n process.env.OPENAI_BASE_URL = `${url}/v1`;\n process.env.ANTHROPIC_BASE_URL = `${url}/v1`;\n }\n\n handle = {\n llm,\n url,\n fakesFor(testId?: string, opts: FakesForOptions = {}): FakesTarget {\n const id = resolveTestId(testId);\n state.register(id, opts.context ?? null);\n return fakesTarget(url, id, opts);\n },\n fakesReport(testId?: string, opts: { context?: string } = {}): Promise<McpFakesReport> {\n return fetchFakesReport(url, resolveTestId(testId), opts.context ?? null);\n },\n };\n });\n\n beforeEach((ctx) => {\n current = ctx.task;\n if (refusesConcurrent(ctx.task)) {\n throw new Error(\n 'useAimock(): fakesReport is not supported in concurrent tests; set fakesReport: \"off\" for this suite or run it sequentially',\n );\n }\n if (handle) {\n handle.llm.closeLiveSessions();\n handle.llm.resetMatchCounts();\n state.beforeEach(handle.llm);\n }\n });\n\n afterEach(async (ctx) => {\n try {\n handle?.llm.closeLiveSessions();\n if (handle && !refusesConcurrent(ctx.task)) await state.afterEach(handle.llm, handle.url);\n } finally {\n current = null;\n }\n });\n\n afterAll(async () => {\n if (handle) {\n if (options.patchEnv !== false) {\n if (origOpenaiUrl !== undefined) process.env.OPENAI_BASE_URL = origOpenaiUrl;\n else delete process.env.OPENAI_BASE_URL;\n if (origAnthropicUrl !== undefined) process.env.ANTHROPIC_BASE_URL = origAnthropicUrl;\n else delete process.env.ANTHROPIC_BASE_URL;\n }\n await handle.llm.stop();\n handle = null;\n }\n });\n\n return () => {\n if (!handle) {\n throw new Error(\"useAimock(): server not started — are you calling this inside a test?\");\n }\n return handle;\n };\n}\n\n/**\n * Load a fixture file or directory into `llm`: its LLM fixtures and its\n * `mcpFakes` blocks, which the LLMock buffers and auto-mounts at start (F12).\n * A `FixtureLoadError` (a bad `mcpFakes` block) or a `MisbehaviorConfigError`\n * (a bad fixture `misbehavior` key, thrown only with `enableMisbehavior: true`)\n * propagates and fails `beforeAll`; any other load failure is only warned\n * about, as before.\n */\nfunction loadFixtures(llm: LLMock, fixturePath: string): void {\n try {\n const stat = statSync(fixturePath);\n if (stat.isDirectory()) {\n llm.loadFixtureDir(fixturePath);\n } else {\n llm.loadFixtureFile(fixturePath);\n }\n } catch (err) {\n if (err instanceof FixtureLoadError || err instanceof MisbehaviorConfigError) throw err;\n console.warn(\n `[aimock] Failed to load fixtures from ${fixturePath}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\nexport { LLMock } from \"./llmock.js\";\nexport type { MockServerOptions, Fixture } from \"./types.js\";\nexport type { FakesForOptions, FakesReportMode, FakesTarget } from \"./fakes-plugin.js\";\nexport type { McpFakesReport } from \"./mcp-fakes-report.js\";\n"],"mappings":"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"}
|
package/dist/vitest.d.cts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Fixture, MockServerOptions } from "./types.cjs";
|
|
2
2
|
import { LLMock } from "./llmock.cjs";
|
|
3
|
+
import { McpFakesReport } from "./mcp-fakes-report.cjs";
|
|
4
|
+
import { FakesForOptions, FakesReportMode, FakesTarget } from "./fakes-plugin.cjs";
|
|
3
5
|
|
|
4
6
|
//#region src/vitest.d.ts
|
|
5
7
|
|
|
@@ -8,6 +10,13 @@ interface UseAimockOptions extends MockServerOptions {
|
|
|
8
10
|
fixtures?: string;
|
|
9
11
|
/** If true, sets process.env.OPENAI_BASE_URL to the mock URL + /v1. */
|
|
10
12
|
patchEnv?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The per-test MCP fake report, read in `afterEach` (RP7): `"off"` (the
|
|
15
|
+
* default), `"warn"` (print a failing report) or `"fail"` (throw
|
|
16
|
+
* `AimockFakesReportError`). Not supported in concurrent tests: under
|
|
17
|
+
* `"warn"` or `"fail"`, a concurrent test fails in `beforeEach`.
|
|
18
|
+
*/
|
|
19
|
+
fakesReport?: FakesReportMode;
|
|
11
20
|
}
|
|
12
21
|
interface AimockHandle {
|
|
13
22
|
/** The LLMock instance. */
|
|
@@ -15,17 +24,41 @@ interface AimockHandle {
|
|
|
15
24
|
/** The server URL (e.g., http://127.0.0.1:4010). */
|
|
16
25
|
readonly url: string;
|
|
17
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The handle `useAimock` returns: an `AimockHandle` plus the MCP fakes
|
|
29
|
+
* helpers (RP7). A separate type, so that `AimockHandle` stays as it was and
|
|
30
|
+
* code that builds its own `AimockHandle` keeps compiling.
|
|
31
|
+
*/
|
|
32
|
+
interface AimockFakesHandle extends AimockHandle {
|
|
33
|
+
/**
|
|
34
|
+
* The MCP URL and headers for the fakes scoped to `testId` (RP7), and
|
|
35
|
+
* registers that identity for this test's report. With no `testId`, the
|
|
36
|
+
* default is `<file> › <describe…> › <test>` (TI1); it is used only here and
|
|
37
|
+
* in `fakesReport`, never on LLM traffic (TI4). Throws
|
|
38
|
+
* `AimockTestIdCollisionError` when two tests share a default id, in a
|
|
39
|
+
* concurrent test, or outside a test.
|
|
40
|
+
*/
|
|
41
|
+
fakesFor(testId?: string, opts?: FakesForOptions): FakesTarget;
|
|
42
|
+
/** The MCP fake report for `testId` (default as in `fakesFor`). */
|
|
43
|
+
fakesReport(testId?: string, opts?: {
|
|
44
|
+
context?: string;
|
|
45
|
+
}): Promise<McpFakesReport>;
|
|
46
|
+
}
|
|
18
47
|
/**
|
|
19
48
|
* Start an aimock server for the duration of the test suite.
|
|
20
49
|
*
|
|
21
|
-
* - `beforeAll`: starts the server and optionally loads fixtures
|
|
22
|
-
*
|
|
23
|
-
* - `
|
|
50
|
+
* - `beforeAll`: starts the server and optionally loads fixtures (LLM fixtures and
|
|
51
|
+
* `mcpFakes` blocks; a bad `mcpFakes` block fails `beforeAll`)
|
|
52
|
+
* - `beforeEach`: closes Live sessions and resets fixture match counts and MCP fake
|
|
53
|
+
* consumption (not fixtures or fakes); under `fakesReport: "warn" | "fail"` it
|
|
54
|
+
* throws in a concurrent test, which the per-test report does not support
|
|
55
|
+
* - `afterEach`: closes Live sessions owned by this helper's server, then checks
|
|
56
|
+
* the MCP fake report under `fakesReport: "warn" | "fail"`
|
|
24
57
|
* - `afterAll`: stops the server
|
|
25
58
|
*
|
|
26
59
|
* Returns a getter function — call it inside tests to access the handle.
|
|
27
60
|
*/
|
|
28
|
-
declare function useAimock(options?: UseAimockOptions): () =>
|
|
61
|
+
declare function useAimock(options?: UseAimockOptions): () => AimockFakesHandle;
|
|
29
62
|
//#endregion
|
|
30
|
-
export { AimockHandle, type Fixture, LLMock, type MockServerOptions, UseAimockOptions, useAimock };
|
|
63
|
+
export { AimockFakesHandle, AimockHandle, type FakesForOptions, type FakesReportMode, type FakesTarget, type Fixture, LLMock, type McpFakesReport, type MockServerOptions, UseAimockOptions, useAimock };
|
|
31
64
|
//# sourceMappingURL=vitest.d.cts.map
|
package/dist/vitest.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest.d.cts","names":[],"sources":["../src/vitest.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vitest.d.cts","names":[],"sources":["../src/vitest.ts"],"sourcesContent":[],"mappings":";;;;;;;AAqEqE,UArCpD,gBAAA,SAAyB,iBAqC2B,CAAA;;UAX1B,CAAA,EAAA,MAAA;EAAY;EAyCvC,QAAA,CAAA,EAAA,OAAS;EAAA;;;;;;gBAxDT;;UAGC,YAAA;;gBAED;;;;;;;;;UAUC,iBAAA,SAA0B;;;;;;;;;mCASR,kBAAkB;;;;MAEQ,QAAQ;;;;;;;;;;;;;;;;iBA8BrD,SAAA,WAAmB,yBAA8B"}
|
package/dist/vitest.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Fixture, MockServerOptions } from "./types.js";
|
|
2
2
|
import { LLMock } from "./llmock.js";
|
|
3
|
+
import { McpFakesReport } from "./mcp-fakes-report.js";
|
|
4
|
+
import { FakesForOptions, FakesReportMode, FakesTarget } from "./fakes-plugin.js";
|
|
3
5
|
|
|
4
6
|
//#region src/vitest.d.ts
|
|
5
7
|
|
|
@@ -8,6 +10,13 @@ interface UseAimockOptions extends MockServerOptions {
|
|
|
8
10
|
fixtures?: string;
|
|
9
11
|
/** If true, sets process.env.OPENAI_BASE_URL to the mock URL + /v1. */
|
|
10
12
|
patchEnv?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The per-test MCP fake report, read in `afterEach` (RP7): `"off"` (the
|
|
15
|
+
* default), `"warn"` (print a failing report) or `"fail"` (throw
|
|
16
|
+
* `AimockFakesReportError`). Not supported in concurrent tests: under
|
|
17
|
+
* `"warn"` or `"fail"`, a concurrent test fails in `beforeEach`.
|
|
18
|
+
*/
|
|
19
|
+
fakesReport?: FakesReportMode;
|
|
11
20
|
}
|
|
12
21
|
interface AimockHandle {
|
|
13
22
|
/** The LLMock instance. */
|
|
@@ -15,17 +24,41 @@ interface AimockHandle {
|
|
|
15
24
|
/** The server URL (e.g., http://127.0.0.1:4010). */
|
|
16
25
|
readonly url: string;
|
|
17
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The handle `useAimock` returns: an `AimockHandle` plus the MCP fakes
|
|
29
|
+
* helpers (RP7). A separate type, so that `AimockHandle` stays as it was and
|
|
30
|
+
* code that builds its own `AimockHandle` keeps compiling.
|
|
31
|
+
*/
|
|
32
|
+
interface AimockFakesHandle extends AimockHandle {
|
|
33
|
+
/**
|
|
34
|
+
* The MCP URL and headers for the fakes scoped to `testId` (RP7), and
|
|
35
|
+
* registers that identity for this test's report. With no `testId`, the
|
|
36
|
+
* default is `<file> › <describe…> › <test>` (TI1); it is used only here and
|
|
37
|
+
* in `fakesReport`, never on LLM traffic (TI4). Throws
|
|
38
|
+
* `AimockTestIdCollisionError` when two tests share a default id, in a
|
|
39
|
+
* concurrent test, or outside a test.
|
|
40
|
+
*/
|
|
41
|
+
fakesFor(testId?: string, opts?: FakesForOptions): FakesTarget;
|
|
42
|
+
/** The MCP fake report for `testId` (default as in `fakesFor`). */
|
|
43
|
+
fakesReport(testId?: string, opts?: {
|
|
44
|
+
context?: string;
|
|
45
|
+
}): Promise<McpFakesReport>;
|
|
46
|
+
}
|
|
18
47
|
/**
|
|
19
48
|
* Start an aimock server for the duration of the test suite.
|
|
20
49
|
*
|
|
21
|
-
* - `beforeAll`: starts the server and optionally loads fixtures
|
|
22
|
-
*
|
|
23
|
-
* - `
|
|
50
|
+
* - `beforeAll`: starts the server and optionally loads fixtures (LLM fixtures and
|
|
51
|
+
* `mcpFakes` blocks; a bad `mcpFakes` block fails `beforeAll`)
|
|
52
|
+
* - `beforeEach`: closes Live sessions and resets fixture match counts and MCP fake
|
|
53
|
+
* consumption (not fixtures or fakes); under `fakesReport: "warn" | "fail"` it
|
|
54
|
+
* throws in a concurrent test, which the per-test report does not support
|
|
55
|
+
* - `afterEach`: closes Live sessions owned by this helper's server, then checks
|
|
56
|
+
* the MCP fake report under `fakesReport: "warn" | "fail"`
|
|
24
57
|
* - `afterAll`: stops the server
|
|
25
58
|
*
|
|
26
59
|
* Returns a getter function — call it inside tests to access the handle.
|
|
27
60
|
*/
|
|
28
|
-
declare function useAimock(options?: UseAimockOptions): () =>
|
|
61
|
+
declare function useAimock(options?: UseAimockOptions): () => AimockFakesHandle;
|
|
29
62
|
//#endregion
|
|
30
|
-
export { AimockHandle, type Fixture, LLMock, type MockServerOptions, UseAimockOptions, useAimock };
|
|
63
|
+
export { AimockFakesHandle, AimockHandle, type FakesForOptions, type FakesReportMode, type FakesTarget, type Fixture, LLMock, type McpFakesReport, type MockServerOptions, UseAimockOptions, useAimock };
|
|
31
64
|
//# sourceMappingURL=vitest.d.ts.map
|
package/dist/vitest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest.d.ts","names":[],"sources":["../src/vitest.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vitest.d.ts","names":[],"sources":["../src/vitest.ts"],"sourcesContent":[],"mappings":";;;;;;;AAqEqE,UArCpD,gBAAA,SAAyB,iBAqC2B,CAAA;;UAX1B,CAAA,EAAA,MAAA;EAAY;EAyCvC,QAAA,CAAA,EAAA,OAAS;EAAA;;;;;;gBAxDT;;UAGC,YAAA;;gBAED;;;;;;;;;UAUC,iBAAA,SAA0B;;;;;;;;;mCASR,kBAAkB;;;;MAEQ,QAAQ;;;;;;;;;;;;;;;;iBA8BrD,SAAA,WAAmB,yBAA8B"}
|
package/dist/vitest.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FixtureLoadError, MisbehaviorConfigError } from "./fixture-loader.js";
|
|
2
|
+
import { AimockTestIdCollisionError } from "./mcp-fakes-report.js";
|
|
2
3
|
import { LLMock } from "./llmock.js";
|
|
4
|
+
import { FakesPluginState, TEST_ID_SEPARATOR, fakesReportMode, fakesTarget, fetchFakesReport } from "./fakes-plugin.js";
|
|
3
5
|
import { statSync } from "node:fs";
|
|
4
6
|
import { resolve } from "node:path";
|
|
5
7
|
import { afterAll, afterEach, beforeAll, beforeEach } from "vitest";
|
|
@@ -17,12 +19,26 @@ import { afterAll, afterEach, beforeAll, beforeEach } from "vitest";
|
|
|
17
19
|
* const res = await fetch(`${mock().url}/v1/chat/completions`, { ... });
|
|
18
20
|
* });
|
|
19
21
|
*/
|
|
22
|
+
/** TI1: `<file> › <name1> › … › <nameN>`; unnamed suites add no segment. */
|
|
23
|
+
function defaultVitestId(task) {
|
|
24
|
+
const names = [];
|
|
25
|
+
for (let suite = task.suite; suite !== void 0 && suite !== task.file; suite = suite.suite) if (suite.name !== "") names.unshift(suite.name);
|
|
26
|
+
return [
|
|
27
|
+
task.file.name,
|
|
28
|
+
...names,
|
|
29
|
+
task.name
|
|
30
|
+
].join(TEST_ID_SEPARATOR);
|
|
31
|
+
}
|
|
20
32
|
/**
|
|
21
33
|
* Start an aimock server for the duration of the test suite.
|
|
22
34
|
*
|
|
23
|
-
* - `beforeAll`: starts the server and optionally loads fixtures
|
|
24
|
-
*
|
|
25
|
-
* - `
|
|
35
|
+
* - `beforeAll`: starts the server and optionally loads fixtures (LLM fixtures and
|
|
36
|
+
* `mcpFakes` blocks; a bad `mcpFakes` block fails `beforeAll`)
|
|
37
|
+
* - `beforeEach`: closes Live sessions and resets fixture match counts and MCP fake
|
|
38
|
+
* consumption (not fixtures or fakes); under `fakesReport: "warn" | "fail"` it
|
|
39
|
+
* throws in a concurrent test, which the per-test report does not support
|
|
40
|
+
* - `afterEach`: closes Live sessions owned by this helper's server, then checks
|
|
41
|
+
* the MCP fake report under `fakesReport: "warn" | "fail"`
|
|
26
42
|
* - `afterAll`: stops the server
|
|
27
43
|
*
|
|
28
44
|
* Returns a getter function — call it inside tests to access the handle.
|
|
@@ -31,13 +47,34 @@ function useAimock(options = {}) {
|
|
|
31
47
|
let handle = null;
|
|
32
48
|
let origOpenaiUrl;
|
|
33
49
|
let origAnthropicUrl;
|
|
50
|
+
let state = new FakesPluginState("off");
|
|
51
|
+
/** TI5: default id -> the `task.id` that first used it; fresh per `beforeAll`. */
|
|
52
|
+
let idMap = /* @__PURE__ */ new Map();
|
|
53
|
+
let current = null;
|
|
54
|
+
function resolveTestId(testId) {
|
|
55
|
+
if (testId !== void 0) return testId;
|
|
56
|
+
if (current === null) throw new AimockTestIdCollisionError("fakesFor() needs a running test; pass an explicit testId");
|
|
57
|
+
if (current.concurrent === true) throw new AimockTestIdCollisionError("default test ids are not supported in concurrent tests; pass an explicit testId");
|
|
58
|
+
const id = defaultVitestId(current);
|
|
59
|
+
const owner = idMap.get(id);
|
|
60
|
+
if (owner !== void 0 && owner !== current.id) throw new AimockTestIdCollisionError(`two tests share the default test id ${JSON.stringify(id)}; pass an explicit testId`);
|
|
61
|
+
idMap.set(id, current.id);
|
|
62
|
+
return id;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The report keeps one state per `useAimock`, so concurrent tests would
|
|
66
|
+
* clear and read each other's identities: under `"warn"` or `"fail"`, a
|
|
67
|
+
* concurrent test is refused in `beforeEach` (and not reported on).
|
|
68
|
+
*/
|
|
69
|
+
function refusesConcurrent(task) {
|
|
70
|
+
return state.mode !== "off" && task.concurrent === true;
|
|
71
|
+
}
|
|
34
72
|
beforeAll(async () => {
|
|
35
|
-
const { fixtures: fixturePath, patchEnv, ...serverOpts } = options;
|
|
73
|
+
const { fixtures: fixturePath, patchEnv, fakesReport, ...serverOpts } = options;
|
|
74
|
+
state = new FakesPluginState(fakesReportMode(fakesReport));
|
|
75
|
+
idMap = /* @__PURE__ */ new Map();
|
|
36
76
|
const llm = new LLMock(serverOpts);
|
|
37
|
-
if (fixturePath)
|
|
38
|
-
const loadedFixtures = loadFixtures(resolve(fixturePath), options.live);
|
|
39
|
-
for (const f of loadedFixtures) llm.addFixture(f);
|
|
40
|
-
}
|
|
77
|
+
if (fixturePath) loadFixtures(llm, resolve(fixturePath));
|
|
41
78
|
const url = await llm.start();
|
|
42
79
|
if (patchEnv !== false) {
|
|
43
80
|
origOpenaiUrl = process.env.OPENAI_BASE_URL;
|
|
@@ -47,17 +84,33 @@ function useAimock(options = {}) {
|
|
|
47
84
|
}
|
|
48
85
|
handle = {
|
|
49
86
|
llm,
|
|
50
|
-
url
|
|
87
|
+
url,
|
|
88
|
+
fakesFor(testId, opts = {}) {
|
|
89
|
+
const id = resolveTestId(testId);
|
|
90
|
+
state.register(id, opts.context ?? null);
|
|
91
|
+
return fakesTarget(url, id, opts);
|
|
92
|
+
},
|
|
93
|
+
fakesReport(testId, opts = {}) {
|
|
94
|
+
return fetchFakesReport(url, resolveTestId(testId), opts.context ?? null);
|
|
95
|
+
}
|
|
51
96
|
};
|
|
52
97
|
});
|
|
53
|
-
beforeEach(() => {
|
|
98
|
+
beforeEach((ctx) => {
|
|
99
|
+
current = ctx.task;
|
|
100
|
+
if (refusesConcurrent(ctx.task)) throw new Error("useAimock(): fakesReport is not supported in concurrent tests; set fakesReport: \"off\" for this suite or run it sequentially");
|
|
54
101
|
if (handle) {
|
|
55
102
|
handle.llm.closeLiveSessions();
|
|
56
103
|
handle.llm.resetMatchCounts();
|
|
104
|
+
state.beforeEach(handle.llm);
|
|
57
105
|
}
|
|
58
106
|
});
|
|
59
|
-
afterEach(() => {
|
|
60
|
-
|
|
107
|
+
afterEach(async (ctx) => {
|
|
108
|
+
try {
|
|
109
|
+
handle?.llm.closeLiveSessions();
|
|
110
|
+
if (handle && !refusesConcurrent(ctx.task)) await state.afterEach(handle.llm, handle.url);
|
|
111
|
+
} finally {
|
|
112
|
+
current = null;
|
|
113
|
+
}
|
|
61
114
|
});
|
|
62
115
|
afterAll(async () => {
|
|
63
116
|
if (handle) {
|
|
@@ -76,13 +129,21 @@ function useAimock(options = {}) {
|
|
|
76
129
|
return handle;
|
|
77
130
|
};
|
|
78
131
|
}
|
|
79
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Load a fixture file or directory into `llm`: its LLM fixtures and its
|
|
134
|
+
* `mcpFakes` blocks, which the LLMock buffers and auto-mounts at start (F12).
|
|
135
|
+
* A `FixtureLoadError` (a bad `mcpFakes` block) or a `MisbehaviorConfigError`
|
|
136
|
+
* (a bad fixture `misbehavior` key, thrown only with `enableMisbehavior: true`)
|
|
137
|
+
* propagates and fails `beforeAll`; any other load failure is only warned
|
|
138
|
+
* about, as before.
|
|
139
|
+
*/
|
|
140
|
+
function loadFixtures(llm, fixturePath) {
|
|
80
141
|
try {
|
|
81
|
-
if (statSync(fixturePath).isDirectory())
|
|
82
|
-
|
|
142
|
+
if (statSync(fixturePath).isDirectory()) llm.loadFixtureDir(fixturePath);
|
|
143
|
+
else llm.loadFixtureFile(fixturePath);
|
|
83
144
|
} catch (err) {
|
|
145
|
+
if (err instanceof FixtureLoadError || err instanceof MisbehaviorConfigError) throw err;
|
|
84
146
|
console.warn(`[aimock] Failed to load fixtures from ${fixturePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
85
|
-
return [];
|
|
86
147
|
}
|
|
87
148
|
}
|
|
88
149
|
|
package/dist/vitest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest.js","names":[],"sources":["../src/vitest.ts"],"sourcesContent":["/**\n * Vitest integration for aimock.\n *\n * Usage:\n * import { useAimock } from \"@copilotkit/aimock/vitest\";\n *\n * const mock = useAimock({ fixtures: \"./fixtures\" });\n *\n * it(\"responds\", async () => {\n * const res = await fetch(`${mock().url}/v1/chat/completions`, { ... });\n * });\n */\n\nimport { beforeAll, afterAll, beforeEach, afterEach } from \"vitest\";\nimport { LLMock } from \"./llmock.js\";\nimport { loadFixtureFile, loadFixturesFromDir } from \"./fixture-loader.js\";\nimport type { LiveOptions } from \"./live-types.js\";\nimport type { Fixture, MockServerOptions } from \"./types.js\";\nimport { statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport interface UseAimockOptions extends MockServerOptions {\n /** Path to fixture file or directory. Loaded automatically on start. */\n fixtures?: string;\n /** If true, sets process.env.OPENAI_BASE_URL to the mock URL + /v1. */\n patchEnv?: boolean;\n}\n\nexport interface AimockHandle {\n /** The LLMock instance. */\n readonly llm: LLMock;\n /** The server URL (e.g., http://127.0.0.1:4010). */\n readonly url: string;\n}\n\n/**\n * Start an aimock server for the duration of the test suite.\n *\n * - `beforeAll`: starts the server and optionally loads fixtures\n * - `beforeEach`: closes Live sessions and resets fixture match counts (not fixtures)\n * - `afterEach`: closes Live sessions owned by this helper's server\n * - `afterAll`: stops the server\n *\n * Returns a getter function — call it inside tests to access the handle.\n */\nexport function useAimock(options: UseAimockOptions = {}): () => AimockHandle {\n let handle: AimockHandle | null = null;\n let origOpenaiUrl: string | undefined;\n let origAnthropicUrl: string | undefined;\n\n beforeAll(async () => {\n const { fixtures: fixturePath, patchEnv, ...serverOpts } = options;\n const llm = new LLMock(serverOpts);\n\n if (fixturePath) {\n const resolved = resolve(fixturePath);\n const loadedFixtures = loadFixtures(resolved, options.live);\n for (const f of loadedFixtures) {\n llm.addFixture(f);\n }\n }\n\n const url = await llm.start();\n\n if (patchEnv !== false) {\n origOpenaiUrl = process.env.OPENAI_BASE_URL;\n origAnthropicUrl = process.env.ANTHROPIC_BASE_URL;\n process.env.OPENAI_BASE_URL = `${url}/v1`;\n process.env.ANTHROPIC_BASE_URL = `${url}/v1`;\n }\n\n handle = { llm, url };\n });\n\n beforeEach(() => {\n if (handle) {\n handle.llm.closeLiveSessions();\n handle.llm.resetMatchCounts();\n }\n });\n\n afterEach(() => {\n handle?.llm.closeLiveSessions();\n });\n\n afterAll(async () => {\n if (handle) {\n if (options.patchEnv !== false) {\n if (origOpenaiUrl !== undefined) process.env.OPENAI_BASE_URL = origOpenaiUrl;\n else delete process.env.OPENAI_BASE_URL;\n if (origAnthropicUrl !== undefined) process.env.ANTHROPIC_BASE_URL = origAnthropicUrl;\n else delete process.env.ANTHROPIC_BASE_URL;\n }\n await handle.llm.stop();\n handle = null;\n }\n });\n\n return () => {\n if (!handle) {\n throw new Error(\"useAimock(): server not started — are you calling this inside a test?\");\n }\n return handle;\n };\n}\n\nfunction loadFixtures(fixturePath: string, liveOptions?: LiveOptions): Fixture[] {\n try {\n const stat = statSync(fixturePath);\n if (stat.isDirectory()) {\n return loadFixturesFromDir(fixturePath, undefined, liveOptions);\n }\n return loadFixtureFile(fixturePath, undefined, liveOptions);\n } catch (err) {\n console.warn(\n `[aimock] Failed to load fixtures from ${fixturePath}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return [];\n }\n}\n\nexport { LLMock } from \"./llmock.js\";\nexport type { MockServerOptions, Fixture } from \"./types.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,SAAgB,UAAU,UAA4B,EAAE,EAAsB;CAC5E,IAAI,SAA8B;CAClC,IAAI;CACJ,IAAI;AAEJ,WAAU,YAAY;EACpB,MAAM,EAAE,UAAU,aAAa,UAAU,GAAG,eAAe;EAC3D,MAAM,MAAM,IAAI,OAAO,WAAW;AAElC,MAAI,aAAa;GAEf,MAAM,iBAAiB,aADN,QAAQ,YAAY,EACS,QAAQ,KAAK;AAC3D,QAAK,MAAM,KAAK,eACd,KAAI,WAAW,EAAE;;EAIrB,MAAM,MAAM,MAAM,IAAI,OAAO;AAE7B,MAAI,aAAa,OAAO;AACtB,mBAAgB,QAAQ,IAAI;AAC5B,sBAAmB,QAAQ,IAAI;AAC/B,WAAQ,IAAI,kBAAkB,GAAG,IAAI;AACrC,WAAQ,IAAI,qBAAqB,GAAG,IAAI;;AAG1C,WAAS;GAAE;GAAK;GAAK;GACrB;AAEF,kBAAiB;AACf,MAAI,QAAQ;AACV,UAAO,IAAI,mBAAmB;AAC9B,UAAO,IAAI,kBAAkB;;GAE/B;AAEF,iBAAgB;AACd,UAAQ,IAAI,mBAAmB;GAC/B;AAEF,UAAS,YAAY;AACnB,MAAI,QAAQ;AACV,OAAI,QAAQ,aAAa,OAAO;AAC9B,QAAI,kBAAkB,OAAW,SAAQ,IAAI,kBAAkB;QAC1D,QAAO,QAAQ,IAAI;AACxB,QAAI,qBAAqB,OAAW,SAAQ,IAAI,qBAAqB;QAChE,QAAO,QAAQ,IAAI;;AAE1B,SAAM,OAAO,IAAI,MAAM;AACvB,YAAS;;GAEX;AAEF,cAAa;AACX,MAAI,CAAC,OACH,OAAM,IAAI,MAAM,wEAAwE;AAE1F,SAAO;;;AAIX,SAAS,aAAa,aAAqB,aAAsC;AAC/E,KAAI;AAEF,MADa,SAAS,YAAY,CACzB,aAAa,CACpB,QAAO,oBAAoB,aAAa,QAAW,YAAY;AAEjE,SAAO,gBAAgB,aAAa,QAAW,YAAY;UACpD,KAAK;AACZ,UAAQ,KACN,yCAAyC,YAAY,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAC1G;AACD,SAAO,EAAE"}
|
|
1
|
+
{"version":3,"file":"vitest.js","names":[],"sources":["../src/vitest.ts"],"sourcesContent":["/**\n * Vitest integration for aimock.\n *\n * Usage:\n * import { useAimock } from \"@copilotkit/aimock/vitest\";\n *\n * const mock = useAimock({ fixtures: \"./fixtures\" });\n *\n * it(\"responds\", async () => {\n * const res = await fetch(`${mock().url}/v1/chat/completions`, { ... });\n * });\n */\n\nimport { beforeAll, afterAll, beforeEach, afterEach } from \"vitest\";\nimport type { RunnerTestCase, RunnerTestSuite } from \"vitest\";\nimport { LLMock } from \"./llmock.js\";\nimport { FixtureLoadError, MisbehaviorConfigError } from \"./fixture-loader.js\";\nimport type { MockServerOptions } from \"./types.js\";\nimport {\n FakesPluginState,\n TEST_ID_SEPARATOR,\n fakesReportMode,\n fakesTarget,\n fetchFakesReport,\n type FakesForOptions,\n type FakesReportMode,\n type FakesTarget,\n} from \"./fakes-plugin.js\";\nimport { AimockTestIdCollisionError, type McpFakesReport } from \"./mcp-fakes-report.js\";\nimport { statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport interface UseAimockOptions extends MockServerOptions {\n /** Path to fixture file or directory. Loaded automatically on start. */\n fixtures?: string;\n /** If true, sets process.env.OPENAI_BASE_URL to the mock URL + /v1. */\n patchEnv?: boolean;\n /**\n * The per-test MCP fake report, read in `afterEach` (RP7): `\"off\"` (the\n * default), `\"warn\"` (print a failing report) or `\"fail\"` (throw\n * `AimockFakesReportError`). Not supported in concurrent tests: under\n * `\"warn\"` or `\"fail\"`, a concurrent test fails in `beforeEach`.\n */\n fakesReport?: FakesReportMode;\n}\n\nexport interface AimockHandle {\n /** The LLMock instance. */\n readonly llm: LLMock;\n /** The server URL (e.g., http://127.0.0.1:4010). */\n readonly url: string;\n}\n\n/**\n * The handle `useAimock` returns: an `AimockHandle` plus the MCP fakes\n * helpers (RP7). A separate type, so that `AimockHandle` stays as it was and\n * code that builds its own `AimockHandle` keeps compiling.\n */\nexport interface AimockFakesHandle extends AimockHandle {\n /**\n * The MCP URL and headers for the fakes scoped to `testId` (RP7), and\n * registers that identity for this test's report. With no `testId`, the\n * default is `<file> › <describe…> › <test>` (TI1); it is used only here and\n * in `fakesReport`, never on LLM traffic (TI4). Throws\n * `AimockTestIdCollisionError` when two tests share a default id, in a\n * concurrent test, or outside a test.\n */\n fakesFor(testId?: string, opts?: FakesForOptions): FakesTarget;\n /** The MCP fake report for `testId` (default as in `fakesFor`). */\n fakesReport(testId?: string, opts?: { context?: string }): Promise<McpFakesReport>;\n}\n\n/** TI1: `<file> › <name1> › … › <nameN>`; unnamed suites add no segment. */\nfunction defaultVitestId(task: Readonly<RunnerTestCase>): string {\n const names: string[] = [];\n for (\n let suite: RunnerTestSuite | undefined = task.suite;\n suite !== undefined && suite !== task.file;\n suite = suite.suite\n ) {\n if (suite.name !== \"\") names.unshift(suite.name);\n }\n return [task.file.name, ...names, task.name].join(TEST_ID_SEPARATOR);\n}\n\n/**\n * Start an aimock server for the duration of the test suite.\n *\n * - `beforeAll`: starts the server and optionally loads fixtures (LLM fixtures and\n * `mcpFakes` blocks; a bad `mcpFakes` block fails `beforeAll`)\n * - `beforeEach`: closes Live sessions and resets fixture match counts and MCP fake\n * consumption (not fixtures or fakes); under `fakesReport: \"warn\" | \"fail\"` it\n * throws in a concurrent test, which the per-test report does not support\n * - `afterEach`: closes Live sessions owned by this helper's server, then checks\n * the MCP fake report under `fakesReport: \"warn\" | \"fail\"`\n * - `afterAll`: stops the server\n *\n * Returns a getter function — call it inside tests to access the handle.\n */\nexport function useAimock(options: UseAimockOptions = {}): () => AimockFakesHandle {\n let handle: AimockFakesHandle | null = null;\n let origOpenaiUrl: string | undefined;\n let origAnthropicUrl: string | undefined;\n let state = new FakesPluginState(\"off\");\n /** TI5: default id -> the `task.id` that first used it; fresh per `beforeAll`. */\n let idMap = new Map<string, string>();\n let current: Readonly<RunnerTestCase> | null = null;\n\n function resolveTestId(testId: string | undefined): string {\n if (testId !== undefined) return testId; // TI4: an explicit id wins\n if (current === null) {\n throw new AimockTestIdCollisionError(\n \"fakesFor() needs a running test; pass an explicit testId\",\n );\n }\n if (current.concurrent === true) {\n throw new AimockTestIdCollisionError(\n \"default test ids are not supported in concurrent tests; pass an explicit testId\",\n );\n }\n const id = defaultVitestId(current);\n const owner = idMap.get(id);\n if (owner !== undefined && owner !== current.id) {\n throw new AimockTestIdCollisionError(\n `two tests share the default test id ${JSON.stringify(id)}; pass an explicit testId`,\n );\n }\n idMap.set(id, current.id);\n return id;\n }\n\n /**\n * The report keeps one state per `useAimock`, so concurrent tests would\n * clear and read each other's identities: under `\"warn\"` or `\"fail\"`, a\n * concurrent test is refused in `beforeEach` (and not reported on).\n */\n function refusesConcurrent(task: Readonly<RunnerTestCase>): boolean {\n return state.mode !== \"off\" && task.concurrent === true;\n }\n\n beforeAll(async () => {\n const { fixtures: fixturePath, patchEnv, fakesReport, ...serverOpts } = options;\n state = new FakesPluginState(fakesReportMode(fakesReport));\n idMap = new Map();\n const llm = new LLMock(serverOpts);\n\n if (fixturePath) {\n loadFixtures(llm, resolve(fixturePath));\n }\n\n const url = await llm.start();\n\n if (patchEnv !== false) {\n origOpenaiUrl = process.env.OPENAI_BASE_URL;\n origAnthropicUrl = process.env.ANTHROPIC_BASE_URL;\n process.env.OPENAI_BASE_URL = `${url}/v1`;\n process.env.ANTHROPIC_BASE_URL = `${url}/v1`;\n }\n\n handle = {\n llm,\n url,\n fakesFor(testId?: string, opts: FakesForOptions = {}): FakesTarget {\n const id = resolveTestId(testId);\n state.register(id, opts.context ?? null);\n return fakesTarget(url, id, opts);\n },\n fakesReport(testId?: string, opts: { context?: string } = {}): Promise<McpFakesReport> {\n return fetchFakesReport(url, resolveTestId(testId), opts.context ?? null);\n },\n };\n });\n\n beforeEach((ctx) => {\n current = ctx.task;\n if (refusesConcurrent(ctx.task)) {\n throw new Error(\n 'useAimock(): fakesReport is not supported in concurrent tests; set fakesReport: \"off\" for this suite or run it sequentially',\n );\n }\n if (handle) {\n handle.llm.closeLiveSessions();\n handle.llm.resetMatchCounts();\n state.beforeEach(handle.llm);\n }\n });\n\n afterEach(async (ctx) => {\n try {\n handle?.llm.closeLiveSessions();\n if (handle && !refusesConcurrent(ctx.task)) await state.afterEach(handle.llm, handle.url);\n } finally {\n current = null;\n }\n });\n\n afterAll(async () => {\n if (handle) {\n if (options.patchEnv !== false) {\n if (origOpenaiUrl !== undefined) process.env.OPENAI_BASE_URL = origOpenaiUrl;\n else delete process.env.OPENAI_BASE_URL;\n if (origAnthropicUrl !== undefined) process.env.ANTHROPIC_BASE_URL = origAnthropicUrl;\n else delete process.env.ANTHROPIC_BASE_URL;\n }\n await handle.llm.stop();\n handle = null;\n }\n });\n\n return () => {\n if (!handle) {\n throw new Error(\"useAimock(): server not started — are you calling this inside a test?\");\n }\n return handle;\n };\n}\n\n/**\n * Load a fixture file or directory into `llm`: its LLM fixtures and its\n * `mcpFakes` blocks, which the LLMock buffers and auto-mounts at start (F12).\n * A `FixtureLoadError` (a bad `mcpFakes` block) or a `MisbehaviorConfigError`\n * (a bad fixture `misbehavior` key, thrown only with `enableMisbehavior: true`)\n * propagates and fails `beforeAll`; any other load failure is only warned\n * about, as before.\n */\nfunction loadFixtures(llm: LLMock, fixturePath: string): void {\n try {\n const stat = statSync(fixturePath);\n if (stat.isDirectory()) {\n llm.loadFixtureDir(fixturePath);\n } else {\n llm.loadFixtureFile(fixturePath);\n }\n } catch (err) {\n if (err instanceof FixtureLoadError || err instanceof MisbehaviorConfigError) throw err;\n console.warn(\n `[aimock] Failed to load fixtures from ${fixturePath}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\nexport { LLMock } from \"./llmock.js\";\nexport type { MockServerOptions, Fixture } from \"./types.js\";\nexport type { FakesForOptions, FakesReportMode, FakesTarget } from \"./fakes-plugin.js\";\nexport type { McpFakesReport } from \"./mcp-fakes-report.js\";\n"],"mappings":"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"}
|
package/dist/watcher.cjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
2
|
const require_fixture_loader = require('./fixture-loader.cjs');
|
|
3
3
|
let node_fs = require("node:fs");
|
|
4
|
+
let node_path = require("node:path");
|
|
4
5
|
|
|
5
6
|
//#region src/watcher.ts
|
|
6
7
|
const DEBOUNCE_MS = 500;
|
|
7
8
|
function watchFixtures(fixturePath, fixtures, loadFn, opts) {
|
|
8
|
-
const { logger, validate, validateFn } = opts;
|
|
9
|
+
const { logger, validate, validateFn, ignore } = opts;
|
|
9
10
|
let debounceTimer = null;
|
|
10
11
|
function reload() {
|
|
11
12
|
logger.info(`File changed — reloading fixtures from ${fixturePath}...`);
|
|
@@ -36,7 +37,10 @@ function watchFixtures(fixturePath, fixtures, loadFn, opts) {
|
|
|
36
37
|
fixtures.push(...newFixtures);
|
|
37
38
|
logger.info(`Reloaded ${newFixtures.length} fixture(s)`);
|
|
38
39
|
}
|
|
39
|
-
const watcher = (0, node_fs.watch)(fixturePath, { recursive: true }, () => {
|
|
40
|
+
const watcher = (0, node_fs.watch)(fixturePath, { recursive: true }, (_event, filename) => {
|
|
41
|
+
if (ignore && filename) try {
|
|
42
|
+
if (ignore((0, node_path.join)(fixturePath, filename))) return;
|
|
43
|
+
} catch {}
|
|
40
44
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
41
45
|
debounceTimer = setTimeout(reload, DEBOUNCE_MS);
|
|
42
46
|
});
|
package/dist/watcher.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcher.cjs","names":[],"sources":["../src/watcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from \"node:fs\";\nimport type { Fixture } from \"./types.js\";\nimport type { Logger } from \"./logger.js\";\nimport { clearFixtureQueue, type ValidationResult } from \"./fixture-loader.js\";\n\nconst DEBOUNCE_MS = 500;\n\nexport function watchFixtures(\n fixturePath: string,\n fixtures: Fixture[],\n loadFn: () => Fixture[],\n opts: {\n logger: Logger;\n validate?: boolean;\n validateFn?: (fixtures: Fixture[]) => ValidationResult[];\n },\n): { close: () => void } {\n const { logger, validate, validateFn } = opts;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n function reload() {\n logger.info(`File changed — reloading fixtures from ${fixturePath}...`);\n\n let newFixtures: Fixture[];\n try {\n newFixtures = loadFn();\n } catch (err) {\n logger.error(\"Failed to reload fixtures:\", err);\n logger.error(\"Previous fixtures remain active. Fix the error and save again to retry.\");\n return;\n }\n\n if (newFixtures.length === 0 && fixtures.length > 0) {\n logger.warn(\n \"Reload produced 0 fixtures — keeping previous fixtures. Check fixture file for errors.\",\n );\n return;\n }\n\n if (validate && validateFn) {\n const results = validateFn(newFixtures);\n const errors = results.filter((r) => r.severity === \"error\");\n const warnings = results.filter((r) => r.severity === \"warning\");\n\n for (const w of warnings) {\n logger.warn(`Fixture ${w.fixtureIndex}: ${w.message}`);\n }\n\n if (errors.length > 0) {\n for (const e of errors) {\n logger.error(`Fixture ${e.fixtureIndex}: ${e.message}`);\n }\n logger.error(`${errors.length} validation error(s) — keeping previous fixtures`);\n return;\n }\n }\n\n // Replace in-place to preserve array reference identity. Same door as\n // reset: a one-shot claim parked across the reload must not re-arm into it.\n clearFixtureQueue(fixtures);\n fixtures.push(...newFixtures);\n logger.info(`Reloaded ${newFixtures.length} fixture(s)`);\n }\n\n const watcher: FSWatcher = watch(fixturePath, { recursive: true }, () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(reload, DEBOUNCE_MS);\n });\n\n watcher.on(\"error\", (err: Error) => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = null;\n try {\n watcher.close();\n } catch {\n /* already dead */\n }\n logger.error(`File watcher error on ${fixturePath}: ${err.message}`);\n logger.error(\"Fixture auto-reload is no longer active. Restart the server to resume watching.\");\n });\n\n return {\n close() {\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n },\n };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"watcher.cjs","names":[],"sources":["../src/watcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Fixture } from \"./types.js\";\nimport type { Logger } from \"./logger.js\";\nimport { clearFixtureQueue, type ValidationResult } from \"./fixture-loader.js\";\n\nconst DEBOUNCE_MS = 500;\n\nexport function watchFixtures(\n fixturePath: string,\n fixtures: Fixture[],\n loadFn: () => Fixture[],\n opts: {\n logger: Logger;\n validate?: boolean;\n validateFn?: (fixtures: Fixture[]) => ValidationResult[];\n /** MR13: an event for a path this returns true for does not reload. */\n ignore?: (absPath: string) => boolean;\n },\n): { close: () => void } {\n const { logger, validate, validateFn, ignore } = opts;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n function reload() {\n logger.info(`File changed — reloading fixtures from ${fixturePath}...`);\n\n let newFixtures: Fixture[];\n try {\n newFixtures = loadFn();\n } catch (err) {\n logger.error(\"Failed to reload fixtures:\", err);\n logger.error(\"Previous fixtures remain active. Fix the error and save again to retry.\");\n return;\n }\n\n if (newFixtures.length === 0 && fixtures.length > 0) {\n logger.warn(\n \"Reload produced 0 fixtures — keeping previous fixtures. Check fixture file for errors.\",\n );\n return;\n }\n\n if (validate && validateFn) {\n const results = validateFn(newFixtures);\n const errors = results.filter((r) => r.severity === \"error\");\n const warnings = results.filter((r) => r.severity === \"warning\");\n\n for (const w of warnings) {\n logger.warn(`Fixture ${w.fixtureIndex}: ${w.message}`);\n }\n\n if (errors.length > 0) {\n for (const e of errors) {\n logger.error(`Fixture ${e.fixtureIndex}: ${e.message}`);\n }\n logger.error(`${errors.length} validation error(s) — keeping previous fixtures`);\n return;\n }\n }\n\n // Replace in-place to preserve array reference identity. Same door as\n // reset: a one-shot claim parked across the reload must not re-arm into it.\n clearFixtureQueue(fixtures);\n fixtures.push(...newFixtures);\n logger.info(`Reloaded ${newFixtures.length} fixture(s)`);\n }\n\n const watcher: FSWatcher = watch(fixturePath, { recursive: true }, (_event, filename) => {\n if (ignore && filename) {\n try {\n if (ignore(join(fixturePath, filename))) return;\n } catch {\n /* a failing ignore never kills the watcher: reload */\n }\n }\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(reload, DEBOUNCE_MS);\n });\n\n watcher.on(\"error\", (err: Error) => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = null;\n try {\n watcher.close();\n } catch {\n /* already dead */\n }\n logger.error(`File watcher error on ${fixturePath}: ${err.message}`);\n logger.error(\"Fixture auto-reload is no longer active. Restart the server to resume watching.\");\n });\n\n return {\n close() {\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n },\n };\n}\n"],"mappings":";;;;;;AAMA,MAAM,cAAc;AAEpB,SAAgB,cACd,aACA,UACA,QACA,MAOuB;CACvB,MAAM,EAAE,QAAQ,UAAU,YAAY,WAAW;CACjD,IAAI,gBAAsD;CAE1D,SAAS,SAAS;AAChB,SAAO,KAAK,0CAA0C,YAAY,KAAK;EAEvE,IAAI;AACJ,MAAI;AACF,iBAAc,QAAQ;WACf,KAAK;AACZ,UAAO,MAAM,8BAA8B,IAAI;AAC/C,UAAO,MAAM,0EAA0E;AACvF;;AAGF,MAAI,YAAY,WAAW,KAAK,SAAS,SAAS,GAAG;AACnD,UAAO,KACL,yFACD;AACD;;AAGF,MAAI,YAAY,YAAY;GAC1B,MAAM,UAAU,WAAW,YAAY;GACvC,MAAM,SAAS,QAAQ,QAAQ,MAAM,EAAE,aAAa,QAAQ;GAC5D,MAAM,WAAW,QAAQ,QAAQ,MAAM,EAAE,aAAa,UAAU;AAEhE,QAAK,MAAM,KAAK,SACd,QAAO,KAAK,WAAW,EAAE,aAAa,IAAI,EAAE,UAAU;AAGxD,OAAI,OAAO,SAAS,GAAG;AACrB,SAAK,MAAM,KAAK,OACd,QAAO,MAAM,WAAW,EAAE,aAAa,IAAI,EAAE,UAAU;AAEzD,WAAO,MAAM,GAAG,OAAO,OAAO,kDAAkD;AAChF;;;AAMJ,2CAAkB,SAAS;AAC3B,WAAS,KAAK,GAAG,YAAY;AAC7B,SAAO,KAAK,YAAY,YAAY,OAAO,aAAa;;CAG1D,MAAM,6BAA2B,aAAa,EAAE,WAAW,MAAM,GAAG,QAAQ,aAAa;AACvF,MAAI,UAAU,SACZ,KAAI;AACF,OAAI,2BAAY,aAAa,SAAS,CAAC,CAAE;UACnC;AAIV,MAAI,cAAe,cAAa,cAAc;AAC9C,kBAAgB,WAAW,QAAQ,YAAY;GAC/C;AAEF,SAAQ,GAAG,UAAU,QAAe;AAClC,MAAI,cAAe,cAAa,cAAc;AAC9C,kBAAgB;AAChB,MAAI;AACF,WAAQ,OAAO;UACT;AAGR,SAAO,MAAM,yBAAyB,YAAY,IAAI,IAAI,UAAU;AACpE,SAAO,MAAM,kFAAkF;GAC/F;AAEF,QAAO,EACL,QAAQ;AACN,MAAI,cAAe,cAAa,cAAc;AAC9C,UAAQ,OAAO;IAElB"}
|
package/dist/watcher.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { clearFixtureQueue } from "./fixture-loader.js";
|
|
2
2
|
import { watch } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
3
4
|
|
|
4
5
|
//#region src/watcher.ts
|
|
5
6
|
const DEBOUNCE_MS = 500;
|
|
6
7
|
function watchFixtures(fixturePath, fixtures, loadFn, opts) {
|
|
7
|
-
const { logger, validate, validateFn } = opts;
|
|
8
|
+
const { logger, validate, validateFn, ignore } = opts;
|
|
8
9
|
let debounceTimer = null;
|
|
9
10
|
function reload() {
|
|
10
11
|
logger.info(`File changed — reloading fixtures from ${fixturePath}...`);
|
|
@@ -35,7 +36,10 @@ function watchFixtures(fixturePath, fixtures, loadFn, opts) {
|
|
|
35
36
|
fixtures.push(...newFixtures);
|
|
36
37
|
logger.info(`Reloaded ${newFixtures.length} fixture(s)`);
|
|
37
38
|
}
|
|
38
|
-
const watcher = watch(fixturePath, { recursive: true }, () => {
|
|
39
|
+
const watcher = watch(fixturePath, { recursive: true }, (_event, filename) => {
|
|
40
|
+
if (ignore && filename) try {
|
|
41
|
+
if (ignore(join(fixturePath, filename))) return;
|
|
42
|
+
} catch {}
|
|
39
43
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
40
44
|
debounceTimer = setTimeout(reload, DEBOUNCE_MS);
|
|
41
45
|
});
|
package/dist/watcher.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcher.js","names":[],"sources":["../src/watcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from \"node:fs\";\nimport type { Fixture } from \"./types.js\";\nimport type { Logger } from \"./logger.js\";\nimport { clearFixtureQueue, type ValidationResult } from \"./fixture-loader.js\";\n\nconst DEBOUNCE_MS = 500;\n\nexport function watchFixtures(\n fixturePath: string,\n fixtures: Fixture[],\n loadFn: () => Fixture[],\n opts: {\n logger: Logger;\n validate?: boolean;\n validateFn?: (fixtures: Fixture[]) => ValidationResult[];\n },\n): { close: () => void } {\n const { logger, validate, validateFn } = opts;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n function reload() {\n logger.info(`File changed — reloading fixtures from ${fixturePath}...`);\n\n let newFixtures: Fixture[];\n try {\n newFixtures = loadFn();\n } catch (err) {\n logger.error(\"Failed to reload fixtures:\", err);\n logger.error(\"Previous fixtures remain active. Fix the error and save again to retry.\");\n return;\n }\n\n if (newFixtures.length === 0 && fixtures.length > 0) {\n logger.warn(\n \"Reload produced 0 fixtures — keeping previous fixtures. Check fixture file for errors.\",\n );\n return;\n }\n\n if (validate && validateFn) {\n const results = validateFn(newFixtures);\n const errors = results.filter((r) => r.severity === \"error\");\n const warnings = results.filter((r) => r.severity === \"warning\");\n\n for (const w of warnings) {\n logger.warn(`Fixture ${w.fixtureIndex}: ${w.message}`);\n }\n\n if (errors.length > 0) {\n for (const e of errors) {\n logger.error(`Fixture ${e.fixtureIndex}: ${e.message}`);\n }\n logger.error(`${errors.length} validation error(s) — keeping previous fixtures`);\n return;\n }\n }\n\n // Replace in-place to preserve array reference identity. Same door as\n // reset: a one-shot claim parked across the reload must not re-arm into it.\n clearFixtureQueue(fixtures);\n fixtures.push(...newFixtures);\n logger.info(`Reloaded ${newFixtures.length} fixture(s)`);\n }\n\n const watcher: FSWatcher = watch(fixturePath, { recursive: true }, () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(reload, DEBOUNCE_MS);\n });\n\n watcher.on(\"error\", (err: Error) => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = null;\n try {\n watcher.close();\n } catch {\n /* already dead */\n }\n logger.error(`File watcher error on ${fixturePath}: ${err.message}`);\n logger.error(\"Fixture auto-reload is no longer active. Restart the server to resume watching.\");\n });\n\n return {\n close() {\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n },\n };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"watcher.js","names":[],"sources":["../src/watcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Fixture } from \"./types.js\";\nimport type { Logger } from \"./logger.js\";\nimport { clearFixtureQueue, type ValidationResult } from \"./fixture-loader.js\";\n\nconst DEBOUNCE_MS = 500;\n\nexport function watchFixtures(\n fixturePath: string,\n fixtures: Fixture[],\n loadFn: () => Fixture[],\n opts: {\n logger: Logger;\n validate?: boolean;\n validateFn?: (fixtures: Fixture[]) => ValidationResult[];\n /** MR13: an event for a path this returns true for does not reload. */\n ignore?: (absPath: string) => boolean;\n },\n): { close: () => void } {\n const { logger, validate, validateFn, ignore } = opts;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n function reload() {\n logger.info(`File changed — reloading fixtures from ${fixturePath}...`);\n\n let newFixtures: Fixture[];\n try {\n newFixtures = loadFn();\n } catch (err) {\n logger.error(\"Failed to reload fixtures:\", err);\n logger.error(\"Previous fixtures remain active. Fix the error and save again to retry.\");\n return;\n }\n\n if (newFixtures.length === 0 && fixtures.length > 0) {\n logger.warn(\n \"Reload produced 0 fixtures — keeping previous fixtures. Check fixture file for errors.\",\n );\n return;\n }\n\n if (validate && validateFn) {\n const results = validateFn(newFixtures);\n const errors = results.filter((r) => r.severity === \"error\");\n const warnings = results.filter((r) => r.severity === \"warning\");\n\n for (const w of warnings) {\n logger.warn(`Fixture ${w.fixtureIndex}: ${w.message}`);\n }\n\n if (errors.length > 0) {\n for (const e of errors) {\n logger.error(`Fixture ${e.fixtureIndex}: ${e.message}`);\n }\n logger.error(`${errors.length} validation error(s) — keeping previous fixtures`);\n return;\n }\n }\n\n // Replace in-place to preserve array reference identity. Same door as\n // reset: a one-shot claim parked across the reload must not re-arm into it.\n clearFixtureQueue(fixtures);\n fixtures.push(...newFixtures);\n logger.info(`Reloaded ${newFixtures.length} fixture(s)`);\n }\n\n const watcher: FSWatcher = watch(fixturePath, { recursive: true }, (_event, filename) => {\n if (ignore && filename) {\n try {\n if (ignore(join(fixturePath, filename))) return;\n } catch {\n /* a failing ignore never kills the watcher: reload */\n }\n }\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(reload, DEBOUNCE_MS);\n });\n\n watcher.on(\"error\", (err: Error) => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = null;\n try {\n watcher.close();\n } catch {\n /* already dead */\n }\n logger.error(`File watcher error on ${fixturePath}: ${err.message}`);\n logger.error(\"Fixture auto-reload is no longer active. Restart the server to resume watching.\");\n });\n\n return {\n close() {\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n },\n };\n}\n"],"mappings":";;;;;AAMA,MAAM,cAAc;AAEpB,SAAgB,cACd,aACA,UACA,QACA,MAOuB;CACvB,MAAM,EAAE,QAAQ,UAAU,YAAY,WAAW;CACjD,IAAI,gBAAsD;CAE1D,SAAS,SAAS;AAChB,SAAO,KAAK,0CAA0C,YAAY,KAAK;EAEvE,IAAI;AACJ,MAAI;AACF,iBAAc,QAAQ;WACf,KAAK;AACZ,UAAO,MAAM,8BAA8B,IAAI;AAC/C,UAAO,MAAM,0EAA0E;AACvF;;AAGF,MAAI,YAAY,WAAW,KAAK,SAAS,SAAS,GAAG;AACnD,UAAO,KACL,yFACD;AACD;;AAGF,MAAI,YAAY,YAAY;GAC1B,MAAM,UAAU,WAAW,YAAY;GACvC,MAAM,SAAS,QAAQ,QAAQ,MAAM,EAAE,aAAa,QAAQ;GAC5D,MAAM,WAAW,QAAQ,QAAQ,MAAM,EAAE,aAAa,UAAU;AAEhE,QAAK,MAAM,KAAK,SACd,QAAO,KAAK,WAAW,EAAE,aAAa,IAAI,EAAE,UAAU;AAGxD,OAAI,OAAO,SAAS,GAAG;AACrB,SAAK,MAAM,KAAK,OACd,QAAO,MAAM,WAAW,EAAE,aAAa,IAAI,EAAE,UAAU;AAEzD,WAAO,MAAM,GAAG,OAAO,OAAO,kDAAkD;AAChF;;;AAMJ,oBAAkB,SAAS;AAC3B,WAAS,KAAK,GAAG,YAAY;AAC7B,SAAO,KAAK,YAAY,YAAY,OAAO,aAAa;;CAG1D,MAAM,UAAqB,MAAM,aAAa,EAAE,WAAW,MAAM,GAAG,QAAQ,aAAa;AACvF,MAAI,UAAU,SACZ,KAAI;AACF,OAAI,OAAO,KAAK,aAAa,SAAS,CAAC,CAAE;UACnC;AAIV,MAAI,cAAe,cAAa,cAAc;AAC9C,kBAAgB,WAAW,QAAQ,YAAY;GAC/C;AAEF,SAAQ,GAAG,UAAU,QAAe;AAClC,MAAI,cAAe,cAAa,cAAc;AAC9C,kBAAgB;AAChB,MAAI;AACF,WAAQ,OAAO;UACT;AAGR,SAAO,MAAM,yBAAyB,YAAY,IAAI,IAAI,UAAU;AACpE,SAAO,MAAM,kFAAkF;GAC/F;AAEF,QAAO,EACL,QAAQ;AACN,MAAI,cAAe,cAAa,cAAc;AAC9C,UAAQ,OAAO;IAElB"}
|