assert-json-body 1.0.2
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/README.md +352 -0
- package/dist/assertion/diff-recordings-vs-spec.d.ts +3 -0
- package/dist/assertion/diff-recordings-vs-spec.d.ts.map +1 -0
- package/dist/assertion/diff-recordings-vs-spec.js +176 -0
- package/dist/assertion/diff-recordings-vs-spec.js.map +1 -0
- package/dist/assertion/index.d.ts +2 -0
- package/dist/assertion/index.d.ts.map +1 -0
- package/dist/assertion/index.js +2 -0
- package/dist/assertion/index.js.map +1 -0
- package/dist/assertion/recorder.d.ts +7 -0
- package/dist/assertion/recorder.d.ts.map +1 -0
- package/dist/assertion/recorder.js +81 -0
- package/dist/assertion/recorder.js.map +1 -0
- package/dist/assertion/summarize-recordings.d.ts +3 -0
- package/dist/assertion/summarize-recordings.d.ts.map +1 -0
- package/dist/assertion/summarize-recordings.js +121 -0
- package/dist/assertion/summarize-recordings.js.map +1 -0
- package/dist/assertion/validator.d.ts +2 -0
- package/dist/assertion/validator.d.ts.map +1 -0
- package/dist/assertion/validator.js +3 -0
- package/dist/assertion/validator.js.map +1 -0
- package/dist/base.d.ts +2 -0
- package/dist/base.d.ts.map +1 -0
- package/dist/base.js +3 -0
- package/dist/base.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +84 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/extractor/src/lib/child-expansion.d.ts +8 -0
- package/dist/extractor/src/lib/child-expansion.d.ts.map +1 -0
- package/dist/extractor/src/lib/child-expansion.js +72 -0
- package/dist/extractor/src/lib/child-expansion.js.map +1 -0
- package/dist/extractor/src/lib/git.d.ts +9 -0
- package/dist/extractor/src/lib/git.d.ts.map +1 -0
- package/dist/extractor/src/lib/git.js +22 -0
- package/dist/extractor/src/lib/git.js.map +1 -0
- package/dist/extractor/src/lib/schema-flatten.d.ts +10 -0
- package/dist/extractor/src/lib/schema-flatten.d.ts.map +1 -0
- package/dist/extractor/src/lib/schema-flatten.js +203 -0
- package/dist/extractor/src/lib/schema-flatten.js.map +1 -0
- package/dist/extractor/src/lib/type-utils.d.ts +5 -0
- package/dist/extractor/src/lib/type-utils.d.ts.map +1 -0
- package/dist/extractor/src/lib/type-utils.js +27 -0
- package/dist/extractor/src/lib/type-utils.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/assert.d.ts +27 -0
- package/dist/lib/assert.d.ts.map +1 -0
- package/dist/lib/assert.js +32 -0
- package/dist/lib/assert.js.map +1 -0
- package/dist/lib/child-expansion.d.ts +2 -0
- package/dist/lib/child-expansion.d.ts.map +1 -0
- package/dist/lib/child-expansion.js +2 -0
- package/dist/lib/child-expansion.js.map +1 -0
- package/dist/lib/config.d.ts +27 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +139 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/extractor.d.ts +6 -0
- package/dist/lib/extractor.d.ts.map +1 -0
- package/dist/lib/extractor.js +164 -0
- package/dist/lib/extractor.js.map +1 -0
- package/dist/lib/git.d.ts +2 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +2 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/recorder.d.ts +8 -0
- package/dist/lib/recorder.d.ts.map +1 -0
- package/dist/lib/recorder.js +68 -0
- package/dist/lib/recorder.js.map +1 -0
- package/dist/lib/responses.d.ts +13 -0
- package/dist/lib/responses.d.ts.map +1 -0
- package/dist/lib/responses.js +114 -0
- package/dist/lib/responses.js.map +1 -0
- package/dist/lib/schema-flatten.d.ts +2 -0
- package/dist/lib/schema-flatten.d.ts.map +1 -0
- package/dist/lib/schema-flatten.js +2 -0
- package/dist/lib/schema-flatten.js.map +1 -0
- package/dist/lib/tools/diff-recordings-vs-spec.d.ts +2 -0
- package/dist/lib/tools/diff-recordings-vs-spec.d.ts.map +1 -0
- package/dist/lib/tools/diff-recordings-vs-spec.js +2 -0
- package/dist/lib/tools/diff-recordings-vs-spec.js.map +1 -0
- package/dist/lib/tools/recorder.d.ts +2 -0
- package/dist/lib/tools/recorder.d.ts.map +1 -0
- package/dist/lib/tools/recorder.js +3 -0
- package/dist/lib/tools/recorder.js.map +1 -0
- package/dist/lib/tools/summarize-recordings.d.ts +2 -0
- package/dist/lib/tools/summarize-recordings.d.ts.map +1 -0
- package/dist/lib/tools/summarize-recordings.js +2 -0
- package/dist/lib/tools/summarize-recordings.js.map +1 -0
- package/dist/lib/type-utils.d.ts +2 -0
- package/dist/lib/type-utils.d.ts.map +1 -0
- package/dist/lib/type-utils.js +2 -0
- package/dist/lib/type-utils.js.map +1 -0
- package/dist/lib/validator.d.ts +33 -0
- package/dist/lib/validator.d.ts.map +1 -0
- package/dist/lib/validator.js +179 -0
- package/dist/lib/validator.js.map +1 -0
- package/dist/types/index.d.ts +88 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist-cjs/assertion/diff-recordings-vs-spec.d.ts +3 -0
- package/dist-cjs/assertion/diff-recordings-vs-spec.d.ts.map +1 -0
- package/dist-cjs/assertion/diff-recordings-vs-spec.js +178 -0
- package/dist-cjs/assertion/diff-recordings-vs-spec.js.map +1 -0
- package/dist-cjs/assertion/index.d.ts +2 -0
- package/dist-cjs/assertion/index.d.ts.map +1 -0
- package/dist-cjs/assertion/index.js +3 -0
- package/dist-cjs/assertion/index.js.map +1 -0
- package/dist-cjs/assertion/recorder.d.ts +7 -0
- package/dist-cjs/assertion/recorder.d.ts.map +1 -0
- package/dist-cjs/assertion/recorder.js +84 -0
- package/dist-cjs/assertion/recorder.js.map +1 -0
- package/dist-cjs/assertion/summarize-recordings.d.ts +3 -0
- package/dist-cjs/assertion/summarize-recordings.d.ts.map +1 -0
- package/dist-cjs/assertion/summarize-recordings.js +123 -0
- package/dist-cjs/assertion/summarize-recordings.js.map +1 -0
- package/dist-cjs/assertion/validator.d.ts +2 -0
- package/dist-cjs/assertion/validator.d.ts.map +1 -0
- package/dist-cjs/assertion/validator.js +7 -0
- package/dist-cjs/assertion/validator.js.map +1 -0
- package/dist-cjs/base.d.ts +2 -0
- package/dist-cjs/base.d.ts.map +1 -0
- package/dist-cjs/base.js +7 -0
- package/dist-cjs/base.js.map +1 -0
- package/dist-cjs/cli/index.d.ts +3 -0
- package/dist-cjs/cli/index.d.ts.map +1 -0
- package/dist-cjs/cli/index.js +86 -0
- package/dist-cjs/cli/index.js.map +1 -0
- package/dist-cjs/extractor/src/lib/child-expansion.d.ts +8 -0
- package/dist-cjs/extractor/src/lib/child-expansion.d.ts.map +1 -0
- package/dist-cjs/extractor/src/lib/child-expansion.js +76 -0
- package/dist-cjs/extractor/src/lib/child-expansion.js.map +1 -0
- package/dist-cjs/extractor/src/lib/git.d.ts +9 -0
- package/dist-cjs/extractor/src/lib/git.d.ts.map +1 -0
- package/dist-cjs/extractor/src/lib/git.js +26 -0
- package/dist-cjs/extractor/src/lib/git.js.map +1 -0
- package/dist-cjs/extractor/src/lib/schema-flatten.d.ts +10 -0
- package/dist-cjs/extractor/src/lib/schema-flatten.d.ts.map +1 -0
- package/dist-cjs/extractor/src/lib/schema-flatten.js +211 -0
- package/dist-cjs/extractor/src/lib/schema-flatten.js.map +1 -0
- package/dist-cjs/extractor/src/lib/type-utils.d.ts +5 -0
- package/dist-cjs/extractor/src/lib/type-utils.d.ts.map +1 -0
- package/dist-cjs/extractor/src/lib/type-utils.js +32 -0
- package/dist-cjs/extractor/src/lib/type-utils.js.map +1 -0
- package/dist-cjs/index.d.ts +5 -0
- package/dist-cjs/index.d.ts.map +1 -0
- package/dist-cjs/index.js +26 -0
- package/dist-cjs/index.js.map +1 -0
- package/dist-cjs/lib/assert.d.ts +27 -0
- package/dist-cjs/lib/assert.d.ts.map +1 -0
- package/dist-cjs/lib/assert.js +35 -0
- package/dist-cjs/lib/assert.js.map +1 -0
- package/dist-cjs/lib/child-expansion.d.ts +2 -0
- package/dist-cjs/lib/child-expansion.d.ts.map +1 -0
- package/dist-cjs/lib/child-expansion.js +7 -0
- package/dist-cjs/lib/child-expansion.js.map +1 -0
- package/dist-cjs/lib/config.d.ts +27 -0
- package/dist-cjs/lib/config.d.ts.map +1 -0
- package/dist-cjs/lib/config.js +147 -0
- package/dist-cjs/lib/config.js.map +1 -0
- package/dist-cjs/lib/extractor.d.ts +6 -0
- package/dist-cjs/lib/extractor.d.ts.map +1 -0
- package/dist-cjs/lib/extractor.js +172 -0
- package/dist-cjs/lib/extractor.js.map +1 -0
- package/dist-cjs/lib/git.d.ts +2 -0
- package/dist-cjs/lib/git.d.ts.map +1 -0
- package/dist-cjs/lib/git.js +6 -0
- package/dist-cjs/lib/git.js.map +1 -0
- package/dist-cjs/lib/recorder.d.ts +8 -0
- package/dist-cjs/lib/recorder.d.ts.map +1 -0
- package/dist-cjs/lib/recorder.js +71 -0
- package/dist-cjs/lib/recorder.js.map +1 -0
- package/dist-cjs/lib/responses.d.ts +13 -0
- package/dist-cjs/lib/responses.d.ts.map +1 -0
- package/dist-cjs/lib/responses.js +117 -0
- package/dist-cjs/lib/responses.js.map +1 -0
- package/dist-cjs/lib/schema-flatten.d.ts +2 -0
- package/dist-cjs/lib/schema-flatten.d.ts.map +1 -0
- package/dist-cjs/lib/schema-flatten.js +11 -0
- package/dist-cjs/lib/schema-flatten.js.map +1 -0
- package/dist-cjs/lib/tools/diff-recordings-vs-spec.d.ts +2 -0
- package/dist-cjs/lib/tools/diff-recordings-vs-spec.d.ts.map +1 -0
- package/dist-cjs/lib/tools/diff-recordings-vs-spec.js +18 -0
- package/dist-cjs/lib/tools/diff-recordings-vs-spec.js.map +1 -0
- package/dist-cjs/lib/tools/recorder.d.ts +2 -0
- package/dist-cjs/lib/tools/recorder.d.ts.map +1 -0
- package/dist-cjs/lib/tools/recorder.js +19 -0
- package/dist-cjs/lib/tools/recorder.js.map +1 -0
- package/dist-cjs/lib/tools/summarize-recordings.d.ts +2 -0
- package/dist-cjs/lib/tools/summarize-recordings.d.ts.map +1 -0
- package/dist-cjs/lib/tools/summarize-recordings.js +18 -0
- package/dist-cjs/lib/tools/summarize-recordings.js.map +1 -0
- package/dist-cjs/lib/type-utils.d.ts +2 -0
- package/dist-cjs/lib/type-utils.d.ts.map +1 -0
- package/dist-cjs/lib/type-utils.js +8 -0
- package/dist-cjs/lib/type-utils.js.map +1 -0
- package/dist-cjs/lib/validator.d.ts +33 -0
- package/dist-cjs/lib/validator.d.ts.map +1 -0
- package/dist-cjs/lib/validator.js +182 -0
- package/dist-cjs/lib/validator.js.map +1 -0
- package/dist-cjs/package.json +1 -0
- package/dist-cjs/types/index.d.ts +88 -0
- package/dist-cjs/types/index.d.ts.map +1 -0
- package/dist-cjs/types/index.js +4 -0
- package/dist-cjs/types/index.js.map +1 -0
- package/package.json +125 -0
package/README.md
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# assert-json-body
|
|
2
|
+
|
|
3
|
+
[](https://github.com/camunda/assert-json-body/actions/workflows/release.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/assert-json-body)
|
|
5
|
+
|
|
6
|
+
Framework-agnostic toolkit to:
|
|
7
|
+
- Extract OpenAPI response schemas into a compact `responses.json` artifact
|
|
8
|
+
- Validate real JSON response bodies against the extracted required/optional field model
|
|
9
|
+
- Assert inside any test runner (Vitest, Jest, Playwright, etc.)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
npm install assert-json-body
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
1. (Optional) Initialize a config file:
|
|
20
|
+
```
|
|
21
|
+
npx assert-json-body config:init
|
|
22
|
+
```
|
|
23
|
+
Produces `assert-json-body.config.json` (edit repo, spec path, output dir, etc.).
|
|
24
|
+
|
|
25
|
+
2. Extract responses from your OpenAPI spec:
|
|
26
|
+
```
|
|
27
|
+
npx assert-json-body extract
|
|
28
|
+
```
|
|
29
|
+
This writes (by default):
|
|
30
|
+
- `./assert-json-body/responses.json` (schema bundle)
|
|
31
|
+
- `./assert-json-body/index.ts` (auto-generated typed wrapper)
|
|
32
|
+
or the configured `responsesFile` for the JSON schema artifact.
|
|
33
|
+
|
|
34
|
+
The init step also adds an npm script for convenience:
|
|
35
|
+
```jsonc
|
|
36
|
+
// package.json
|
|
37
|
+
{
|
|
38
|
+
"scripts": {
|
|
39
|
+
"responses:regenerate": "assert-json-body extract"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
So you can run:
|
|
44
|
+
```
|
|
45
|
+
npm run responses:regenerate
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
3. Validate in a test (untyped import):
|
|
49
|
+
```ts
|
|
50
|
+
import { validateResponseShape } from 'assert-json-body';
|
|
51
|
+
|
|
52
|
+
// Suppose you just performed an HTTP request and have jsonBody
|
|
53
|
+
validateResponseShape({ path: '/process-instance/create', method: 'POST', status: '200' }, jsonBody);
|
|
54
|
+
// Throws if JSON shape violates required field presence / type rules.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
4. Prefer typed validation (after extract):
|
|
58
|
+
```ts
|
|
59
|
+
import { validateResponseShape } from './assert-json-body/index';
|
|
60
|
+
|
|
61
|
+
// Now path/method/status are constrained to extracted endpoints
|
|
62
|
+
validateResponseShape({ path: '/process-instance/create', method: 'POST', status: '200' }, jsonBody);
|
|
63
|
+
|
|
64
|
+
// @ts-expect-error invalid status not in spec
|
|
65
|
+
// validateResponseShape({ path: '/process-instance/create', method: 'POST', status: '418' }, jsonBody);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Regenerate typed file whenever the spec changes by re-running `extract` (commit both `responses.json` and `index.ts` if you track API contract changes in version control).
|
|
69
|
+
|
|
70
|
+
You can control default throw/record behavior globally via config or env (see below) and override per call.
|
|
71
|
+
|
|
72
|
+
### CI Integration
|
|
73
|
+
Keep the generated artifacts (`responses.json`, `index.ts`) in sync with the upstream spec during continuous integration:
|
|
74
|
+
|
|
75
|
+
Example GitHub Actions step (add after install):
|
|
76
|
+
```yaml
|
|
77
|
+
- name: Regenerate response schemas
|
|
78
|
+
run: npm run responses:regenerate
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
If you commit the generated files:
|
|
82
|
+
1. Run the regenerate step early (before tests).
|
|
83
|
+
2. Add a check that the working tree is clean to ensure developers didn’t forget to re-run extraction locally:
|
|
84
|
+
```yaml
|
|
85
|
+
- name: Verify no uncommitted changes
|
|
86
|
+
run: |
|
|
87
|
+
git diff --exit-code || (echo 'Generated response artifacts out of date. Run: npm run responses:regenerate' && exit 1)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
If you prefer not to commit generated artifacts:
|
|
91
|
+
- Add the output directory (default `assert-json-body/`) to `.gitignore`.
|
|
92
|
+
- Always run `npm run responses:regenerate` before building / testing.
|
|
93
|
+
|
|
94
|
+
Caching tip: if your spec repo is large, you can cache the sparse checkout directory by keying on the spec ref (commit SHA) to speed up subsequent runs.
|
|
95
|
+
|
|
96
|
+
## Configuration
|
|
97
|
+
|
|
98
|
+
Config file: `assert-json-body.config.json` (created with `npx assert-json-body config:init`).
|
|
99
|
+
|
|
100
|
+
The configuration is now split into two blocks:
|
|
101
|
+
|
|
102
|
+
1. `extract`: Controls OpenAPI repo checkout and artifact generation.
|
|
103
|
+
2. `validate`: Controls global validation behaviour (recording & throw semantics).
|
|
104
|
+
|
|
105
|
+
### extract block
|
|
106
|
+
|
|
107
|
+
| Field | Type | Default | Description | Env override(s) |
|
|
108
|
+
|-------|------|---------|-------------|-----------------|
|
|
109
|
+
| `repo` | string | `https://github.com/camunda/camunda-orchestration-cluster-api` | Git repository containing the OpenAPI spec | `AJB_REPO`, `REPO` |
|
|
110
|
+
| `specPath` | string | `specification/rest-api.yaml` | Path to OpenAPI spec inside the repo | `AJB_SPEC_PATH`, `SPEC_PATH` |
|
|
111
|
+
| `ref` | string | `main` | Git ref (branch/tag/sha) to checkout | `AJB_REF`, `SPEC_REF`, `REF` |
|
|
112
|
+
| `outputDir` | string | `assert-json-body` | Directory to write `responses.json` + generated `index.ts` | `AJB_OUTPUT_DIR`, `OUTPUT_DIR` |
|
|
113
|
+
| `preserveCheckout` | boolean | `false` | Keep sparse checkout working copy (debug) | `AJB_PRESERVE_CHECKOUT`, `PRESERVE_SPEC_CHECKOUT` |
|
|
114
|
+
| `dryRun` | boolean | `false` | Parse spec but do not write files | `AJB_DRY_RUN` |
|
|
115
|
+
| `logLevel` | enum | `info` | `silent` `error` `warn` `info` `debug` | `AJB_LOG_LEVEL` |
|
|
116
|
+
| `failIfExists` | boolean | `false` | Abort if target responses file already exists | `AJB_FAIL_IF_EXISTS` |
|
|
117
|
+
| `responsesFile` | string | — | Optional explicit path for responses JSON (advanced) | `AJB_RESPONSES_FILE`, `ROUTE_TEST_RESPONSES_FILE` |
|
|
118
|
+
|
|
119
|
+
### validate block
|
|
120
|
+
|
|
121
|
+
| Field | Type | Default | Description | Env override(s) |
|
|
122
|
+
|-------|------|---------|-------------|-----------------|
|
|
123
|
+
| `recordResponses` | boolean | `false` | Globally enable body recording | `AJB_RECORD`, `TEST_RESPONSE_BODY_RECORD` |
|
|
124
|
+
| `throwOnValidationFail` | boolean | `true` | Throw vs structured `{ ok:false }` result | `AJB_THROW_ON_FAIL` |
|
|
125
|
+
|
|
126
|
+
Additional env variables:
|
|
127
|
+
|
|
128
|
+
| Env | Purpose |
|
|
129
|
+
|-----|---------|
|
|
130
|
+
| `TEST_RESPONSE_BODY_RECORD_DIR` | Directory where JSONL body recordings are written when recording is active |
|
|
131
|
+
|
|
132
|
+
Example full config:
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"extract": {
|
|
136
|
+
"repo": "https://github.com/camunda/camunda-orchestration-cluster-api",
|
|
137
|
+
"specPath": "specification/openapi.yaml",
|
|
138
|
+
"ref": "main",
|
|
139
|
+
"outputDir": "assert-json-body",
|
|
140
|
+
"preserveCheckout": false,
|
|
141
|
+
"dryRun": false,
|
|
142
|
+
"logLevel": "info",
|
|
143
|
+
"failIfExists": false
|
|
144
|
+
},
|
|
145
|
+
"validate": {
|
|
146
|
+
"recordResponses": false,
|
|
147
|
+
"throwOnValidationFail": true
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Notes:
|
|
153
|
+
- The responses schema file defaults to `<outputDir>/responses.json` unless overridden.
|
|
154
|
+
- Boolean env overrides accept `1|true|yes` (case-insensitive).
|
|
155
|
+
- Precedence per value: CLI flag > environment variable > config file > built-in default.
|
|
156
|
+
|
|
157
|
+
## Schema Resolution Precedence
|
|
158
|
+
|
|
159
|
+
When `validateResponseShape` looks for the schema artifact, precedence is:
|
|
160
|
+
1. Explicit option: `responsesFilePath` passed to the function
|
|
161
|
+
2. Environment variable: `ROUTE_TEST_RESPONSES_FILE` or `AJB_RESPONSES_FILE`
|
|
162
|
+
3. Config file: `extract.responsesFile` or `<outputDir>/responses.json`
|
|
163
|
+
4. Default fallback: `./assert-json-body/responses.json`
|
|
164
|
+
|
|
165
|
+
All file issues (missing, unreadable, parse errors, malformed structure) throw clear errors.
|
|
166
|
+
|
|
167
|
+
## API Reference
|
|
168
|
+
|
|
169
|
+
### `validateResponseShape(spec, body, options?)`
|
|
170
|
+
Single unified API: validates `body` against the schema entry. Supports optional structured result mode and recording.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
validateResponseShape(
|
|
174
|
+
{ path: '/foo', method: 'GET', status: '200' },
|
|
175
|
+
jsonBody,
|
|
176
|
+
{ responsesFilePath: './custom/responses.json', configPath: './custom-config.json' }
|
|
177
|
+
);
|
|
178
|
+
```
|
|
179
|
+
Default behavior: throws on mismatch (configurable). If you pass `throw:false` (or set global flag) it returns a structured object:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
interface ValidateResultBase {
|
|
183
|
+
ok: boolean;
|
|
184
|
+
errors?: string[]; // present when ok === false
|
|
185
|
+
response: unknown; // the original response body you passed in
|
|
186
|
+
schema: { // the schema fragment used for validation
|
|
187
|
+
required: FieldSpec[];
|
|
188
|
+
optional: FieldSpec[];
|
|
189
|
+
};
|
|
190
|
+
routeContext: RouteContext; // resolved route/method/status + flattened field specs
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Examples:
|
|
195
|
+
```ts
|
|
196
|
+
// Success (non-throw mode)
|
|
197
|
+
const r1 = validateResponseShape({ path: '/foo', method: 'GET', status: '200' }, body, { throw: false });
|
|
198
|
+
if (!r1.ok) throw new Error('unexpected');
|
|
199
|
+
console.log(r1.schema.required.map(f => f.name));
|
|
200
|
+
|
|
201
|
+
// Failure (non-throw mode)
|
|
202
|
+
const r2 = validateResponseShape({ path: '/foo', method: 'GET', status: '200' }, otherBody, { throw: false });
|
|
203
|
+
if (!r2.ok) {
|
|
204
|
+
console.warn(r2.errors); // array of error lines
|
|
205
|
+
console.log(r2.routeContext.status); // resolved status used
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### Options
|
|
210
|
+
- `responsesFilePath` / `configPath` – override resolution
|
|
211
|
+
- `throw?: boolean` – override global throw setting
|
|
212
|
+
- `record?: boolean | { label?: string }` – enable recording for this call
|
|
213
|
+
|
|
214
|
+
### Types
|
|
215
|
+
`FieldSpec`, `RouteContext`, and other structural types are exported from `@/types`.
|
|
216
|
+
|
|
217
|
+
### Generated Typed Entry (after extract)
|
|
218
|
+
After running the extractor you can import a strongly-typed version of `validateResponseShape` that constrains `path`, `method` and `status` to only the extracted endpoints:
|
|
219
|
+
```ts
|
|
220
|
+
import { validateResponseShape } from './assert-json-body/index';
|
|
221
|
+
|
|
222
|
+
// Autocomplete + compile-time safety for path/method/status
|
|
223
|
+
validateResponseShape({ path: '/process-instance/create', method: 'POST', status: '200' }, body);
|
|
224
|
+
|
|
225
|
+
// @ts-expect-error invalid status for that route will fail type-check
|
|
226
|
+
// validateResponseShape({ path: '/process-instance/create', method: 'POST', status: '418' }, body);
|
|
227
|
+
```
|
|
228
|
+
You can also use the exported helper types:
|
|
229
|
+
```ts
|
|
230
|
+
import type { RoutePath, MethodFor, StatusFor } from './assert-json-body/index';
|
|
231
|
+
|
|
232
|
+
type AnyRoute = RoutePath;
|
|
233
|
+
type GetStatus<P extends RoutePath> = StatusFor<P, MethodFor<P>>;
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## FAQ
|
|
237
|
+
|
|
238
|
+
**Why do I get two files (`responses.json` and `index.ts`)?**
|
|
239
|
+
`responses.json` is the runtime artifact used for validation. `index.ts` adds compile-time safety and autocomplete for route specifications.
|
|
240
|
+
|
|
241
|
+
**Do I need to commit `index.ts`?**
|
|
242
|
+
Recommended: yes. Changes in that file make API evolution explicit in diffs. If you prefer to ignore it, ensure your build pipeline runs `assert-json-body extract` first.
|
|
243
|
+
|
|
244
|
+
**Can I disable type emission?**
|
|
245
|
+
Not yet—emission is always on. Open an issue if you need a toggle.
|
|
246
|
+
|
|
247
|
+
**Large spec performance concerns?**
|
|
248
|
+
The generated `index.ts` only stores a nested map of route/method/status flags, not full schema trees, keeping file size modest. Extremely large specs are still typically < a few hundred KB.
|
|
249
|
+
|
|
250
|
+
**How do I update types after the spec changes?**
|
|
251
|
+
Re-run `npx assert-json-body extract`. The `index.ts` file is regenerated deterministically.
|
|
252
|
+
|
|
253
|
+
**Do I still need to import from the package root?**
|
|
254
|
+
Use the package root for generic validation utilities (`validateResponseShape`), and the generated `./assert-json-body/index` for strongly typed validation.
|
|
255
|
+
|
|
256
|
+
## Releasing & Versioning
|
|
257
|
+
|
|
258
|
+
This project uses [semantic-release](https://semantic-release.gitbook.io/) with Conventional Commits to automate:
|
|
259
|
+
- Version determination (based on commit messages)
|
|
260
|
+
- CHANGELOG generation (`CHANGELOG.md`)
|
|
261
|
+
- GitHub release notes
|
|
262
|
+
- npm publication
|
|
263
|
+
|
|
264
|
+
Every push to `main` triggers the release workflow. Ensure your commits follow Conventional Commit prefixes so changes are categorized correctly:
|
|
265
|
+
|
|
266
|
+
Common types:
|
|
267
|
+
- `feat:` – new feature (minor release)
|
|
268
|
+
- `fix:` – bug fix (patch release)
|
|
269
|
+
- `docs:` – documentation only
|
|
270
|
+
- `refactor:` – code change that neither fixes a bug nor adds a feature
|
|
271
|
+
- `perf:` – performance improvement
|
|
272
|
+
- `test:` – adding or correcting tests
|
|
273
|
+
- `chore:` – build / tooling / infra
|
|
274
|
+
|
|
275
|
+
Breaking changes: add a footer line `BREAKING CHANGE: <description>` (or use `!` after the type, e.g. `feat!: drop Node 16`).
|
|
276
|
+
|
|
277
|
+
Example commit message:
|
|
278
|
+
```
|
|
279
|
+
feat: add structured validation result for non-throw mode
|
|
280
|
+
|
|
281
|
+
BREAKING CHANGE: removed deprecated assertResponseShape in favor of unified validateResponseShape
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Manual version bumps in `package.json` are not needed; semantic-release will handle it.
|
|
285
|
+
|
|
286
|
+
Local commit messages are validated by commitlint + husky (commit-msg hook). If a commit is rejected, adjust the prefix / format to match Conventional Commits.
|
|
287
|
+
|
|
288
|
+
## CLI Commands
|
|
289
|
+
|
|
290
|
+
| Command | Description |
|
|
291
|
+
|---------|-------------|
|
|
292
|
+
| `assert-json-body extract` | Performs sparse checkout + OpenAPI parse + response schema flattening into `responses.json` and emits typed `index.ts`. |
|
|
293
|
+
| `assert-json-body config:init` | Creates a starter `assert-json-body.config.json`. |
|
|
294
|
+
|
|
295
|
+
Environment variables (selected):
|
|
296
|
+
- `ROUTE_TEST_RESPONSES_FILE` / `AJB_RESPONSES_FILE` – override schema file
|
|
297
|
+
- `TEST_RESPONSE_BODY_RECORD_DIR` – directory to write JSONL body recordings
|
|
298
|
+
- `AJB_RECORD` / `TEST_RESPONSE_BODY_RECORD` – set default recording on (true/1/yes)
|
|
299
|
+
- `AJB_THROW_ON_FAIL` – set default throw behavior (true/false)
|
|
300
|
+
|
|
301
|
+
## Recording (Optional)
|
|
302
|
+
|
|
303
|
+
Set `TEST_RESPONSE_BODY_RECORD_DIR=./recordings` and either:
|
|
304
|
+
```ts
|
|
305
|
+
// Per-call recording
|
|
306
|
+
validateResponseShape({ path: '/foo', method: 'GET', status: '200' }, body, { record: { label: 'GET /foo success' } });
|
|
307
|
+
|
|
308
|
+
// Or enable globally (env): AJB_RECORD=true
|
|
309
|
+
```
|
|
310
|
+
Produces JSONL rows with required field list, top-level present, deep presence and body snapshot.
|
|
311
|
+
|
|
312
|
+
## Integration Tests
|
|
313
|
+
|
|
314
|
+
An optional end-to-end integration test suite lives under `integration/` and is excluded from the default unit test run.
|
|
315
|
+
|
|
316
|
+
Run unit tests (fast, pure):
|
|
317
|
+
```
|
|
318
|
+
npm test
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Run integration tests (performs real OpenAPI extraction and live HTTP calls):
|
|
322
|
+
```
|
|
323
|
+
npm run test:integration
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Local requirements:
|
|
327
|
+
- Start the target service (expected at `http://localhost:8080` by default), or
|
|
328
|
+
- Set `TEST_BASE_URL` to point to a running instance
|
|
329
|
+
|
|
330
|
+
CI (Docker) example:
|
|
331
|
+
```yaml
|
|
332
|
+
- name: Start API container
|
|
333
|
+
run: |
|
|
334
|
+
docker run -d --name api -p 8080:8080 your/api:image
|
|
335
|
+
for i in {1..30}; do curl -sf http://localhost:8080/license && break; sleep 1; done
|
|
336
|
+
- name: Run integration tests
|
|
337
|
+
run: npm run test:integration
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
If the service is unreachable, the integration test logs a warning and exits early (treated as a soft skip).
|
|
341
|
+
|
|
342
|
+
## Error Messages
|
|
343
|
+
|
|
344
|
+
Errors show a capped (first 15) list of issues (missing, type, enum, extra) with JSON Pointer paths. Additional errors are summarized with a count.
|
|
345
|
+
|
|
346
|
+
## Precedence Test Illustration
|
|
347
|
+
|
|
348
|
+
See `src/tests/precedence.spec.ts` for an executable example verifying explicit > env > config > default ordering.
|
|
349
|
+
|
|
350
|
+
## License
|
|
351
|
+
ISC (see `LICENSE`).
|
|
352
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-recordings-vs-spec.d.ts","sourceRoot":"","sources":["../../src/assertion/diff-recordings-vs-spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
/*
|
|
3
|
+
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
|
|
4
|
+
* one or more contributor license agreements. See the NOTICE file distributed
|
|
5
|
+
* with this work for additional information regarding copyright ownership.
|
|
6
|
+
* Licensed under the Camunda License 1.0. You may not use this file
|
|
7
|
+
* except in compliance with the Camunda License 1.0.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Diff recorder observations against the spec-derived responses.json to
|
|
11
|
+
* recommend which optional fields (top-level & deep) should be promoted
|
|
12
|
+
* to required based on presence frequency.
|
|
13
|
+
*
|
|
14
|
+
* Data sources:
|
|
15
|
+
* 1. Recording directory (JSONL) from TEST_RESPONSE_BODY_RECORD_DIR (or arg1)
|
|
16
|
+
* 2. responses.json (default path or ROUTE_TEST_RESPONSES_FILE override)
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* ts-node diff-recordings-vs-spec.ts <record-dir?> <thresholdPct=100>
|
|
20
|
+
*
|
|
21
|
+
* Output: JSON report similar to summarize-recordings but restricted only to
|
|
22
|
+
* fields that are optional in the current spec.
|
|
23
|
+
*/
|
|
24
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
25
|
+
import { resolve } from 'node:path';
|
|
26
|
+
const recordDir = process.argv[2] || process.env.TEST_RESPONSE_BODY_RECORD_DIR;
|
|
27
|
+
const thresholdPct = Number(process.argv[3] || '100');
|
|
28
|
+
if (!recordDir) {
|
|
29
|
+
console.error('Usage: diff-recordings-vs-spec <record-dir?> <thresholdPct=100>');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
// Resolve responses.json path (same logic as runtime module)
|
|
33
|
+
const RESPONSES_FILE_ENV = process.env.ROUTE_TEST_RESPONSES_FILE;
|
|
34
|
+
const DEFAULT_RESPONSES_PATH = resolve(__dirname, '../../response-required-extractor/output/responses.json');
|
|
35
|
+
const RESPONSES_PATH = RESPONSES_FILE_ENV
|
|
36
|
+
? resolve(process.cwd(), RESPONSES_FILE_ENV)
|
|
37
|
+
: DEFAULT_RESPONSES_PATH;
|
|
38
|
+
let parsedSpec = null;
|
|
39
|
+
try {
|
|
40
|
+
parsedSpec = JSON.parse(readFileSync(RESPONSES_PATH, 'utf8'));
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
console.error('Failed to load responses.json at', RESPONSES_PATH, e);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
function specKey(path, method, status) {
|
|
47
|
+
return `${method.toUpperCase()} ${status} ${path}`;
|
|
48
|
+
}
|
|
49
|
+
const specIndex = new Map();
|
|
50
|
+
for (const entry of parsedSpec.responses) {
|
|
51
|
+
specIndex.set(specKey(entry.path, entry.method, entry.status), {
|
|
52
|
+
required: new Set((entry.schema.required || []).map((f) => f.name)),
|
|
53
|
+
optional: new Set((entry.schema.optional || []).map((f) => f.name)),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function recKey(route, method, status) {
|
|
57
|
+
return `${(method || 'ANY').toUpperCase()} ${(status || 'ANY')} ${route}`;
|
|
58
|
+
}
|
|
59
|
+
const aggIndex = new Map();
|
|
60
|
+
function isJsonl(f) { return f.endsWith('.jsonl'); }
|
|
61
|
+
for (const file of readdirSync(recordDir)) {
|
|
62
|
+
if (!isJsonl(file))
|
|
63
|
+
continue;
|
|
64
|
+
const full = resolve(recordDir, file);
|
|
65
|
+
const st = statSync(full);
|
|
66
|
+
if (!st.isFile())
|
|
67
|
+
continue;
|
|
68
|
+
const lines = readFileSync(full, 'utf8').split(/\n+/).filter(Boolean);
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
try {
|
|
71
|
+
const obj = JSON.parse(line);
|
|
72
|
+
const key = recKey(obj.route, obj.method, obj.status);
|
|
73
|
+
let a = aggIndex.get(key);
|
|
74
|
+
if (!a) {
|
|
75
|
+
a = {
|
|
76
|
+
route: obj.route,
|
|
77
|
+
method: (obj.method || 'ANY').toUpperCase(),
|
|
78
|
+
status: obj.status || 'ANY',
|
|
79
|
+
samples: 0,
|
|
80
|
+
required: new Set(obj.required || []),
|
|
81
|
+
presentCounts: new Map(),
|
|
82
|
+
deepCounts: new Map(),
|
|
83
|
+
};
|
|
84
|
+
aggIndex.set(key, a);
|
|
85
|
+
}
|
|
86
|
+
a.samples++;
|
|
87
|
+
for (const f of obj.present || []) {
|
|
88
|
+
a.presentCounts.set(f, (a.presentCounts.get(f) || 0) + 1);
|
|
89
|
+
}
|
|
90
|
+
for (const p of obj.deepPresent || []) {
|
|
91
|
+
a.deepCounts.set(p, (a.deepCounts.get(p) || 0) + 1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch { /* ignore malformed */ }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const results = [];
|
|
98
|
+
for (const [, agg] of aggIndex.entries()) {
|
|
99
|
+
// Only attempt method/status exact matches in spec; skip wildcard ANY groups
|
|
100
|
+
if (agg.method === 'ANY' || agg.status === 'ANY')
|
|
101
|
+
continue;
|
|
102
|
+
const sKey = specKey(agg.route, agg.method, agg.status);
|
|
103
|
+
const spec = specIndex.get(sKey);
|
|
104
|
+
if (!spec)
|
|
105
|
+
continue; // skip recordings not in spec (maybe new route)
|
|
106
|
+
const thresholdCount = Math.ceil((thresholdPct / 100) * agg.samples);
|
|
107
|
+
const candidates = [];
|
|
108
|
+
// Top-level optional candidates
|
|
109
|
+
for (const [field, count] of agg.presentCounts.entries()) {
|
|
110
|
+
if (spec.required.has(field))
|
|
111
|
+
continue; // already required
|
|
112
|
+
if (!spec.optional.has(field))
|
|
113
|
+
continue; // ignore undeclared extras
|
|
114
|
+
if (count >= thresholdCount) {
|
|
115
|
+
candidates.push({
|
|
116
|
+
field,
|
|
117
|
+
pct: (count / agg.samples) * 100,
|
|
118
|
+
count,
|
|
119
|
+
level: 'top',
|
|
120
|
+
reason: `Observed in ${count}/${agg.samples} samples (${((count / agg.samples) * 100).toFixed(1)}%)`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Deep candidates: we only look at pointers where the first segment is an optional top-level field
|
|
125
|
+
for (const [ptr, count] of agg.deepCounts.entries()) {
|
|
126
|
+
if (!ptr.startsWith('/'))
|
|
127
|
+
continue;
|
|
128
|
+
const topSeg = ptr.split('/')[1];
|
|
129
|
+
if (!spec.optional.has(topSeg))
|
|
130
|
+
continue; // only consider deep paths under optional parents
|
|
131
|
+
if (spec.required.has(topSeg))
|
|
132
|
+
continue; // parent already required
|
|
133
|
+
if (count >= thresholdCount) {
|
|
134
|
+
candidates.push({
|
|
135
|
+
field: ptr,
|
|
136
|
+
pct: (count / agg.samples) * 100,
|
|
137
|
+
count,
|
|
138
|
+
level: 'deep',
|
|
139
|
+
reason: `Deep presence under optional parent ${topSeg} in ${count}/${agg.samples} samples (${((count / agg.samples) * 100).toFixed(1)}%)`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
candidates.sort((a, b) => b.pct - a.pct || a.field.localeCompare(b.field));
|
|
144
|
+
results.push({
|
|
145
|
+
route: agg.route,
|
|
146
|
+
method: agg.method,
|
|
147
|
+
status: agg.status,
|
|
148
|
+
samples: agg.samples,
|
|
149
|
+
specRequired: Array.from(spec.required).sort(),
|
|
150
|
+
specOptional: Array.from(spec.optional).sort(),
|
|
151
|
+
candidates,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
results.sort((a, b) => a.route.localeCompare(b.route) ||
|
|
155
|
+
a.method.localeCompare(b.method) ||
|
|
156
|
+
a.status.localeCompare(b.status));
|
|
157
|
+
const payload = {
|
|
158
|
+
generatedAt: new Date().toISOString(),
|
|
159
|
+
thresholdPct,
|
|
160
|
+
totalGroups: results.length,
|
|
161
|
+
items: results,
|
|
162
|
+
};
|
|
163
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
164
|
+
// Human summary to stderr
|
|
165
|
+
console.error('\n--- Promotion diff (spec optional -> always observed) ---');
|
|
166
|
+
for (const r of results) {
|
|
167
|
+
const top = r.candidates
|
|
168
|
+
.filter((c) => c.level === 'top')
|
|
169
|
+
.slice(0, 6)
|
|
170
|
+
.map((c) => `${c.field}:${c.pct.toFixed(0)}%`)
|
|
171
|
+
.join(', ');
|
|
172
|
+
if (top) {
|
|
173
|
+
console.error(`${r.method} ${r.status} ${r.route} samples=${r.samples} promote: ${top}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=diff-recordings-vs-spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-recordings-vs-spec.js","sourceRoot":"","sources":["../../src/assertion/diff-recordings-vs-spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/assertion/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/assertion/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/assertion/recorder.ts"],"names":[],"mappings":"AASA,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAmB/C,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAC/B,QAAQ,EAAE,YAAY,CAAC;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,IAAI,CAsDP"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
|
|
3
|
+
* one or more contributor license agreements. See the NOTICE file distributed
|
|
4
|
+
* with this work for additional information regarding copyright ownership.
|
|
5
|
+
* Licensed under the Camunda License 1.0. You may not use this file
|
|
6
|
+
* except in compliance with the Camunda License 1.0.
|
|
7
|
+
*/
|
|
8
|
+
import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
9
|
+
const RECORD_DIR = process.env.TEST_RESPONSE_BODY_RECORD_DIR;
|
|
10
|
+
function ensureRecordDir() {
|
|
11
|
+
if (!RECORD_DIR)
|
|
12
|
+
return;
|
|
13
|
+
if (!existsSync(RECORD_DIR)) {
|
|
14
|
+
try {
|
|
15
|
+
mkdirSync(RECORD_DIR, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
/* ignore */
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function sanitizeForFile(name) {
|
|
23
|
+
return name.replace(/^\//, '').replace(/[^A-Za-z0-9._-]+/g, '_');
|
|
24
|
+
}
|
|
25
|
+
export function recordBody(opts) {
|
|
26
|
+
if (!RECORD_DIR)
|
|
27
|
+
return;
|
|
28
|
+
try {
|
|
29
|
+
ensureRecordDir();
|
|
30
|
+
const { routeCtx, body, testTitle } = opts;
|
|
31
|
+
const fileBase = `${(routeCtx.method || 'ANY').toUpperCase()}_${routeCtx.status || 'ANY'}_${sanitizeForFile(routeCtx.route)}`;
|
|
32
|
+
const file = `${RECORD_DIR}/${fileBase}.jsonl`;
|
|
33
|
+
const present = [];
|
|
34
|
+
const deepSet = new Set();
|
|
35
|
+
const isObj = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
36
|
+
const escape = (seg) => seg.replace(/~/g, '~0').replace(/\//g, '~1');
|
|
37
|
+
const addPath = (p) => {
|
|
38
|
+
if (p)
|
|
39
|
+
deepSet.add(p);
|
|
40
|
+
};
|
|
41
|
+
const walk = (val, base) => {
|
|
42
|
+
if (isObj(val)) {
|
|
43
|
+
for (const key of Object.keys(val)) {
|
|
44
|
+
const ptr = `${base}/${escape(key)}`;
|
|
45
|
+
addPath(ptr);
|
|
46
|
+
walk(val[key], ptr);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (Array.isArray(val)) {
|
|
50
|
+
for (let i = 0; i < val.length; i++) {
|
|
51
|
+
const el = val[i];
|
|
52
|
+
if (isObj(el) || Array.isArray(el)) {
|
|
53
|
+
walk(el, `${base}/*`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
if (isObj(body)) {
|
|
59
|
+
for (const k of Object.keys(body))
|
|
60
|
+
present.push(k);
|
|
61
|
+
walk(body, '');
|
|
62
|
+
}
|
|
63
|
+
const deepPresent = Array.from(deepSet).sort();
|
|
64
|
+
const line = JSON.stringify({
|
|
65
|
+
ts: new Date().toISOString(),
|
|
66
|
+
route: routeCtx.route,
|
|
67
|
+
method: routeCtx.method,
|
|
68
|
+
status: routeCtx.status,
|
|
69
|
+
test: testTitle,
|
|
70
|
+
required: routeCtx.requiredFieldNames,
|
|
71
|
+
present,
|
|
72
|
+
deepPresent,
|
|
73
|
+
body,
|
|
74
|
+
});
|
|
75
|
+
appendFileSync(file, line + '\n');
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
/* ignore */
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.js","sourceRoot":"","sources":["../../src/assertion/recorder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarize-recordings.d.ts","sourceRoot":"","sources":["../../src/assertion/summarize-recordings.ts"],"names":[],"mappings":""}
|