@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,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
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { generateModels } from '../../src/kotlin/models.js';
|
|
3
|
+
import { generateEnums } from '../../src/kotlin/enums.js';
|
|
4
|
+
import type { EmitterContext, ApiSpec, Model } from '@workos/oagen';
|
|
5
|
+
import { defaultSdkBehavior } from '@workos/oagen';
|
|
6
|
+
|
|
7
|
+
const emptySpec: ApiSpec = {
|
|
8
|
+
name: 'Test',
|
|
9
|
+
version: '1.0.0',
|
|
10
|
+
baseUrl: '',
|
|
11
|
+
services: [],
|
|
12
|
+
models: [],
|
|
13
|
+
enums: [],
|
|
14
|
+
sdk: defaultSdkBehavior(),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const ctx: EmitterContext = {
|
|
18
|
+
namespace: 'workos',
|
|
19
|
+
namespacePascal: 'WorkOS',
|
|
20
|
+
spec: emptySpec,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe('kotlin/models', () => {
|
|
24
|
+
it('returns empty for no models', () => {
|
|
25
|
+
generateEnums([], ctx);
|
|
26
|
+
expect(generateModels([], ctx)).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('generates a Kotlin data class with Jackson annotations', () => {
|
|
30
|
+
const models: Model[] = [
|
|
31
|
+
{
|
|
32
|
+
name: 'Organization',
|
|
33
|
+
fields: [
|
|
34
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
35
|
+
{ name: 'name', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
36
|
+
{
|
|
37
|
+
name: 'created_at',
|
|
38
|
+
type: { kind: 'primitive', type: 'string', format: 'date-time' },
|
|
39
|
+
required: true,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'external_id',
|
|
43
|
+
type: { kind: 'nullable', inner: { kind: 'primitive', type: 'string' } },
|
|
44
|
+
required: false,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
generateEnums([], ctx);
|
|
51
|
+
const files = generateModels(models, { ...ctx, spec: { ...emptySpec, models } });
|
|
52
|
+
|
|
53
|
+
expect(files.length).toBeGreaterThanOrEqual(1);
|
|
54
|
+
const modelFile = files.find((f) => f.path.includes('Organization.kt'))!;
|
|
55
|
+
expect(modelFile).toBeDefined();
|
|
56
|
+
|
|
57
|
+
const content = modelFile.content;
|
|
58
|
+
expect(content).toContain('data class Organization');
|
|
59
|
+
expect(content).toContain('@JsonProperty("id")');
|
|
60
|
+
expect(content).toContain('@JvmField');
|
|
61
|
+
expect(content).toContain('OffsetDateTime');
|
|
62
|
+
expect(content).toContain('externalId: String?');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('skips list wrapper and list metadata models', () => {
|
|
66
|
+
const models: Model[] = [
|
|
67
|
+
{
|
|
68
|
+
name: 'Organization',
|
|
69
|
+
fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'OrganizationList',
|
|
73
|
+
fields: [
|
|
74
|
+
{
|
|
75
|
+
name: 'data',
|
|
76
|
+
type: { kind: 'array', items: { kind: 'model', name: 'Organization' } },
|
|
77
|
+
required: true,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'list_metadata',
|
|
81
|
+
type: { kind: 'model', name: 'ListMetadata' },
|
|
82
|
+
required: true,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'ListMetadata',
|
|
88
|
+
fields: [
|
|
89
|
+
{ name: 'before', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
90
|
+
{ name: 'after', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
generateEnums([], ctx);
|
|
96
|
+
const files = generateModels(models, { ...ctx, spec: { ...emptySpec, models } });
|
|
97
|
+
const filePaths = files.map((f) => f.path);
|
|
98
|
+
|
|
99
|
+
expect(filePaths.some((p) => p.includes('Organization.kt') && !p.includes('List'))).toBe(true);
|
|
100
|
+
expect(filePaths.some((p) => p.includes('OrganizationList.kt'))).toBe(false);
|
|
101
|
+
expect(filePaths.some((p) => p.includes('ListMetadata.kt'))).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('deduplicates structurally identical models preferring shorter names', () => {
|
|
105
|
+
const models: Model[] = [
|
|
106
|
+
{
|
|
107
|
+
name: 'EmailChangeConfirmationUser',
|
|
108
|
+
fields: [
|
|
109
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
110
|
+
{ name: 'email', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'User',
|
|
115
|
+
fields: [
|
|
116
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
117
|
+
{ name: 'email', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
generateEnums([], ctx);
|
|
123
|
+
const files = generateModels(models, { ...ctx, spec: { ...emptySpec, models } });
|
|
124
|
+
|
|
125
|
+
// User should be the canonical (shorter name) — a data class
|
|
126
|
+
const userFile = files.find((f) => f.path.includes('/User.kt'))!;
|
|
127
|
+
expect(userFile).toBeDefined();
|
|
128
|
+
expect(userFile.content).toContain('data class User');
|
|
129
|
+
|
|
130
|
+
// EmailChangeConfirmationUser should be the typealias
|
|
131
|
+
const aliasFile = files.find((f) => f.path.includes('/EmailChangeConfirmationUser.kt'))!;
|
|
132
|
+
expect(aliasFile).toBeDefined();
|
|
133
|
+
expect(aliasFile.content).toContain('typealias EmailChangeConfirmationUser = User');
|
|
134
|
+
});
|
|
135
|
+
});
|