@powerduck/openapi-cli 0.2.0 → 0.2.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 +204 -560
- package/dist/chunk-6VN2PVGQ.js +1 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-XORDZRWZ.js +0 -1
package/README.md
CHANGED
|
@@ -1,266 +1,144 @@
|
|
|
1
1
|
# @powerduck/openapi-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CI-ready command-line tool for batch-testing OpenAPI 3.2 documents across six protocols. Runs every operation in your spec, runs assertions against each response, and produces JSON, CLI, and HTML reports.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@powerduck/openapi-cli)
|
|
6
|
+
[](https://github.com/PowerDuckie/openapi-cli/blob/main/LICENSE)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Features
|
|
8
|
+
## Links
|
|
10
9
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
- **Flexible filtering** — run a subset by method, path, tag, or operationId
|
|
17
|
-
- **Minimal runtime dependencies** — `@powerduck/request`, `commander`, and `@apidevtools/json-schema-ref-parser` for `$ref` resolution
|
|
10
|
+
- [Official Website](https://www.powerduck.com/opensource/openapi-cli.html)
|
|
11
|
+
- [Documentation](https://www.powerduck.com/docs/openapi-cli/introduction)
|
|
12
|
+
- [Live Demo](https://www.powerduck.com/demo/openapi-cli.html)
|
|
13
|
+
- [GitHub](https://github.com/PowerDuckie/openapi-cli)
|
|
14
|
+
- [npm](https://www.npmjs.com/package/@powerduck/openapi-cli)
|
|
18
15
|
|
|
19
16
|
---
|
|
20
17
|
|
|
21
|
-
##
|
|
18
|
+
## Quick Start
|
|
22
19
|
|
|
23
|
-
###
|
|
20
|
+
### Install
|
|
24
21
|
|
|
25
22
|
```bash
|
|
26
23
|
npm install -g @powerduck/openapi-cli
|
|
27
24
|
```
|
|
28
25
|
|
|
29
|
-
###
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
npm install --save-dev @powerduck/openapi-cli
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### npx (one-off)
|
|
26
|
+
### Run tests from the CLI
|
|
36
27
|
|
|
37
28
|
```bash
|
|
38
|
-
|
|
29
|
+
openapi-cli --spec openapi.json --server https://api.example.com
|
|
39
30
|
```
|
|
40
31
|
|
|
41
|
-
|
|
32
|
+
### Programmatic usage
|
|
42
33
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
## Quick Start
|
|
34
|
+
```typescript
|
|
35
|
+
import { resolveConfig, runTests, generateJsonReport, generateHtmlReport } from "@powerduck/openapi-cli";
|
|
46
36
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
37
|
+
const config = resolveConfig({
|
|
38
|
+
spec: "./openapi.json",
|
|
39
|
+
format: "json,html",
|
|
40
|
+
output: "./reports",
|
|
41
|
+
concurrency: 5,
|
|
42
|
+
});
|
|
50
43
|
|
|
51
|
-
|
|
52
|
-
openapi-cli --spec openapi.json \
|
|
53
|
-
--server https://api.staging.example.com \
|
|
54
|
-
--output ./reports
|
|
44
|
+
const report = await runTests(config);
|
|
55
45
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
--method get \
|
|
59
|
-
--concurrency 10
|
|
46
|
+
const jsonPath = generateJsonReport(report, config.outputDir);
|
|
47
|
+
const htmlPath = generateHtmlReport(report, config.outputDir);
|
|
60
48
|
|
|
61
|
-
|
|
62
|
-
openapi-cli --config openapi-cli.config.json
|
|
49
|
+
console.log("Passed:", report.passed, "/", report.total);
|
|
63
50
|
```
|
|
64
51
|
|
|
65
|
-
|
|
52
|
+
### GitHub Actions CI
|
|
66
53
|
|
|
67
|
-
|
|
54
|
+
```yaml
|
|
55
|
+
name: API Tests
|
|
56
|
+
on: [push, pull_request]
|
|
68
57
|
|
|
58
|
+
jobs:
|
|
59
|
+
test:
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@v4
|
|
63
|
+
- uses: actions/setup-node@v4
|
|
64
|
+
with:
|
|
65
|
+
node-version: 20
|
|
66
|
+
- run: npm ci
|
|
67
|
+
- name: Run API tests
|
|
68
|
+
run: npx @powerduck/openapi-cli --spec openapi.json --server ${{ secrets.API_URL }} --output ./reports
|
|
69
|
+
env:
|
|
70
|
+
BEARER_TOKEN: ${{ secrets.BEARER_TOKEN }}
|
|
71
|
+
- name: Upload reports
|
|
72
|
+
if: always()
|
|
73
|
+
uses: actions/upload-artifact@v4
|
|
74
|
+
with:
|
|
75
|
+
name: openapi-cli-reports
|
|
76
|
+
path: ./reports
|
|
69
77
|
```
|
|
70
|
-
openapi-cli [options]
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Core
|
|
74
|
-
|
|
75
|
-
| Option | Short | Default | Description |
|
|
76
|
-
|--------|-------|---------|-------------|
|
|
77
|
-
| `--spec <path-or-url>` | `-s` | **required** | Path or `http(s)://` URL to an OpenAPI 3.2 JSON document |
|
|
78
|
-
| `--server <url>` | `-S` | *(from spec)* | Override the server base URL |
|
|
79
|
-
| `--output <dir>` | `-o` | `./openapi-cli-report` | Directory for generated reports |
|
|
80
|
-
| `--format <formats>` | `-f` | `json,cli,html` | Comma-separated report formats: `json`, `cli`, `html` |
|
|
81
|
-
| `--config <path>` | `-c` | — | Path to a JSON config file (CLI args take precedence) |
|
|
82
|
-
|
|
83
|
-
### Execution
|
|
84
|
-
|
|
85
|
-
| Option | Short | Default | Description |
|
|
86
|
-
|--------|-------|---------|-------------|
|
|
87
|
-
| `--concurrency <n>` | `-n` | `5` | Maximum number of concurrent requests |
|
|
88
|
-
| `--timeout <ms>` | `-t` | `30000` | Per-request timeout in milliseconds (also applies to remote spec fetch) |
|
|
89
|
-
| `--no-fail-on-error` | — | off | Exit with code 0 even when tests fail |
|
|
90
|
-
|
|
91
|
-
### Filtering
|
|
92
|
-
|
|
93
|
-
| Option | Default | Description |
|
|
94
|
-
|--------|---------|-------------|
|
|
95
|
-
| `--method <methods>` | — | Comma-separated HTTP methods to include (e.g. `get,post`) |
|
|
96
|
-
| `--path <patterns>` | — | Comma-separated regex patterns matched against the path |
|
|
97
|
-
| `--tag <tags>` | — | Comma-separated OpenAPI tags to include |
|
|
98
|
-
| `--operation-id <ids>` | — | Comma-separated operationIds to include |
|
|
99
|
-
|
|
100
|
-
### Network & TLS
|
|
101
|
-
|
|
102
|
-
| Option | Short | Default | Description |
|
|
103
|
-
|--------|-------|---------|-------------|
|
|
104
|
-
| `--proxy <url>` | `-p` | — | HTTP(S) proxy URL (e.g. `http://proxy.corp:8080`) |
|
|
105
|
-
| `--ca <path>` | — | — | Path to a CA certificate bundle (PEM) |
|
|
106
|
-
| `--cert <path>` | — | — | Path to a client certificate (PEM) for mTLS |
|
|
107
|
-
| `--key <path>` | — | — | Path to a client private key (PEM) for mTLS |
|
|
108
|
-
| `--insecure` | `-k` | off | Skip TLS certificate verification |
|
|
109
|
-
|
|
110
|
-
### Authentication & Headers
|
|
111
|
-
|
|
112
|
-
| Option | Short | Default | Description |
|
|
113
|
-
|--------|-------|---------|-------------|
|
|
114
|
-
| `--header <headers...>` | `-H` | — | Extra request headers, `Key: Value` format. Repeatable. |
|
|
115
|
-
| `--bearer <token>` | — | — | Bearer token for the `Authorization` header |
|
|
116
|
-
| `--variable <vars...>` | `-v` | — | Postman variables, `KEY=VALUE` format. Repeatable. |
|
|
117
|
-
| `--env <path>` | — | — | Path to a `.env` file (does not override existing env vars) |
|
|
118
|
-
|
|
119
|
-
### gRPC
|
|
120
|
-
|
|
121
|
-
| Option | Default | Description |
|
|
122
|
-
|--------|---------|-------------|
|
|
123
|
-
| `--grpc-no-reflection` | off | Disable gRPC server reflection; use proto files instead |
|
|
124
|
-
| `--grpc-proto <paths...>` | — | Paths to `.proto` files or directories containing them |
|
|
125
|
-
|
|
126
|
-
### MCP
|
|
127
|
-
|
|
128
|
-
| Option | Default | Description |
|
|
129
|
-
|--------|---------|-------------|
|
|
130
|
-
| `--mcp-transport <transport>` | `streamable-http` | MCP transport: `streamable-http` or `stdio` |
|
|
131
|
-
| `--mcp-command <command>` | — | MCP stdio command (e.g. `npx`) |
|
|
132
|
-
| `--mcp-args <args>` | — | MCP stdio arguments, space- or comma-separated |
|
|
133
|
-
| `--mcp-cwd <dir>` | — | Working directory for the MCP stdio subprocess |
|
|
134
|
-
|
|
135
|
-
### Informational
|
|
136
|
-
|
|
137
|
-
| Option | Short | Description |
|
|
138
|
-
|--------|-------|-------------|
|
|
139
|
-
| `--help` | `-h` | Show help |
|
|
140
|
-
| `--version` | `-V` | Show version |
|
|
141
78
|
|
|
142
79
|
---
|
|
143
80
|
|
|
144
|
-
##
|
|
145
|
-
|
|
146
|
-
A JSON config file can be used instead of (or in addition to) CLI arguments. CLI arguments always take precedence.
|
|
147
|
-
|
|
148
|
-
```json
|
|
149
|
-
{
|
|
150
|
-
"specPath": "./openapi.json",
|
|
151
|
-
"serverUrl": "https://api.staging.example.com",
|
|
152
|
-
"outputDir": "./reports",
|
|
153
|
-
"formats": ["json", "html"],
|
|
154
|
-
"concurrency": 10,
|
|
155
|
-
"timeout": 15000,
|
|
156
|
-
"failOnError": true,
|
|
157
|
-
"proxy": "http://proxy.corp:8080",
|
|
158
|
-
"tls": {
|
|
159
|
-
"strictSSL": true,
|
|
160
|
-
"caCert": "./ca-bundle.pem",
|
|
161
|
-
"clientCert": "./client.crt",
|
|
162
|
-
"clientKey": "./client.key"
|
|
163
|
-
},
|
|
164
|
-
"headers": {
|
|
165
|
-
"X-API-Key": "${API_KEY}",
|
|
166
|
-
"X-Environment": "staging"
|
|
167
|
-
},
|
|
168
|
-
"auth": {
|
|
169
|
-
"type": "bearer",
|
|
170
|
-
"token": "${BEARER_TOKEN}"
|
|
171
|
-
},
|
|
172
|
-
"variables": {
|
|
173
|
-
"host": "api.staging.example.com"
|
|
174
|
-
},
|
|
175
|
-
"filter": {
|
|
176
|
-
"methods": ["get", "post"],
|
|
177
|
-
"tags": ["v2", "public"],
|
|
178
|
-
"paths": ["^/api/v2"],
|
|
179
|
-
"operationIds": ["getUser", "createUser"]
|
|
180
|
-
},
|
|
181
|
-
"grpcReflection": true,
|
|
182
|
-
"grpcProtoPaths": ["./proto"],
|
|
183
|
-
"mcpTransport": "streamable-http",
|
|
184
|
-
"mcpCommand": "npx",
|
|
185
|
-
"mcpArgs": ["-y", "@modelcontextprotocol/server-everything"],
|
|
186
|
-
"mcpCwd": "./mcp-servers"
|
|
187
|
-
}
|
|
188
|
-
```
|
|
81
|
+
## Features
|
|
189
82
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
| `tls.strictSSL` | `boolean` | Verify TLS certificates (default `true`) |
|
|
203
|
-
| `tls.caCert` | `string` | CA certificate bundle path |
|
|
204
|
-
| `tls.clientCert` | `string` | Client certificate path (mTLS) |
|
|
205
|
-
| `tls.clientKey` | `string` | Client private key path (mTLS) |
|
|
206
|
-
| `headers` | `Record<string,string>` | Extra request headers |
|
|
207
|
-
| `auth.type` | `string` | Auth type (currently `bearer`) |
|
|
208
|
-
| `auth.token` | `string` | Bearer token |
|
|
209
|
-
| `variables` | `Record<string,string>` | Postman variables |
|
|
210
|
-
| `filter.methods` | `string[]` | HTTP method filter |
|
|
211
|
-
| `filter.paths` | `string[]` | Path regex filter |
|
|
212
|
-
| `filter.tags` | `string[]` | OpenAPI tag filter |
|
|
213
|
-
| `filter.operationIds` | `string[]` | operationId filter |
|
|
214
|
-
| `grpcReflection` | `boolean` | Use gRPC server reflection |
|
|
215
|
-
| `grpcProtoPaths` | `string[]` | Proto file/directory paths |
|
|
216
|
-
| `mcpTransport` | `string` | `streamable-http` or `stdio` |
|
|
217
|
-
| `mcpCommand` | `string` | MCP stdio command |
|
|
218
|
-
| `mcpArgs` | `string[]` | MCP stdio arguments |
|
|
219
|
-
| `mcpCwd` | `string` | MCP stdio working directory |
|
|
83
|
+
- **CI-ready** — non-zero exit code on test failure, drops straight into GitHub Actions, GitLab CI, or any pipeline
|
|
84
|
+
- **Six protocols** — HTTP, SSE, WebSocket, GraphQL, gRPC, and MCP operations in one spec, one runner
|
|
85
|
+
- **Two assertion styles** — declarative `x-tests` arrays for simple checks, and full Postman `pm.test()` / `pm.expect()` scripts for complex ones
|
|
86
|
+
- **Three report formats** — machine-readable JSON, colored human-readable CLI output, and self-contained interactive HTML
|
|
87
|
+
- **Local or remote specs** — load from a file path or an `http(s)://` URL with redirect following and timeout enforcement
|
|
88
|
+
- **Flexible filtering** — run a subset of operations by method, path, tag, or operationId
|
|
89
|
+
- **Bounded concurrency** — configurable max concurrent requests with per-request timeout
|
|
90
|
+
- **Authentication** — Bearer token, Basic auth, API key (header/query), with environment variable expansion
|
|
91
|
+
- **Proxy support** — HTTP(S) proxy URL for corporate networks
|
|
92
|
+
- **TLS configuration** — CA cert, client cert/key, and insecure mode for self-signed certs
|
|
93
|
+
- **Built on `@powerduck/openapi-request`** — full OpenAPI parameter serialization (path/query/header/cookie styles and explode rules)
|
|
94
|
+
- **Dual ESM/CJS builds** — works with `import` and `require`
|
|
220
95
|
|
|
221
96
|
---
|
|
222
97
|
|
|
223
|
-
##
|
|
224
|
-
|
|
225
|
-
### Local file
|
|
98
|
+
## CLI Usage
|
|
226
99
|
|
|
227
|
-
```bash
|
|
228
|
-
openapi-cli --spec ./api/openapi.json
|
|
229
100
|
```
|
|
101
|
+
openapi-cli [options]
|
|
230
102
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
openapi-cli
|
|
103
|
+
Options:
|
|
104
|
+
--spec <path> Path or URL to OpenAPI 3.2 spec (required)
|
|
105
|
+
--server <url> Override server base URL from spec
|
|
106
|
+
--output <dir> Output directory for reports (default: ./openapi-cli-report)
|
|
107
|
+
--format <formats> Comma-separated report formats: json,cli,html (default: json,cli,html)
|
|
108
|
+
--concurrency <n> Max concurrent requests (default: 5)
|
|
109
|
+
--timeout <ms> Per-request timeout in ms (default: 30000)
|
|
110
|
+
--filter-method <m> Filter by HTTP method (comma-separated)
|
|
111
|
+
--filter-path <regex> Filter by path regex
|
|
112
|
+
--filter-tag <tag> Filter by tag (comma-separated)
|
|
113
|
+
--filter-operation <id> Filter by operationId (comma-separated)
|
|
114
|
+
--bearer <token> Bearer token for Authorization header
|
|
115
|
+
--basic <user:pass> Basic auth credentials
|
|
116
|
+
--apikey <key=value> API key (name=value), use --apikey-in to set location
|
|
117
|
+
--apikey-in <location> API key location: header or query (default: header)
|
|
118
|
+
--header "Name: value" Extra header (repeatable)
|
|
119
|
+
--env-file <path> Path to .env file
|
|
120
|
+
--proxy <url> HTTP(S) proxy URL
|
|
121
|
+
--insecure Disable TLS certificate verification
|
|
122
|
+
--ca-cert <path> Path to CA certificate bundle (PEM)
|
|
123
|
+
--client-cert <path> Path to client certificate (PEM) for mTLS
|
|
124
|
+
--client-key <path> Path to client private key (PEM) for mTLS
|
|
125
|
+
--no-fail-on-error Exit 0 even if tests fail
|
|
126
|
+
--config <path> Path to JSON config file
|
|
127
|
+
--grpc-reflection Enable gRPC server reflection (default: true)
|
|
128
|
+
--grpc-proto <path> Path to .proto file or directory (repeatable)
|
|
129
|
+
--mcp-transport <mode> MCP transport: streamable-http or stdio (default: streamable-http)
|
|
130
|
+
--mcp-command <cmd> MCP stdio command to spawn
|
|
131
|
+
--mcp-args <args> MCP stdio command arguments (comma-separated)
|
|
132
|
+
--mcp-cwd <dir> MCP stdio working directory
|
|
133
|
+
-h, --help Show help
|
|
134
|
+
-v, --version Show version
|
|
235
135
|
```
|
|
236
136
|
|
|
237
|
-
Remote specs are fetched with:
|
|
238
|
-
- A configurable timeout (`--timeout`, default 30s)
|
|
239
|
-
- Up to 5 automatic redirects
|
|
240
|
-
- A descriptive error on HTTP failure (status code + message)
|
|
241
|
-
- JSON validation after download
|
|
242
|
-
|
|
243
|
-
The `User-Agent` header is set to `@powerduck/openapi-cli`.
|
|
244
|
-
|
|
245
137
|
---
|
|
246
138
|
|
|
247
|
-
##
|
|
139
|
+
## Declarative Assertions (x-tests)
|
|
248
140
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
- **Supported**: internal refs to any location in the same document (`#/...`)
|
|
252
|
-
- **Not resolved**: external file or URL refs (`./other.json#/...`, `https://...`) — these are left as-is
|
|
253
|
-
- **Circular refs**: safely ignored (no infinite recursion)
|
|
254
|
-
|
|
255
|
-
Resolution is powered by [`@apidevtools/json-schema-ref-parser`](https://www.npmjs.com/package/@apidevtools/json-schema-ref-parser).
|
|
256
|
-
|
|
257
|
-
---
|
|
258
|
-
|
|
259
|
-
## Assertions
|
|
260
|
-
|
|
261
|
-
### Declarative assertions (`x-tests`)
|
|
262
|
-
|
|
263
|
-
Add an `x-tests` array to any operation. Each assertion is an object with a `name` and an `assert` type.
|
|
141
|
+
Add an `x-tests` array to any operation. Each entry needs a `name` and an `assert` type:
|
|
264
142
|
|
|
265
143
|
```json
|
|
266
144
|
{
|
|
@@ -275,404 +153,161 @@ Add an `x-tests` array to any operation. Each assertion is an object with a `nam
|
|
|
275
153
|
{ "name": "user is active", "assert": "jsonPath", "path": "$.status", "equals": "active" },
|
|
276
154
|
{ "name": "response under 2s", "assert": "responseTime", "max": 2000 },
|
|
277
155
|
{ "name": "body contains email", "assert": "bodyContains", "contains": "@" }
|
|
278
|
-
]
|
|
156
|
+
],
|
|
157
|
+
"responses": { "200": { "description": "ok" } }
|
|
279
158
|
}
|
|
280
159
|
}
|
|
281
160
|
}
|
|
282
161
|
}
|
|
283
162
|
```
|
|
284
163
|
|
|
285
|
-
|
|
164
|
+
### Assertion Types
|
|
286
165
|
|
|
287
|
-
|
|
|
288
|
-
|
|
289
|
-
| `status` | `value`
|
|
290
|
-
| `header` | `key
|
|
291
|
-
| `bodyContains` | `contains`
|
|
292
|
-
| `bodyEquals` | `body`
|
|
293
|
-
| `jsonPath` | `path
|
|
294
|
-
| `responseTime` | `max`
|
|
166
|
+
| `assert` | Required fields | Purpose |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| `status` | `value` | HTTP status equals `value` |
|
|
169
|
+
| `header` | `key`; optional `contains` | Header exists; optionally includes a substring |
|
|
170
|
+
| `bodyContains` | `contains` | Response body includes the substring |
|
|
171
|
+
| `bodyEquals` | `body` | Response body exactly equals `body` |
|
|
172
|
+
| `jsonPath` | `path`; optional `equals` / `exists` | Resolve a JSONPath and compare or check existence |
|
|
173
|
+
| `responseTime` | `max` | Duration must be `<= max` ms |
|
|
295
174
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
Supports dot notation and array indices:
|
|
299
|
-
|
|
300
|
-
- `$.data.id` → `body.data.id`
|
|
301
|
-
- `$.users[0].name` → `body.users[0].name`
|
|
302
|
-
- `data.items[2].value` → leading `$` is optional
|
|
175
|
+
---
|
|
303
176
|
|
|
304
|
-
|
|
177
|
+
## Postman Scripts
|
|
305
178
|
|
|
306
|
-
|
|
179
|
+
Embed a full Postman test script via `x-postman-scripts`:
|
|
307
180
|
|
|
308
181
|
```json
|
|
309
182
|
{
|
|
310
183
|
"paths": {
|
|
311
184
|
"/users": {
|
|
312
185
|
"get": {
|
|
186
|
+
"operationId": "listUsers",
|
|
313
187
|
"x-postman-scripts": {
|
|
314
|
-
"test": "pm.test('status is 200', () => pm.response.to.have.status(200)); pm.test('users is an array', () => pm.expect(pm.response.json().users).to.be.an('array'));
|
|
315
|
-
}
|
|
188
|
+
"test": "pm.test('status is 200', () => pm.response.to.have.status(200)); pm.test('users is an array', () => pm.expect(pm.response.json().users).to.be.an('array'));"
|
|
189
|
+
},
|
|
190
|
+
"responses": { "200": { "description": "ok" } }
|
|
316
191
|
}
|
|
317
192
|
}
|
|
318
193
|
}
|
|
319
194
|
}
|
|
320
195
|
```
|
|
321
196
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
### Implicit assertions (no user assertions defined)
|
|
325
|
-
|
|
326
|
-
When an operation defines **no** `x-tests` and **no** `x-postman-scripts`, the CLI adds an implicit success check using each protocol's conventional success criterion. This prevents 404s, 500s, and gRPC errors from being silently marked as passed.
|
|
327
|
-
|
|
328
|
-
| Protocol | Implicit success criterion | Failure example |
|
|
329
|
-
|----------|---------------------------|-----------------|
|
|
330
|
-
| HTTP | Status code is 2xx (200–299) | 404, 500, 301 |
|
|
331
|
-
| SSE | Status code is 2xx (200–299) | 404, 500 |
|
|
332
|
-
| GraphQL | Status 2xx **and** no top-level `errors` array | 200 with `{"errors": [...]}` |
|
|
333
|
-
| gRPC | Status code is `0` (OK) | code 13 (INTERNAL), code 5 (NOT_FOUND) |
|
|
334
|
-
| MCP | JSON-RPC response has `result` and no `error` | `{"error": {"code": -32601}}` |
|
|
335
|
-
| WebSocket | Connection reached `open` state with no error | connection refused, handshake failure |
|
|
336
|
-
|
|
337
|
-
If you need to test a non-success response (e.g., asserting that an endpoint returns 404), define an explicit `x-tests` assertion — implicit checks are skipped when any user assertion exists.
|
|
197
|
+
Declarative `x-tests` and `x-postman-scripts` may coexist on the same operation.
|
|
338
198
|
|
|
339
199
|
---
|
|
340
200
|
|
|
341
|
-
##
|
|
342
|
-
|
|
343
|
-
### HTTP / SSE
|
|
344
|
-
|
|
345
|
-
Standard OpenAPI operations are executed as HTTP requests. SSE is auto-detected when:
|
|
346
|
-
- The response content type is `text/event-stream`, or
|
|
347
|
-
- The operation has `x-response-stream: true`
|
|
201
|
+
## Programmatic API
|
|
348
202
|
|
|
349
|
-
|
|
203
|
+
### `resolveConfig(args: CliArgs): CliConfig`
|
|
350
204
|
|
|
351
|
-
|
|
205
|
+
Resolves CLI arguments into a normalized config. `CliArgs` uses string fields (`format`, `output`); `CliConfig` uses parsed fields (`formats` array, `outputDir` string).
|
|
352
206
|
|
|
353
|
-
```
|
|
354
|
-
{
|
|
355
|
-
"
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
"endpoint": "https://api.example.com/graphql",
|
|
361
|
-
"query": "query GetUser($id: ID!) { user(id: $id) { name email } }",
|
|
362
|
-
"operationName": "GetUser",
|
|
363
|
-
"variablesSchema": {
|
|
364
|
-
"type": "object",
|
|
365
|
-
"properties": { "id": { "type": "string" } },
|
|
366
|
-
"required": ["id"]
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
207
|
+
```typescript
|
|
208
|
+
const config = resolveConfig({
|
|
209
|
+
spec: "./openapi.json",
|
|
210
|
+
format: "json,html",
|
|
211
|
+
output: "./reports",
|
|
212
|
+
concurrency: 10,
|
|
213
|
+
});
|
|
373
214
|
```
|
|
374
215
|
|
|
375
|
-
###
|
|
216
|
+
### `runTests(config: CliConfig): Promise<TestReport>`
|
|
376
217
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
```json
|
|
380
|
-
{
|
|
381
|
-
"paths": {
|
|
382
|
-
"/grpc": {
|
|
383
|
-
"post": {
|
|
384
|
-
"x-protocol": "grpc",
|
|
385
|
-
"x-grpc": {
|
|
386
|
-
"address": "127.0.0.1:50051",
|
|
387
|
-
"service": "demo.echo.Echo",
|
|
388
|
-
"method": "Say",
|
|
389
|
-
"kind": "unary",
|
|
390
|
-
"reflection": true
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
```
|
|
218
|
+
Runs all operations and returns a report.
|
|
397
219
|
|
|
398
|
-
|
|
399
|
-
|-------|-------------|
|
|
400
|
-
| `address` | gRPC server `host:port` |
|
|
401
|
-
| `service` | Fully-qualified service name |
|
|
402
|
-
| `method` | Method name |
|
|
403
|
-
| `kind` | `unary`, `server_streaming`, `client_streaming`, `bidi_streaming` |
|
|
404
|
-
| `reflection` | Use server reflection (default `true`) |
|
|
405
|
-
| `protoPaths` | Paths to `.proto` files when reflection is off |
|
|
406
|
-
| `metadata` | Initial metadata (headers) |
|
|
407
|
-
| `sample` | Sample request message (JSON) |
|
|
220
|
+
### `generateJsonReport(report, outputDir): string`
|
|
408
221
|
|
|
409
|
-
|
|
222
|
+
Writes `report.json` and returns the file path.
|
|
410
223
|
|
|
411
|
-
|
|
412
|
-
{
|
|
413
|
-
"paths": {
|
|
414
|
-
"/mcp": {
|
|
415
|
-
"post": {
|
|
416
|
-
"x-protocol": "mcp",
|
|
417
|
-
"x-mcp": {
|
|
418
|
-
"endpoint": "http://127.0.0.1:4200/mcp",
|
|
419
|
-
"method": "tools/call",
|
|
420
|
-
"name": "get_weather",
|
|
421
|
-
"arguments": { "city": "San Francisco" }
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
```
|
|
224
|
+
### `generateHtmlReport(report, outputDir): string`
|
|
428
225
|
|
|
429
|
-
|
|
226
|
+
Writes a self-contained `report.html` and returns the file path.
|
|
430
227
|
|
|
431
|
-
|
|
228
|
+
---
|
|
432
229
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
230
|
+
## CliConfig Fields
|
|
231
|
+
|
|
232
|
+
| Field | Type | Default | Description |
|
|
233
|
+
|---|---|---|---|
|
|
234
|
+
| `specPath` | `string` | **required** | Path or URL to OpenAPI 3.2 JSON spec |
|
|
235
|
+
| `serverUrl` | `string` | *(from spec)* | Override server base URL |
|
|
236
|
+
| `outputDir` | `string` | `"./openapi-cli-report"` | Directory for report files |
|
|
237
|
+
| `formats` | `ReportFormat[]` | `["json","cli","html"]` | Report formats to generate |
|
|
238
|
+
| `filter` | `FilterConfig` | `undefined` | Filter which operations to test |
|
|
239
|
+
| `concurrency` | `number` | `5` | Max concurrent requests |
|
|
240
|
+
| `timeout` | `number` | `30000` | Per-request timeout in ms |
|
|
241
|
+
| `proxy` | `string` | `undefined` | HTTP(S) proxy URL |
|
|
242
|
+
| `tls` | `TlsConfig` | `undefined` | TLS / certificate configuration |
|
|
243
|
+
| `headers` | `Record<string, string>` | `undefined` | Extra headers sent with every request |
|
|
244
|
+
| `auth` | `AuthConfig` | `undefined` | Authentication configuration |
|
|
245
|
+
| `variables` | `Record<string, string>` | `undefined` | Postman `{{name}}` variables to inject |
|
|
246
|
+
| `failOnError` | `boolean` | `true` | Exit with non-zero code if any test fails |
|
|
247
|
+
| `envFile` | `string` | `undefined` | Path to a `.env` file |
|
|
248
|
+
| `grpcReflection` | `boolean` | `true` | gRPC: use server reflection |
|
|
249
|
+
| `grpcProtoPaths` | `string[]` | `undefined` | gRPC: paths to `.proto` files |
|
|
250
|
+
| `mcpTransport` | `"streamable-http" \| "stdio"` | `"streamable-http"` | MCP transport |
|
|
251
|
+
| `mcpCommand` | `string` | `undefined` | MCP stdio command |
|
|
252
|
+
| `mcpArgs` | `string[]` | `undefined` | MCP stdio arguments |
|
|
253
|
+
| `mcpCwd` | `string` | `undefined` | MCP stdio working directory |
|
|
449
254
|
|
|
450
255
|
---
|
|
451
256
|
|
|
452
|
-
##
|
|
257
|
+
## Config File
|
|
453
258
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
A complete machine-readable report:
|
|
259
|
+
A JSON config file matching `Partial<CliConfig>`. Pass with `--config <path>`:
|
|
457
260
|
|
|
458
261
|
```json
|
|
459
262
|
{
|
|
460
|
-
"
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
263
|
+
"specPath": "./openapi.json",
|
|
264
|
+
"serverUrl": "https://api.staging.example.com",
|
|
265
|
+
"outputDir": "./reports",
|
|
266
|
+
"formats": ["json", "html"],
|
|
267
|
+
"concurrency": 10,
|
|
268
|
+
"timeout": 15000,
|
|
269
|
+
"failOnError": true,
|
|
270
|
+
"headers": {
|
|
271
|
+
"X-API-Key": "${API_KEY}",
|
|
272
|
+
"X-Environment": "staging"
|
|
468
273
|
},
|
|
469
|
-
"
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
"response": { "status": 200, "contentType": "application/json", "body": { "id": 1 } },
|
|
478
|
-
"assertions": [
|
|
479
|
-
{ "name": "status is 200", "passed": true }
|
|
480
|
-
],
|
|
481
|
-
"timestamp": "2024-01-01T00:00:00.000Z"
|
|
482
|
-
}
|
|
483
|
-
],
|
|
484
|
-
"config": { "specPath": "./openapi.json" },
|
|
485
|
-
"generatedAt": "2024-01-01T00:00:05.000Z",
|
|
486
|
-
"version": "0.1.0"
|
|
274
|
+
"auth": {
|
|
275
|
+
"type": "bearer",
|
|
276
|
+
"token": "${BEARER_TOKEN}"
|
|
277
|
+
},
|
|
278
|
+
"filter": {
|
|
279
|
+
"methods": ["get", "post"],
|
|
280
|
+
"tags": ["v2", "public"]
|
|
281
|
+
}
|
|
487
282
|
}
|
|
488
283
|
```
|
|
489
284
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
Colored terminal output with a progress bar, per-test status, and assertion details. Failed assertions show error messages inline.
|
|
493
|
-
|
|
494
|
-
### HTML (`report.html`)
|
|
495
|
-
|
|
496
|
-
A self-contained HTML file with:
|
|
497
|
-
- Summary strip (total, passed, failed, errors, pass rate, duration)
|
|
498
|
-
- Thin progress bar
|
|
499
|
-
- Filter buttons (All / Passed / Failed / Errors)
|
|
500
|
-
- Expandable test rows with assertion details and response body preview
|
|
501
|
-
- Automatic light/dark mode based on system preference
|
|
502
|
-
- No external dependencies — opens in any browser
|
|
285
|
+
Environment variable expansion (`${VAR_NAME}`) is applied to `serverUrl`, `proxy`, `headers` values, `auth.token`, and `variables` values.
|
|
503
286
|
|
|
504
287
|
---
|
|
505
288
|
|
|
506
|
-
##
|
|
289
|
+
## Supported Protocols
|
|
507
290
|
|
|
508
|
-
|
|
291
|
+
| Protocol | Identifier | How it is selected |
|
|
292
|
+
|---|---|---|
|
|
293
|
+
| HTTP | `http` | Default for every standard operation |
|
|
294
|
+
| Server-Sent Events | `sse` | `text/event-stream` response content type, or `x-response-stream: true` |
|
|
295
|
+
| WebSocket | `websocket` | `x-protocol: "websocket"` |
|
|
296
|
+
| GraphQL | `graphql` | `x-protocol: "graphql"` |
|
|
297
|
+
| gRPC | `grpc` | `x-protocol: "grpc"` |
|
|
298
|
+
| MCP | `mcp` | `x-protocol: "mcp"` |
|
|
509
299
|
|
|
510
|
-
|
|
511
|
-
name: API Tests
|
|
512
|
-
on: [push, pull_request]
|
|
513
|
-
|
|
514
|
-
jobs:
|
|
515
|
-
test:
|
|
516
|
-
runs-on: ubuntu-latest
|
|
517
|
-
steps:
|
|
518
|
-
- uses: actions/checkout@v4
|
|
519
|
-
- uses: actions/setup-node@v4
|
|
520
|
-
with:
|
|
521
|
-
node-version: 20
|
|
522
|
-
- run: npm ci
|
|
523
|
-
- name: Run API tests
|
|
524
|
-
run: npx @powerduck/openapi-cli --spec openapi.json --server ${{ secrets.API_URL }} --output ./reports
|
|
525
|
-
env:
|
|
526
|
-
API_KEY: ${{ secrets.API_KEY }}
|
|
527
|
-
- name: Upload reports
|
|
528
|
-
if: always()
|
|
529
|
-
uses: actions/upload-artifact@v4
|
|
530
|
-
with:
|
|
531
|
-
name: openapi-cli-reports
|
|
532
|
-
path: ./reports
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
### GitLab CI
|
|
536
|
-
|
|
537
|
-
```yaml
|
|
538
|
-
api_test:
|
|
539
|
-
stage: test
|
|
540
|
-
image: node:20
|
|
541
|
-
script:
|
|
542
|
-
- npm ci
|
|
543
|
-
- npx @powerduck/openapi-cli --spec openapi.json --output ./reports
|
|
544
|
-
artifacts:
|
|
545
|
-
when: always
|
|
546
|
-
paths:
|
|
547
|
-
- ./reports/
|
|
548
|
-
expire_in: 30 days
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
### Exit codes
|
|
552
|
-
|
|
553
|
-
| Code | Meaning |
|
|
554
|
-
|------|---------|
|
|
555
|
-
| `0` | All tests passed (or `--no-fail-on-error` is set) |
|
|
556
|
-
| `1` | One or more tests failed |
|
|
557
|
-
| `2` | Configuration error, spec load failure, or runtime error |
|
|
300
|
+
Protocol can also be forced with the `x-protocol` extension on either the operation or the path item.
|
|
558
301
|
|
|
559
302
|
---
|
|
560
303
|
|
|
561
|
-
##
|
|
304
|
+
## Reports
|
|
562
305
|
|
|
563
|
-
|
|
564
|
-
import {
|
|
565
|
-
runTests,
|
|
566
|
-
resolveConfig,
|
|
567
|
-
generateJsonReport,
|
|
568
|
-
printCliReport,
|
|
569
|
-
generateHtmlReport,
|
|
570
|
-
loadSpec,
|
|
571
|
-
isRemoteSpec,
|
|
572
|
-
} from "@powerduck/openapi-cli";
|
|
573
|
-
|
|
574
|
-
// Build a config from args
|
|
575
|
-
const config = resolveConfig({
|
|
576
|
-
spec: "./openapi.json",
|
|
577
|
-
server: "https://api.staging.example.com",
|
|
578
|
-
concurrency: 10,
|
|
579
|
-
});
|
|
306
|
+
After a run, the tool writes (by default into `./openapi-cli-report`):
|
|
580
307
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
// Generate reports
|
|
585
|
-
printCliReport(report);
|
|
586
|
-
generateJsonReport(report, "./reports");
|
|
587
|
-
generateHtmlReport(report, "./reports");
|
|
588
|
-
|
|
589
|
-
// Load a spec directly (local or remote)
|
|
590
|
-
const spec = await loadSpec(config);
|
|
591
|
-
|
|
592
|
-
// Check if a path is remote
|
|
593
|
-
console.log(isRemoteSpec("https://example.com/spec.json")); // true
|
|
594
|
-
|
|
595
|
-
// Exit with the right code
|
|
596
|
-
if (report.summary.failed > 0 || report.summary.errors > 0) {
|
|
597
|
-
process.exit(1);
|
|
598
|
-
}
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
---
|
|
602
|
-
|
|
603
|
-
## Environment Variables
|
|
604
|
-
|
|
605
|
-
### `.env` files
|
|
606
|
-
|
|
607
|
-
Use `--env .env` to load variables from a file into `process.env` before config resolution. Format is `KEY=VALUE` per line.
|
|
608
|
-
|
|
609
|
-
```env
|
|
610
|
-
# Lines starting with # are ignored
|
|
611
|
-
API_KEY=sk-live-abc123
|
|
612
|
-
BEARER_TOKEN="eyJhbGciOi..."
|
|
613
|
-
API_HOST=api.staging.example.com
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
Behavior:
|
|
617
|
-
- Existing `process.env` variables are **not** overwritten.
|
|
618
|
-
- Surrounding single or double quotes are stripped from values.
|
|
619
|
-
- Blank lines and comment lines (`#`) are ignored.
|
|
620
|
-
- A missing `.env` file is silently ignored (no error).
|
|
621
|
-
|
|
622
|
-
Loaded variables are available for `${VAR_NAME}` expansion in CLI config values (see below). To make a variable available as a Postman `{{variable}}` inside the OpenAPI spec, pass it with `--variable KEY=${KEY}`.
|
|
623
|
-
|
|
624
|
-
### Variable expansion
|
|
625
|
-
|
|
626
|
-
`${VAR_NAME}` syntax is expanded in these config fields:
|
|
627
|
-
- `serverUrl`
|
|
628
|
-
- `proxy`
|
|
629
|
-
- `headers` (values)
|
|
630
|
-
- `auth.token`
|
|
631
|
-
- `variables` (values)
|
|
632
|
-
|
|
633
|
-
Example:
|
|
634
|
-
|
|
635
|
-
```bash
|
|
636
|
-
openapi-cli --spec openapi.json \
|
|
637
|
-
--server 'https://${API_HOST}/v1' \
|
|
638
|
-
--header 'Authorization: Bearer ${BEARER_TOKEN}'
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
---
|
|
642
|
-
|
|
643
|
-
## Troubleshooting
|
|
644
|
-
|
|
645
|
-
### "OpenAPI spec not found"
|
|
646
|
-
|
|
647
|
-
Verify the path exists. For remote specs, check the URL is reachable and returns HTTP 200.
|
|
648
|
-
|
|
649
|
-
### "Spec is not valid JSON"
|
|
650
|
-
|
|
651
|
-
The spec must be a JSON object with a `paths` property. YAML specs are not supported — convert to JSON first.
|
|
652
|
-
|
|
653
|
-
### "Failed to fetch spec: HTTP 401"
|
|
654
|
-
|
|
655
|
-
The remote spec URL requires authentication. Download it locally first or use a URL that is publicly accessible.
|
|
656
|
-
|
|
657
|
-
### gRPC "server reflection unavailable"
|
|
658
|
-
|
|
659
|
-
Either enable reflection on the server, or use `--grpc-no-reflection` with `--grpc-proto` pointing to your `.proto` files.
|
|
660
|
-
|
|
661
|
-
### MCP stdio "command not found"
|
|
662
|
-
|
|
663
|
-
Ensure `--mcp-command` is on your `PATH`, or use an absolute path. For `npx`-based servers, make sure the package is installed or use `npx -y <package>`.
|
|
664
|
-
|
|
665
|
-
### Tests are slow
|
|
666
|
-
|
|
667
|
-
Reduce `--concurrency` if the target server rate-limits. Increase `--timeout` for slow endpoints. Use `--method` or `--tag` to test a subset.
|
|
668
|
-
|
|
669
|
-
### Tests fail with "(implicit)" in the assertion name
|
|
670
|
-
|
|
671
|
-
This means the operation has no user-defined assertions and the implicit protocol check failed. For HTTP this means the status was not 2xx; for gRPC the code was not 0; for MCP the response contained a JSON-RPC error. Add an explicit `x-tests` assertion if the non-success response is expected (e.g. testing a 404 endpoint).
|
|
672
|
-
|
|
673
|
-
### All tests show "error" status
|
|
674
|
-
|
|
675
|
-
Check the `error` field in `report.json` for details. Common causes: wrong server URL, network connectivity, missing auth headers, or TLS certificate issues (try `--insecure` for testing).
|
|
308
|
+
- `report.json` — the complete `TestReport` object as pretty-printed JSON
|
|
309
|
+
- `report.html` — a self-contained, dependency-free HTML report with a summary strip, progress bar, filter buttons, and expandable test rows
|
|
310
|
+
- stdout — a colored CLI summary with a progress bar and per-test assertions
|
|
676
311
|
|
|
677
312
|
---
|
|
678
313
|
|
|
@@ -692,11 +327,20 @@ npm run build
|
|
|
692
327
|
npm test
|
|
693
328
|
|
|
694
329
|
# Watch mode
|
|
695
|
-
|
|
330
|
+
npx vitest watch
|
|
696
331
|
```
|
|
697
332
|
|
|
698
333
|
---
|
|
699
334
|
|
|
335
|
+
## Related Packages
|
|
336
|
+
|
|
337
|
+
- [`@powerduck/openapi-request`](https://www.npmjs.com/package/@powerduck/openapi-request) — Execute OpenAPI operations with full parameter serialization
|
|
338
|
+
- [`@powerduck/openapi-parser`](https://www.npmjs.com/package/@powerduck/openapi-parser) — OpenAPI 3.2 parser, validator, and upgrader
|
|
339
|
+
- [`@powerduck/openapi-codegen`](https://www.npmjs.com/package/@powerduck/openapi-codegen) — Generate runnable request examples in 21 languages
|
|
340
|
+
- [`@powerduck/x-to-openapi`](https://www.npmjs.com/package/@powerduck/x-to-openapi) — Convert curl commands and Postman Collections to OpenAPI 3.2
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
700
344
|
## License
|
|
701
345
|
|
|
702
346
|
MIT
|