@workos/oagen-emitters 0.2.1 → 0.3.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 +8 -0
- package/README.md +129 -0
- package/dist/index.d.mts +10 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +11893 -3226
- package/dist/index.mjs.map +1 -1
- 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 +298 -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-go.ts +116 -42
- package/smoke/sdk-php.ts +28 -26
- package/smoke/sdk-python.ts +5 -2
- 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 +81 -0
- package/src/go/manifest.ts +36 -0
- package/src/go/models.ts +254 -0
- package/src/go/naming.ts +191 -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 +3 -0
- package/src/node/client.ts +78 -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 +2 -9
- package/src/node/models.ts +178 -21
- package/src/node/naming.ts +49 -111
- package/src/node/resources.ts +374 -364
- package/src/node/sdk-errors.ts +41 -0
- package/src/node/tests.ts +32 -12
- 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 +171 -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 +298 -0
- package/src/php/resources.ts +561 -0
- package/src/php/tests.ts +533 -0
- package/src/php/type-map.ts +90 -0
- package/src/php/utils.ts +18 -0
- package/src/php/wrappers.ts +151 -0
- package/src/python/client.ts +337 -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 +209 -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 +255 -0
- package/src/shared/naming-utils.ts +107 -0
- package/src/shared/non-spec-services.ts +54 -0
- package/src/shared/resolved-ops.ts +109 -0
- package/src/shared/wrapper-utils.ts +59 -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/node/client.test.ts +18 -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 +99 -69
- package/test/node/serializers.test.ts +3 -1
- package/test/node/type-map.test.ts +11 -0
- package/test/php/client.test.ts +94 -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 +644 -0
- package/test/php/tests.test.ts +118 -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,408 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import type { EmitterContext, ApiSpec, Service, Operation, Model } from '@workos/oagen';
|
|
3
|
+
import { defaultSdkBehavior } from '@workos/oagen';
|
|
4
|
+
import { generateResources } from '../../src/go/resources.js';
|
|
5
|
+
|
|
6
|
+
function makeSpec(services: Service[], models: Model[] = []): ApiSpec {
|
|
7
|
+
return {
|
|
8
|
+
name: 'Test',
|
|
9
|
+
version: '1.0.0',
|
|
10
|
+
baseUrl: '',
|
|
11
|
+
services,
|
|
12
|
+
models,
|
|
13
|
+
enums: [],
|
|
14
|
+
sdk: defaultSdkBehavior(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function makeCtx(spec: ApiSpec): EmitterContext {
|
|
19
|
+
return {
|
|
20
|
+
namespace: 'workos',
|
|
21
|
+
namespacePascal: 'WorkOS',
|
|
22
|
+
spec,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function makeOp(overrides: Partial<Operation>): Operation {
|
|
27
|
+
return {
|
|
28
|
+
name: 'listOrganizations',
|
|
29
|
+
httpMethod: 'get',
|
|
30
|
+
path: '/organizations',
|
|
31
|
+
pathParams: [],
|
|
32
|
+
queryParams: [],
|
|
33
|
+
headerParams: [],
|
|
34
|
+
requestBody: undefined,
|
|
35
|
+
response: { kind: 'model', name: 'Organization' },
|
|
36
|
+
errors: [],
|
|
37
|
+
injectIdempotencyKey: false,
|
|
38
|
+
...overrides,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('go/resources', () => {
|
|
43
|
+
it('returns empty for no services', () => {
|
|
44
|
+
const spec = makeSpec([]);
|
|
45
|
+
expect(generateResources([], makeCtx(spec))).toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('generates a service file with methods', () => {
|
|
49
|
+
const services: Service[] = [
|
|
50
|
+
{
|
|
51
|
+
name: 'Organizations',
|
|
52
|
+
operations: [
|
|
53
|
+
makeOp({
|
|
54
|
+
name: 'listOrganizations',
|
|
55
|
+
httpMethod: 'get',
|
|
56
|
+
path: '/organizations',
|
|
57
|
+
queryParams: [
|
|
58
|
+
{
|
|
59
|
+
name: 'limit',
|
|
60
|
+
type: { kind: 'primitive', type: 'integer' },
|
|
61
|
+
required: false,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
pagination: {
|
|
65
|
+
strategy: 'cursor',
|
|
66
|
+
param: 'after',
|
|
67
|
+
dataPath: 'data',
|
|
68
|
+
itemType: { kind: 'model', name: 'Organization' },
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
makeOp({
|
|
72
|
+
name: 'getOrganization',
|
|
73
|
+
httpMethod: 'get',
|
|
74
|
+
path: '/organizations/{id}',
|
|
75
|
+
pathParams: [
|
|
76
|
+
{
|
|
77
|
+
name: 'id',
|
|
78
|
+
type: { kind: 'primitive', type: 'string' },
|
|
79
|
+
required: true,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
}),
|
|
83
|
+
makeOp({
|
|
84
|
+
name: 'createOrganization',
|
|
85
|
+
httpMethod: 'post',
|
|
86
|
+
path: '/organizations',
|
|
87
|
+
requestBody: { kind: 'model', name: 'CreateOrganizationRequest' },
|
|
88
|
+
}),
|
|
89
|
+
makeOp({
|
|
90
|
+
name: 'deleteOrganization',
|
|
91
|
+
httpMethod: 'delete',
|
|
92
|
+
path: '/organizations/{id}',
|
|
93
|
+
pathParams: [
|
|
94
|
+
{
|
|
95
|
+
name: 'id',
|
|
96
|
+
type: { kind: 'primitive', type: 'string' },
|
|
97
|
+
required: true,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
response: { kind: 'primitive', type: 'unknown' },
|
|
101
|
+
}),
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
const spec = makeSpec(services, [
|
|
106
|
+
{
|
|
107
|
+
name: 'CreateOrganizationRequest',
|
|
108
|
+
fields: [{ name: 'name', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
109
|
+
},
|
|
110
|
+
]);
|
|
111
|
+
const ctx = makeCtx(spec);
|
|
112
|
+
const files = generateResources(services, ctx);
|
|
113
|
+
|
|
114
|
+
expect(files.length).toBeGreaterThanOrEqual(1);
|
|
115
|
+
const content = files[0].content;
|
|
116
|
+
expect(content).toContain('package workos');
|
|
117
|
+
expect(content).toContain('type organizationService struct {');
|
|
118
|
+
expect(content).toContain('Limit *int `url:"limit,omitempty" json:"-"`');
|
|
119
|
+
expect(content).toContain('func (s *organizationService) List(');
|
|
120
|
+
expect(content).toContain('func (s *organizationService) Get(');
|
|
121
|
+
expect(content).toContain('func (s *organizationService) Create(');
|
|
122
|
+
expect(content).toContain('func (s *organizationService) Delete(');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('generates path interpolation with fmt.Sprintf', () => {
|
|
126
|
+
const services: Service[] = [
|
|
127
|
+
{
|
|
128
|
+
name: 'Users',
|
|
129
|
+
operations: [
|
|
130
|
+
makeOp({
|
|
131
|
+
name: 'getUser',
|
|
132
|
+
httpMethod: 'get',
|
|
133
|
+
path: '/users/{id}',
|
|
134
|
+
pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
135
|
+
}),
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
const spec = makeSpec(services);
|
|
140
|
+
const files = generateResources(services, makeCtx(spec));
|
|
141
|
+
const content = files[0].content;
|
|
142
|
+
expect(content).toContain('fmt.Sprintf("/users/%s", id)');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('generates paginated methods returning Iterator', () => {
|
|
146
|
+
const services: Service[] = [
|
|
147
|
+
{
|
|
148
|
+
name: 'Users',
|
|
149
|
+
operations: [
|
|
150
|
+
makeOp({
|
|
151
|
+
name: 'listUsers',
|
|
152
|
+
httpMethod: 'get',
|
|
153
|
+
path: '/users',
|
|
154
|
+
pagination: {
|
|
155
|
+
strategy: 'cursor',
|
|
156
|
+
param: 'after',
|
|
157
|
+
dataPath: 'data',
|
|
158
|
+
itemType: { kind: 'model', name: 'User' },
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
const spec = makeSpec(services);
|
|
165
|
+
const files = generateResources(services, makeCtx(spec));
|
|
166
|
+
const content = files[0].content;
|
|
167
|
+
expect(content).toContain('*Iterator[User]');
|
|
168
|
+
expect(content).toContain('newIterator[User](ctx, s.client, "GET", "/users", nil, "after", "data", opts)');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('generates delete methods returning error', () => {
|
|
172
|
+
const services: Service[] = [
|
|
173
|
+
{
|
|
174
|
+
name: 'Items',
|
|
175
|
+
operations: [
|
|
176
|
+
makeOp({
|
|
177
|
+
name: 'deleteItem',
|
|
178
|
+
httpMethod: 'delete',
|
|
179
|
+
path: '/items/{id}',
|
|
180
|
+
pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
181
|
+
response: { kind: 'primitive', type: 'unknown' },
|
|
182
|
+
}),
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
const spec = makeSpec(services);
|
|
187
|
+
const files = generateResources(services, makeCtx(spec));
|
|
188
|
+
const content = files[0].content;
|
|
189
|
+
expect(content).toContain(') error {');
|
|
190
|
+
expect(content).toContain('return err');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('generates params struct for body + query params', () => {
|
|
194
|
+
const services: Service[] = [
|
|
195
|
+
{
|
|
196
|
+
name: 'Users',
|
|
197
|
+
operations: [
|
|
198
|
+
makeOp({
|
|
199
|
+
name: 'createUser',
|
|
200
|
+
httpMethod: 'post',
|
|
201
|
+
path: '/users',
|
|
202
|
+
requestBody: { kind: 'model', name: 'CreateUserRequest' },
|
|
203
|
+
queryParams: [
|
|
204
|
+
{
|
|
205
|
+
name: 'notify',
|
|
206
|
+
type: { kind: 'primitive', type: 'boolean' },
|
|
207
|
+
required: false,
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
}),
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
];
|
|
214
|
+
const spec = makeSpec(services, [
|
|
215
|
+
{
|
|
216
|
+
name: 'CreateUserRequest',
|
|
217
|
+
fields: [{ name: 'email', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
218
|
+
},
|
|
219
|
+
]);
|
|
220
|
+
const files = generateResources(services, makeCtx(spec));
|
|
221
|
+
const content = files[0].content;
|
|
222
|
+
expect(content).toContain('type UsersCreateParams struct {');
|
|
223
|
+
expect(content).toContain('Email string `json:"email"`');
|
|
224
|
+
expect(content).toContain('Notify *bool `url:"notify,omitempty" json:"-"`');
|
|
225
|
+
expect(content).toContain('request(ctx, "POST", "/users", params, params, &result, opts)');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('emits Deprecated comment for deprecated body field in params struct', () => {
|
|
229
|
+
const services: Service[] = [
|
|
230
|
+
{
|
|
231
|
+
name: 'Users',
|
|
232
|
+
operations: [
|
|
233
|
+
makeOp({
|
|
234
|
+
name: 'createUser',
|
|
235
|
+
httpMethod: 'post',
|
|
236
|
+
path: '/users',
|
|
237
|
+
requestBody: { kind: 'model', name: 'CreateUserRequest' },
|
|
238
|
+
}),
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
];
|
|
242
|
+
const spec = makeSpec(services, [
|
|
243
|
+
{
|
|
244
|
+
name: 'CreateUserRequest',
|
|
245
|
+
fields: [
|
|
246
|
+
{
|
|
247
|
+
name: 'email',
|
|
248
|
+
type: { kind: 'primitive', type: 'string' },
|
|
249
|
+
required: true,
|
|
250
|
+
description: 'The user email.',
|
|
251
|
+
deprecated: true,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: 'name',
|
|
255
|
+
type: { kind: 'primitive', type: 'string' },
|
|
256
|
+
required: true,
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
]);
|
|
261
|
+
const files = generateResources(services, makeCtx(spec));
|
|
262
|
+
const content = files[0].content;
|
|
263
|
+
expect(content).toContain('\t// Email is the user email.\n\t//\n\t// Deprecated: this field is deprecated.');
|
|
264
|
+
expect(content).not.toMatch(/Deprecated.*\n\tName/);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('emits Deprecated comment for deprecated query param in params struct', () => {
|
|
268
|
+
const services: Service[] = [
|
|
269
|
+
{
|
|
270
|
+
name: 'Users',
|
|
271
|
+
operations: [
|
|
272
|
+
makeOp({
|
|
273
|
+
name: 'listUsers',
|
|
274
|
+
httpMethod: 'get',
|
|
275
|
+
path: '/users',
|
|
276
|
+
queryParams: [
|
|
277
|
+
{
|
|
278
|
+
name: 'old_filter',
|
|
279
|
+
type: { kind: 'primitive', type: 'string' },
|
|
280
|
+
required: false,
|
|
281
|
+
description: 'A legacy filter.',
|
|
282
|
+
deprecated: true,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: 'limit',
|
|
286
|
+
type: { kind: 'primitive', type: 'integer' },
|
|
287
|
+
required: false,
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
}),
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
];
|
|
294
|
+
const spec = makeSpec(services);
|
|
295
|
+
const files = generateResources(services, makeCtx(spec));
|
|
296
|
+
const content = files[0].content;
|
|
297
|
+
expect(content).toContain(
|
|
298
|
+
'\t// OldFilter is a legacy filter.\n\t//\n\t// Deprecated: this parameter is deprecated.',
|
|
299
|
+
);
|
|
300
|
+
expect(content).not.toMatch(/Deprecated.*\n\tLimit/);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('emits Deprecated note in godoc for deprecated path param', () => {
|
|
304
|
+
const services: Service[] = [
|
|
305
|
+
{
|
|
306
|
+
name: 'Items',
|
|
307
|
+
operations: [
|
|
308
|
+
makeOp({
|
|
309
|
+
name: 'getItem',
|
|
310
|
+
httpMethod: 'get',
|
|
311
|
+
path: '/items/{old_id}',
|
|
312
|
+
pathParams: [
|
|
313
|
+
{
|
|
314
|
+
name: 'old_id',
|
|
315
|
+
type: { kind: 'primitive', type: 'string' },
|
|
316
|
+
required: true,
|
|
317
|
+
deprecated: true,
|
|
318
|
+
description: 'use new_id instead',
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
}),
|
|
322
|
+
],
|
|
323
|
+
},
|
|
324
|
+
];
|
|
325
|
+
const spec = makeSpec(services);
|
|
326
|
+
const files = generateResources(services, makeCtx(spec));
|
|
327
|
+
const content = files[0].content;
|
|
328
|
+
expect(content).toContain('// Deprecated parameter OldID: use new_id instead');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('emits Deprecated note in godoc for deprecated path param without description', () => {
|
|
332
|
+
const services: Service[] = [
|
|
333
|
+
{
|
|
334
|
+
name: 'Items',
|
|
335
|
+
operations: [
|
|
336
|
+
makeOp({
|
|
337
|
+
name: 'getItem',
|
|
338
|
+
httpMethod: 'get',
|
|
339
|
+
path: '/items/{old_id}',
|
|
340
|
+
pathParams: [
|
|
341
|
+
{
|
|
342
|
+
name: 'old_id',
|
|
343
|
+
type: { kind: 'primitive', type: 'string' },
|
|
344
|
+
required: true,
|
|
345
|
+
deprecated: true,
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
}),
|
|
349
|
+
],
|
|
350
|
+
},
|
|
351
|
+
];
|
|
352
|
+
const spec = makeSpec(services);
|
|
353
|
+
const files = generateResources(services, makeCtx(spec));
|
|
354
|
+
const content = files[0].content;
|
|
355
|
+
expect(content).toContain('// Deprecated parameter OldID.');
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('emits Deprecated in godoc for deprecated operation', () => {
|
|
359
|
+
const services: Service[] = [
|
|
360
|
+
{
|
|
361
|
+
name: 'Items',
|
|
362
|
+
operations: [
|
|
363
|
+
makeOp({
|
|
364
|
+
name: 'getItem',
|
|
365
|
+
httpMethod: 'get',
|
|
366
|
+
path: '/items/{id}',
|
|
367
|
+
pathParams: [
|
|
368
|
+
{
|
|
369
|
+
name: 'id',
|
|
370
|
+
type: { kind: 'primitive', type: 'string' },
|
|
371
|
+
required: true,
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
deprecated: true,
|
|
375
|
+
description: 'Get an item.',
|
|
376
|
+
}),
|
|
377
|
+
],
|
|
378
|
+
},
|
|
379
|
+
];
|
|
380
|
+
const spec = makeSpec(services);
|
|
381
|
+
const files = generateResources(services, makeCtx(spec));
|
|
382
|
+
const content = files[0].content;
|
|
383
|
+
expect(content).toContain('// Deprecated: this operation is deprecated.');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('uses params.Body for non-model request bodies', () => {
|
|
387
|
+
const services: Service[] = [
|
|
388
|
+
{
|
|
389
|
+
name: 'Connect',
|
|
390
|
+
operations: [
|
|
391
|
+
makeOp({
|
|
392
|
+
name: 'createApplications',
|
|
393
|
+
httpMethod: 'post',
|
|
394
|
+
path: '/connect/applications',
|
|
395
|
+
requestBody: { kind: 'primitive', type: 'unknown' },
|
|
396
|
+
response: { kind: 'model', name: 'ConnectApplication' },
|
|
397
|
+
}),
|
|
398
|
+
],
|
|
399
|
+
},
|
|
400
|
+
];
|
|
401
|
+
const spec = makeSpec(services);
|
|
402
|
+
const files = generateResources(services, makeCtx(spec));
|
|
403
|
+
const content = files[0].content;
|
|
404
|
+
expect(content).toContain('type ConnectCreateApplicationsParams struct {');
|
|
405
|
+
expect(content).toContain('Body interface{} `json:"-"`');
|
|
406
|
+
expect(content).toContain('request(ctx, "POST", "/connect/applications", nil, params.Body, &result, opts)');
|
|
407
|
+
});
|
|
408
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import type { EmitterContext, ApiSpec, Service, Operation, Model } from '@workos/oagen';
|
|
3
|
+
import { defaultSdkBehavior } from '@workos/oagen';
|
|
4
|
+
import { generateTests } from '../../src/go/tests.js';
|
|
5
|
+
|
|
6
|
+
function makeOp(overrides: Partial<Operation>): Operation {
|
|
7
|
+
return {
|
|
8
|
+
name: 'getOrganization',
|
|
9
|
+
httpMethod: 'get',
|
|
10
|
+
path: '/organizations/{id}',
|
|
11
|
+
pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
12
|
+
queryParams: [],
|
|
13
|
+
headerParams: [],
|
|
14
|
+
requestBody: undefined,
|
|
15
|
+
response: { kind: 'model', name: 'Organization' },
|
|
16
|
+
errors: [],
|
|
17
|
+
injectIdempotencyKey: false,
|
|
18
|
+
...overrides,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function makeSpec(services: Service[], models: Model[] = []): ApiSpec {
|
|
23
|
+
return {
|
|
24
|
+
name: 'Test',
|
|
25
|
+
version: '1.0.0',
|
|
26
|
+
baseUrl: '',
|
|
27
|
+
services,
|
|
28
|
+
models: [
|
|
29
|
+
{
|
|
30
|
+
name: 'Organization',
|
|
31
|
+
fields: [
|
|
32
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
33
|
+
{ name: 'name', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
...models,
|
|
37
|
+
],
|
|
38
|
+
enums: [],
|
|
39
|
+
sdk: defaultSdkBehavior(),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function makeCtx(spec: ApiSpec): EmitterContext {
|
|
44
|
+
return {
|
|
45
|
+
namespace: 'workos',
|
|
46
|
+
namespacePascal: 'WorkOS',
|
|
47
|
+
spec,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe('go/tests', () => {
|
|
52
|
+
it('generates test files and fixtures', () => {
|
|
53
|
+
const services: Service[] = [
|
|
54
|
+
{
|
|
55
|
+
name: 'Organizations',
|
|
56
|
+
operations: [makeOp({})],
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
const spec = makeSpec(services);
|
|
60
|
+
const files = generateTests(spec, makeCtx(spec));
|
|
61
|
+
|
|
62
|
+
// Should have fixture files and test files
|
|
63
|
+
const testFiles = files.filter((f) => f.path.endsWith('_test.go'));
|
|
64
|
+
const fixtureFiles = files.filter((f) => f.path.startsWith('testdata/'));
|
|
65
|
+
|
|
66
|
+
expect(testFiles.length).toBeGreaterThanOrEqual(1);
|
|
67
|
+
expect(fixtureFiles.length).toBeGreaterThanOrEqual(1);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('generates httptest-based tests', () => {
|
|
71
|
+
const services: Service[] = [
|
|
72
|
+
{
|
|
73
|
+
name: 'Organizations',
|
|
74
|
+
operations: [makeOp({})],
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
const spec = makeSpec(services);
|
|
78
|
+
const files = generateTests(spec, makeCtx(spec));
|
|
79
|
+
const testFile = files.find((f) => f.path.endsWith('_test.go') && !f.path.includes('helpers'))!;
|
|
80
|
+
const content = testFile.content;
|
|
81
|
+
|
|
82
|
+
expect(content).toContain('package workos_test');
|
|
83
|
+
expect(content).toContain('httptest.NewServer');
|
|
84
|
+
expect(content).toContain('require.Equal');
|
|
85
|
+
expect(content).toContain('workos.NewClient');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('generates shared test helpers file', () => {
|
|
89
|
+
const services: Service[] = [
|
|
90
|
+
{
|
|
91
|
+
name: 'Organizations',
|
|
92
|
+
operations: [makeOp({})],
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
const spec = makeSpec(services);
|
|
96
|
+
const files = generateTests(spec, makeCtx(spec));
|
|
97
|
+
const helperFile = files.find((f) => f.path === 'helpers_test.go')!;
|
|
98
|
+
expect(helperFile).toBeDefined();
|
|
99
|
+
expect(helperFile.content).toContain('ptrString');
|
|
100
|
+
expect(helperFile.content).toContain('ptrInt');
|
|
101
|
+
expect(helperFile.content).toContain('setupTestServer');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('generates error test for 404 and 422', () => {
|
|
105
|
+
const services: Service[] = [
|
|
106
|
+
{
|
|
107
|
+
name: 'Organizations',
|
|
108
|
+
operations: [makeOp({})],
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
const spec = makeSpec(services);
|
|
112
|
+
const files = generateTests(spec, makeCtx(spec));
|
|
113
|
+
const testFile = files.find((f) => f.path.endsWith('_test.go') && !f.path.includes('helpers'))!;
|
|
114
|
+
const content = testFile.content;
|
|
115
|
+
|
|
116
|
+
expect(content).toContain('Error404');
|
|
117
|
+
expect(content).toContain('NotFoundError');
|
|
118
|
+
expect(content).toContain('Error422');
|
|
119
|
+
expect(content).toContain('UnprocessableEntityError');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('generates delete operation tests with no response assertion', () => {
|
|
123
|
+
const services: Service[] = [
|
|
124
|
+
{
|
|
125
|
+
name: 'Organizations',
|
|
126
|
+
operations: [
|
|
127
|
+
makeOp({
|
|
128
|
+
name: 'deleteOrganization',
|
|
129
|
+
httpMethod: 'delete',
|
|
130
|
+
response: { kind: 'primitive', type: 'unknown' },
|
|
131
|
+
}),
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
const spec = makeSpec(services);
|
|
136
|
+
const files = generateTests(spec, makeCtx(spec));
|
|
137
|
+
const testFile = files.find((f) => f.path.endsWith('_test.go') && !f.path.includes('helpers'))!;
|
|
138
|
+
const content = testFile.content;
|
|
139
|
+
|
|
140
|
+
expect(content).toContain('StatusNoContent');
|
|
141
|
+
expect(content).toContain('require.NoError(t, err)');
|
|
142
|
+
});
|
|
143
|
+
});
|
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 = {
|
|
@@ -261,6 +259,7 @@ describe('generateClient', () => {
|
|
|
261
259
|
services: [eventService],
|
|
262
260
|
models: [eventModel, otherModel],
|
|
263
261
|
enums: [],
|
|
262
|
+
sdk: defaultSdkBehavior(),
|
|
264
263
|
};
|
|
265
264
|
|
|
266
265
|
const surface: ApiSurface = {
|
|
@@ -300,9 +299,10 @@ describe('generateClient', () => {
|
|
|
300
299
|
expect(content).not.toContain('export type { Event,');
|
|
301
300
|
expect(content).not.toContain('export type { Event }');
|
|
302
301
|
|
|
303
|
-
// EventCursor is
|
|
304
|
-
//
|
|
305
|
-
expect(content).toContain("export * from './common/interfaces'");
|
|
302
|
+
// EventCursor is unreachable (not referenced by any service), so it should
|
|
303
|
+
// NOT be exported — oagen only generates interface files for reachable models
|
|
304
|
+
expect(content).not.toContain("export * from './common/interfaces'");
|
|
305
|
+
expect(content).not.toContain('EventCursor');
|
|
306
306
|
|
|
307
307
|
// The resource class export should still be present
|
|
308
308
|
expect(content).toContain("export { Events } from './events/events'");
|
|
@@ -324,6 +324,7 @@ describe('generateClient', () => {
|
|
|
324
324
|
],
|
|
325
325
|
},
|
|
326
326
|
],
|
|
327
|
+
sdk: defaultSdkBehavior(),
|
|
327
328
|
};
|
|
328
329
|
|
|
329
330
|
const surface: ApiSurface = {
|
|
@@ -454,6 +455,7 @@ describe('generateClient', () => {
|
|
|
454
455
|
services: [service, enumService, dirService],
|
|
455
456
|
models: [model],
|
|
456
457
|
enums: [enumDef, aliasEnumDef],
|
|
458
|
+
sdk: defaultSdkBehavior(),
|
|
457
459
|
};
|
|
458
460
|
const enumCtx: EmitterContext = {
|
|
459
461
|
namespace: 'workos',
|
|
@@ -575,6 +577,7 @@ describe('generateClient', () => {
|
|
|
575
577
|
services: [connectionsService, radarService],
|
|
576
578
|
models: [connectionModel, radarModel],
|
|
577
579
|
enums: [],
|
|
580
|
+
sdk: defaultSdkBehavior(),
|
|
578
581
|
};
|
|
579
582
|
|
|
580
583
|
const coveredCtx: EmitterContext = {
|
|
@@ -715,6 +718,7 @@ describe('generateClient', () => {
|
|
|
715
718
|
services: [partialService],
|
|
716
719
|
models: [dirModel],
|
|
717
720
|
enums: [],
|
|
721
|
+
sdk: defaultSdkBehavior(),
|
|
718
722
|
};
|
|
719
723
|
|
|
720
724
|
const partialCtx: EmitterContext = {
|
|
@@ -818,6 +822,7 @@ describe('generateClient', () => {
|
|
|
818
822
|
services: [mfaService],
|
|
819
823
|
models: [mfaModel],
|
|
820
824
|
enums: [],
|
|
825
|
+
sdk: defaultSdkBehavior(),
|
|
821
826
|
};
|
|
822
827
|
|
|
823
828
|
const namingOnlyCtx: EmitterContext = {
|
|
@@ -859,6 +864,7 @@ describe('isServiceCoveredByExisting', () => {
|
|
|
859
864
|
services: [],
|
|
860
865
|
models: [],
|
|
861
866
|
enums: [],
|
|
867
|
+
sdk: defaultSdkBehavior(),
|
|
862
868
|
};
|
|
863
869
|
|
|
864
870
|
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 = {
|