@workos/oagen-emitters 0.0.1 → 0.2.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/.github/workflows/release-please.yml +9 -1
- package/.husky/commit-msg +0 -0
- package/.husky/pre-commit +1 -0
- package/.husky/pre-push +1 -0
- package/.prettierignore +1 -0
- package/.release-please-manifest.json +3 -0
- package/.vscode/settings.json +3 -0
- package/CHANGELOG.md +54 -0
- package/README.md +2 -2
- package/dist/index.d.mts +7 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3522 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +14 -18
- package/release-please-config.json +11 -0
- package/src/node/client.ts +437 -204
- package/src/node/common.ts +74 -4
- package/src/node/config.ts +1 -0
- package/src/node/enums.ts +50 -6
- package/src/node/errors.ts +78 -3
- package/src/node/fixtures.ts +84 -15
- package/src/node/index.ts +2 -2
- package/src/node/manifest.ts +4 -2
- package/src/node/models.ts +195 -79
- package/src/node/naming.ts +16 -1
- package/src/node/resources.ts +721 -106
- package/src/node/serializers.ts +510 -52
- package/src/node/tests.ts +621 -105
- package/src/node/type-map.ts +89 -11
- package/src/node/utils.ts +377 -114
- package/test/node/client.test.ts +979 -15
- package/test/node/enums.test.ts +0 -1
- package/test/node/errors.test.ts +4 -21
- package/test/node/models.test.ts +409 -2
- package/test/node/naming.test.ts +0 -3
- package/test/node/resources.test.ts +964 -7
- package/test/node/serializers.test.ts +212 -3
- package/tsconfig.json +2 -3
- package/{tsup.config.ts → tsdown.config.ts} +1 -1
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -2158
package/test/node/client.test.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { generateClient } from '../../src/node/client.js';
|
|
3
|
-
import
|
|
3
|
+
import { isServiceCoveredByExisting } from '../../src/node/utils.js';
|
|
4
|
+
import type { EmitterContext, ApiSpec, Service, Model, Enum } from '@workos/oagen';
|
|
5
|
+
import type { ApiSurface } from '@workos/oagen/compat';
|
|
4
6
|
|
|
5
7
|
const service: Service = {
|
|
6
8
|
name: 'Organizations',
|
|
@@ -40,7 +42,6 @@ const ctx: EmitterContext = {
|
|
|
40
42
|
namespace: 'workos',
|
|
41
43
|
namespacePascal: 'WorkOS',
|
|
42
44
|
spec,
|
|
43
|
-
irVersion: 6,
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
describe('generateClient', () => {
|
|
@@ -50,11 +51,16 @@ describe('generateClient', () => {
|
|
|
50
51
|
expect(workosFile).toBeDefined();
|
|
51
52
|
|
|
52
53
|
const content = workosFile!.content;
|
|
53
|
-
expect(content).toContain('export class WorkOS {');
|
|
54
|
+
expect(content).toContain('export class WorkOS extends WorkOSBase {');
|
|
54
55
|
expect(content).toContain('readonly organizations = new Organizations(this);');
|
|
55
|
-
expect(content).toContain(
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
expect(content).toContain("import { WorkOSBase } from './common/workos-base';");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('allows workos.ts to participate in integration (no integrateTarget: false)', () => {
|
|
60
|
+
const files = generateClient(spec, ctx);
|
|
61
|
+
const workosFile = files.find((f) => f.path === 'src/workos.ts');
|
|
62
|
+
expect(workosFile).toBeDefined();
|
|
63
|
+
expect(workosFile!.integrateTarget).not.toBe(false);
|
|
58
64
|
});
|
|
59
65
|
|
|
60
66
|
it('generates barrel exports', () => {
|
|
@@ -66,10 +72,22 @@ describe('generateClient', () => {
|
|
|
66
72
|
expect(content).toContain("export * from './common/exceptions';");
|
|
67
73
|
expect(content).toContain("export { AutoPaginatable } from './common/utils/pagination';");
|
|
68
74
|
expect(content).toContain("export { WorkOS } from './workos';");
|
|
69
|
-
|
|
75
|
+
// Service types are now re-exported via the service barrel
|
|
76
|
+
expect(content).toContain("export * from './organizations/interfaces';");
|
|
77
|
+
expect(content).not.toContain('export type { Organization, OrganizationResponse }');
|
|
70
78
|
expect(content).toContain("export { Organizations } from './organizations/organizations';");
|
|
71
79
|
});
|
|
72
80
|
|
|
81
|
+
it('generates per-service barrel files', () => {
|
|
82
|
+
const files = generateClient(spec, ctx);
|
|
83
|
+
const serviceBarrel = files.find((f) => f.path === 'src/organizations/interfaces/index.ts');
|
|
84
|
+
expect(serviceBarrel).toBeDefined();
|
|
85
|
+
|
|
86
|
+
const content = serviceBarrel!.content;
|
|
87
|
+
expect(content).toContain("export * from './organization.interface';");
|
|
88
|
+
expect(serviceBarrel!.skipIfExists).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
73
91
|
it('generates package.json and tsconfig.json', () => {
|
|
74
92
|
const files = generateClient(spec, ctx);
|
|
75
93
|
const pkg = files.find((f) => f.path === 'package.json');
|
|
@@ -118,7 +136,6 @@ describe('generateClient', () => {
|
|
|
118
136
|
namespace: 'workos',
|
|
119
137
|
namespacePascal: 'WorkOS',
|
|
120
138
|
spec: overlaySpec,
|
|
121
|
-
irVersion: 6,
|
|
122
139
|
overlayLookup: {
|
|
123
140
|
methodByOperation: new Map([
|
|
124
141
|
[
|
|
@@ -147,19 +164,966 @@ describe('generateClient', () => {
|
|
|
147
164
|
|
|
148
165
|
const barrel = files.find((f) => f.path === 'src/index.ts');
|
|
149
166
|
expect(barrel).toBeDefined();
|
|
150
|
-
// Barrel export uses resolved name
|
|
167
|
+
// Barrel export uses resolved name for resource class
|
|
151
168
|
expect(barrel!.content).toContain("from './mfa/mfa'");
|
|
169
|
+
// Service barrel uses resolved directory name
|
|
170
|
+
expect(barrel!.content).toContain("export * from './mfa/interfaces'");
|
|
171
|
+
|
|
172
|
+
// Per-service barrel is generated with resolved directory
|
|
173
|
+
const serviceBarrel = files.find((f) => f.path === 'src/mfa/interfaces/index.ts');
|
|
174
|
+
expect(serviceBarrel).toBeDefined();
|
|
175
|
+
expect(serviceBarrel!.content).toContain("export * from './authentication-factor.interface';");
|
|
152
176
|
});
|
|
153
177
|
|
|
154
|
-
it('
|
|
178
|
+
it('does not generate error handling in WorkOS client (lives in WorkOSBase)', () => {
|
|
155
179
|
const files = generateClient(spec, ctx);
|
|
156
180
|
const workosFile = files.find((f) => f.path === 'src/workos.ts')!;
|
|
157
181
|
const content = workosFile.content;
|
|
158
182
|
|
|
159
|
-
expect(content).toContain('
|
|
160
|
-
expect(content).toContain('
|
|
161
|
-
expect(content).toContain('
|
|
162
|
-
|
|
163
|
-
|
|
183
|
+
expect(content).not.toContain('handleHttpError');
|
|
184
|
+
expect(content).not.toContain('UnauthorizedException');
|
|
185
|
+
expect(content).not.toContain('NotFoundException');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('skips explicit model export when name is already in apiSurface.exports', () => {
|
|
189
|
+
// Simulates the Event shadowing bug: the existing SDK already exports "Event"
|
|
190
|
+
// via a wildcard re-export (e.g., a hand-written 60+ member discriminated union).
|
|
191
|
+
// The barrel must not emit an explicit `export type { Event }` that would shadow it.
|
|
192
|
+
const eventService: Service = {
|
|
193
|
+
name: 'Events',
|
|
194
|
+
operations: [
|
|
195
|
+
{
|
|
196
|
+
name: 'listEvents',
|
|
197
|
+
httpMethod: 'get',
|
|
198
|
+
path: '/events',
|
|
199
|
+
pathParams: [],
|
|
200
|
+
queryParams: [],
|
|
201
|
+
headerParams: [],
|
|
202
|
+
response: { kind: 'model', name: 'Event' },
|
|
203
|
+
errors: [],
|
|
204
|
+
injectIdempotencyKey: false,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const eventModel: Model = {
|
|
210
|
+
name: 'Event',
|
|
211
|
+
fields: [
|
|
212
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
213
|
+
{ name: 'event', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const otherModel: Model = {
|
|
218
|
+
name: 'EventCursor',
|
|
219
|
+
fields: [{ name: 'cursor', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const eventSpec: ApiSpec = {
|
|
223
|
+
name: 'Test',
|
|
224
|
+
version: '1.0.0',
|
|
225
|
+
baseUrl: 'https://api.example.com',
|
|
226
|
+
services: [eventService],
|
|
227
|
+
models: [eventModel, otherModel],
|
|
228
|
+
enums: [],
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const surface: ApiSurface = {
|
|
232
|
+
language: 'node',
|
|
233
|
+
extractedFrom: '/tmp/test-sdk',
|
|
234
|
+
extractedAt: '2025-01-01T00:00:00Z',
|
|
235
|
+
classes: {},
|
|
236
|
+
interfaces: {
|
|
237
|
+
Event: {
|
|
238
|
+
name: 'Event',
|
|
239
|
+
sourceFile: 'src/common/interfaces/event.interface.ts',
|
|
240
|
+
fields: {},
|
|
241
|
+
extends: [],
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
typeAliases: {},
|
|
245
|
+
enums: {},
|
|
246
|
+
// The existing SDK's barrel re-exports "Event" via a wildcard chain
|
|
247
|
+
exports: {
|
|
248
|
+
'src/common/interfaces/event.interface.ts': ['Event'],
|
|
249
|
+
'src/index.ts': ['Event', 'WorkOS', 'Events'],
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const eventCtx: EmitterContext = {
|
|
254
|
+
namespace: 'workos',
|
|
255
|
+
namespacePascal: 'WorkOS',
|
|
256
|
+
spec: eventSpec,
|
|
257
|
+
apiSurface: surface,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const files = generateClient(eventSpec, eventCtx);
|
|
261
|
+
const barrel = files.find((f) => f.path === 'src/index.ts')!;
|
|
262
|
+
const content = barrel.content;
|
|
263
|
+
|
|
264
|
+
// Event must NOT appear as an explicit named export — it would shadow the wildcard
|
|
265
|
+
expect(content).not.toContain('export type { Event,');
|
|
266
|
+
expect(content).not.toContain('export type { Event }');
|
|
267
|
+
|
|
268
|
+
// EventCursor is NOT in apiSurface.exports, so it should still be exported
|
|
269
|
+
// (via common barrel wildcard since it's unassigned to any service)
|
|
270
|
+
expect(content).toContain("export * from './common/interfaces'");
|
|
271
|
+
|
|
272
|
+
// The resource class export should still be present
|
|
273
|
+
expect(content).toContain("export { Events } from './events/events'");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('skips explicit enum export when name is already in apiSurface.exports', () => {
|
|
277
|
+
const enumSpec: ApiSpec = {
|
|
278
|
+
name: 'Test',
|
|
279
|
+
version: '1.0.0',
|
|
280
|
+
baseUrl: 'https://api.example.com',
|
|
281
|
+
services: [service],
|
|
282
|
+
models: [model],
|
|
283
|
+
enums: [
|
|
284
|
+
{
|
|
285
|
+
name: 'EventType',
|
|
286
|
+
values: [
|
|
287
|
+
{ name: 'CONNECTION_ACTIVATED', value: 'connection.activated' },
|
|
288
|
+
{ name: 'CONNECTION_DELETED', value: 'connection.deleted' },
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const surface: ApiSurface = {
|
|
295
|
+
language: 'node',
|
|
296
|
+
extractedFrom: '/tmp/test-sdk',
|
|
297
|
+
extractedAt: '2025-01-01T00:00:00Z',
|
|
298
|
+
classes: {},
|
|
299
|
+
interfaces: {},
|
|
300
|
+
typeAliases: {},
|
|
301
|
+
enums: {},
|
|
302
|
+
exports: {
|
|
303
|
+
'src/common/interfaces/event-type.interface.ts': ['EventType'],
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const enumCtx: EmitterContext = {
|
|
308
|
+
namespace: 'workos',
|
|
309
|
+
namespacePascal: 'WorkOS',
|
|
310
|
+
spec: enumSpec,
|
|
311
|
+
apiSurface: surface,
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const files = generateClient(enumSpec, enumCtx);
|
|
315
|
+
const barrel = files.find((f) => f.path === 'src/index.ts')!;
|
|
316
|
+
|
|
317
|
+
// EventType should NOT appear as an explicit export — already covered by wildcard
|
|
318
|
+
expect(barrel.content).not.toContain('export type { EventType }');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('emits model exports normally when no apiSurface is present', () => {
|
|
322
|
+
// Without apiSurface, all models should be exported via service barrel
|
|
323
|
+
const files = generateClient(spec, ctx);
|
|
324
|
+
const barrel = files.find((f) => f.path === 'src/index.ts')!;
|
|
325
|
+
expect(barrel.content).toContain("export * from './organizations/interfaces'");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('renders spec.description as JSDoc on WorkOS class', () => {
|
|
329
|
+
const specWithDesc: ApiSpec = {
|
|
330
|
+
...spec,
|
|
331
|
+
description: 'The WorkOS API provides a unified interface for enterprise features.',
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const descCtx: EmitterContext = {
|
|
335
|
+
namespace: 'workos',
|
|
336
|
+
namespacePascal: 'WorkOS',
|
|
337
|
+
spec: specWithDesc,
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const files = generateClient(specWithDesc, descCtx);
|
|
341
|
+
const workosFile = files.find((f) => f.path === 'src/workos.ts')!;
|
|
342
|
+
const content = workosFile.content;
|
|
343
|
+
|
|
344
|
+
expect(content).toContain('/** The WorkOS API provides a unified interface for enterprise features. */');
|
|
345
|
+
expect(content).toContain('export class WorkOS extends WorkOSBase {');
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('uses value export for baseline TS enums and type export for type aliases', () => {
|
|
349
|
+
const enumDef: Enum = {
|
|
350
|
+
name: 'ConnectionType',
|
|
351
|
+
values: [
|
|
352
|
+
{ name: 'ADFSSAML', value: 'ADFSSAML' },
|
|
353
|
+
{ name: 'GoogleOAuth', value: 'GoogleOAuth' },
|
|
354
|
+
],
|
|
355
|
+
};
|
|
356
|
+
const aliasEnumDef: Enum = {
|
|
357
|
+
name: 'DirectoryState',
|
|
358
|
+
values: [
|
|
359
|
+
{ name: 'active', value: 'active' },
|
|
360
|
+
{ name: 'inactive', value: 'inactive' },
|
|
361
|
+
],
|
|
362
|
+
};
|
|
363
|
+
const enumService: Service = {
|
|
364
|
+
name: 'Connections',
|
|
365
|
+
operations: [
|
|
366
|
+
{
|
|
367
|
+
name: 'listConnections',
|
|
368
|
+
httpMethod: 'get',
|
|
369
|
+
path: '/connections',
|
|
370
|
+
pathParams: [],
|
|
371
|
+
queryParams: [
|
|
372
|
+
{
|
|
373
|
+
name: 'type',
|
|
374
|
+
type: { kind: 'enum', name: 'ConnectionType', values: ['ADFSSAML', 'GoogleOAuth'] },
|
|
375
|
+
required: false,
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
headerParams: [],
|
|
379
|
+
response: { kind: 'model', name: 'Organization' },
|
|
380
|
+
errors: [],
|
|
381
|
+
injectIdempotencyKey: false,
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
};
|
|
385
|
+
const dirService: Service = {
|
|
386
|
+
name: 'Directories',
|
|
387
|
+
operations: [
|
|
388
|
+
{
|
|
389
|
+
name: 'listDirectories',
|
|
390
|
+
httpMethod: 'get',
|
|
391
|
+
path: '/directories',
|
|
392
|
+
pathParams: [],
|
|
393
|
+
queryParams: [
|
|
394
|
+
{
|
|
395
|
+
name: 'state',
|
|
396
|
+
type: { kind: 'enum', name: 'DirectoryState', values: ['active', 'inactive'] },
|
|
397
|
+
required: false,
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
headerParams: [],
|
|
401
|
+
response: { kind: 'model', name: 'Organization' },
|
|
402
|
+
errors: [],
|
|
403
|
+
injectIdempotencyKey: false,
|
|
404
|
+
},
|
|
405
|
+
],
|
|
406
|
+
};
|
|
407
|
+
const enumSpec: ApiSpec = {
|
|
408
|
+
name: 'Test',
|
|
409
|
+
version: '1.0.0',
|
|
410
|
+
baseUrl: 'https://api.example.com',
|
|
411
|
+
services: [service, enumService, dirService],
|
|
412
|
+
models: [model],
|
|
413
|
+
enums: [enumDef, aliasEnumDef],
|
|
414
|
+
};
|
|
415
|
+
const enumCtx: EmitterContext = {
|
|
416
|
+
namespace: 'workos',
|
|
417
|
+
namespacePascal: 'WorkOS',
|
|
418
|
+
spec: enumSpec,
|
|
419
|
+
apiSurface: {
|
|
420
|
+
language: 'node',
|
|
421
|
+
extractedFrom: 'test',
|
|
422
|
+
extractedAt: '2024-01-01',
|
|
423
|
+
interfaces: {},
|
|
424
|
+
classes: {},
|
|
425
|
+
enums: {
|
|
426
|
+
ConnectionType: {
|
|
427
|
+
name: 'ConnectionType',
|
|
428
|
+
members: { ADFSSAML: 'ADFSSAML', GoogleOAuth: 'GoogleOAuth' },
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
typeAliases: {},
|
|
432
|
+
exports: {},
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const files = generateClient(enumSpec, enumCtx);
|
|
437
|
+
const barrel = files.find((f) => f.path === 'src/index.ts');
|
|
438
|
+
expect(barrel).toBeDefined();
|
|
439
|
+
|
|
440
|
+
const content = barrel!.content;
|
|
441
|
+
// Both enums are now re-exported via per-service barrel wildcards
|
|
442
|
+
expect(content).toContain("export * from './connections/interfaces'");
|
|
443
|
+
expect(content).toContain("export * from './directories/interfaces'");
|
|
444
|
+
// Individual enum exports should NOT appear (covered by wildcard)
|
|
445
|
+
expect(content).not.toContain('export { ConnectionType }');
|
|
446
|
+
expect(content).not.toContain('export type { DirectoryState }');
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('skips services whose endpoints are fully covered by existing hand-written services', () => {
|
|
450
|
+
const connectionsService: Service = {
|
|
451
|
+
name: 'Connections',
|
|
452
|
+
operations: [
|
|
453
|
+
{
|
|
454
|
+
name: 'listConnections',
|
|
455
|
+
httpMethod: 'get',
|
|
456
|
+
path: '/connections',
|
|
457
|
+
pathParams: [],
|
|
458
|
+
queryParams: [],
|
|
459
|
+
headerParams: [],
|
|
460
|
+
response: { kind: 'model', name: 'ConnectionList' },
|
|
461
|
+
errors: [],
|
|
462
|
+
injectIdempotencyKey: false,
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
name: 'getConnection',
|
|
466
|
+
httpMethod: 'get',
|
|
467
|
+
path: '/connections/{id}',
|
|
468
|
+
pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
469
|
+
queryParams: [],
|
|
470
|
+
headerParams: [],
|
|
471
|
+
response: { kind: 'model', name: 'Connection' },
|
|
472
|
+
errors: [],
|
|
473
|
+
injectIdempotencyKey: false,
|
|
474
|
+
},
|
|
475
|
+
],
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const connectionModel: Model = {
|
|
479
|
+
name: 'Connection',
|
|
480
|
+
fields: [
|
|
481
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
482
|
+
{ name: 'name', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
483
|
+
],
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const radarService: Service = {
|
|
487
|
+
name: 'Radar',
|
|
488
|
+
operations: [
|
|
489
|
+
{
|
|
490
|
+
name: 'assess',
|
|
491
|
+
httpMethod: 'post',
|
|
492
|
+
path: '/radar/assess',
|
|
493
|
+
pathParams: [],
|
|
494
|
+
queryParams: [],
|
|
495
|
+
headerParams: [],
|
|
496
|
+
response: { kind: 'model', name: 'RadarResult' },
|
|
497
|
+
errors: [],
|
|
498
|
+
injectIdempotencyKey: false,
|
|
499
|
+
},
|
|
500
|
+
],
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const radarModel: Model = {
|
|
504
|
+
name: 'RadarResult',
|
|
505
|
+
fields: [{ name: 'score', type: { kind: 'primitive', type: 'number' }, required: true }],
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
const coveredSpec: ApiSpec = {
|
|
509
|
+
name: 'Test',
|
|
510
|
+
version: '1.0.0',
|
|
511
|
+
baseUrl: 'https://api.example.com',
|
|
512
|
+
services: [connectionsService, radarService],
|
|
513
|
+
models: [connectionModel, radarModel],
|
|
514
|
+
enums: [],
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const coveredCtx: EmitterContext = {
|
|
518
|
+
namespace: 'workos',
|
|
519
|
+
namespacePascal: 'WorkOS',
|
|
520
|
+
spec: coveredSpec,
|
|
521
|
+
apiSurface: {
|
|
522
|
+
language: 'node',
|
|
523
|
+
extractedFrom: 'test',
|
|
524
|
+
extractedAt: '2024-01-01',
|
|
525
|
+
interfaces: {},
|
|
526
|
+
classes: {
|
|
527
|
+
Sso: {
|
|
528
|
+
name: 'Sso',
|
|
529
|
+
methods: {
|
|
530
|
+
listConnections: [
|
|
531
|
+
{
|
|
532
|
+
name: 'listConnections',
|
|
533
|
+
params: [],
|
|
534
|
+
returnType: 'Promise<AutoPaginatable<Connection>>',
|
|
535
|
+
async: true,
|
|
536
|
+
},
|
|
537
|
+
],
|
|
538
|
+
getConnection: [
|
|
539
|
+
{
|
|
540
|
+
name: 'getConnection',
|
|
541
|
+
params: [{ name: 'id', type: 'string', optional: false }],
|
|
542
|
+
returnType: 'Promise<Connection>',
|
|
543
|
+
async: true,
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
},
|
|
547
|
+
properties: {},
|
|
548
|
+
constructorParams: [],
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
enums: {},
|
|
552
|
+
typeAliases: {},
|
|
553
|
+
exports: {},
|
|
554
|
+
},
|
|
555
|
+
overlayLookup: {
|
|
556
|
+
methodByOperation: new Map([
|
|
557
|
+
[
|
|
558
|
+
'GET /connections',
|
|
559
|
+
{
|
|
560
|
+
className: 'Sso',
|
|
561
|
+
methodName: 'listConnections',
|
|
562
|
+
params: [],
|
|
563
|
+
returnType: 'Promise<AutoPaginatable<Connection>>',
|
|
564
|
+
},
|
|
565
|
+
],
|
|
566
|
+
[
|
|
567
|
+
'GET /connections/{id}',
|
|
568
|
+
{
|
|
569
|
+
className: 'Sso',
|
|
570
|
+
methodName: 'getConnection',
|
|
571
|
+
params: [{ name: 'id', type: 'string', optional: false }],
|
|
572
|
+
returnType: 'Promise<Connection>',
|
|
573
|
+
},
|
|
574
|
+
],
|
|
575
|
+
]),
|
|
576
|
+
httpKeyByMethod: new Map(),
|
|
577
|
+
interfaceByName: new Map(),
|
|
578
|
+
typeAliasByName: new Map(),
|
|
579
|
+
requiredExports: new Map(),
|
|
580
|
+
modelNameByIR: new Map(),
|
|
581
|
+
fileBySymbol: new Map(),
|
|
582
|
+
},
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
const files = generateClient(coveredSpec, coveredCtx);
|
|
586
|
+
const workosFile = files.find((f) => f.path === 'src/workos.ts')!;
|
|
587
|
+
const content = workosFile.content;
|
|
588
|
+
|
|
589
|
+
// Connections service should NOT appear (fully covered by Sso in baseline)
|
|
590
|
+
expect(content).not.toContain('Connections');
|
|
591
|
+
expect(content).not.toContain("from './sso/sso'");
|
|
592
|
+
|
|
593
|
+
// Radar service should still appear (not covered)
|
|
594
|
+
expect(content).toContain('readonly radar = new Radar(this);');
|
|
595
|
+
expect(content).toContain("import { Radar } from './radar/radar';");
|
|
596
|
+
|
|
597
|
+
// Barrel should also skip the Connections resource class export
|
|
598
|
+
const barrel = files.find((f) => f.path === 'src/index.ts')!;
|
|
599
|
+
const barrelContent = barrel.content;
|
|
600
|
+
expect(barrelContent).not.toContain('export { Sso }');
|
|
601
|
+
expect(barrelContent).not.toContain('export { Connections }');
|
|
602
|
+
|
|
603
|
+
// But model types from the Connections service should still be exported
|
|
604
|
+
// (via the service barrel wildcard for the resolved directory)
|
|
605
|
+
expect(barrelContent).toContain("export * from './sso/interfaces'");
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it('does not skip services when only some operations are covered', () => {
|
|
609
|
+
const partialService: Service = {
|
|
610
|
+
name: 'Directories',
|
|
611
|
+
operations: [
|
|
612
|
+
{
|
|
613
|
+
name: 'listDirectories',
|
|
614
|
+
httpMethod: 'get',
|
|
615
|
+
path: '/directories',
|
|
616
|
+
pathParams: [],
|
|
617
|
+
queryParams: [],
|
|
618
|
+
headerParams: [],
|
|
619
|
+
response: { kind: 'model', name: 'DirectoryList' },
|
|
620
|
+
errors: [],
|
|
621
|
+
injectIdempotencyKey: false,
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
name: 'createDirectory',
|
|
625
|
+
httpMethod: 'post',
|
|
626
|
+
path: '/directories',
|
|
627
|
+
pathParams: [],
|
|
628
|
+
queryParams: [],
|
|
629
|
+
headerParams: [],
|
|
630
|
+
response: { kind: 'model', name: 'Directory' },
|
|
631
|
+
errors: [],
|
|
632
|
+
injectIdempotencyKey: false,
|
|
633
|
+
},
|
|
634
|
+
],
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
const dirModel: Model = {
|
|
638
|
+
name: 'Directory',
|
|
639
|
+
fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
const partialSpec: ApiSpec = {
|
|
643
|
+
name: 'Test',
|
|
644
|
+
version: '1.0.0',
|
|
645
|
+
baseUrl: 'https://api.example.com',
|
|
646
|
+
services: [partialService],
|
|
647
|
+
models: [dirModel],
|
|
648
|
+
enums: [],
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
const partialCtx: EmitterContext = {
|
|
652
|
+
namespace: 'workos',
|
|
653
|
+
namespacePascal: 'WorkOS',
|
|
654
|
+
spec: partialSpec,
|
|
655
|
+
apiSurface: {
|
|
656
|
+
language: 'node',
|
|
657
|
+
extractedFrom: 'test',
|
|
658
|
+
extractedAt: '2024-01-01',
|
|
659
|
+
interfaces: {},
|
|
660
|
+
classes: {
|
|
661
|
+
DirectorySync: {
|
|
662
|
+
name: 'DirectorySync',
|
|
663
|
+
methods: {
|
|
664
|
+
listDirectories: [
|
|
665
|
+
{
|
|
666
|
+
name: 'listDirectories',
|
|
667
|
+
params: [],
|
|
668
|
+
returnType: 'Promise<AutoPaginatable<Directory>>',
|
|
669
|
+
async: true,
|
|
670
|
+
},
|
|
671
|
+
],
|
|
672
|
+
},
|
|
673
|
+
properties: {},
|
|
674
|
+
constructorParams: [],
|
|
675
|
+
},
|
|
676
|
+
},
|
|
677
|
+
enums: {},
|
|
678
|
+
typeAliases: {},
|
|
679
|
+
exports: {},
|
|
680
|
+
},
|
|
681
|
+
overlayLookup: {
|
|
682
|
+
methodByOperation: new Map([
|
|
683
|
+
[
|
|
684
|
+
'GET /directories',
|
|
685
|
+
{
|
|
686
|
+
className: 'DirectorySync',
|
|
687
|
+
methodName: 'listDirectories',
|
|
688
|
+
params: [],
|
|
689
|
+
returnType: 'Promise<AutoPaginatable<Directory>>',
|
|
690
|
+
},
|
|
691
|
+
],
|
|
692
|
+
]),
|
|
693
|
+
httpKeyByMethod: new Map(),
|
|
694
|
+
interfaceByName: new Map(),
|
|
695
|
+
typeAliasByName: new Map(),
|
|
696
|
+
requiredExports: new Map(),
|
|
697
|
+
modelNameByIR: new Map(),
|
|
698
|
+
fileBySymbol: new Map(),
|
|
699
|
+
},
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
const files = generateClient(partialSpec, partialCtx);
|
|
703
|
+
const workosFile = files.find((f) => f.path === 'src/workos.ts')!;
|
|
704
|
+
const content = workosFile.content;
|
|
705
|
+
|
|
706
|
+
// Service should still be generated because it has an uncovered operation
|
|
707
|
+
expect(content).toContain('DirectorySync');
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
it('does not skip services when no overlay is provided', () => {
|
|
711
|
+
const files = generateClient(spec, ctx);
|
|
712
|
+
const workosFile = files.find((f) => f.path === 'src/workos.ts')!;
|
|
713
|
+
expect(workosFile.content).toContain('readonly organizations = new Organizations(this);');
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
it('does not skip services when overlay exists but no apiSurface baseline', () => {
|
|
717
|
+
const mfaService: Service = {
|
|
718
|
+
name: 'MultiFactorAuth',
|
|
719
|
+
operations: [
|
|
720
|
+
{
|
|
721
|
+
name: 'enrollFactor',
|
|
722
|
+
httpMethod: 'post',
|
|
723
|
+
path: '/auth/factors/enroll',
|
|
724
|
+
pathParams: [],
|
|
725
|
+
queryParams: [],
|
|
726
|
+
headerParams: [],
|
|
727
|
+
response: { kind: 'model', name: 'AuthenticationFactor' },
|
|
728
|
+
errors: [],
|
|
729
|
+
injectIdempotencyKey: true,
|
|
730
|
+
},
|
|
731
|
+
],
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
const mfaModel: Model = {
|
|
735
|
+
name: 'AuthenticationFactor',
|
|
736
|
+
fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
const mfaSpec: ApiSpec = {
|
|
740
|
+
name: 'Test',
|
|
741
|
+
version: '1.0.0',
|
|
742
|
+
baseUrl: 'https://api.example.com',
|
|
743
|
+
services: [mfaService],
|
|
744
|
+
models: [mfaModel],
|
|
745
|
+
enums: [],
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
const namingOnlyCtx: EmitterContext = {
|
|
749
|
+
namespace: 'workos',
|
|
750
|
+
namespacePascal: 'WorkOS',
|
|
751
|
+
spec: mfaSpec,
|
|
752
|
+
overlayLookup: {
|
|
753
|
+
methodByOperation: new Map([
|
|
754
|
+
[
|
|
755
|
+
'POST /auth/factors/enroll',
|
|
756
|
+
{ className: 'Mfa', methodName: 'enrollFactor', params: [], returnType: 'void' },
|
|
757
|
+
],
|
|
758
|
+
]),
|
|
759
|
+
httpKeyByMethod: new Map(),
|
|
760
|
+
interfaceByName: new Map(),
|
|
761
|
+
typeAliasByName: new Map(),
|
|
762
|
+
requiredExports: new Map(),
|
|
763
|
+
modelNameByIR: new Map(),
|
|
764
|
+
fileBySymbol: new Map(),
|
|
765
|
+
},
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
const files = generateClient(mfaSpec, namingOnlyCtx);
|
|
769
|
+
const workosFile = files.find((f) => f.path === 'src/workos.ts')!;
|
|
770
|
+
expect(workosFile.content).toContain('readonly mfa = new Mfa(this);');
|
|
771
|
+
});
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
describe('isServiceCoveredByExisting', () => {
|
|
775
|
+
const emptySpec: ApiSpec = {
|
|
776
|
+
name: 'Test',
|
|
777
|
+
version: '1.0.0',
|
|
778
|
+
baseUrl: '',
|
|
779
|
+
services: [],
|
|
780
|
+
models: [],
|
|
781
|
+
enums: [],
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
it('returns false when no overlay is provided', () => {
|
|
785
|
+
const svc: Service = {
|
|
786
|
+
name: 'Connections',
|
|
787
|
+
operations: [
|
|
788
|
+
{
|
|
789
|
+
name: 'listConnections',
|
|
790
|
+
httpMethod: 'get',
|
|
791
|
+
path: '/connections',
|
|
792
|
+
pathParams: [],
|
|
793
|
+
queryParams: [],
|
|
794
|
+
headerParams: [],
|
|
795
|
+
response: { kind: 'model', name: 'ConnectionList' },
|
|
796
|
+
errors: [],
|
|
797
|
+
injectIdempotencyKey: false,
|
|
798
|
+
},
|
|
799
|
+
],
|
|
800
|
+
};
|
|
801
|
+
const noOverlayCtx: EmitterContext = {
|
|
802
|
+
namespace: 'workos',
|
|
803
|
+
namespacePascal: 'WorkOS',
|
|
804
|
+
spec: emptySpec,
|
|
805
|
+
};
|
|
806
|
+
expect(isServiceCoveredByExisting(svc, noOverlayCtx)).toBe(false);
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
it('returns false when overlay is empty', () => {
|
|
810
|
+
const svc: Service = {
|
|
811
|
+
name: 'Connections',
|
|
812
|
+
operations: [
|
|
813
|
+
{
|
|
814
|
+
name: 'listConnections',
|
|
815
|
+
httpMethod: 'get',
|
|
816
|
+
path: '/connections',
|
|
817
|
+
pathParams: [],
|
|
818
|
+
queryParams: [],
|
|
819
|
+
headerParams: [],
|
|
820
|
+
response: { kind: 'model', name: 'ConnectionList' },
|
|
821
|
+
errors: [],
|
|
822
|
+
injectIdempotencyKey: false,
|
|
823
|
+
},
|
|
824
|
+
],
|
|
825
|
+
};
|
|
826
|
+
const emptyOverlayCtx: EmitterContext = {
|
|
827
|
+
namespace: 'workos',
|
|
828
|
+
namespacePascal: 'WorkOS',
|
|
829
|
+
spec: emptySpec,
|
|
830
|
+
overlayLookup: {
|
|
831
|
+
methodByOperation: new Map(),
|
|
832
|
+
httpKeyByMethod: new Map(),
|
|
833
|
+
interfaceByName: new Map(),
|
|
834
|
+
typeAliasByName: new Map(),
|
|
835
|
+
requiredExports: new Map(),
|
|
836
|
+
modelNameByIR: new Map(),
|
|
837
|
+
fileBySymbol: new Map(),
|
|
838
|
+
},
|
|
839
|
+
};
|
|
840
|
+
expect(isServiceCoveredByExisting(svc, emptyOverlayCtx)).toBe(false);
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
it('returns true when all operations are covered by overlay and class exists in baseline', () => {
|
|
844
|
+
const svc: Service = {
|
|
845
|
+
name: 'Connections',
|
|
846
|
+
operations: [
|
|
847
|
+
{
|
|
848
|
+
name: 'listConnections',
|
|
849
|
+
httpMethod: 'get',
|
|
850
|
+
path: '/connections',
|
|
851
|
+
pathParams: [],
|
|
852
|
+
queryParams: [],
|
|
853
|
+
headerParams: [],
|
|
854
|
+
response: { kind: 'model', name: 'ConnectionList' },
|
|
855
|
+
errors: [],
|
|
856
|
+
injectIdempotencyKey: false,
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
name: 'getConnection',
|
|
860
|
+
httpMethod: 'get',
|
|
861
|
+
path: '/connections/{id}',
|
|
862
|
+
pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
863
|
+
queryParams: [],
|
|
864
|
+
headerParams: [],
|
|
865
|
+
response: { kind: 'model', name: 'Connection' },
|
|
866
|
+
errors: [],
|
|
867
|
+
injectIdempotencyKey: false,
|
|
868
|
+
},
|
|
869
|
+
],
|
|
870
|
+
};
|
|
871
|
+
const fullCoverageCtx: EmitterContext = {
|
|
872
|
+
namespace: 'workos',
|
|
873
|
+
namespacePascal: 'WorkOS',
|
|
874
|
+
spec: emptySpec,
|
|
875
|
+
apiSurface: {
|
|
876
|
+
language: 'node',
|
|
877
|
+
extractedFrom: 'test',
|
|
878
|
+
extractedAt: '2024-01-01',
|
|
879
|
+
interfaces: {},
|
|
880
|
+
classes: {
|
|
881
|
+
Sso: {
|
|
882
|
+
name: 'Sso',
|
|
883
|
+
methods: {},
|
|
884
|
+
properties: {},
|
|
885
|
+
constructorParams: [],
|
|
886
|
+
},
|
|
887
|
+
},
|
|
888
|
+
enums: {},
|
|
889
|
+
typeAliases: {},
|
|
890
|
+
exports: {},
|
|
891
|
+
},
|
|
892
|
+
overlayLookup: {
|
|
893
|
+
methodByOperation: new Map([
|
|
894
|
+
[
|
|
895
|
+
'GET /connections',
|
|
896
|
+
{
|
|
897
|
+
className: 'Sso',
|
|
898
|
+
methodName: 'listConnections',
|
|
899
|
+
params: [],
|
|
900
|
+
returnType: 'Promise<AutoPaginatable<Connection>>',
|
|
901
|
+
},
|
|
902
|
+
],
|
|
903
|
+
[
|
|
904
|
+
'GET /connections/{id}',
|
|
905
|
+
{
|
|
906
|
+
className: 'Sso',
|
|
907
|
+
methodName: 'getConnection',
|
|
908
|
+
params: [{ name: 'id', type: 'string', optional: false }],
|
|
909
|
+
returnType: 'Promise<Connection>',
|
|
910
|
+
},
|
|
911
|
+
],
|
|
912
|
+
]),
|
|
913
|
+
httpKeyByMethod: new Map(),
|
|
914
|
+
interfaceByName: new Map(),
|
|
915
|
+
typeAliasByName: new Map(),
|
|
916
|
+
requiredExports: new Map(),
|
|
917
|
+
modelNameByIR: new Map(),
|
|
918
|
+
fileBySymbol: new Map(),
|
|
919
|
+
},
|
|
920
|
+
};
|
|
921
|
+
expect(isServiceCoveredByExisting(svc, fullCoverageCtx)).toBe(true);
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
it('returns false when only some operations are covered', () => {
|
|
925
|
+
const svc: Service = {
|
|
926
|
+
name: 'Directories',
|
|
927
|
+
operations: [
|
|
928
|
+
{
|
|
929
|
+
name: 'listDirectories',
|
|
930
|
+
httpMethod: 'get',
|
|
931
|
+
path: '/directories',
|
|
932
|
+
pathParams: [],
|
|
933
|
+
queryParams: [],
|
|
934
|
+
headerParams: [],
|
|
935
|
+
response: { kind: 'model', name: 'DirectoryList' },
|
|
936
|
+
errors: [],
|
|
937
|
+
injectIdempotencyKey: false,
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
name: 'createDirectory',
|
|
941
|
+
httpMethod: 'post',
|
|
942
|
+
path: '/directories',
|
|
943
|
+
pathParams: [],
|
|
944
|
+
queryParams: [],
|
|
945
|
+
headerParams: [],
|
|
946
|
+
response: { kind: 'model', name: 'Directory' },
|
|
947
|
+
errors: [],
|
|
948
|
+
injectIdempotencyKey: false,
|
|
949
|
+
},
|
|
950
|
+
],
|
|
951
|
+
};
|
|
952
|
+
const partialCtx: EmitterContext = {
|
|
953
|
+
namespace: 'workos',
|
|
954
|
+
namespacePascal: 'WorkOS',
|
|
955
|
+
spec: emptySpec,
|
|
956
|
+
apiSurface: {
|
|
957
|
+
language: 'node',
|
|
958
|
+
extractedFrom: 'test',
|
|
959
|
+
extractedAt: '2024-01-01',
|
|
960
|
+
interfaces: {},
|
|
961
|
+
classes: {
|
|
962
|
+
DirectorySync: {
|
|
963
|
+
name: 'DirectorySync',
|
|
964
|
+
methods: {},
|
|
965
|
+
properties: {},
|
|
966
|
+
constructorParams: [],
|
|
967
|
+
},
|
|
968
|
+
},
|
|
969
|
+
enums: {},
|
|
970
|
+
typeAliases: {},
|
|
971
|
+
exports: {},
|
|
972
|
+
},
|
|
973
|
+
overlayLookup: {
|
|
974
|
+
methodByOperation: new Map([
|
|
975
|
+
[
|
|
976
|
+
'GET /directories',
|
|
977
|
+
{
|
|
978
|
+
className: 'DirectorySync',
|
|
979
|
+
methodName: 'listDirectories',
|
|
980
|
+
params: [],
|
|
981
|
+
returnType: 'Promise<AutoPaginatable<Directory>>',
|
|
982
|
+
},
|
|
983
|
+
],
|
|
984
|
+
]),
|
|
985
|
+
httpKeyByMethod: new Map(),
|
|
986
|
+
interfaceByName: new Map(),
|
|
987
|
+
typeAliasByName: new Map(),
|
|
988
|
+
requiredExports: new Map(),
|
|
989
|
+
modelNameByIR: new Map(),
|
|
990
|
+
fileBySymbol: new Map(),
|
|
991
|
+
},
|
|
992
|
+
};
|
|
993
|
+
expect(isServiceCoveredByExisting(svc, partialCtx)).toBe(false);
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
it('returns false for services with zero operations', () => {
|
|
997
|
+
const emptySvc: Service = {
|
|
998
|
+
name: 'Empty',
|
|
999
|
+
operations: [],
|
|
1000
|
+
};
|
|
1001
|
+
const overlayCtx: EmitterContext = {
|
|
1002
|
+
namespace: 'workos',
|
|
1003
|
+
namespacePascal: 'WorkOS',
|
|
1004
|
+
spec: emptySpec,
|
|
1005
|
+
apiSurface: {
|
|
1006
|
+
language: 'node',
|
|
1007
|
+
extractedFrom: 'test',
|
|
1008
|
+
extractedAt: '2024-01-01',
|
|
1009
|
+
interfaces: {},
|
|
1010
|
+
classes: {
|
|
1011
|
+
Other: { name: 'Other', methods: {}, properties: {}, constructorParams: [] },
|
|
1012
|
+
},
|
|
1013
|
+
enums: {},
|
|
1014
|
+
typeAliases: {},
|
|
1015
|
+
exports: {},
|
|
1016
|
+
},
|
|
1017
|
+
overlayLookup: {
|
|
1018
|
+
methodByOperation: new Map([
|
|
1019
|
+
['GET /something', { className: 'Other', methodName: 'doSomething', params: [], returnType: 'void' }],
|
|
1020
|
+
]),
|
|
1021
|
+
httpKeyByMethod: new Map(),
|
|
1022
|
+
interfaceByName: new Map(),
|
|
1023
|
+
typeAliasByName: new Map(),
|
|
1024
|
+
requiredExports: new Map(),
|
|
1025
|
+
modelNameByIR: new Map(),
|
|
1026
|
+
fileBySymbol: new Map(),
|
|
1027
|
+
},
|
|
1028
|
+
};
|
|
1029
|
+
expect(isServiceCoveredByExisting(emptySvc, overlayCtx)).toBe(false);
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
it('returns false when overlay covers operations but target class is not in baseline', () => {
|
|
1033
|
+
const svc: Service = {
|
|
1034
|
+
name: 'Connections',
|
|
1035
|
+
operations: [
|
|
1036
|
+
{
|
|
1037
|
+
name: 'listConnections',
|
|
1038
|
+
httpMethod: 'get',
|
|
1039
|
+
path: '/connections',
|
|
1040
|
+
pathParams: [],
|
|
1041
|
+
queryParams: [],
|
|
1042
|
+
headerParams: [],
|
|
1043
|
+
response: { kind: 'model', name: 'ConnectionList' },
|
|
1044
|
+
errors: [],
|
|
1045
|
+
injectIdempotencyKey: false,
|
|
1046
|
+
},
|
|
1047
|
+
],
|
|
1048
|
+
};
|
|
1049
|
+
const missingClassCtx: EmitterContext = {
|
|
1050
|
+
namespace: 'workos',
|
|
1051
|
+
namespacePascal: 'WorkOS',
|
|
1052
|
+
spec: emptySpec,
|
|
1053
|
+
apiSurface: {
|
|
1054
|
+
language: 'node',
|
|
1055
|
+
extractedFrom: 'test',
|
|
1056
|
+
extractedAt: '2024-01-01',
|
|
1057
|
+
interfaces: {},
|
|
1058
|
+
classes: {},
|
|
1059
|
+
enums: {},
|
|
1060
|
+
typeAliases: {},
|
|
1061
|
+
exports: {},
|
|
1062
|
+
},
|
|
1063
|
+
overlayLookup: {
|
|
1064
|
+
methodByOperation: new Map([
|
|
1065
|
+
[
|
|
1066
|
+
'GET /connections',
|
|
1067
|
+
{
|
|
1068
|
+
className: 'Sso',
|
|
1069
|
+
methodName: 'listConnections',
|
|
1070
|
+
params: [],
|
|
1071
|
+
returnType: 'Promise<AutoPaginatable<Connection>>',
|
|
1072
|
+
},
|
|
1073
|
+
],
|
|
1074
|
+
]),
|
|
1075
|
+
httpKeyByMethod: new Map(),
|
|
1076
|
+
interfaceByName: new Map(),
|
|
1077
|
+
typeAliasByName: new Map(),
|
|
1078
|
+
requiredExports: new Map(),
|
|
1079
|
+
modelNameByIR: new Map(),
|
|
1080
|
+
fileBySymbol: new Map(),
|
|
1081
|
+
},
|
|
1082
|
+
};
|
|
1083
|
+
expect(isServiceCoveredByExisting(svc, missingClassCtx)).toBe(false);
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
it('returns false when no apiSurface is provided', () => {
|
|
1087
|
+
const svc: Service = {
|
|
1088
|
+
name: 'Connections',
|
|
1089
|
+
operations: [
|
|
1090
|
+
{
|
|
1091
|
+
name: 'listConnections',
|
|
1092
|
+
httpMethod: 'get',
|
|
1093
|
+
path: '/connections',
|
|
1094
|
+
pathParams: [],
|
|
1095
|
+
queryParams: [],
|
|
1096
|
+
headerParams: [],
|
|
1097
|
+
response: { kind: 'model', name: 'ConnectionList' },
|
|
1098
|
+
errors: [],
|
|
1099
|
+
injectIdempotencyKey: false,
|
|
1100
|
+
},
|
|
1101
|
+
],
|
|
1102
|
+
};
|
|
1103
|
+
const noSurfaceCtx: EmitterContext = {
|
|
1104
|
+
namespace: 'workos',
|
|
1105
|
+
namespacePascal: 'WorkOS',
|
|
1106
|
+
spec: emptySpec,
|
|
1107
|
+
overlayLookup: {
|
|
1108
|
+
methodByOperation: new Map([
|
|
1109
|
+
[
|
|
1110
|
+
'GET /connections',
|
|
1111
|
+
{
|
|
1112
|
+
className: 'Sso',
|
|
1113
|
+
methodName: 'listConnections',
|
|
1114
|
+
params: [],
|
|
1115
|
+
returnType: 'Promise<AutoPaginatable<Connection>>',
|
|
1116
|
+
},
|
|
1117
|
+
],
|
|
1118
|
+
]),
|
|
1119
|
+
httpKeyByMethod: new Map(),
|
|
1120
|
+
interfaceByName: new Map(),
|
|
1121
|
+
typeAliasByName: new Map(),
|
|
1122
|
+
requiredExports: new Map(),
|
|
1123
|
+
modelNameByIR: new Map(),
|
|
1124
|
+
fileBySymbol: new Map(),
|
|
1125
|
+
},
|
|
1126
|
+
};
|
|
1127
|
+
expect(isServiceCoveredByExisting(svc, noSurfaceCtx)).toBe(false);
|
|
164
1128
|
});
|
|
165
1129
|
});
|