x-openapi-flow 1.4.4 → 1.5.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/README.md +188 -1
- package/adapters/flow-output-adapters.js +2 -0
- package/adapters/tests/flow-test-adapter.js +254 -0
- package/bin/x-openapi-flow.js +942 -15
- package/lib/error-codes.js +289 -0
- package/lib/openapi-state-machine-adapter.js +151 -0
- package/lib/runtime-guard/core.js +176 -0
- package/lib/runtime-guard/errors.js +85 -0
- package/lib/runtime-guard/express.js +70 -0
- package/lib/runtime-guard/fastify.js +65 -0
- package/lib/runtime-guard/index.js +15 -0
- package/lib/runtime-guard/model.js +85 -0
- package/lib/runtime-guard.js +3 -0
- package/lib/state-machine-engine.js +208 -0
- package/lib/validator.js +378 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -54,6 +54,34 @@ Turn your OpenAPI spec into a single source of truth for API behavior:
|
|
|
54
54
|
|
|
55
55
|
## Quick Start
|
|
56
56
|
|
|
57
|
+
Fastest way to see value (guided scaffold):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx x-openapi-flow quickstart
|
|
61
|
+
cd x-openapi-flow-quickstart
|
|
62
|
+
npm install
|
|
63
|
+
npm start
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Optional runtime:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx x-openapi-flow quickstart --runtime fastify
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Then run:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
curl -s -X POST http://localhost:3110/orders
|
|
76
|
+
curl -i -X POST http://localhost:3110/orders/<id>/ship
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Expected: `409 INVALID_STATE_TRANSITION`.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
If you already have an OpenAPI file, use the sidecar workflow:
|
|
84
|
+
|
|
57
85
|
Initialize flow support in your project:
|
|
58
86
|
|
|
59
87
|
```bash
|
|
@@ -76,6 +104,12 @@ This will:
|
|
|
76
104
|
|
|
77
105
|
💡 Tip: run this in CI to enforce API workflow correctness
|
|
78
106
|
|
|
107
|
+
### Less Verbose DSL for Large Flows
|
|
108
|
+
|
|
109
|
+
For larger APIs, you can define flow rules by resource (with shared transitions/defaults) and reduce duplication in sidecar files.
|
|
110
|
+
|
|
111
|
+
See: [Sidecar Contract](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/reference/Sidecar-Contract.md)
|
|
112
|
+
|
|
79
113
|
<a id="mermaid-example"></a>
|
|
80
114
|
### Real Lifecycle Example
|
|
81
115
|
|
|
@@ -124,6 +158,147 @@ await payment.capture();
|
|
|
124
158
|
|
|
125
159
|
> This SDK guides developers through valid transition paths, following patterns used by market leaders to ensure safe and intuitive integrations.
|
|
126
160
|
|
|
161
|
+
## Runtime Enforcement (Express + Fastify)
|
|
162
|
+
|
|
163
|
+
CI validation is important, but production safety needs request-time enforcement.
|
|
164
|
+
|
|
165
|
+
`x-openapi-flow` now includes an official runtime guard for Node.js that can block invalid state transitions during request handling.
|
|
166
|
+
|
|
167
|
+
- Works with **Express** and **Fastify**
|
|
168
|
+
- Resolves operations by `operationId` (when available) or by method + route
|
|
169
|
+
- Reads current resource state using your own persistence callback
|
|
170
|
+
- Blocks invalid transitions with explicit `409` error payloads
|
|
171
|
+
|
|
172
|
+
Install and use directly in your API server:
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
const {
|
|
176
|
+
createExpressFlowGuard,
|
|
177
|
+
createFastifyFlowGuard,
|
|
178
|
+
} = require("x-openapi-flow/lib/runtime-guard");
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Express example:
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
const express = require("express");
|
|
185
|
+
const { createExpressFlowGuard } = require("x-openapi-flow/lib/runtime-guard");
|
|
186
|
+
const openapi = require("./openapi.flow.json");
|
|
187
|
+
|
|
188
|
+
const app = express();
|
|
189
|
+
|
|
190
|
+
app.use(
|
|
191
|
+
createExpressFlowGuard({
|
|
192
|
+
openapi,
|
|
193
|
+
async getCurrentState({ resourceId }) {
|
|
194
|
+
if (!resourceId) return null;
|
|
195
|
+
return paymentStore.getState(resourceId); // your DB/service lookup
|
|
196
|
+
},
|
|
197
|
+
resolveResourceId: ({ params }) => params.id || null,
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Fastify example:
|
|
203
|
+
|
|
204
|
+
```js
|
|
205
|
+
const fastify = require("fastify")();
|
|
206
|
+
const { createFastifyFlowGuard } = require("x-openapi-flow/lib/runtime-guard");
|
|
207
|
+
const openapi = require("./openapi.flow.json");
|
|
208
|
+
|
|
209
|
+
fastify.addHook(
|
|
210
|
+
"preHandler",
|
|
211
|
+
createFastifyFlowGuard({
|
|
212
|
+
openapi,
|
|
213
|
+
async getCurrentState({ resourceId }) {
|
|
214
|
+
if (!resourceId) return null;
|
|
215
|
+
return paymentStore.getState(resourceId);
|
|
216
|
+
},
|
|
217
|
+
resolveResourceId: ({ params }) => params.id || null,
|
|
218
|
+
})
|
|
219
|
+
);
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Error payload for blocked transition:
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"error": {
|
|
227
|
+
"code": "INVALID_STATE_TRANSITION",
|
|
228
|
+
"message": "Blocked invalid transition for operation 'capturePayment'. Current state 'CREATED' cannot transition to this operation.",
|
|
229
|
+
"operation_id": "capturePayment",
|
|
230
|
+
"current_state": "CREATED",
|
|
231
|
+
"allowed_from_states": ["AUTHORIZED"],
|
|
232
|
+
"resource_id": "pay_123"
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
More details: [Runtime Guard](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/reference/Runtime-Guard.md)
|
|
238
|
+
|
|
239
|
+
### 5-Minute Demo: Real Runtime Block (E-commerce Orders)
|
|
240
|
+
|
|
241
|
+
Want to see the value immediately? Use the official minimal demo:
|
|
242
|
+
|
|
243
|
+
- [example/runtime-guard/minimal-order/README.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/example/runtime-guard/minimal-order/README.md)
|
|
244
|
+
|
|
245
|
+
Run in under 5 minutes:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
cd example/runtime-guard/minimal-order
|
|
249
|
+
npm install
|
|
250
|
+
npm start
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Create an order, then try to ship before payment (must return `409 INVALID_STATE_TRANSITION`):
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
curl -s -X POST http://localhost:3110/orders
|
|
257
|
+
curl -i -X POST http://localhost:3110/orders/<id>/ship
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
HTTPie equivalent:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
http POST :3110/orders
|
|
264
|
+
http -v POST :3110/orders/<id>/ship
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Programmatic State Machine Engine
|
|
268
|
+
|
|
269
|
+
Use a reusable deterministic engine independently of CLI and OpenAPI parsing:
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
const { createStateMachineEngine } = require("x-openapi-flow/lib/state-machine-engine");
|
|
273
|
+
|
|
274
|
+
const engine = createStateMachineEngine({
|
|
275
|
+
transitions: [
|
|
276
|
+
{ from: "CREATED", action: "confirm", to: "CONFIRMED" },
|
|
277
|
+
{ from: "CONFIRMED", action: "ship", to: "SHIPPED" },
|
|
278
|
+
],
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
engine.canTransition("CREATED", "confirm");
|
|
282
|
+
engine.getNextState("CREATED", "confirm");
|
|
283
|
+
engine.validateFlow({ startState: "CREATED", actions: ["confirm", "ship"] });
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
More details: [State Machine Engine](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/reference/State-Machine-Engine.md)
|
|
287
|
+
|
|
288
|
+
### OpenAPI to Engine Adapter
|
|
289
|
+
|
|
290
|
+
Convert `x-openapi-flow` metadata to a pure engine definition:
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
const { createStateMachineAdapterModel } = require("x-openapi-flow/lib/openapi-state-machine-adapter");
|
|
294
|
+
const { createStateMachineEngine } = require("x-openapi-flow/lib/state-machine-engine");
|
|
295
|
+
|
|
296
|
+
const model = createStateMachineAdapterModel({ openapiPath: "./openapi.flow.yaml" });
|
|
297
|
+
const engine = createStateMachineEngine(model.definition);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
More details: [OpenAPI State Machine Adapter](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/reference/OpenAPI-State-Machine-Adapter.md)
|
|
301
|
+
|
|
127
302
|
## Who Benefits Most
|
|
128
303
|
|
|
129
304
|
x-openapi-flow is ideal for teams and organizations that want **clear, enforceable API workflows**:
|
|
@@ -227,6 +402,8 @@ npx x-openapi-flow version # show version
|
|
|
227
402
|
npx x-openapi-flow doctor [--config path] # check setup and config
|
|
228
403
|
|
|
229
404
|
npx x-openapi-flow completion [bash|zsh] # enable shell autocompletion
|
|
405
|
+
|
|
406
|
+
npx x-openapi-flow quickstart [--dir path] [--runtime express|fastify] [--force] # scaffold runnable onboarding project
|
|
230
407
|
```
|
|
231
408
|
|
|
232
409
|
### Workflow Management
|
|
@@ -239,7 +416,7 @@ npx x-openapi-flow init [--flows path] [--force] [--dry-run]
|
|
|
239
416
|
npx x-openapi-flow apply [openapi-file] [--flows path] [--out path]
|
|
240
417
|
|
|
241
418
|
# validate transitions
|
|
242
|
-
npx x-openapi-flow validate <openapi-file> [--profile core|relaxed|strict] [--strict-quality]
|
|
419
|
+
npx x-openapi-flow validate <openapi-file> [--profile core|relaxed|strict] [--strict-quality] [--semantic]
|
|
243
420
|
```
|
|
244
421
|
|
|
245
422
|
### Visualization & Documentation
|
|
@@ -262,6 +439,16 @@ npx x-openapi-flow export-doc-flows [openapi-file] [--output path] [--format mar
|
|
|
262
439
|
npx x-openapi-flow generate-sdk [openapi-file] --lang typescript [--output path]
|
|
263
440
|
```
|
|
264
441
|
|
|
442
|
+
### Test Generation
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
# generate executable flow tests (happy path + invalid transitions)
|
|
446
|
+
npx x-openapi-flow generate-flow-tests [openapi-file] [--format jest|vitest|postman] [--output path]
|
|
447
|
+
|
|
448
|
+
# postman/newman-oriented collection with flow scripts
|
|
449
|
+
npx x-openapi-flow generate-flow-tests [openapi-file] --format postman [--output path] [--with-scripts]
|
|
450
|
+
```
|
|
451
|
+
|
|
265
452
|
Full details:
|
|
266
453
|
|
|
267
454
|
- [CLI-Reference.md](https://github.com/tiago-marques/x-openapi-flow/blob/main/docs/wiki/reference/CLI-Reference.md)
|
|
@@ -6,10 +6,12 @@ const { exportDocFlows } = require("./docs/doc-adapter");
|
|
|
6
6
|
const { generatePostmanCollection } = require("./collections/postman-adapter");
|
|
7
7
|
const { generateInsomniaWorkspace } = require("./collections/insomnia-adapter");
|
|
8
8
|
const { generateRedocPackage } = require("./ui/redoc-adapter");
|
|
9
|
+
const { generateFlowTests } = require("./tests/flow-test-adapter");
|
|
9
10
|
|
|
10
11
|
module.exports = {
|
|
11
12
|
exportDocFlows,
|
|
12
13
|
generatePostmanCollection,
|
|
13
14
|
generateInsomniaWorkspace,
|
|
14
15
|
generateRedocPackage,
|
|
16
|
+
generateFlowTests,
|
|
15
17
|
};
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { loadApi } = require("../../lib/validator");
|
|
6
|
+
const { createStateMachineAdapterModel } = require("../../lib/openapi-state-machine-adapter");
|
|
7
|
+
const { createStateMachineEngine } = require("../../lib/state-machine-engine");
|
|
8
|
+
const { generatePostmanCollection } = require("../collections/postman-adapter");
|
|
9
|
+
|
|
10
|
+
function getFormat(options) {
|
|
11
|
+
const format = String(options.format || "jest").toLowerCase();
|
|
12
|
+
if (!["jest", "vitest", "postman"].includes(format)) {
|
|
13
|
+
throw new Error(`Unsupported test format '${format}'. Use 'jest', 'vitest', or 'postman'.`);
|
|
14
|
+
}
|
|
15
|
+
return format;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getDefaultOutputPath(format) {
|
|
19
|
+
if (format === "postman") {
|
|
20
|
+
return path.resolve(process.cwd(), "x-openapi-flow.flow-tests.postman_collection.json");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (format === "vitest") {
|
|
24
|
+
return path.resolve(process.cwd(), "x-openapi-flow.flow.generated.vitest.test.js");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return path.resolve(process.cwd(), "x-openapi-flow.flow.generated.jest.test.js");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function buildHappyPaths(engine) {
|
|
31
|
+
const transitions = engine.getTransitions();
|
|
32
|
+
const outgoing = new Map();
|
|
33
|
+
const indegree = new Map();
|
|
34
|
+
const states = engine.getStates();
|
|
35
|
+
|
|
36
|
+
for (const state of states) {
|
|
37
|
+
outgoing.set(state, []);
|
|
38
|
+
indegree.set(state, 0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const transition of transitions) {
|
|
42
|
+
if (!outgoing.has(transition.from)) {
|
|
43
|
+
outgoing.set(transition.from, []);
|
|
44
|
+
}
|
|
45
|
+
outgoing.get(transition.from).push(transition);
|
|
46
|
+
indegree.set(transition.to, (indegree.get(transition.to) || 0) + 1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const transitionList of outgoing.values()) {
|
|
50
|
+
transitionList.sort((left, right) => left.action.localeCompare(right.action));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const starts = states.filter((state) => (indegree.get(state) || 0) === 0);
|
|
54
|
+
const roots = starts.length > 0 ? starts : states;
|
|
55
|
+
const maxDepth = Math.max(3, states.length + 2);
|
|
56
|
+
const maxPaths = 50;
|
|
57
|
+
const paths = [];
|
|
58
|
+
|
|
59
|
+
function walk(currentState, actions, visitedKeys, depth) {
|
|
60
|
+
if (paths.length >= maxPaths) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const nextTransitions = outgoing.get(currentState) || [];
|
|
65
|
+
if (nextTransitions.length === 0 || depth >= maxDepth) {
|
|
66
|
+
if (actions.length > 0) {
|
|
67
|
+
paths.push({
|
|
68
|
+
startState: actions[0].from,
|
|
69
|
+
actions: actions.map((entry) => entry.action),
|
|
70
|
+
finalState: currentState,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (const transition of nextTransitions) {
|
|
77
|
+
const visitKey = `${transition.from}::${transition.action}::${transition.to}`;
|
|
78
|
+
if (visitedKeys.has(visitKey)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const nextVisited = new Set(visitedKeys);
|
|
83
|
+
nextVisited.add(visitKey);
|
|
84
|
+
|
|
85
|
+
walk(
|
|
86
|
+
transition.to,
|
|
87
|
+
actions.concat([{ from: transition.from, action: transition.action }]),
|
|
88
|
+
nextVisited,
|
|
89
|
+
depth + 1
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const root of roots) {
|
|
95
|
+
walk(root, [], new Set(), 0);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const dedup = new Map();
|
|
99
|
+
for (const entry of paths) {
|
|
100
|
+
const key = `${entry.startState}::${entry.actions.join(",")}`;
|
|
101
|
+
if (!dedup.has(key)) {
|
|
102
|
+
dedup.set(key, entry);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return [...dedup.values()];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function buildInvalidCases(engine) {
|
|
110
|
+
const transitions = engine.getTransitions();
|
|
111
|
+
const states = engine.getStates();
|
|
112
|
+
const knownActions = [...new Set(transitions.map((transition) => transition.action))].sort();
|
|
113
|
+
const cases = [];
|
|
114
|
+
|
|
115
|
+
for (const state of states) {
|
|
116
|
+
const available = engine.getAvailableActions(state);
|
|
117
|
+
const disallowedAction = knownActions.find((action) => !available.includes(action));
|
|
118
|
+
|
|
119
|
+
if (disallowedAction) {
|
|
120
|
+
cases.push({
|
|
121
|
+
state,
|
|
122
|
+
action: disallowedAction,
|
|
123
|
+
availableActions: available,
|
|
124
|
+
});
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
cases.push({
|
|
129
|
+
state,
|
|
130
|
+
action: "__invalid_action__",
|
|
131
|
+
availableActions: available,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return cases;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function buildJestOrVitestContent({ format, sourcePath, definition, happyPaths, invalidCases }) {
|
|
139
|
+
const frameworkPrelude = format === "vitest"
|
|
140
|
+
? "const { describe, test, expect } = require(\"vitest\");\n"
|
|
141
|
+
: "";
|
|
142
|
+
|
|
143
|
+
const title = format === "vitest" ? "Vitest" : "Jest";
|
|
144
|
+
|
|
145
|
+
return `"use strict";
|
|
146
|
+
|
|
147
|
+
${frameworkPrelude}const { createStateMachineEngine } = require("x-openapi-flow/lib/state-machine-engine");
|
|
148
|
+
|
|
149
|
+
const definition = ${JSON.stringify(definition, null, 2)};
|
|
150
|
+
const happyPaths = ${JSON.stringify(happyPaths, null, 2)};
|
|
151
|
+
const invalidCases = ${JSON.stringify(invalidCases, null, 2)};
|
|
152
|
+
|
|
153
|
+
describe("x-openapi-flow generated tests (${title})", () => {
|
|
154
|
+
const engine = createStateMachineEngine(definition);
|
|
155
|
+
|
|
156
|
+
test("has transitions in definition", () => {
|
|
157
|
+
expect(Array.isArray(definition.transitions)).toBe(true);
|
|
158
|
+
expect(definition.transitions.length).toBeGreaterThan(0);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("happy paths", () => {
|
|
162
|
+
for (const pathCase of happyPaths) {
|
|
163
|
+
test(
|
|
164
|
+
\`valid flow: \${pathCase.startState} -> \${pathCase.actions.join(" -> ")}\`,
|
|
165
|
+
() => {
|
|
166
|
+
const result = engine.validateFlow({
|
|
167
|
+
startState: pathCase.startState,
|
|
168
|
+
actions: pathCase.actions,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
expect(result.ok).toBe(true);
|
|
172
|
+
expect(result.finalState).toBe(pathCase.finalState);
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("invalid transitions", () => {
|
|
179
|
+
for (const invalidCase of invalidCases) {
|
|
180
|
+
test(
|
|
181
|
+
\`blocks invalid action \${invalidCase.action} from state \${invalidCase.state}\`,
|
|
182
|
+
() => {
|
|
183
|
+
expect(engine.canTransition(invalidCase.state, invalidCase.action)).toBe(false);
|
|
184
|
+
|
|
185
|
+
const result = engine.validateFlow({
|
|
186
|
+
startState: invalidCase.state,
|
|
187
|
+
actions: [invalidCase.action],
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(result.ok).toBe(false);
|
|
191
|
+
expect(result.error.code).toBe("INVALID_TRANSITION");
|
|
192
|
+
expect(Array.isArray(result.error.availableActions)).toBe(true);
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Generated from: ${sourcePath}
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function generateFlowTests(options) {
|
|
204
|
+
const format = getFormat(options || {});
|
|
205
|
+
const apiPath = path.resolve(options.apiPath);
|
|
206
|
+
const outputPath = path.resolve(options.outputPath || getDefaultOutputPath(format));
|
|
207
|
+
|
|
208
|
+
if (format === "postman") {
|
|
209
|
+
const postman = generatePostmanCollection({
|
|
210
|
+
apiPath,
|
|
211
|
+
outputPath,
|
|
212
|
+
withScripts: options.withScripts !== false,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
format,
|
|
217
|
+
outputPath: postman.outputPath,
|
|
218
|
+
flowCount: postman.flowCount,
|
|
219
|
+
happyPathTests: null,
|
|
220
|
+
invalidCaseTests: null,
|
|
221
|
+
withScripts: postman.withScripts,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const api = loadApi(apiPath);
|
|
226
|
+
const model = createStateMachineAdapterModel({ openapi: api });
|
|
227
|
+
const engine = createStateMachineEngine(model.definition);
|
|
228
|
+
|
|
229
|
+
const happyPaths = buildHappyPaths(engine);
|
|
230
|
+
const invalidCases = buildInvalidCases(engine);
|
|
231
|
+
const content = buildJestOrVitestContent({
|
|
232
|
+
format,
|
|
233
|
+
sourcePath: apiPath,
|
|
234
|
+
definition: model.definition,
|
|
235
|
+
happyPaths,
|
|
236
|
+
invalidCases,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
240
|
+
fs.writeFileSync(outputPath, content, "utf8");
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
format,
|
|
244
|
+
outputPath,
|
|
245
|
+
flowCount: model.definition.transitions.length,
|
|
246
|
+
happyPathTests: happyPaths.length,
|
|
247
|
+
invalidCaseTests: invalidCases.length,
|
|
248
|
+
withScripts: null,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = {
|
|
253
|
+
generateFlowTests,
|
|
254
|
+
};
|