@workos/oagen-emitters 0.2.1 → 0.4.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/.husky/pre-commit +1 -0
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +15 -0
- package/README.md +129 -0
- package/dist/index.d.mts +13 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +14549 -3385
- package/dist/index.mjs.map +1 -1
- package/docs/sdk-architecture/dotnet.md +336 -0
- package/docs/sdk-architecture/go.md +338 -0
- package/docs/sdk-architecture/php.md +315 -0
- package/docs/sdk-architecture/python.md +511 -0
- package/oagen.config.ts +328 -2
- package/package.json +9 -5
- package/scripts/generate-php.js +13 -0
- package/scripts/git-push-with-published-oagen.sh +21 -0
- package/smoke/sdk-dotnet.ts +45 -12
- package/smoke/sdk-go.ts +116 -42
- package/smoke/sdk-php.ts +28 -26
- package/smoke/sdk-python.ts +5 -2
- package/src/dotnet/client.ts +89 -0
- package/src/dotnet/enums.ts +323 -0
- package/src/dotnet/fixtures.ts +236 -0
- package/src/dotnet/index.ts +246 -0
- package/src/dotnet/manifest.ts +36 -0
- package/src/dotnet/models.ts +344 -0
- package/src/dotnet/naming.ts +330 -0
- package/src/dotnet/resources.ts +622 -0
- package/src/dotnet/tests.ts +693 -0
- package/src/dotnet/type-map.ts +201 -0
- package/src/dotnet/wrappers.ts +186 -0
- package/src/go/client.ts +141 -0
- package/src/go/enums.ts +196 -0
- package/src/go/fixtures.ts +212 -0
- package/src/go/index.ts +84 -0
- package/src/go/manifest.ts +36 -0
- package/src/go/models.ts +254 -0
- package/src/go/naming.ts +179 -0
- package/src/go/resources.ts +827 -0
- package/src/go/tests.ts +751 -0
- package/src/go/type-map.ts +82 -0
- package/src/go/wrappers.ts +261 -0
- package/src/index.ts +4 -0
- package/src/kotlin/client.ts +53 -0
- package/src/kotlin/enums.ts +162 -0
- package/src/kotlin/index.ts +92 -0
- package/src/kotlin/manifest.ts +55 -0
- package/src/kotlin/models.ts +395 -0
- package/src/kotlin/naming.ts +223 -0
- package/src/kotlin/overrides.ts +25 -0
- package/src/kotlin/resources.ts +667 -0
- package/src/kotlin/tests.ts +1019 -0
- package/src/kotlin/type-map.ts +123 -0
- package/src/kotlin/wrappers.ts +168 -0
- package/src/node/client.ts +128 -115
- package/src/node/enums.ts +9 -0
- package/src/node/errors.ts +37 -232
- package/src/node/field-plan.ts +726 -0
- package/src/node/fixtures.ts +9 -1
- package/src/node/index.ts +3 -9
- package/src/node/models.ts +178 -21
- package/src/node/naming.ts +49 -111
- package/src/node/resources.ts +527 -397
- package/src/node/sdk-errors.ts +41 -0
- package/src/node/tests.ts +69 -19
- package/src/node/type-map.ts +4 -2
- package/src/node/utils.ts +13 -71
- package/src/node/wrappers.ts +151 -0
- package/src/php/client.ts +179 -0
- package/src/php/enums.ts +67 -0
- package/src/php/errors.ts +9 -0
- package/src/php/fixtures.ts +181 -0
- package/src/php/index.ts +96 -0
- package/src/php/manifest.ts +36 -0
- package/src/php/models.ts +310 -0
- package/src/php/naming.ts +279 -0
- package/src/php/resources.ts +636 -0
- package/src/php/tests.ts +609 -0
- package/src/php/type-map.ts +90 -0
- package/src/php/utils.ts +18 -0
- package/src/php/wrappers.ts +152 -0
- package/src/python/client.ts +345 -0
- package/src/python/enums.ts +313 -0
- package/src/python/fixtures.ts +196 -0
- package/src/python/index.ts +95 -0
- package/src/python/manifest.ts +38 -0
- package/src/python/models.ts +688 -0
- package/src/python/naming.ts +189 -0
- package/src/python/resources.ts +1322 -0
- package/src/python/tests.ts +1335 -0
- package/src/python/type-map.ts +93 -0
- package/src/python/wrappers.ts +191 -0
- package/src/shared/model-utils.ts +472 -0
- package/src/shared/naming-utils.ts +154 -0
- package/src/shared/non-spec-services.ts +54 -0
- package/src/shared/resolved-ops.ts +109 -0
- package/src/shared/wrapper-utils.ts +70 -0
- package/test/dotnet/client.test.ts +121 -0
- package/test/dotnet/enums.test.ts +193 -0
- package/test/dotnet/errors.test.ts +9 -0
- package/test/dotnet/manifest.test.ts +82 -0
- package/test/dotnet/models.test.ts +260 -0
- package/test/dotnet/resources.test.ts +255 -0
- package/test/dotnet/tests.test.ts +202 -0
- package/test/go/client.test.ts +92 -0
- package/test/go/enums.test.ts +132 -0
- package/test/go/errors.test.ts +9 -0
- package/test/go/models.test.ts +265 -0
- package/test/go/resources.test.ts +408 -0
- package/test/go/tests.test.ts +143 -0
- package/test/kotlin/models.test.ts +135 -0
- package/test/kotlin/tests.test.ts +176 -0
- package/test/node/client.test.ts +92 -12
- package/test/node/enums.test.ts +2 -0
- package/test/node/errors.test.ts +2 -41
- package/test/node/models.test.ts +2 -0
- package/test/node/naming.test.ts +23 -0
- package/test/node/resources.test.ts +315 -84
- package/test/node/serializers.test.ts +3 -1
- package/test/node/type-map.test.ts +11 -0
- package/test/php/client.test.ts +95 -0
- package/test/php/enums.test.ts +173 -0
- package/test/php/errors.test.ts +9 -0
- package/test/php/models.test.ts +497 -0
- package/test/php/resources.test.ts +682 -0
- package/test/php/tests.test.ts +185 -0
- package/test/python/client.test.ts +200 -0
- package/test/python/enums.test.ts +228 -0
- package/test/python/errors.test.ts +16 -0
- package/test/python/manifest.test.ts +74 -0
- package/test/python/models.test.ts +716 -0
- package/test/python/resources.test.ts +617 -0
- package/test/python/tests.test.ts +202 -0
- package/src/node/common.ts +0 -273
- package/src/node/config.ts +0 -71
- package/src/node/serializers.ts +0 -746
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { generateTests } from '../../src/kotlin/tests.js';
|
|
3
|
+
import { generateEnums } from '../../src/kotlin/enums.js';
|
|
4
|
+
import type { EmitterContext, ApiSpec, Service, Model, ResolvedOperation } from '@workos/oagen';
|
|
5
|
+
import { defaultSdkBehavior } from '@workos/oagen';
|
|
6
|
+
|
|
7
|
+
const models: Model[] = [
|
|
8
|
+
{
|
|
9
|
+
name: 'Organization',
|
|
10
|
+
fields: [
|
|
11
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
12
|
+
{ name: 'name', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const services: Service[] = [
|
|
18
|
+
{
|
|
19
|
+
name: 'Organizations',
|
|
20
|
+
operations: [
|
|
21
|
+
{
|
|
22
|
+
name: 'getOrganization',
|
|
23
|
+
httpMethod: 'get',
|
|
24
|
+
path: '/organizations/{id}',
|
|
25
|
+
pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
26
|
+
queryParams: [],
|
|
27
|
+
headerParams: [],
|
|
28
|
+
response: { kind: 'model', name: 'Organization' },
|
|
29
|
+
errors: [],
|
|
30
|
+
injectIdempotencyKey: false,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'deleteOrganization',
|
|
34
|
+
httpMethod: 'delete',
|
|
35
|
+
path: '/organizations/{id}',
|
|
36
|
+
pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
37
|
+
queryParams: [],
|
|
38
|
+
headerParams: [],
|
|
39
|
+
response: { kind: 'primitive', type: 'unknown' },
|
|
40
|
+
errors: [],
|
|
41
|
+
injectIdempotencyKey: false,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const spec: ApiSpec = {
|
|
48
|
+
name: 'TestAPI',
|
|
49
|
+
version: '1.0.0',
|
|
50
|
+
baseUrl: 'https://api.workos.com',
|
|
51
|
+
services,
|
|
52
|
+
models,
|
|
53
|
+
enums: [],
|
|
54
|
+
sdk: defaultSdkBehavior(),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function buildResolvedOps(services: Service[]): ResolvedOperation[] {
|
|
58
|
+
return services.flatMap((svc) =>
|
|
59
|
+
svc.operations.map((op) => ({
|
|
60
|
+
service: svc,
|
|
61
|
+
operation: op,
|
|
62
|
+
methodName: op.name,
|
|
63
|
+
mountOn: svc.name,
|
|
64
|
+
})),
|
|
65
|
+
) as ResolvedOperation[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const ctx: EmitterContext = {
|
|
69
|
+
namespace: 'workos',
|
|
70
|
+
namespacePascal: 'WorkOS',
|
|
71
|
+
spec,
|
|
72
|
+
resolvedOperations: buildResolvedOps(services),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
describe('kotlin/tests', () => {
|
|
76
|
+
it('generates per-mount-group test files', () => {
|
|
77
|
+
generateEnums([], ctx);
|
|
78
|
+
const files = generateTests(spec, ctx);
|
|
79
|
+
const testFile = files.find((f) => f.path.includes('OrganizationsTest.kt'));
|
|
80
|
+
expect(testFile).toBeDefined();
|
|
81
|
+
|
|
82
|
+
const content = testFile!.content;
|
|
83
|
+
expect(content).toContain('class OrganizationsTest');
|
|
84
|
+
expect(content).toContain('TestBase');
|
|
85
|
+
expect(content).toContain('@Test');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('generates happy-path test for void/delete methods', () => {
|
|
89
|
+
generateEnums([], ctx);
|
|
90
|
+
const files = generateTests(spec, ctx);
|
|
91
|
+
const testFile = files.find((f) => f.path.includes('OrganizationsTest.kt'))!;
|
|
92
|
+
const content = testFile.content;
|
|
93
|
+
|
|
94
|
+
// Delete method should have an active test, not a @Disabled placeholder.
|
|
95
|
+
// Method name is trimmed from deleteOrganization -> delete by resolveMethodName.
|
|
96
|
+
expect(content).toContain('delete completes without throwing');
|
|
97
|
+
expect(content).not.toContain('@Disabled("generator: could not synthesize required arguments for delete")');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('generates field-value assertions for non-void responses', () => {
|
|
101
|
+
generateEnums([], ctx);
|
|
102
|
+
const files = generateTests(spec, ctx);
|
|
103
|
+
const testFile = files.find((f) => f.path.includes('OrganizationsTest.kt'))!;
|
|
104
|
+
const content = testFile.content;
|
|
105
|
+
|
|
106
|
+
// GET method returning Organization should assert field values
|
|
107
|
+
expect(content).toContain('assertEquals("sample", result.id)');
|
|
108
|
+
expect(content).toContain('assertEquals("sample", result.name)');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('generates error-mapping tests', () => {
|
|
112
|
+
generateEnums([], ctx);
|
|
113
|
+
const files = generateTests(spec, ctx);
|
|
114
|
+
const testFile = files.find((f) => f.path.includes('OrganizationsTest.kt'))!;
|
|
115
|
+
const content = testFile.content;
|
|
116
|
+
|
|
117
|
+
expect(content).toContain('UnauthorizedException');
|
|
118
|
+
expect(content).toContain('NotFoundException');
|
|
119
|
+
expect(content).toContain('RateLimitException');
|
|
120
|
+
expect(content).toContain('GenericServerException');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('generates round-trip test using synthJson for broader coverage', () => {
|
|
124
|
+
generateEnums([], ctx);
|
|
125
|
+
const files = generateTests(spec, ctx);
|
|
126
|
+
const roundTrip = files.find((f) => f.path.includes('GeneratedModelRoundTripTest.kt'));
|
|
127
|
+
expect(roundTrip).toBeDefined();
|
|
128
|
+
|
|
129
|
+
const content = roundTrip!.content;
|
|
130
|
+
expect(content).toContain('Organization round-trips through Jackson');
|
|
131
|
+
expect(content).toContain('assertEquals(tree1, tree2)');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('generates forward-compat test with OffsetDateTime round-trip', () => {
|
|
135
|
+
generateEnums([], ctx);
|
|
136
|
+
const files = generateTests(spec, ctx);
|
|
137
|
+
const fwdCompat = files.find((f) => f.path.includes('GeneratedForwardCompatTest.kt'));
|
|
138
|
+
expect(fwdCompat).toBeDefined();
|
|
139
|
+
|
|
140
|
+
const content = fwdCompat!.content;
|
|
141
|
+
expect(content).toContain('OffsetDateTime round-trips');
|
|
142
|
+
expect(content).toContain('assertEquals(parsed.toInstant(), reparsed.toInstant())');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('emits valid ISO-8601 for date-time fields in round-trip fixtures', () => {
|
|
146
|
+
const dtModels: Model[] = [
|
|
147
|
+
{
|
|
148
|
+
name: 'Event',
|
|
149
|
+
fields: [
|
|
150
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
151
|
+
{
|
|
152
|
+
name: 'created_at',
|
|
153
|
+
type: { kind: 'primitive', type: 'string', format: 'date-time' },
|
|
154
|
+
required: true,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
const dtSpec: ApiSpec = { ...spec, models: dtModels };
|
|
160
|
+
const dtCtx: EmitterContext = {
|
|
161
|
+
...ctx,
|
|
162
|
+
spec: dtSpec,
|
|
163
|
+
resolvedOperations: buildResolvedOps(services),
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
generateEnums([], dtCtx);
|
|
167
|
+
const files = generateTests(dtSpec, dtCtx);
|
|
168
|
+
const roundTrip = files.find((f) => f.path.includes('GeneratedModelRoundTripTest.kt'));
|
|
169
|
+
expect(roundTrip).toBeDefined();
|
|
170
|
+
|
|
171
|
+
const content = roundTrip!.content;
|
|
172
|
+
// Should use ISO-8601 timestamp, not "sample"
|
|
173
|
+
expect(content).toContain('2024-01-01T00:00:00Z');
|
|
174
|
+
expect(content).not.toMatch(/created_at.*"sample"/);
|
|
175
|
+
});
|
|
176
|
+
});
|
package/test/node/client.test.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
2
2
|
import { generateClient } from '../../src/node/client.js';
|
|
3
3
|
import { isServiceCoveredByExisting } from '../../src/node/utils.js';
|
|
4
4
|
import type { EmitterContext, ApiSpec, Service, Model, Enum } from '@workos/oagen';
|
|
5
|
+
import { defaultSdkBehavior } from '@workos/oagen';
|
|
5
6
|
import type { ApiSurface } from '@workos/oagen/compat';
|
|
6
7
|
|
|
7
8
|
const service: Service = {
|
|
@@ -46,6 +47,7 @@ const spec: ApiSpec = {
|
|
|
46
47
|
services: [service],
|
|
47
48
|
models: [model],
|
|
48
49
|
enums: [],
|
|
50
|
+
sdk: defaultSdkBehavior(),
|
|
49
51
|
};
|
|
50
52
|
|
|
51
53
|
const ctx: EmitterContext = {
|
|
@@ -98,16 +100,11 @@ describe('generateClient', () => {
|
|
|
98
100
|
expect(serviceBarrel!.skipIfExists).toBe(true);
|
|
99
101
|
});
|
|
100
102
|
|
|
101
|
-
it('
|
|
103
|
+
it('does not generate package.json, tsconfig.json, or worker barrel (now hand-maintained)', () => {
|
|
102
104
|
const files = generateClient(spec, ctx);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
expect(pkg).toBeDefined();
|
|
107
|
-
expect(pkg!.skipIfExists).toBe(true);
|
|
108
|
-
|
|
109
|
-
expect(tsconfig).toBeDefined();
|
|
110
|
-
expect(tsconfig!.skipIfExists).toBe(true);
|
|
105
|
+
expect(files.find((f) => f.path === 'package.json')).toBeUndefined();
|
|
106
|
+
expect(files.find((f) => f.path === 'tsconfig.json')).toBeUndefined();
|
|
107
|
+
expect(files.find((f) => f.path === 'src/index.worker.ts')).toBeUndefined();
|
|
111
108
|
});
|
|
112
109
|
|
|
113
110
|
it('uses overlay-resolved names for imports and accessors', () => {
|
|
@@ -146,6 +143,7 @@ describe('generateClient', () => {
|
|
|
146
143
|
services: [mfaService],
|
|
147
144
|
models: [mfaModel],
|
|
148
145
|
enums: [],
|
|
146
|
+
sdk: defaultSdkBehavior(),
|
|
149
147
|
};
|
|
150
148
|
|
|
151
149
|
const overlayCtx: EmitterContext = {
|
|
@@ -196,6 +194,80 @@ describe('generateClient', () => {
|
|
|
196
194
|
expect(serviceBarrel!.content).toContain("export * from './authentication-factor.interface';");
|
|
197
195
|
});
|
|
198
196
|
|
|
197
|
+
it('propagates @deprecated from baseline service class to the property declaration', () => {
|
|
198
|
+
// Regression test for PR #1535 reviewer comment r3075509969: when a
|
|
199
|
+
// service class has `@deprecated` in its JSDoc, TS's deprecation-lint
|
|
200
|
+
// only fires at `new X()` call sites — not at `workos.x` access unless
|
|
201
|
+
// the property itself is annotated. The emitter propagates the class
|
|
202
|
+
// deprecation to the property JSDoc so IDEs surface the strikethrough
|
|
203
|
+
// on every access.
|
|
204
|
+
const deprecatedCtx: EmitterContext = {
|
|
205
|
+
...ctx,
|
|
206
|
+
apiSurface: {
|
|
207
|
+
language: 'node',
|
|
208
|
+
extractedFrom: 'test',
|
|
209
|
+
extractedAt: '2026-01-01',
|
|
210
|
+
classes: {
|
|
211
|
+
Organizations: {
|
|
212
|
+
name: 'Organizations',
|
|
213
|
+
methods: {},
|
|
214
|
+
properties: {},
|
|
215
|
+
constructorParams: [{ name: 'workos', type: 'WorkOS', optional: false }],
|
|
216
|
+
deprecationMessage: 'Use `workos.connect` instead.',
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
interfaces: {},
|
|
220
|
+
typeAliases: {},
|
|
221
|
+
enums: {},
|
|
222
|
+
exports: {},
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const files = generateClient(spec, deprecatedCtx);
|
|
227
|
+
const workosFile = files.find((f) => f.path === 'src/workos.ts')!;
|
|
228
|
+
const content = workosFile.content;
|
|
229
|
+
|
|
230
|
+
// Property JSDoc carries the deprecation and the directive is preserved
|
|
231
|
+
// on the line immediately above the accessor.
|
|
232
|
+
expect(content).toMatch(
|
|
233
|
+
/\/\*\* @deprecated Use `workos\.connect` instead\. \*\/\s+readonly organizations = new Organizations\(this\);/,
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('emits a bare @deprecated when the baseline class deprecation has no message', () => {
|
|
238
|
+
const deprecatedCtx: EmitterContext = {
|
|
239
|
+
...ctx,
|
|
240
|
+
apiSurface: {
|
|
241
|
+
language: 'node',
|
|
242
|
+
extractedFrom: 'test',
|
|
243
|
+
extractedAt: '2026-01-01',
|
|
244
|
+
classes: {
|
|
245
|
+
Organizations: {
|
|
246
|
+
name: 'Organizations',
|
|
247
|
+
methods: {},
|
|
248
|
+
properties: {},
|
|
249
|
+
constructorParams: [{ name: 'workos', type: 'WorkOS', optional: false }],
|
|
250
|
+
deprecationMessage: '',
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
interfaces: {},
|
|
254
|
+
typeAliases: {},
|
|
255
|
+
enums: {},
|
|
256
|
+
exports: {},
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const files = generateClient(spec, deprecatedCtx);
|
|
261
|
+
const workosFile = files.find((f) => f.path === 'src/workos.ts')!;
|
|
262
|
+
expect(workosFile.content).toMatch(/\/\*\* @deprecated \*\/\s+readonly organizations = new Organizations\(this\);/);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('does not emit @deprecated when the baseline class has no deprecationMessage', () => {
|
|
266
|
+
const files = generateClient(spec, ctx);
|
|
267
|
+
const workosFile = files.find((f) => f.path === 'src/workos.ts')!;
|
|
268
|
+
expect(workosFile.content).not.toContain('@deprecated');
|
|
269
|
+
});
|
|
270
|
+
|
|
199
271
|
it('does not generate error handling in WorkOS client (lives in WorkOSBase)', () => {
|
|
200
272
|
const files = generateClient(spec, ctx);
|
|
201
273
|
const workosFile = files.find((f) => f.path === 'src/workos.ts')!;
|
|
@@ -261,6 +333,7 @@ describe('generateClient', () => {
|
|
|
261
333
|
services: [eventService],
|
|
262
334
|
models: [eventModel, otherModel],
|
|
263
335
|
enums: [],
|
|
336
|
+
sdk: defaultSdkBehavior(),
|
|
264
337
|
};
|
|
265
338
|
|
|
266
339
|
const surface: ApiSurface = {
|
|
@@ -300,9 +373,10 @@ describe('generateClient', () => {
|
|
|
300
373
|
expect(content).not.toContain('export type { Event,');
|
|
301
374
|
expect(content).not.toContain('export type { Event }');
|
|
302
375
|
|
|
303
|
-
// EventCursor is
|
|
304
|
-
//
|
|
305
|
-
expect(content).toContain("export * from './common/interfaces'");
|
|
376
|
+
// EventCursor is unreachable (not referenced by any service), so it should
|
|
377
|
+
// NOT be exported — oagen only generates interface files for reachable models
|
|
378
|
+
expect(content).not.toContain("export * from './common/interfaces'");
|
|
379
|
+
expect(content).not.toContain('EventCursor');
|
|
306
380
|
|
|
307
381
|
// The resource class export should still be present
|
|
308
382
|
expect(content).toContain("export { Events } from './events/events'");
|
|
@@ -324,6 +398,7 @@ describe('generateClient', () => {
|
|
|
324
398
|
],
|
|
325
399
|
},
|
|
326
400
|
],
|
|
401
|
+
sdk: defaultSdkBehavior(),
|
|
327
402
|
};
|
|
328
403
|
|
|
329
404
|
const surface: ApiSurface = {
|
|
@@ -454,6 +529,7 @@ describe('generateClient', () => {
|
|
|
454
529
|
services: [service, enumService, dirService],
|
|
455
530
|
models: [model],
|
|
456
531
|
enums: [enumDef, aliasEnumDef],
|
|
532
|
+
sdk: defaultSdkBehavior(),
|
|
457
533
|
};
|
|
458
534
|
const enumCtx: EmitterContext = {
|
|
459
535
|
namespace: 'workos',
|
|
@@ -575,6 +651,7 @@ describe('generateClient', () => {
|
|
|
575
651
|
services: [connectionsService, radarService],
|
|
576
652
|
models: [connectionModel, radarModel],
|
|
577
653
|
enums: [],
|
|
654
|
+
sdk: defaultSdkBehavior(),
|
|
578
655
|
};
|
|
579
656
|
|
|
580
657
|
const coveredCtx: EmitterContext = {
|
|
@@ -715,6 +792,7 @@ describe('generateClient', () => {
|
|
|
715
792
|
services: [partialService],
|
|
716
793
|
models: [dirModel],
|
|
717
794
|
enums: [],
|
|
795
|
+
sdk: defaultSdkBehavior(),
|
|
718
796
|
};
|
|
719
797
|
|
|
720
798
|
const partialCtx: EmitterContext = {
|
|
@@ -818,6 +896,7 @@ describe('generateClient', () => {
|
|
|
818
896
|
services: [mfaService],
|
|
819
897
|
models: [mfaModel],
|
|
820
898
|
enums: [],
|
|
899
|
+
sdk: defaultSdkBehavior(),
|
|
821
900
|
};
|
|
822
901
|
|
|
823
902
|
const namingOnlyCtx: EmitterContext = {
|
|
@@ -859,6 +938,7 @@ describe('isServiceCoveredByExisting', () => {
|
|
|
859
938
|
services: [],
|
|
860
939
|
models: [],
|
|
861
940
|
enums: [],
|
|
941
|
+
sdk: defaultSdkBehavior(),
|
|
862
942
|
};
|
|
863
943
|
|
|
864
944
|
it('returns false when no overlay is provided', () => {
|
package/test/node/enums.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { generateEnums } from '../../src/node/enums.js';
|
|
3
3
|
import type { EmitterContext, ApiSpec, Enum, Service } from '@workos/oagen';
|
|
4
|
+
import { defaultSdkBehavior } from '@workos/oagen';
|
|
4
5
|
|
|
5
6
|
const emptySpec: ApiSpec = {
|
|
6
7
|
name: 'Test',
|
|
@@ -9,6 +10,7 @@ const emptySpec: ApiSpec = {
|
|
|
9
10
|
services: [],
|
|
10
11
|
models: [],
|
|
11
12
|
enums: [],
|
|
13
|
+
sdk: defaultSdkBehavior(),
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
const ctx: EmitterContext = {
|
package/test/node/errors.test.ts
CHANGED
|
@@ -2,47 +2,8 @@ import { describe, it, expect } from 'vitest';
|
|
|
2
2
|
import { generateErrors } from '../../src/node/errors.js';
|
|
3
3
|
|
|
4
4
|
describe('generateErrors', () => {
|
|
5
|
-
it('
|
|
5
|
+
it('returns empty array without context (static exceptions now hand-maintained)', () => {
|
|
6
6
|
const files = generateErrors();
|
|
7
|
-
|
|
8
|
-
const names = files.map((f) => f.path);
|
|
9
|
-
expect(names).toContain('src/common/exceptions/bad-request.exception.ts');
|
|
10
|
-
expect(names).toContain('src/common/exceptions/unauthorized.exception.ts');
|
|
11
|
-
expect(names).toContain('src/common/exceptions/not-found.exception.ts');
|
|
12
|
-
expect(names).toContain('src/common/exceptions/conflict.exception.ts');
|
|
13
|
-
expect(names).toContain('src/common/exceptions/unprocessable-entity.exception.ts');
|
|
14
|
-
expect(names).toContain('src/common/exceptions/rate-limit-exceeded.exception.ts');
|
|
15
|
-
expect(names).toContain('src/common/exceptions/generic-server.exception.ts');
|
|
16
|
-
expect(names).toContain('src/common/exceptions/no-api-key-provided.exception.ts');
|
|
17
|
-
expect(names).toContain('src/common/exceptions/index.ts');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('generates NotFoundException with correct status', () => {
|
|
21
|
-
const files = generateErrors();
|
|
22
|
-
const notFoundFile = files.find((f) => f.path.includes('not-found.exception.ts'))!;
|
|
23
|
-
|
|
24
|
-
expect(notFoundFile.content).toContain('export class NotFoundException extends Error');
|
|
25
|
-
expect(notFoundFile.content).toContain('readonly status = 404;');
|
|
26
|
-
expect(notFoundFile.content).toContain('requestID: string');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('generates RateLimitExceededException with retryAfter', () => {
|
|
30
|
-
const files = generateErrors();
|
|
31
|
-
const rateLimitFile = files.find((f) => f.path.includes('rate-limit-exceeded.exception.ts'))!;
|
|
32
|
-
|
|
33
|
-
expect(rateLimitFile.content).toContain('export class RateLimitExceededException extends Error');
|
|
34
|
-
expect(rateLimitFile.content).toContain('readonly status = 429;');
|
|
35
|
-
expect(rateLimitFile.content).toContain('retryAfter?: number');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('generates exception barrel with all exports', () => {
|
|
39
|
-
const files = generateErrors();
|
|
40
|
-
const barrel = files.find((f) => f.path === 'src/common/exceptions/index.ts')!;
|
|
41
|
-
|
|
42
|
-
expect(barrel.content).toContain('export { BadRequestException }');
|
|
43
|
-
expect(barrel.content).toContain('export { UnauthorizedException }');
|
|
44
|
-
expect(barrel.content).toContain('export { NotFoundException }');
|
|
45
|
-
expect(barrel.content).toContain('export { RateLimitExceededException }');
|
|
46
|
-
expect(barrel.content).toContain('export { NoApiKeyProvidedException }');
|
|
7
|
+
expect(files).toEqual([]);
|
|
47
8
|
});
|
|
48
9
|
});
|
package/test/node/models.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { generateModels } from '../../src/node/models.js';
|
|
3
3
|
import type { EmitterContext, ApiSpec, Model, Service } from '@workos/oagen';
|
|
4
|
+
import { defaultSdkBehavior } from '@workos/oagen';
|
|
4
5
|
|
|
5
6
|
const emptySpec: ApiSpec = {
|
|
6
7
|
name: 'Test',
|
|
@@ -9,6 +10,7 @@ const emptySpec: ApiSpec = {
|
|
|
9
10
|
services: [],
|
|
10
11
|
models: [],
|
|
11
12
|
enums: [],
|
|
13
|
+
sdk: defaultSdkBehavior(),
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
const ctx: EmitterContext = {
|
package/test/node/naming.test.ts
CHANGED
|
@@ -10,10 +10,31 @@ import {
|
|
|
10
10
|
servicePropertyName,
|
|
11
11
|
resolveServiceName,
|
|
12
12
|
buildServiceNameMap,
|
|
13
|
+
stripNoiseSuffixes,
|
|
13
14
|
} from '../../src/node/naming.js';
|
|
14
15
|
import type { EmitterContext, ApiSpec, Service } from '@workos/oagen';
|
|
16
|
+
import { defaultSdkBehavior } from '@workos/oagen';
|
|
15
17
|
|
|
16
18
|
describe('naming', () => {
|
|
19
|
+
describe('stripNoiseSuffixes', () => {
|
|
20
|
+
it('strips trailing Dto', () => {
|
|
21
|
+
expect(stripNoiseSuffixes('OrganizationDto')).toBe('Organization');
|
|
22
|
+
expect(stripNoiseSuffixes('UpdateOrganizationDto')).toBe('UpdateOrganization');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('is case-insensitive for the Dto suffix', () => {
|
|
26
|
+
expect(stripNoiseSuffixes('OrganizationDTO')).toBe('Organization');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('does not strip Dto from the middle of a name', () => {
|
|
30
|
+
expect(stripNoiseSuffixes('DtoFactory')).toBe('DtoFactory');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('leaves names without Dto unchanged', () => {
|
|
34
|
+
expect(stripNoiseSuffixes('Organization')).toBe('Organization');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
17
38
|
describe('className', () => {
|
|
18
39
|
it('converts to PascalCase', () => {
|
|
19
40
|
expect(className('organizations')).toBe('Organizations');
|
|
@@ -88,6 +109,7 @@ describe('naming', () => {
|
|
|
88
109
|
services: [],
|
|
89
110
|
models: [],
|
|
90
111
|
enums: [],
|
|
112
|
+
sdk: defaultSdkBehavior(),
|
|
91
113
|
};
|
|
92
114
|
|
|
93
115
|
it('returns overlay class name when available', () => {
|
|
@@ -160,6 +182,7 @@ describe('naming', () => {
|
|
|
160
182
|
services: [],
|
|
161
183
|
models: [],
|
|
162
184
|
enums: [],
|
|
185
|
+
sdk: defaultSdkBehavior(),
|
|
163
186
|
};
|
|
164
187
|
|
|
165
188
|
it('maps IR names to resolved names', () => {
|