callman-mcp 0.8.0 → 0.9.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/CHANGELOG.md +55 -0
- package/README.md +25 -9
- package/dist/client/api.d.ts +39 -1
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/api.js +53 -0
- package/dist/client/api.js.map +1 -1
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +28 -1
- package/dist/client/errors.js.map +1 -1
- package/dist/client/http.d.ts +2 -1
- package/dist/client/http.d.ts.map +1 -1
- package/dist/client/http.js +4 -2
- package/dist/client/http.js.map +1 -1
- package/dist/client/types.d.ts +92 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/types.js +0 -3
- package/dist/client/types.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +8 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +1 -0
- package/dist/context.js.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/resources/docs/mocks.d.ts +1 -1
- package/dist/resources/docs/mocks.d.ts.map +1 -1
- package/dist/resources/docs/mocks.js +349 -114
- package/dist/resources/docs/mocks.js.map +1 -1
- package/dist/resources/docs/overview.d.ts +1 -1
- package/dist/resources/docs/overview.d.ts.map +1 -1
- package/dist/resources/docs/overview.js +5 -1
- package/dist/resources/docs/overview.js.map +1 -1
- package/dist/resources/generated/aiPacks.d.ts +1 -1
- package/dist/resources/generated/aiPacks.js +2 -2
- package/dist/resources/index.js +1 -1
- package/dist/resources/index.js.map +1 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/catalog.d.ts.map +1 -1
- package/dist/tools/catalog.js +15 -1
- package/dist/tools/catalog.js.map +1 -1
- package/dist/tools/import.d.ts.map +1 -1
- package/dist/tools/import.js +7 -1
- package/dist/tools/import.js.map +1 -1
- package/dist/tools/mockData.d.ts +4 -0
- package/dist/tools/mockData.d.ts.map +1 -0
- package/dist/tools/mockData.js +384 -0
- package/dist/tools/mockData.js.map +1 -0
- package/dist/tools/mockEngine.d.ts +129 -0
- package/dist/tools/mockEngine.d.ts.map +1 -0
- package/dist/tools/mockEngine.js +225 -0
- package/dist/tools/mockEngine.js.map +1 -0
- package/dist/tools/mockImport.d.ts +5 -0
- package/dist/tools/mockImport.d.ts.map +1 -0
- package/dist/tools/mockImport.js +298 -0
- package/dist/tools/mockImport.js.map +1 -0
- package/dist/tools/mockSchemas.d.ts +12272 -0
- package/dist/tools/mockSchemas.d.ts.map +1 -0
- package/dist/tools/mockSchemas.js +247 -0
- package/dist/tools/mockSchemas.js.map +1 -0
- package/dist/tools/mockShared.d.ts +5449 -77
- package/dist/tools/mockShared.d.ts.map +1 -1
- package/dist/tools/mockShared.js +101 -43
- package/dist/tools/mockShared.js.map +1 -1
- package/dist/tools/mocks.d.ts.map +1 -1
- package/dist/tools/mocks.js +117 -35
- package/dist/tools/mocks.js.map +1 -1
- package/dist/tools/mocksLocal.d.ts +27 -3
- package/dist/tools/mocksLocal.d.ts.map +1 -1
- package/dist/tools/mocksLocal.js +118 -26
- package/dist/tools/mocksLocal.js.map +1 -1
- package/dist/tools/uiTestAdmin.d.ts.map +1 -1
- package/dist/tools/uiTestAdmin.js +3 -1
- package/dist/tools/uiTestAdmin.js.map +1 -1
- package/dist-cjs/client/api.js +53 -0
- package/dist-cjs/client/errors.js +28 -1
- package/dist-cjs/client/http.js +4 -2
- package/dist-cjs/client/types.js +0 -3
- package/dist-cjs/config.js +2 -0
- package/dist-cjs/context.js +1 -0
- package/dist-cjs/resources/docs/mocks.js +349 -114
- package/dist-cjs/resources/docs/overview.js +5 -1
- package/dist-cjs/resources/generated/aiPacks.js +2 -2
- package/dist-cjs/resources/index.js +1 -1
- package/dist-cjs/server.js +2 -0
- package/dist-cjs/tools/catalog.js +15 -1
- package/dist-cjs/tools/import.js +7 -1
- package/dist-cjs/tools/mockData.js +387 -0
- package/dist-cjs/tools/mockEngine.js +243 -0
- package/dist-cjs/tools/mockImport.js +302 -0
- package/dist-cjs/tools/mockSchemas.js +253 -0
- package/dist-cjs/tools/mockShared.js +113 -53
- package/dist-cjs/tools/mocks.js +121 -39
- package/dist-cjs/tools/mocksLocal.js +133 -35
- package/dist-cjs/tools/uiTestAdmin.js +3 -1
- package/package.json +3 -3
|
@@ -1,9 +1,34 @@
|
|
|
1
|
+
import { MOCK_DATA_OPERATIONS, MOCK_FAKER_HINTS, MOCK_FIELD_TYPES, MOCK_ID_STRATEGIES, MOCK_RESOURCE_ENDPOINTS, MOCK_RESOURCE_TEMPLATES, MOCK_TEMPLATE_CATALOG } from "callman-core/mock-engine";
|
|
2
|
+
// The vocabularies below come from callman-core, so this page always lists
|
|
3
|
+
// exactly the words the engine (and the tools' schemas) take.
|
|
4
|
+
const list = (values) => values.map((value) => `\`${value}\``).join(", ");
|
|
5
|
+
const templateCatalog = () => {
|
|
6
|
+
const groups = new Map();
|
|
7
|
+
for (const entry of MOCK_TEMPLATE_CATALOG)
|
|
8
|
+
groups.set(entry.group, [...(groups.get(entry.group) ?? []), `\`{{${entry.token}}}\` ${entry.description.toLowerCase()}`]);
|
|
9
|
+
return [...groups].map(([group, tokens]) => ` - **${group}**: ${tokens.join(" · ")}`).join("\n");
|
|
10
|
+
};
|
|
1
11
|
export const MOCKS_DOC = `# Mock APIs through MCP
|
|
2
12
|
|
|
3
|
-
A mock API is a fake HTTP API that answers
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
13
|
+
A mock API is a fake HTTP API that answers like the real one. Use it when a user says *"I need an API that
|
|
14
|
+
returns …"* — to build a front end before the backend exists, to test a client against errors, slow answers
|
|
15
|
+
and sign-in, or to give a test a stable dependency. You create it with a few tool calls and hand the user a URL.
|
|
16
|
+
|
|
17
|
+
With **mock engine v2** a mock can replace a real API: it has its own small database (**resources** with
|
|
18
|
+
auto CRUD endpoints, filtering, paging and relations), **routes that read and write that data**, **user
|
|
19
|
+
sign-in** with JWTs, **faults** on purpose and **test sessions** that keep parallel test runs apart — and it
|
|
20
|
+
can be **imported** from an OpenAPI document, a Postman or a Callman collection.
|
|
21
|
+
|
|
22
|
+
## Which engine the server runs
|
|
23
|
+
|
|
24
|
+
\`GET /api/mocks/policy\` says it: \`engine: { version: 2, features, limits }\` means mock engine v2; no
|
|
25
|
+
\`engine\` means v1 (static, template and builder routes over state only). The tools check this for you. On a
|
|
26
|
+
v1 server every v2 part — \`resources\`, \`userAuth\`, \`faults\`, routes with \`data\` / \`faults\` /
|
|
27
|
+
\`protected\`, the resource / record / session tools and import_mock — is refused with
|
|
28
|
+
**MOCK_ENGINE_UNSUPPORTED before anything is created** (an older server would silently drop what it does not
|
|
29
|
+
know). The same answer carries the mock lifetime: \`defaultTtlHours\` / \`maxTtlHours\` are set by the
|
|
30
|
+
deployment (on-prem the admin's mock lifetime, 2 days unless changed); \`engine.limits\` has this
|
|
31
|
+
installation's limits.
|
|
7
32
|
|
|
8
33
|
## Two places a mock can live (\`target\`)
|
|
9
34
|
|
|
@@ -11,53 +36,126 @@ calls and hand the user a URL.
|
|
|
11
36
|
|---|---|---|
|
|
12
37
|
| Where | On the Callman server | Inside the user's Callman desktop app |
|
|
13
38
|
| URL | \`<server>/mock/<slug>/…\` — public, reachable by anyone with the URL (or the token) | \`http://127.0.0.1:<port>/…\` — this computer only |
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
| Lifetime | expires after 1 / 6 / 24 / 48 h (default 48) | until deleted; lives while the app is open |
|
|
39
|
+
| Answers | static, **template**, **builder** presets, **variants**, request validation, delay; v2: resources, data routes, sign-in, faults, sessions | static on every desktop; everything else on a desktop app with mock engine v2 (else 409 **LOCAL_MOCK_DESKTOP_OUTDATED**) |
|
|
40
|
+
| Lifetime | the deployment's mock lifetime by default, any shorter \`ttlHours\`; renew with \`update_mock { ttlHours }\` | until deleted; lives while the app is open |
|
|
17
41
|
| Needs | token with \`mocks:*\` | token with \`mocks:*\` **and** the desktop app open, signed in, with Settings → AI tools → "Let AI tools manage local mock APIs on this computer" on |
|
|
18
42
|
| Routes are edited with | add_mock_route / update_mock_route / delete_mock_route | \`update_mock { routes }\` — REPLACES all routes |
|
|
19
43
|
| Always on? | yes (no start/stop) | start_mock / stop_mock |
|
|
20
44
|
|
|
21
|
-
If the server's \`publicUrl\` does not answer from your machine (an installation that never set its public
|
|
45
|
+
If the server's \`publicUrl\` does not answer from your machine (an installation that never set its public
|
|
46
|
+
address announces a host nobody can reach), use \`alternativeUrl\` from the same result — it is the API
|
|
47
|
+
address plus \`/mock/<slug>\`.
|
|
22
48
|
|
|
23
|
-
Pick **server** unless the user wants it on their own machine, needs no network exposure, or a tool on
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
49
|
+
Pick **server** unless the user wants it on their own machine, needs no network exposure, or a tool on their
|
|
50
|
+
computer must call it at localhost. Desktop calls go through a relay: the app picks the request up within a
|
|
51
|
+
few seconds. If it answers \`LOCAL_MOCK_DESKTOP_OFFLINE\`, tell the user to open the app and switch the setting
|
|
52
|
+
on (or fall back to a server mock).
|
|
27
53
|
|
|
28
|
-
## From a user's request to
|
|
54
|
+
## From a user's request to a mock
|
|
29
55
|
|
|
30
56
|
1. List first (\`list_mocks\`) — reuse or extend a mock that already fits instead of creating a duplicate.
|
|
31
|
-
2.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
57
|
+
2. There is a spec or a collection already? → \`import_mock\` (dryRun first).
|
|
58
|
+
3. Decide per endpoint:
|
|
59
|
+
- things clients **create, list, read, change, delete** (users, orders, products) → a **resource**: one
|
|
60
|
+
entry gives all CRUD endpoints, validation, paging, filters and relations;
|
|
61
|
+
- an operation over that data that is not plain CRUD (cancel an order, find a user by email, count) → a
|
|
62
|
+
route with \`data\`;
|
|
63
|
+
- a fixed answer → a static route; values from the request or random data → a template route;
|
|
64
|
+
- sign-in, "only logged-in users", "users see only their own orders" → \`userAuth\` + \`protected\` /
|
|
65
|
+
\`ownerField\`;
|
|
66
|
+
- "sometimes fails", "is slow", "fails the first two calls" → \`faults\`;
|
|
67
|
+
- different answers for different inputs → variants.
|
|
68
|
+
4. Call \`create_mock\` with all of it at once — on a v2 server it is one atomic request (all or nothing). The
|
|
69
|
+
result has the URL and a \`curl\` line per route, resource endpoint and sign-in endpoint — give them to the
|
|
70
|
+
user, and try one if you can. \`dryRun: true\` first shows what would be sent and any \`issues\`.
|
|
71
|
+
5. Iterate: add_mock_route / update_mock_route, add_mock_resource / update_mock_resource; read what real
|
|
72
|
+
clients sent with \`get_mock_logs\` and what they stored with \`query_mock_records\`.
|
|
73
|
+
|
|
74
|
+
## Resources — a database inside the mock (engine v2)
|
|
40
75
|
|
|
41
|
-
\`\`\`
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"routes": [
|
|
45
|
-
{ "method": "GET", "path": "/orders", "statusCode": 200, "body": [{ "id": 1, "status": "NEW" }, { "id": 2, "status": "PAID" }] },
|
|
46
|
-
{ "method": "GET", "path": "/orders/:id", "response": { "type": "template", "statusCode": 200, "body": { "id": "{{request.params.id}}", "status": "NEW", "createdAt": "{{now}}" } } },
|
|
47
|
-
{ "method": "POST", "path": "/orders", "statusCode": 201, "body": { "id": "{{faker.uuid}}" }, "delay": { "mode": "fixed", "ms": 300 } }
|
|
48
|
-
]
|
|
49
|
-
}
|
|
76
|
+
\`\`\`
|
|
77
|
+
{ name, template?, basePath?, idField?, idStrategy?, lookupField?, allowClientId?, fields?, additionalFields?,
|
|
78
|
+
timestamps?, ownerField?, protected?, endpoints?, list?, seed? }
|
|
50
79
|
\`\`\`
|
|
51
80
|
|
|
52
|
-
|
|
81
|
+
- **name**: lowercase first letter, letters / digits / \`_\` / \`-\` (users, orders, line-items); unique per mock.
|
|
82
|
+
- **template**: start from a ready resource — ${list(MOCK_RESOURCE_TEMPLATES.map((template) => template.id))} — with
|
|
83
|
+
fields, id strategy and generated seed; keys you pass override it (\`{ "template": "users" }\` alone works).
|
|
84
|
+
- **basePath**: default \`/<name>\`. **idField**: default \`id\`. **idStrategy**: ${list(MOCK_ID_STRATEGIES)}
|
|
85
|
+
(default uuid; \`client\` = the caller sends it). **lookupField**: what \`/:id\` looks up by (e.g. \`slug\`).
|
|
86
|
+
- **fields**: \`{ name, type, required?, nullable?, unique?, uniqueCaseInsensitive?, default?, enum?, min?, max?,
|
|
87
|
+
minLength?, maxLength?, pattern?, items?, readOnly?, writeOnly?, ref?, fakerHint? }\`. Types:
|
|
88
|
+
${list(MOCK_FIELD_TYPES)}. A \`password\` field is stored salted-hashed and never returned; \`default\` may be a
|
|
89
|
+
template (\`"{{now}}"\`, \`"{{auth.sub}}"\`); \`fakerHint\` says how seed data is generated
|
|
90
|
+
(${list(MOCK_FAKER_HINTS.slice(0, 12))} … any \`$random*\` token, or a template). No fields = schemaless: anything goes.
|
|
91
|
+
- **additionalFields**: \`allow\` (default) / \`strip\` / \`reject\` (400) for fields that are not declared.
|
|
92
|
+
- **timestamps**: \`{ createdAt, updatedAt }\` field names (default those; \`null\` switches one off).
|
|
93
|
+
- **seed**: \`{ records?: [...], generate?: N, salt? }\` — every new copy of the data starts with the records plus N
|
|
94
|
+
generated ones. \`reset_mock_data\` goes back to it.
|
|
95
|
+
- **endpoints**: per auto endpoint (${list(MOCK_RESOURCE_ENDPOINTS)}): \`false\` switches it off; an object sets
|
|
96
|
+
\`protected\`, \`faults\`, \`delay\`, \`responses\` (per outcome) or \`requestValidation\`.
|
|
97
|
+
- **list**: \`envelope\` (\`array\` default; \`data-meta\` → \`{ data, meta: { total, page, limit } }\`; \`items\`; \`custom\`
|
|
98
|
+
with \`customBody\`), \`defaultLimit\` (null = all up to \`maxLimit\`), \`maxLimit\`, \`totalCountHeader\`, \`linkHeader\`,
|
|
99
|
+
\`filterMode\` (\`declared\` fields only / \`any\` query parameter), \`searchable\` (\`?q=\`), \`defaultSort\`.
|
|
100
|
+
|
|
101
|
+
### Auto CRUD endpoints
|
|
102
|
+
|
|
103
|
+
| Request | Answer |
|
|
104
|
+
|---|---|
|
|
105
|
+
| \`GET /<basePath>\` | the list (query language below), \`X-Total-Count\` and \`Link\` headers |
|
|
106
|
+
| \`POST /<basePath>\` | 201 + the record (id and timestamps generated), \`Location\` header |
|
|
107
|
+
| \`GET /<basePath>/:id\` | the record, or 404 |
|
|
108
|
+
| \`PUT /<basePath>/:id\` | replace (id kept) · \`PATCH\` change the given fields |
|
|
109
|
+
| \`DELETE /<basePath>/:id\` | 204 (relations: cascade / nullify / restrict → 409) |
|
|
110
|
+
| \`GET\\|POST /<parent>/:<fk>/<child>\` | nested, for every \`ref\` field (\`/users/7/orders\`) |
|
|
111
|
+
|
|
112
|
+
Writes are validated: **400** \`{ error, invalidFields: [{ field, message }] }\`, **409** \`{ error, field }\` for a
|
|
113
|
+
taken unique value or id, **413** for a record over the size limit, **409** at the record limit. These
|
|
114
|
+
endpoints are computed, never stored as routes: they do not count against the route limit, and an explicit
|
|
115
|
+
route with the same path wins. Records can also be read and written without HTTP: \`query_mock_records\`,
|
|
116
|
+
\`insert_mock_records\`, \`update_mock_records\`, \`delete_mock_records\`.
|
|
117
|
+
|
|
118
|
+
### The list query language (json-server style)
|
|
119
|
+
|
|
120
|
+
| Query | Meaning |
|
|
121
|
+
|---|---|
|
|
122
|
+
| \`status=paid\` · \`status=paid&status=new\` | equals · repeated = any of them |
|
|
123
|
+
| \`total_gte=10\` \`_gt\` \`_lte\` \`_lt\` \`_ne\` | comparisons |
|
|
124
|
+
| \`id_in=1,2,3\` · \`id_nin=4\` | in / not in a list |
|
|
125
|
+
| \`email_exists=true\` | field present (or \`false\`: missing) |
|
|
126
|
+
| \`tags_contains=red\` | an array field holds the value |
|
|
127
|
+
| \`name_like=ada\` | case-insensitive substring (never a regular expression) |
|
|
128
|
+
| \`address.city=Baku\` | nested fields |
|
|
129
|
+
| \`q=ada\` | full-text search (\`list.searchable\`, or a schemaless resource) |
|
|
130
|
+
| \`_sort=-total,name\` (or \`_sort=total&_order=desc\`) | sort, \`-\` = descending |
|
|
131
|
+
| \`_page=2&_limit=10\` (or \`_per_page\`) · \`_start=20&_end=30\` | paging · slicing |
|
|
132
|
+
| \`_expand=user\` · \`_embed=orders\` | inline the record a ref points to · the records pointing here |
|
|
133
|
+
| \`_fields=id,name\` | only these fields |
|
|
134
|
+
|
|
135
|
+
\`query_mock_records\` takes the same as arguments: \`where\` (equality), \`filters: [{ field, op, value }]\`
|
|
136
|
+
(\`op\`: eq ne gt gte lt lte in nin exists contains like), \`q\`, \`sort\`, \`page\` / \`limit\` (default 20),
|
|
137
|
+
\`start\` / \`end\`, \`expand\`, \`embed\`, \`fields\` — or \`query: "status=paid&_sort=-total"\` verbatim — and \`id\` for
|
|
138
|
+
one record. It returns the records, the total and the query string to reuse with curl.
|
|
139
|
+
|
|
140
|
+
### Relations — \`ref\`, \`_expand\`, \`_embed\`
|
|
141
|
+
|
|
142
|
+
A field \`{ "name": "userId", "type": "string", "ref": { "resource": "users", "onDelete": "cascade" } }\` makes
|
|
143
|
+
orders point at users: \`GET /orders?_expand=user\` inlines each order's user (the name is the field without
|
|
144
|
+
\`Id\`; \`ref.as\` changes it), \`GET /users/7?_embed=orders\` lists a user's orders, and \`/users/7/orders\` answers
|
|
145
|
+
GET and POST. \`onDelete\`: \`restrict\` (deleting the user answers 409 while orders exist), \`cascade\`
|
|
146
|
+
(deletes them), \`nullify\` (sets the field null), \`none\` (default). \`ref.check\` (default true) refuses an
|
|
147
|
+
order whose user does not exist (400).
|
|
148
|
+
|
|
149
|
+
## Routes
|
|
53
150
|
|
|
54
151
|
\`\`\`
|
|
55
|
-
{ method, path, name?, response | (statusCode?, headers?, body?), variants?, delay?, requestValidation
|
|
152
|
+
{ method, path, name?, response | (statusCode?, headers?, body?), variants?, delay?, requestValidation?,
|
|
153
|
+
data?, faults?, protected? }
|
|
56
154
|
\`\`\`
|
|
57
155
|
|
|
58
156
|
- **method**: GET POST PUT DELETE PATCH or ANY (any method that no specific route takes).
|
|
59
|
-
- **path**: starts with \`/\`; letters, digits and \`/ _ - : * .\`; no \`..\`; at most 256 characters
|
|
60
|
-
\`:name\` is a path parameter (letters, digits, underscore: \`:userId\`, not \`:user-id\`). A trailing
|
|
157
|
+
- **path**: starts with \`/\`; letters, digits and \`/ _ - : * .\`; no \`..\`; at most 256 characters; \`/__callman\`
|
|
158
|
+
is reserved. \`:name\` is a path parameter (letters, digits, underscore: \`:userId\`, not \`:user-id\`). A trailing
|
|
61
159
|
\`/*\` (or a \`*\` segment) matches everything below. The most specific route wins (literal segment >
|
|
62
160
|
parameter > wildcard); a trailing slash is ignored. One route per method + path.
|
|
63
161
|
- **response**: the full shape below. **Shorthand** for the common case: \`statusCode\` (default 200),
|
|
@@ -65,8 +163,9 @@ create_mock {
|
|
|
65
163
|
- **name**: a label (shown in lists; usable as a reference).
|
|
66
164
|
- **delay**: \`{ mode: "none" }\`, \`{ mode: "fixed", ms }\` or \`{ mode: "random", minMs, maxMs }\` — the
|
|
67
165
|
answer is held back that long (server ≤ 60 000 ms, desktop ≤ 30 000 ms). Use it to test timeouts and spinners.
|
|
166
|
+
- **data**, **faults**, **protected** (engine v2): below.
|
|
68
167
|
|
|
69
|
-
### Responses
|
|
168
|
+
### Responses
|
|
70
169
|
|
|
71
170
|
\`\`\`
|
|
72
171
|
{ "type": "static", "statusCode": 200, "headers": { "X-Foo": "1" }, "body": <any JSON> }
|
|
@@ -77,26 +176,22 @@ create_mock {
|
|
|
77
176
|
- **body**: any JSON value. An object/array is sent as JSON (\`content-type: application/json\` unless you
|
|
78
177
|
set one); a *string* is sent as is (\`text/plain\` unless you set a content type — for XML/HTML put
|
|
79
178
|
the text in \`body\` and set \`headers\`). No body = empty response (use for 204).
|
|
80
|
-
- **template** — every string in the body may contain \`{{expression}}\`. A string that is
|
|
81
|
-
expression keeps its type (number, boolean, object); inside longer text the value becomes text.
|
|
82
|
-
Unknown expressions become \`null\` (whole) or empty (inline).
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
### Builder presets (server)
|
|
93
|
-
|
|
94
|
-
A builder route does the work of a REST endpoint over **state** (arrays of objects, stored per mock).
|
|
95
|
-
\`config.preset\` must equal \`preset\`. Required keys are marked; the rest have defaults.
|
|
179
|
+
- **template** — every string in the body (and in header values) may contain \`{{expression}}\`. A string that is
|
|
180
|
+
*only* one expression keeps its type (number, boolean, object); inside longer text the value becomes text.
|
|
181
|
+
Unknown expressions become \`null\` (whole) or empty (inline). Example:
|
|
182
|
+
\`{ "id": "{{request.params.id}}", "email": "{{faker.email}}", "age": "{{random.int(18,65)}}" }\` (\`age\` becomes a number).
|
|
183
|
+
The expressions:
|
|
184
|
+
${templateCatalog()}
|
|
185
|
+
|
|
186
|
+
### Builder presets (state-backed, every engine)
|
|
187
|
+
|
|
188
|
+
A builder route does the work of a REST endpoint over **state** (arrays of objects, stored per mock). On a v2
|
|
189
|
+
server prefer a **resource** — it does all of this with validation, relations and sessions. \`config\` is typed
|
|
190
|
+
per preset; \`config.preset\` must equal \`preset\`. Required keys are marked; the rest have defaults.
|
|
96
191
|
|
|
97
192
|
| preset | config | what it does |
|
|
98
193
|
|---|---|---|
|
|
99
|
-
| \`list\` | **source**: \`{kind:"static",data:[…]}\` or \`{kind:"state",stateKey}\`; \`filtering:{enabled,rules:[{queryParam,field,operator:equals\\|contains\\|startsWith\\|in}]}\`; \`sorting:{enabled,sortByParam:"sortBy",orderParam:"order",allowedFields:[…]}\`; \`pagination:{enabled,strategy:page\\|offset\\|cursor,defaultPageSize:20,maxPageSize:100,envelope:standard\\|data-meta\\|raw}\`; \`statusCode\`, \`headers\` | filter by query params, sort, paginate
|
|
194
|
+
| \`list\` | **source**: \`{kind:"static",data:[…]}\` or \`{kind:"state",stateKey}\`; \`filtering:{enabled,rules:[{queryParam,field,operator:equals\\|contains\\|startsWith\\|in}]}\`; \`sorting:{enabled,sortByParam:"sortBy",orderParam:"order",allowedFields:[…]}\`; \`pagination:{enabled,strategy:page\\|offset\\|cursor,defaultPageSize:20,maxPageSize:100,envelope:standard\\|data-meta\\|raw}\`; \`statusCode\`, \`headers\` | filter by query params, sort, paginate. Envelopes: standard \`{items,total,page,totalPages}\`, data-meta \`{data,meta:{total,page,limit}}\`, raw array |
|
|
100
195
|
| \`detail\` | **source**, **notFoundBehavior**:\`{statusCode:404,body?}\`, \`paramName:"id"\`, \`lookupField:"id"\` | the item whose \`lookupField\` equals the path parameter; otherwise the not-found answer |
|
|
101
196
|
| \`create\` | **stateKey**, \`autoGenerate:{id:"uuid"\\|"incremental",createdAt,updatedAt}\`, \`requiredFields:[…]\` (missing → 400), \`responseBehavior:return-created\\|return-id\\|status-only\`, \`statusCode:201\` | appends the request body (plus generated fields) to \`state[stateKey]\` |
|
|
102
197
|
| \`update\` | **stateKey**, **notFoundBehavior**, \`paramName\`, \`lookupField\`, \`mergeStrategy:partial\\|full\`, \`autoUpdateUpdatedAt:true\`, \`statusCode:200\` | replaces/merges the matching item with the request body |
|
|
@@ -104,83 +199,213 @@ A builder route does the work of a REST endpoint over **state** (arrays of objec
|
|
|
104
199
|
| \`echo\` | \`statusCode\`, \`headers\` | answers with the request: \`{method,params,query,headers,body}\` — handy to see what a client sends |
|
|
105
200
|
| \`custom\` | \`statusCode\`, \`headers\`, \`body\` | a fixed answer through the builder |
|
|
106
201
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
create_mock { "name": "Users", "slug": "users-api", "routes": [
|
|
111
|
-
{ "method": "GET", "path": "/users", "response": { "type": "builder", "preset": "list", "config": { "preset": "list", "source": { "kind": "state", "stateKey": "users" }, "pagination": { "enabled": true } } } },
|
|
112
|
-
{ "method": "GET", "path": "/users/:id", "response": { "type": "builder", "preset": "detail", "config": { "preset": "detail", "source": { "kind": "state", "stateKey": "users" }, "notFoundBehavior": { "statusCode": 404 } } } },
|
|
113
|
-
{ "method": "POST", "path": "/users", "response": { "type": "builder", "preset": "create", "config": { "preset": "create", "stateKey": "users", "autoGenerate": { "id": "incremental", "createdAt": true }, "requiredFields": ["name"] } } },
|
|
114
|
-
{ "method": "PATCH", "path": "/users/:id", "response": { "type": "builder", "preset": "update", "config": { "preset": "update", "stateKey": "users", "notFoundBehavior": { "statusCode": 404 } } } },
|
|
115
|
-
{ "method": "DELETE", "path": "/users/:id", "response": { "type": "builder", "preset": "delete", "config": { "preset": "delete", "stateKey": "users", "notFoundBehavior": { "statusCode": 404 } } } }
|
|
116
|
-
] }
|
|
117
|
-
set_mock_state { "mock": "users-api", "initialState": { "users": [{ "id": 1, "name": "Ada" }] }, "state": { "users": [{ "id": 1, "name": "Ada" }] } }
|
|
118
|
-
\`\`\`
|
|
119
|
-
|
|
120
|
-
### State
|
|
202
|
+
State is a JSON object per mock (limit 1 MB). \`get_mock_state\` shows \`state\` (live) and \`initialState\`;
|
|
203
|
+
\`set_mock_state\` replaces either wholesale; \`reset_mock_state\` copies \`initialState\` back into \`state\`.
|
|
204
|
+
\`{{state.key}}\` templates read it. Resource data is separate (\`reset_mock_data\`).
|
|
121
205
|
|
|
122
|
-
|
|
123
|
-
arrive (a missing array is created by the first \`create\`); \`list\`/\`detail\` with \`source.kind: "state"\` and
|
|
124
|
-
\`{{state.key}}\` templates read it. \`get_mock_state\` shows \`state\` (live) and \`initialState\`;
|
|
125
|
-
\`set_mock_state\` replaces either wholesale; \`reset_mock_state\` copies \`initialState\` back into \`state\`
|
|
126
|
-
(do that between test runs). Seed *both* when you want a reset to return to the seed data.
|
|
127
|
-
|
|
128
|
-
### Variants — different answers for different requests (server)
|
|
206
|
+
### Variants — different answers for different requests
|
|
129
207
|
|
|
130
208
|
A route can carry up to 20 variants. Each has \`priority\` (lower is checked first), a \`condition\` and a
|
|
131
209
|
\`response\` (same shapes as above). The first variant whose condition matches answers; otherwise the
|
|
132
|
-
route's own \`response\` does.
|
|
210
|
+
route's own \`response\` (or \`data\`) does.
|
|
133
211
|
|
|
134
212
|
\`\`\`json
|
|
135
213
|
{ "method": "GET", "path": "/profile",
|
|
136
214
|
"response": { "type": "static", "statusCode": 200, "body": { "name": "Ada" } },
|
|
137
215
|
"variants": [
|
|
138
|
-
{ "priority": 1, "condition": { "type": "
|
|
139
|
-
"response": { "type": "static", "statusCode": 401, "body": { "error": "unauthorized" } } },
|
|
140
|
-
{ "priority": 2, "condition": { "type": "any", "rules": [{ "field": "query.fail", "operator": "equals", "value": "1" }] },
|
|
216
|
+
{ "priority": 1, "condition": { "type": "any", "rules": [{ "field": "query.fail", "operator": "equals", "value": "1" }] },
|
|
141
217
|
"response": { "type": "static", "statusCode": 500, "body": { "error": "boom" } } } ] }
|
|
142
218
|
\`\`\`
|
|
143
219
|
|
|
144
220
|
- \`condition.type\`: \`all\` (every rule) or \`any\` (at least one); 1–20 rules.
|
|
145
|
-
- \`field\`: \`headers.<name>\`, \`params.<name>\`, \`query.<name>\`, \`body.<dotted.path
|
|
221
|
+
- \`field\`: \`headers.<name>\`, \`params.<name>\`, \`query.<name>\`, \`body.<dotted.path>\` (v2 also \`auth.sub\`,
|
|
222
|
+
\`auth.claims.<name>\`, \`session.id\`).
|
|
146
223
|
- \`operator\`: exists, not-exists (null and empty count as missing), equals, not-equals, contains,
|
|
147
|
-
starts-with, matches (a regular expression in \`value\`;
|
|
224
|
+
starts-with, matches (a regular expression in \`value\`; unsafe patterns are refused). Values are compared as text.
|
|
225
|
+
|
|
226
|
+
### Request validation
|
|
227
|
+
|
|
228
|
+
\`requestValidation: { "mode": "schema", "schema": { …JSON Schema draft-07… } }\` makes the route reject a body
|
|
229
|
+
that does not match with **400** and the list of invalid fields. Checked on POST, PUT and PATCH. \`mode: "any"\`
|
|
230
|
+
(default) accepts anything; \`update_mock_route\` with \`requestValidation: null\` removes the gate.
|
|
231
|
+
|
|
232
|
+
### Data routes — \`data\` (engine v2)
|
|
233
|
+
|
|
234
|
+
A route that reads or writes a resource instead of answering a fixed body:
|
|
235
|
+
|
|
236
|
+
\`\`\`
|
|
237
|
+
data: { operation, resource, match?: [{ field, op?, value, skipIfEmpty? }], write?: { source?, pick?, omit?, set? },
|
|
238
|
+
mode?: "merge"|"replace", many?, find?: { useRequestQuery?, sort?, limit? }, responses? }
|
|
239
|
+
\`\`\`
|
|
240
|
+
|
|
241
|
+
- **operation**: ${list(MOCK_DATA_OPERATIONS)}. insert creates from the body; find answers a list (with
|
|
242
|
+
\`find.useRequestQuery\` the caller's list query applies too); findOne / update / delete / upsert act on the
|
|
243
|
+
first match (\`many: true\` → every match); count answers \`{ count }\`.
|
|
244
|
+
- **match**: every rule must hold; \`value\` is a template or a literal — \`"{{request.params.id}}"\`,
|
|
245
|
+
\`"{{request.query.status}}"\`, \`"{{auth.sub}}"\`; \`op\` eq (default) ne gt gte lt lte in contains like;
|
|
246
|
+
\`skipIfEmpty\` ignores a rule whose value is empty (optional filters).
|
|
247
|
+
- **write**: \`source\` (default \`"{{request.body}}"\`), \`pick\` / \`omit\` fields, \`set\` fields last
|
|
248
|
+
(\`{ "status": "cancelled", "cancelledAt": "{{now}}" }\`).
|
|
249
|
+
- The answer is the record(s); no match → 404. **responses** overrides per outcome (success, created, notFound,
|
|
250
|
+
conflict, validationError, unauthorized, forbidden, limitExceeded, payloadTooLarge); a **template** \`response\`
|
|
251
|
+
on the route shapes the success answer with \`{{record}}\`, \`{{records}}\`, \`{{total}}\`. A static response
|
|
252
|
+
or the shorthand is refused on a data route.
|
|
253
|
+
|
|
254
|
+
\`\`\`json
|
|
255
|
+
{ "method": "GET", "path": "/users/by-email/:email", "data": { "operation": "findOne", "resource": "users", "match": [{ "field": "email", "value": "{{request.params.email}}" }] } }
|
|
256
|
+
{ "method": "POST", "path": "/orders/:id/cancel", "data": { "operation": "update", "resource": "orders", "match": [{ "field": "id", "value": "{{request.params.id}}" }], "write": { "source": "{}", "set": { "status": "cancelled" } } } }
|
|
257
|
+
{ "method": "GET", "path": "/stats/orders", "data": { "operation": "count", "resource": "orders", "match": [{ "field": "status", "value": "{{request.query.status}}", "skipIfEmpty": true }] } }
|
|
258
|
+
\`\`\`
|
|
259
|
+
|
|
260
|
+
## User sign-in — \`userAuth\` (engine v2)
|
|
261
|
+
|
|
262
|
+
\`\`\`
|
|
263
|
+
userAuth: { usersResource?: "users", identityField?: "email", passwordField?: "password", tokenTtlSeconds?: 3600,
|
|
264
|
+
roleClaim?: "role", claims?, protect?: "none"|"resources"|"all", endpoints?, responses?, issuer?, audience? }
|
|
265
|
+
\`\`\`
|
|
266
|
+
|
|
267
|
+
- \`POST /auth/register\` (201) and \`POST /auth/login\` (200) take \`{ <identityField>, <passwordField> }\` (register:
|
|
268
|
+
the whole user) and answer \`{ accessToken, tokenType: "Bearer", expiresIn, user }\`; \`GET /auth/me\` answers the
|
|
269
|
+
signed-in user. Paths change with \`endpoints: { login: { path }, register: { enabled: false } }\`.
|
|
270
|
+
- The users resource needs the identity field (make it \`unique\`) and a \`password\` field (type password).
|
|
271
|
+
- Tokens are HS256 JWTs signed with a per-mock secret (never shown); callers send \`Authorization: Bearer <token>\`.
|
|
272
|
+
Missing / invalid → **401** with \`WWW-Authenticate: Bearer\`; a role that does not fit → **403**.
|
|
273
|
+
- What needs a user: \`protect\` sets the default — \`resources\` (default: every resource endpoint), \`all\` (every
|
|
274
|
+
route too) or \`none\`. \`protected\` on a route, a resource or one endpoint overrides it: \`true\` (any signed-in
|
|
275
|
+
user), \`{ "roles": ["admin"] }\` (the \`roleClaim\` field of the user must be one of them), \`false\` (public).
|
|
276
|
+
- \`ownerField\` on a resource (\`"ownerField": "userId"\`) scopes it to the signed-in user: they list, read and
|
|
277
|
+
change only their own records, and new records get their id.
|
|
278
|
+
- Templates see the user: \`{{auth.sub}}\`, \`{{auth.user.<field>}}\`, \`{{auth.claims.<name>}}\`.
|
|
279
|
+
- \`X-Mock-Token\` (the mock's own access gate, \`auth: "token"\`) is separate and checked first.
|
|
280
|
+
- \`update_mock { userAuth }\` changes it (the secret is kept), \`update_mock { userAuth: null }\` switches it off.
|
|
281
|
+
|
|
282
|
+
## Faults — fail on purpose (engine v2)
|
|
283
|
+
|
|
284
|
+
\`\`\`
|
|
285
|
+
faults: { errorRate?, errorStatus?: 500, errorBody?, delay?, timeoutRate?, dropRate?, failFirst? }
|
|
286
|
+
\`\`\`
|
|
287
|
+
|
|
288
|
+
| Key | Effect |
|
|
289
|
+
|---|---|
|
|
290
|
+
| \`errorRate\` | % of requests answered with \`errorStatus\` (and \`errorBody\`) |
|
|
291
|
+
| \`delay\` | extra delay on every request (\`{ mode: "fixed", ms }\` / \`{ mode: "random", minMs, maxMs }\`) |
|
|
292
|
+
| \`timeoutRate\` | % of requests held unanswered, then 504 — the client's timeout fires first |
|
|
293
|
+
| \`dropRate\` | % of requests whose connection is closed without an answer (a proxy in front shows 502) |
|
|
294
|
+
| \`failFirst\` | the first N calls (per test session) fail with \`errorStatus\`, then it recovers — retry logic |
|
|
295
|
+
|
|
296
|
+
Set them for the whole mock (\`create_mock\` / \`update_mock { faults }\`, null removes), for one route (the
|
|
297
|
+
route's \`faults\`) or for one resource endpoint (\`endpoints: { create: { faults: … } }\`). \`reset_mock_data\`
|
|
298
|
+
restarts the \`failFirst\` counters. A mock whose faults were set as full rules (conditions, sequences) shows
|
|
299
|
+
them as \`faultRules\` next to the flat form.
|
|
300
|
+
|
|
301
|
+
## Test sessions — \`X-Mock-Session\` (engine v2)
|
|
302
|
+
|
|
303
|
+
A caller that sends \`X-Mock-Session: <id>\` (1–64 letters, digits, \`. _ : -\`) gets **its own copy of the data**,
|
|
304
|
+
seeded on first use and dropped after being idle — so parallel test runs never see each other's writes. Too many
|
|
305
|
+
active sessions → 429 (\`x-mock-limit-reason: session-limit\`). A caller can read and reset its own session at
|
|
306
|
+
\`GET /__callman/session\` and \`POST /__callman/session/reset\`. From here: \`list_mock_sessions\`, and \`session\`
|
|
307
|
+
on query_mock_records, insert / update / delete_mock_records and reset_mock_data.
|
|
308
|
+
|
|
309
|
+
## Import — \`import_mock\` (engine v2)
|
|
310
|
+
|
|
311
|
+
Builds a mock from an **OpenAPI 3.x / Swagger 2.0** document (JSON or YAML), a **Postman** collection (its saved
|
|
312
|
+
examples) or a **Callman** collection (\`collection\`: its requests with their newest recorded responses,
|
|
313
|
+
secrets redacted). One source: \`content\` (the document text, e.g. a file you read), \`url\` or \`filePath\`.
|
|
314
|
+
**This MCP server reads URLs and files itself — the Callman server never fetches anything.** Files, and URLs on
|
|
315
|
+
localhost or a private network, are refused (MOCK_IMPORT_SOURCE_BLOCKED) unless the user set
|
|
316
|
+
\`CALLMAN_ALLOW_LOCAL_SOURCES=1\` for this server — read the file yourself and pass \`content\` instead.
|
|
317
|
+
|
|
318
|
+
- \`mode: "auto"\` (default): CRUD groups (\`GET/POST /pets\` + \`GET/PUT/DELETE /pets/{id}\`) become resources with
|
|
319
|
+
the examples as seed records; everything else becomes routes. \`mode: "routes"\`: one route per operation.
|
|
320
|
+
- Extra responses become variants; a caller picks one with \`X-Mock-Response-Code: 404\` or
|
|
321
|
+
\`X-Mock-Response-Name: <example name>\`. OpenAPI request schemas become request validation
|
|
322
|
+
(\`validateRequests: false\` turns it off); \`keepServerBasePath\` keeps \`/api/v3\` in the paths.
|
|
323
|
+
- \`dryRun: true\` shows the routes and resources it would create and the **report**: what was converted, what
|
|
324
|
+
needs attention (\`warning\`) and what was \`lost\` — read it to the user.
|
|
325
|
+
- \`into: { mock, strategy }\` adds to an existing mock: \`merge\` (default) keeps its routes and resources and adds
|
|
326
|
+
the new ones (same method + path or resource name is skipped); \`replace\` swaps them.
|
|
327
|
+
- \`target: "desktop"\` converts on the server and writes a local mock to the desktop app.
|
|
328
|
+
|
|
329
|
+
## Worked example — users + orders CRUD with login
|
|
330
|
+
|
|
331
|
+
\`\`\`json
|
|
332
|
+
create_mock {
|
|
333
|
+
"name": "Shop API", "slug": "shop-api",
|
|
334
|
+
"resources": [
|
|
335
|
+
{ "template": "users" },
|
|
336
|
+
{ "name": "orders", "idStrategy": "incremental", "ownerField": "userId",
|
|
337
|
+
"fields": [
|
|
338
|
+
{ "name": "userId", "type": "string", "ref": { "resource": "users", "onDelete": "cascade" } },
|
|
339
|
+
{ "name": "total", "type": "number", "required": true, "min": 0, "fakerHint": "$randomPrice" },
|
|
340
|
+
{ "name": "status", "type": "string", "enum": ["pending", "paid", "cancelled"], "default": "pending" }
|
|
341
|
+
] }
|
|
342
|
+
],
|
|
343
|
+
"userAuth": { "usersResource": "users", "protect": "resources" },
|
|
344
|
+
"routes": [
|
|
345
|
+
{ "method": "POST", "path": "/orders/:id/cancel", "protected": true,
|
|
346
|
+
"data": { "operation": "update", "resource": "orders",
|
|
347
|
+
"match": [{ "field": "id", "value": "{{request.params.id}}" }, { "field": "userId", "value": "{{auth.sub}}" }],
|
|
348
|
+
"write": { "source": "{}", "set": { "status": "cancelled" } } } },
|
|
349
|
+
{ "method": "GET", "path": "/health", "statusCode": 200, "body": { "ok": true }, "protected": false }
|
|
350
|
+
]
|
|
351
|
+
}
|
|
352
|
+
\`\`\`
|
|
353
|
+
|
|
354
|
+
What the client (or you, with the curl lines from the result) can now do against \`<server>/mock/shop-api\`:
|
|
355
|
+
|
|
356
|
+
\`\`\`
|
|
357
|
+
POST /auth/register { "email": "ada@example.com", "password": "secret123", "name": "Ada" } → 201 { accessToken, user }
|
|
358
|
+
POST /auth/login { "email": "ada@example.com", "password": "secret123" } → 200 { accessToken, tokenType, expiresIn, user }
|
|
359
|
+
GET /auth/me Authorization: Bearer <accessToken> → Ada (no password)
|
|
360
|
+
POST /orders { "total": 42 } Authorization: Bearer … → 201 { id: 1, userId: <Ada's id>, total: 42, status: "pending", createdAt, updatedAt }
|
|
361
|
+
GET /orders?status=pending&_sort=-total&_expand=user (Bearer) → Ada's orders only, each with its user; X-Total-Count
|
|
362
|
+
PATCH /orders/1 { "status": "paid" } (Bearer) → the changed order
|
|
363
|
+
POST /orders/1/cancel (Bearer) → status "cancelled"
|
|
364
|
+
POST /orders { "total": -1 } (Bearer) → 400 { error, invalidFields: [{ field: "total", … }] }
|
|
365
|
+
GET /orders (no token) → 401
|
|
366
|
+
\`\`\`
|
|
148
367
|
|
|
149
|
-
|
|
368
|
+
Then, for the user's tests:
|
|
150
369
|
|
|
151
|
-
\`
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
370
|
+
- \`update_mock { "mock": "shop-api", "faults": { "failFirst": 2, "errorStatus": 503 } }\` — the first two calls fail,
|
|
371
|
+
the third succeeds (retry logic); \`reset_mock_data\` restarts the count.
|
|
372
|
+
- Each test run sends its own \`X-Mock-Session: run-<n>\` header → isolated data; \`list_mock_sessions\` shows them.
|
|
373
|
+
- \`query_mock_records { "mock": "shop-api", "resource": "orders", "filters": [{ "field": "total", "op": "gte", "value": 10 }], "expand": ["user"] }\`
|
|
374
|
+
shows what the client stored; \`insert_mock_records\` prepares data; \`reset_mock_data\` puts the seed back.
|
|
155
375
|
|
|
156
|
-
|
|
376
|
+
## Auth of the mock itself (server)
|
|
157
377
|
|
|
158
|
-
\`auth: "public"\` (default) — anyone with the URL. \`auth: "token"\` — callers must send
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
378
|
+
\`auth: "public"\` (default) — anyone with the URL. \`auth: "token"\` — callers must send \`X-Mock-Token: <token>\`
|
|
379
|
+
(else 401). The token is generated by the server and returned **once**, in the create_mock / update_mock result;
|
|
380
|
+
\`get_mock\` keeps it masked unless the token holds \`environments:reveal\` and you pass \`reveal: true\`. Never put
|
|
381
|
+
it into places the user did not ask for. (User sign-in above is a different thing: it simulates the API's own login.)
|
|
162
382
|
|
|
163
|
-
## Limits (server defaults; an installation may change them)
|
|
383
|
+
## Limits (server defaults; an installation may change them — \`GET /api/mocks/policy\` → \`engine.limits\`)
|
|
164
384
|
|
|
165
|
-
20 mocks per workspace · 50 routes per mock · response body ≤ 100 KB · request body ≤ 1 MB ·
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
385
|
+
20 mocks per workspace · 50 routes per mock · response body ≤ 100 KB · request body ≤ 1 MB · state ≤ 1 MB ·
|
|
386
|
+
20 000 requests in total per mock, then 429 · 30 requests per second per mock, then 429 · the 500 newest request
|
|
387
|
+
logs are kept · slugs: 3–48 chars, lowercase letters/digits/single dashes, start with a letter, a few reserved
|
|
388
|
+
words (api, mock, admin …) refused · lifetime up to the deployment's mock lifetime (longer requests are capped),
|
|
389
|
+
then **410 Gone** — renew before that with \`update_mock { ttlHours }\`. A flood from one address gets 429 with
|
|
390
|
+
\`X-Mock-Limit-Reason: ip-rate-limit\`. Engine v2: 20 resources per mock, 100 fields per resource, 1 000 records
|
|
391
|
+
per resource, 16 KB per record, 512 KB of seed records, 20 active test sessions (idle after 60 min), 2 MB per import.
|
|
392
|
+
Local (desktop): ports 1024–65535 (omit \`port\` and the app picks a free one), the mock
|
|
393
|
+
listens on 127.0.0.1 only; a relay command carries at most 1 MB (a route body sent to an older desktop app: 256 KB).
|
|
171
394
|
|
|
172
395
|
## Desktop (local) mocks
|
|
173
396
|
|
|
174
|
-
\`create_mock { target: "desktop", name, port?, autoStart?, routes? }\` creates a
|
|
175
|
-
app and starts it (\`start: false\` to skip).
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
397
|
+
\`create_mock { target: "desktop", name, port?, autoStart?, routes?, resources?, userAuth?, faults? }\` creates a
|
|
398
|
+
local mock in the user's app and starts it (\`start: false\` to skip). Static routes —
|
|
399
|
+
\`{ method, path, name?, statusCode, headers, body, delay }\` or \`response: { type: "static", … }\` — work with every
|
|
400
|
+
desktop app: \`body\` is text (a string is sent verbatim, anything else as formatted JSON). Templates, variants,
|
|
401
|
+
request validation, resources, data routes, sign-in and faults need a server and a desktop app with mock engine
|
|
402
|
+
v2 (the same engine runs on the desktop); with a v1 server they are refused (LOCAL_MOCK_FEATURE_UNSUPPORTED /
|
|
403
|
+
MOCK_ENGINE_UNSUPPORTED), with an older desktop app the server answers LOCAL_MOCK_DESKTOP_OUTDATED.
|
|
404
|
+
\`:param\` segments and a trailing \`/*\` work; every request is logged (\`get_mock_logs { target: "desktop" }\`, the
|
|
405
|
+
latest requests only, kept until the app quits). To change routes: \`get_mock { target: "desktop" }\`, edit,
|
|
406
|
+
\`update_mock { routes: [...all of them] }\`. The resource, record and session tools take \`target: "desktop"\` too.
|
|
407
|
+
\`start_mock\` / \`stop_mock\` / \`delete_mock\` act on one by name or id. Mocks the AI created carry an "AI" badge in
|
|
408
|
+
the app. The mock is only reachable from the user's computer — don't hand out the URL to others.
|
|
184
409
|
|
|
185
410
|
## Tools
|
|
186
411
|
|
|
@@ -188,23 +413,33 @@ badge in the app. The mock is only reachable from the user's computer — don't
|
|
|
188
413
|
|---|---|---|
|
|
189
414
|
| list_mocks, get_mock, get_mock_logs | mocks:read | \`target\` server/desktop |
|
|
190
415
|
| get_mock_stats, get_mock_state | mocks:read | server |
|
|
416
|
+
| query_mock_records, list_mock_sessions | mocks:read | engine v2, \`target\` server/desktop |
|
|
191
417
|
| create_mock, update_mock | mocks:write | \`target\` server/desktop |
|
|
192
418
|
| add_mock_route, update_mock_route, reorder_mock_routes, set_mock_state, reset_mock_state | mocks:write | server |
|
|
419
|
+
| add_mock_resource, update_mock_resource, insert_mock_records, update_mock_records, reset_mock_data, import_mock | mocks:write | engine v2, \`target\` server/desktop |
|
|
193
420
|
| start_mock, stop_mock | mocks:write | desktop |
|
|
194
421
|
| delete_mock | mocks:delete (+ \`confirm: true\`) | \`target\` server/desktop |
|
|
195
422
|
| delete_mock_route | mocks:delete (+ \`confirm: true\`) | server |
|
|
423
|
+
| delete_mock_resource, delete_mock_records | mocks:delete (+ \`confirm: true\`) | engine v2, \`target\` server/desktop |
|
|
196
424
|
|
|
197
425
|
\`mock\` is the name, the slug or the id. A route is referenced by routeId, \`"GET /users/:id"\`, its path
|
|
198
|
-
(when unique) or its name.
|
|
199
|
-
mocks:delete; otherwise the result says it is kept) and the error names the
|
|
426
|
+
(when unique) or its name; a resource by its name (or id). On a v1 server, if \`create_mock\` fails on a route,
|
|
427
|
+
the new mock is deleted again (needs mocks:delete; otherwise the result says it is kept) and the error names the
|
|
428
|
+
failing route and why; on a v2 server the create is atomic.
|
|
200
429
|
|
|
201
430
|
## Errors you may meet
|
|
202
431
|
|
|
203
432
|
\`PAT_SCOPE_MISSING\` (token lacks mocks:read/write/delete) · \`FEATURE_DISABLED\` (mocks switched off for the
|
|
204
|
-
account) · \`
|
|
205
|
-
\`
|
|
206
|
-
(
|
|
207
|
-
\`
|
|
208
|
-
\`
|
|
433
|
+
account) · \`MOCK_ENGINE_UNSUPPORTED\` (a v2 part on a v1 server — nothing was written) · \`MOCK_DEFINITION_INVALID\`
|
|
434
|
+
(\`details.issues\` names each problem by path; nothing was written) · \`MOCK_SLUG_TAKEN\` (pick another slug) ·
|
|
435
|
+
\`MOCK_LIMIT_REACHED\` (delete an old mock) · \`ROUTE_PATH_CONFLICT\` (the method + path exists) ·
|
|
436
|
+
\`MOCK_ROUTE_LIMIT_REACHED\` · \`MOCK_RESOURCE_CONFLICT\` (a resource with that name exists) ·
|
|
437
|
+
\`MOCK_RESOURCE_NOT_FOUND\` / \`MOCK_RECORD_NOT_FOUND\` · \`MOCK_RECORD_INVALID\` (the record does not fit the fields) ·
|
|
438
|
+
\`MOCK_RECORD_DELETE_BLOCKED\` (a \`restrict\` reference) · \`REVISION_CONFLICT\` (changed elsewhere; read and repeat) ·
|
|
439
|
+
\`MOCK_IMPORT_SOURCE_BLOCKED\` (a file or private URL without CALLMAN_ALLOW_LOCAL_SOURCES) ·
|
|
440
|
+
\`MOCK_IMPORT_UNSUPPORTED\` / \`MOCK_IMPORT_UNREADABLE\` / \`MOCK_IMPORT_TOO_LARGE\` · \`VALIDATION_ERROR\` (details name
|
|
441
|
+
the field) · \`LOCAL_MOCK_DESKTOP_OFFLINE\` (open the desktop app and switch the setting on) ·
|
|
442
|
+
\`LOCAL_MOCK_DESKTOP_OUTDATED\` (update the desktop app, or use a server mock) · \`LOCAL_MOCK_TIMEOUT\` (the app did
|
|
443
|
+
not answer; check with list_mocks before repeating a create) · \`LOCAL_MOCK_FEATURE_UNSUPPORTED\` (static only here).
|
|
209
444
|
`;
|
|
210
445
|
//# sourceMappingURL=mocks.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mocks.js","sourceRoot":"","sources":["../../../src/resources/docs/mocks.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,SAAS,GAAG
|
|
1
|
+
{"version":3,"file":"mocks.js","sourceRoot":"","sources":["../../../src/resources/docs/mocks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjM,2EAA2E;AAC3E,8DAA8D;AAC9D,MAAM,IAAI,GAAG,CAAC,MAAyB,EAAU,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAErG,MAAM,eAAe,GAAG,GAAW,EAAE;IACnC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC3C,KAAK,MAAM,KAAK,IAAI,qBAAqB;QAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,KAAK,CAAC,KAAK,QAAQ,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;IACtK,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,SAAS,KAAK,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpG,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gDAuEuB,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;;oFAExB,IAAI,CAAC,kBAAkB,CAAC;;;;IAIxG,IAAI,CAAC,gBAAgB,CAAC;;KAErB,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;;;;;sCAKF,IAAI,CAAC,uBAAuB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyFjE,eAAe,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAyDA,IAAI,CAAC,oBAAoB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2M5C,CAAC"}
|