@workos/oagen 0.19.6 → 0.21.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/dist/cli/index.mjs +173 -41
- package/dist/cli/index.mjs.map +1 -1
- package/dist/compat/index.mjs +1 -1
- package/dist/{diff-C8mjApjv.mjs → diff-CBFCoTCw.mjs} +6 -2
- package/dist/{diff-C8mjApjv.mjs.map → diff-CBFCoTCw.mjs.map} +1 -1
- package/dist/differ-CC65Efzt.d.mts.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +155 -22
- package/dist/index.mjs.map +1 -1
- package/dist/{overlay-Dk70ilXl.mjs → overlay-cb32jDBS.mjs} +16 -21
- package/dist/overlay-cb32jDBS.mjs.map +1 -0
- package/dist/scripts/smoke/baseline.mjs +153 -20
- package/dist/scripts/smoke/baseline.mjs.map +1 -1
- package/dist/smoke.mjs +153 -20
- package/dist/smoke.mjs.map +1 -1
- package/dist/verify/index.mjs +2 -2
- package/package.json +1 -1
- package/skills/generate-smoke-test/SKILL.md +19 -41
- package/skills/generate-smoke-test/references/implementation-patterns.md +87 -0
- package/dist/overlay-Dk70ilXl.mjs.map +0 -1
package/dist/verify/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { m as CommandError } from "../tree-sitter-DcUvm0KA.mjs";
|
|
2
|
-
import { a as MANIFEST_FILENAME, i as generate, n as getEmitter, t as diffSpecs } from "../diff-
|
|
3
|
-
import { A as getDefaultPolicy, T as apiSurfaceToSnapshot, c as filterSurface, f as specDerivedMethodPaths, i as getExtractor, j as mergePolicy, l as specDerivedEnumValues, n as isPatchableChange, o as diffSnapshots, p as specDerivedNames, r as patchOverlay, t as buildOverlayLookup, u as specDerivedFieldPaths } from "../overlay-
|
|
2
|
+
import { a as MANIFEST_FILENAME, i as generate, n as getEmitter, t as diffSpecs } from "../diff-CBFCoTCw.mjs";
|
|
3
|
+
import { A as getDefaultPolicy, T as apiSurfaceToSnapshot, c as filterSurface, f as specDerivedMethodPaths, i as getExtractor, j as mergePolicy, l as specDerivedEnumValues, n as isPatchableChange, o as diffSnapshots, p as specDerivedNames, r as patchOverlay, t as buildOverlayLookup, u as specDerivedFieldPaths } from "../overlay-cb32jDBS.mjs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { execFileSync } from "node:child_process";
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ Create a self-contained smoke test script for a new SDK language that captures w
|
|
|
11
11
|
|
|
12
12
|
Each language's smoke test is a single file: `smoke/sdk-{lang}.ts` **in the emitter project**. It uses the target language's native HTTP interception to capture what the SDK actually sends over the wire, then outputs `SmokeResults` JSON. The diff tool compares this against a baseline and reports mismatches by severity.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
It imports shared infrastructure from `@workos/oagen/smoke` and implements language-specific parts inline.
|
|
15
15
|
|
|
16
16
|
## Resolve Paths
|
|
17
17
|
|
|
@@ -50,15 +50,15 @@ Store it as `spec`.
|
|
|
50
50
|
|
|
51
51
|
## Step 1: Determine HTTP Interception Strategy
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
Choose the interception mechanism for the target language. It must capture the raw request (method, path, query, body) and response (status, body), storing both in a `currentCapture` variable (~20-30 lines):
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
| Language | Mechanism |
|
|
56
|
+
| ----------- | ------------------------------------------------------ |
|
|
57
|
+
| Node | Patch `globalThis.fetch` |
|
|
58
|
+
| Ruby | WebMock `stub_request` or monkey-patch `Net::HTTP` |
|
|
59
|
+
| Python | `responses`, `respx` (httpx), or `unittest.mock.patch` |
|
|
60
|
+
| Go | Custom `http.RoundTripper` |
|
|
61
|
+
| Java/Kotlin | OkHttp `Interceptor` |
|
|
62
62
|
|
|
63
63
|
## Step 2: Build the SERVICE_MAP
|
|
64
64
|
|
|
@@ -91,48 +91,27 @@ Each language's SDK will have different accessor names — discover them by read
|
|
|
91
91
|
|
|
92
92
|
## Step 3: Implement SDK Method Resolution
|
|
93
93
|
|
|
94
|
-
Adapt the 4-tier resolution to the target language's naming conventions:
|
|
94
|
+
Adapt the 4-tier resolution to the target language's naming conventions (Ruby/Python: `snake_case`, Go: `PascalCase`, Node: `camelCase`):
|
|
95
95
|
|
|
96
|
-
0. **Manifest match** —
|
|
96
|
+
0. **Manifest match** — Primary path. Uses the operations map loaded in Step 2. Fall through if unavailable.
|
|
97
97
|
1. **Exact match** — IR operation name converted to target convention
|
|
98
98
|
2. **CRUD prefix match** — standard verbs (create, list, retrieve/get, update, delete) with service name tiebreaker
|
|
99
99
|
3. **Keyword fuzzy match** — stem words and score overlap
|
|
100
100
|
|
|
101
|
-
Key convention differences: Ruby/Python use `snake_case`, Go uses `PascalCase`, Node uses `camelCase`.
|
|
102
|
-
|
|
103
101
|
Each resolution records provenance metadata (`ExchangeProvenance`) so findings can be traced back to the resolution path.
|
|
104
102
|
|
|
105
103
|
## Step 4: Implement Argument Construction
|
|
106
104
|
|
|
107
|
-
Build SDK call arguments from IR operations
|
|
108
|
-
|
|
109
|
-
- No path params + has body → `method(payload)`
|
|
110
|
-
- No path params + has query params → `method(queryOpts)`
|
|
111
|
-
- Single path param, no body/query → `method(id)` (positional)
|
|
112
|
-
- Complex (path params + body/query) → `method(mergedOptions)`
|
|
113
|
-
- Idempotent POST → append empty options object for idempotency key
|
|
114
|
-
|
|
115
|
-
Choose the right payload convention: Node uses `generateCamelPayload()`, Ruby/Python may use `generatePayload()` directly (snake_case).
|
|
105
|
+
Build SDK call arguments from IR operations. Reference `buildArgs()` in existing smoke scripts and adapt to the target language's calling convention. See [references/implementation-patterns.md](references/implementation-patterns.md) for the concrete branching template covering all argument patterns (positional, payload-only, query-only, complex, idempotent POST).
|
|
116
106
|
|
|
117
107
|
## Step 5: Write `smoke/sdk-{lang}.ts`
|
|
118
108
|
|
|
119
|
-
Create the script **in the emitter project
|
|
120
|
-
|
|
121
|
-
1.
|
|
122
|
-
2. HTTP interception
|
|
123
|
-
3. `main()`
|
|
124
|
-
|
|
125
|
-
- Parse spec via `parseSpec()`
|
|
126
|
-
- Load operations map from `{sdk-path}/.oagen-manifest.json` (emitter-generated). If missing, log a warning — method resolution will rely on heuristic tiers and most operations will likely be skipped.
|
|
127
|
-
- Load and configure the SDK
|
|
128
|
-
- Iterate `planOperations()` groups
|
|
129
|
-
- For each operation: resolve SDK method, resolve path params, build args, call SDK, capture exchange
|
|
130
|
-
- Extract IDs via `ids.extractAndStore()`
|
|
131
|
-
- Track POST creates for cleanup
|
|
132
|
-
- Cleanup created entities in reverse
|
|
133
|
-
- Restore original HTTP behavior
|
|
134
|
-
- Write `smoke-results-sdk-{lang}.json`
|
|
135
|
-
4. Summary output (successes, errors, skipped, unexpected statuses)
|
|
109
|
+
Create the script **in the emitter project**. See [references/implementation-patterns.md](references/implementation-patterns.md) for the full structural template. The script follows this flow:
|
|
110
|
+
|
|
111
|
+
1. Import shared infrastructure from `@workos/oagen/smoke`
|
|
112
|
+
2. Set up HTTP interception (Step 1)
|
|
113
|
+
3. `main()`: parse spec → load operations map → init SDK → iterate `planOperations()` groups → resolve method (Step 3) → build args (Step 4) → call SDK → capture exchange → extract IDs via `ids.extractAndStore()` → track POST creates for cleanup
|
|
114
|
+
4. Cleanup created entities in reverse order, restore HTTP, write `smoke-results-sdk-{lang}.json`
|
|
136
115
|
|
|
137
116
|
## Step 6: Register the Smoke Runner
|
|
138
117
|
|
|
@@ -167,11 +146,10 @@ During initial setup, run `oagen generate` then the smoke test until skips are m
|
|
|
167
146
|
|
|
168
147
|
```bash
|
|
169
148
|
oagen generate --lang {lang} --output {sdk-path} --spec {spec} --namespace {ns}
|
|
170
|
-
# The emitter writes the operations map into .oagen-manifest.json — the smoke test loads it automatically.
|
|
171
149
|
oagen verify --lang {lang} --output {sdk-path} --spec {spec}
|
|
172
150
|
```
|
|
173
151
|
|
|
174
|
-
If many operations are skipped with "No matching SDK method",
|
|
152
|
+
If many operations are skipped with "No matching SDK method", verify the operations map is present (see Step 2).
|
|
175
153
|
|
|
176
154
|
| Exit | Meaning | Output | Action |
|
|
177
155
|
| ---- | ------------- | --------------------------- | --------------------------------------- |
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Smoke Test Implementation Patterns
|
|
2
|
+
|
|
3
|
+
Concrete templates for the language-specific parts of a smoke test script. Adapt naming conventions and HTTP interception to the target language.
|
|
4
|
+
|
|
5
|
+
## Argument Construction (`buildArgs`)
|
|
6
|
+
|
|
7
|
+
Build SDK call arguments from IR operations. Choose payload generator based on target language: Node uses `generateCamelPayload()`, Ruby/Python use `generatePayload()` (snake_case).
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
function buildArgs(op: OperationPlan, spec: ApiSpec): unknown[] {
|
|
11
|
+
const pathParams = op.parameters.filter((p) => p.in === "path");
|
|
12
|
+
const hasBody = !!op.requestBody;
|
|
13
|
+
const hasQuery = op.parameters.some((p) => p.in === "query");
|
|
14
|
+
|
|
15
|
+
if (pathParams.length === 0 && hasBody) return [generatePayload(op, spec)];
|
|
16
|
+
if (pathParams.length === 0 && hasQuery) return [generateQueryParams(op)];
|
|
17
|
+
if (pathParams.length === 1 && !hasBody && !hasQuery)
|
|
18
|
+
return [ids.get(pathParams[0].schema) ?? "test_id"];
|
|
19
|
+
// Complex: merge path params + body/query into options object
|
|
20
|
+
const opts = { ...generatePayload(op, spec), ...resolvePathParams(op) };
|
|
21
|
+
if (hasQuery) Object.assign(opts, generateQueryParams(op));
|
|
22
|
+
// Idempotent POST: append empty options for idempotency key
|
|
23
|
+
if (
|
|
24
|
+
op.method === "post" &&
|
|
25
|
+
op.parameters.some((p) => p.name === "idempotency_key")
|
|
26
|
+
)
|
|
27
|
+
return [opts, {}];
|
|
28
|
+
return [opts];
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Smoke Script Structural Template
|
|
33
|
+
|
|
34
|
+
The complete structure for `smoke/sdk-{lang}.ts`:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import {
|
|
38
|
+
parseSpec,
|
|
39
|
+
planOperations,
|
|
40
|
+
generatePayload,
|
|
41
|
+
generateQueryParams,
|
|
42
|
+
IdRegistry,
|
|
43
|
+
isUnexpectedStatus,
|
|
44
|
+
resolvePath,
|
|
45
|
+
type CapturedExchange,
|
|
46
|
+
type SmokeResults,
|
|
47
|
+
} from "@workos/oagen/smoke";
|
|
48
|
+
|
|
49
|
+
let currentCapture: CapturedExchange | null = null;
|
|
50
|
+
const ids = new IdRegistry();
|
|
51
|
+
|
|
52
|
+
// Interception setup — see language table in SKILL.md Step 1
|
|
53
|
+
|
|
54
|
+
async function main() {
|
|
55
|
+
const spec = await parseSpec(specPath);
|
|
56
|
+
const opsMap = loadManifestOperations(sdkPath); // from Step 2
|
|
57
|
+
const client = initSdk(apiKey);
|
|
58
|
+
const results: SmokeResults = { exchanges: [], errors: [], skipped: [] };
|
|
59
|
+
const cleanups: Array<() => Promise<void>> = [];
|
|
60
|
+
|
|
61
|
+
for (const group of planOperations(spec)) {
|
|
62
|
+
for (const op of group.operations) {
|
|
63
|
+
const resolved = resolveMethod(op, opsMap, client); // Step 3
|
|
64
|
+
if (!resolved) {
|
|
65
|
+
results.skipped.push(op);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const args = buildArgs(op, spec); // Step 4
|
|
69
|
+
try {
|
|
70
|
+
currentCapture = null;
|
|
71
|
+
await resolved.fn(...args);
|
|
72
|
+
if (currentCapture) {
|
|
73
|
+
ids.extractAndStore(currentCapture.response);
|
|
74
|
+
results.exchanges.push(currentCapture);
|
|
75
|
+
if (op.method === "post")
|
|
76
|
+
cleanups.push(() => deleteEntity(client, op));
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
results.errors.push({ op, error: e });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const cleanup of cleanups.reverse()) await cleanup();
|
|
84
|
+
writeFileSync(`smoke-results-sdk-{lang}.json`, JSON.stringify(results));
|
|
85
|
+
printSummary(results); // successes, errors, skipped, unexpected statuses
|
|
86
|
+
}
|
|
87
|
+
```
|