@workos/oagen-emitters 0.12.1 → 0.12.3
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/ci.yml +1 -1
- package/.github/workflows/lint-pr-title.yml +1 -1
- package/.github/workflows/lint.yml +1 -1
- package/.github/workflows/release-please.yml +2 -2
- package/.github/workflows/release.yml +1 -1
- package/.node-version +1 -1
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +14 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{plugin-CmfzawTp.mjs → plugin-D2N2ZT5W.mjs} +2566 -1493
- package/dist/plugin-D2N2ZT5W.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +6 -6
- package/renovate.json +46 -6
- package/src/node/client.ts +19 -32
- package/src/node/enums.ts +67 -30
- package/src/node/errors.ts +2 -8
- package/src/node/field-plan.ts +188 -52
- package/src/node/fixtures.ts +11 -33
- package/src/node/index.ts +354 -20
- package/src/node/live-surface.ts +378 -0
- package/src/node/models.ts +547 -351
- package/src/node/naming.ts +122 -25
- package/src/node/node-overrides.ts +77 -0
- package/src/node/options.ts +41 -0
- package/src/node/path-expression.ts +11 -4
- package/src/node/resources.ts +473 -48
- package/src/node/sdk-errors.ts +0 -16
- package/src/node/tests.ts +152 -93
- package/src/node/type-map.ts +40 -18
- package/src/node/utils.ts +89 -102
- package/src/node/wrappers.ts +0 -20
- package/test/node/client.test.ts +106 -1201
- package/test/node/enums.test.ts +59 -130
- package/test/node/errors.test.ts +2 -3
- package/test/node/live-surface.test.ts +240 -0
- package/test/node/models.test.ts +396 -765
- package/test/node/naming.test.ts +69 -234
- package/test/node/resources.test.ts +435 -2025
- package/test/node/tests.test.ts +214 -0
- package/test/node/type-map.test.ts +49 -54
- package/test/node/utils.test.ts +29 -80
- package/dist/plugin-CmfzawTp.mjs.map +0 -1
- package/test/node/serializers.test.ts +0 -444
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
import type { ApiSpec, EmitterContext, Service, Model } from '@workos/oagen';
|
|
7
|
+
import { defaultSdkBehavior } from '@workos/oagen';
|
|
8
|
+
import { nodeEmitter } from '../../src/node/index.js';
|
|
9
|
+
|
|
10
|
+
const groupModel: Model = {
|
|
11
|
+
name: 'Group',
|
|
12
|
+
fields: [
|
|
13
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
14
|
+
{ name: 'name', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const groupService: Service = {
|
|
19
|
+
name: 'Groups',
|
|
20
|
+
operations: [
|
|
21
|
+
{
|
|
22
|
+
name: 'getGroup',
|
|
23
|
+
httpMethod: 'get',
|
|
24
|
+
path: '/organizations/{organizationId}/groups/{groupId}',
|
|
25
|
+
pathParams: [
|
|
26
|
+
{ name: 'organizationId', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
27
|
+
{ name: 'groupId', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
28
|
+
],
|
|
29
|
+
queryParams: [],
|
|
30
|
+
headerParams: [],
|
|
31
|
+
response: { kind: 'model', name: 'Group' },
|
|
32
|
+
errors: [],
|
|
33
|
+
injectIdempotencyKey: false,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const spec: ApiSpec = {
|
|
39
|
+
name: 'Test',
|
|
40
|
+
version: '1.0.0',
|
|
41
|
+
baseUrl: '',
|
|
42
|
+
services: [groupService],
|
|
43
|
+
models: [groupModel],
|
|
44
|
+
enums: [],
|
|
45
|
+
sdk: defaultSdkBehavior(),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const ctx: EmitterContext = {
|
|
49
|
+
namespace: 'workos',
|
|
50
|
+
namespacePascal: 'WorkOS',
|
|
51
|
+
spec,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function createTrackedSdkRoot(withHandTests = false): string {
|
|
55
|
+
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'node-owned-tests-'));
|
|
56
|
+
fs.mkdirSync(path.join(tmpRoot, 'src', 'groups'), { recursive: true });
|
|
57
|
+
fs.mkdirSync(path.join(tmpRoot, 'src', 'groups', 'fixtures'), { recursive: true });
|
|
58
|
+
fs.writeFileSync(path.join(tmpRoot, 'src', 'workos.ts'), '// @oagen-ignore-file\nexport class WorkOS {}\n');
|
|
59
|
+
if (withHandTests) {
|
|
60
|
+
fs.writeFileSync(path.join(tmpRoot, 'src', 'groups', 'groups.spec.ts'), "describe('old', () => {});\n");
|
|
61
|
+
fs.writeFileSync(path.join(tmpRoot, 'src', 'groups', 'fixtures', 'group.json'), '{"id":"old"}\n');
|
|
62
|
+
}
|
|
63
|
+
execFileSync('git', ['init'], { cwd: tmpRoot, stdio: 'ignore' });
|
|
64
|
+
execFileSync('git', ['add', 'src'], { cwd: tmpRoot, stdio: 'ignore' });
|
|
65
|
+
return tmpRoot;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe('node test generation ownership', () => {
|
|
69
|
+
it('regenerates tests and fixtures for owned services', () => {
|
|
70
|
+
const tmpRoot = createTrackedSdkRoot();
|
|
71
|
+
try {
|
|
72
|
+
const result = nodeEmitter.generateTests!(spec, {
|
|
73
|
+
...ctx,
|
|
74
|
+
outputDir: tmpRoot,
|
|
75
|
+
emitterOptions: { ownedServices: ['Groups'], regenerateOwnedTests: true },
|
|
76
|
+
} as EmitterContext);
|
|
77
|
+
|
|
78
|
+
const testFile = result.find((f) => f.path === 'src/groups/groups.spec.ts');
|
|
79
|
+
const fixtureFile = result.find((f) => f.path === 'src/groups/fixtures/group.json');
|
|
80
|
+
expect(testFile).toBeDefined();
|
|
81
|
+
expect(testFile!.overwriteExisting).toBe(true);
|
|
82
|
+
expect(fixtureFile).toBeDefined();
|
|
83
|
+
expect(fixtureFile!.overwriteExisting).toBe(true);
|
|
84
|
+
} finally {
|
|
85
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('preserves existing hand-written tests and fixtures for owned services', () => {
|
|
90
|
+
const tmpRoot = createTrackedSdkRoot(true);
|
|
91
|
+
try {
|
|
92
|
+
const result = nodeEmitter.generateTests!(spec, {
|
|
93
|
+
...ctx,
|
|
94
|
+
outputDir: tmpRoot,
|
|
95
|
+
emitterOptions: { ownedServices: ['Groups'], regenerateOwnedTests: true },
|
|
96
|
+
} as EmitterContext);
|
|
97
|
+
|
|
98
|
+
expect(result.some((f) => f.path === 'src/groups/groups.spec.ts')).toBe(false);
|
|
99
|
+
expect(result.some((f) => f.path === 'src/groups/fixtures/group.json')).toBe(false);
|
|
100
|
+
} finally {
|
|
101
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('skips tests and fixtures when the service is not owned', () => {
|
|
106
|
+
const tmpRoot = createTrackedSdkRoot();
|
|
107
|
+
try {
|
|
108
|
+
const result = nodeEmitter.generateTests!(spec, {
|
|
109
|
+
...ctx,
|
|
110
|
+
outputDir: tmpRoot,
|
|
111
|
+
emitterOptions: { regenerateOwnedTests: true },
|
|
112
|
+
} as EmitterContext);
|
|
113
|
+
|
|
114
|
+
expect(result.some((f) => f.path.startsWith('src/groups/'))).toBe(false);
|
|
115
|
+
} finally {
|
|
116
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('emits tests and fixtures for adopted services when regenerateOwnedTests is true', () => {
|
|
121
|
+
// Adopted dirs are created by oagen from scratch — no hand-written content
|
|
122
|
+
// to preserve, so scaffolding tests/fixtures is safe and useful.
|
|
123
|
+
const tmpRoot = createTrackedSdkRoot();
|
|
124
|
+
try {
|
|
125
|
+
const result = nodeEmitter.generateTests!(spec, {
|
|
126
|
+
...ctx,
|
|
127
|
+
outputDir: tmpRoot,
|
|
128
|
+
emitterOptions: { adoptMissingServices: true, regenerateOwnedTests: true },
|
|
129
|
+
} as EmitterContext);
|
|
130
|
+
|
|
131
|
+
const testFile = result.find((f) => f.path === 'src/groups/groups.spec.ts');
|
|
132
|
+
const fixtureFile = result.find((f) => f.path === 'src/groups/fixtures/group.json');
|
|
133
|
+
expect(testFile).toBeDefined();
|
|
134
|
+
expect(fixtureFile).toBeDefined();
|
|
135
|
+
} finally {
|
|
136
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('skips tests for adopted services when regenerateOwnedTests is false', () => {
|
|
141
|
+
const tmpRoot = createTrackedSdkRoot();
|
|
142
|
+
try {
|
|
143
|
+
const result = nodeEmitter.generateTests!(spec, {
|
|
144
|
+
...ctx,
|
|
145
|
+
outputDir: tmpRoot,
|
|
146
|
+
emitterOptions: { adoptMissingServices: true },
|
|
147
|
+
} as EmitterContext);
|
|
148
|
+
|
|
149
|
+
expect(result.some((f) => f.path.startsWith('src/groups/'))).toBe(false);
|
|
150
|
+
} finally {
|
|
151
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('skips tests when the resource class diverges from the mount accessor', () => {
|
|
156
|
+
// Hand-written `Webhooks` has a constructor incompatible with WorkOS,
|
|
157
|
+
// so the emitter forks endpoint methods onto a `WebhooksEndpoints`
|
|
158
|
+
// helper class. A generated test would call `workos.webhooks.foo(...)`
|
|
159
|
+
// — but those methods live on the helper, not the crypto class. Skip
|
|
160
|
+
// these tests so we don't ship a spec that fails to compile.
|
|
161
|
+
const webhookOp = {
|
|
162
|
+
name: 'listWebhookEndpoints',
|
|
163
|
+
httpMethod: 'get' as const,
|
|
164
|
+
path: '/webhook_endpoints',
|
|
165
|
+
pathParams: [],
|
|
166
|
+
queryParams: [],
|
|
167
|
+
headerParams: [],
|
|
168
|
+
response: { kind: 'primitive' as const, type: 'unknown' as const },
|
|
169
|
+
errors: [],
|
|
170
|
+
injectIdempotencyKey: false,
|
|
171
|
+
};
|
|
172
|
+
const webhookService: Service = { name: 'Webhooks', operations: [webhookOp] };
|
|
173
|
+
const webhookSpec: ApiSpec = {
|
|
174
|
+
...spec,
|
|
175
|
+
services: [webhookService],
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const tmpRoot = createTrackedSdkRoot();
|
|
179
|
+
try {
|
|
180
|
+
const result = nodeEmitter.generateTests!(webhookSpec, {
|
|
181
|
+
...ctx,
|
|
182
|
+
spec: webhookSpec,
|
|
183
|
+
outputDir: tmpRoot,
|
|
184
|
+
emitterOptions: { adoptMissingServices: true, regenerateOwnedTests: true },
|
|
185
|
+
apiSurface: {
|
|
186
|
+
classes: {
|
|
187
|
+
Webhooks: {
|
|
188
|
+
constructorParams: [{ name: 'crypto', type: 'CryptoProvider' }],
|
|
189
|
+
},
|
|
190
|
+
WorkOS: {
|
|
191
|
+
properties: { webhooks: { type: 'Webhooks' } },
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
resolvedOperations: [
|
|
196
|
+
{
|
|
197
|
+
operation: webhookOp,
|
|
198
|
+
service: webhookService,
|
|
199
|
+
methodName: 'list_webhook_endpoints',
|
|
200
|
+
mountOn: 'Webhooks',
|
|
201
|
+
defaults: {},
|
|
202
|
+
inferFromClient: [],
|
|
203
|
+
urlBuilder: false,
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
} as unknown as EmitterContext);
|
|
207
|
+
|
|
208
|
+
expect(result.some((f) => f.path === 'src/webhooks/webhooks-endpoints.spec.ts')).toBe(false);
|
|
209
|
+
expect(result.some((f) => f.path === 'src/webhooks/webhooks.spec.ts')).toBe(false);
|
|
210
|
+
} finally {
|
|
211
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -3,49 +3,53 @@ import { mapTypeRef, mapWireTypeRef } from '../../src/node/type-map.js';
|
|
|
3
3
|
import type { TypeRef } from '@workos/oagen';
|
|
4
4
|
|
|
5
5
|
describe('mapTypeRef', () => {
|
|
6
|
-
it('maps primitive
|
|
6
|
+
it('maps string primitive', () => {
|
|
7
7
|
const ref: TypeRef = { kind: 'primitive', type: 'string' };
|
|
8
8
|
expect(mapTypeRef(ref)).toBe('string');
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
it('maps
|
|
11
|
+
it('maps integer primitive', () => {
|
|
12
12
|
const ref: TypeRef = { kind: 'primitive', type: 'integer' };
|
|
13
13
|
expect(mapTypeRef(ref)).toBe('number');
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
it('maps primitive
|
|
16
|
+
it('maps boolean primitive', () => {
|
|
17
17
|
const ref: TypeRef = { kind: 'primitive', type: 'boolean' };
|
|
18
18
|
expect(mapTypeRef(ref)).toBe('boolean');
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
it('maps unknown to any', () => {
|
|
21
|
+
it('maps unknown primitive to any', () => {
|
|
22
22
|
const ref: TypeRef = { kind: 'primitive', type: 'unknown' };
|
|
23
23
|
expect(mapTypeRef(ref)).toBe('any');
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
it('maps
|
|
27
|
-
const ref: TypeRef = {
|
|
28
|
-
|
|
29
|
-
items: { kind: 'primitive', type: 'string' },
|
|
30
|
-
};
|
|
31
|
-
expect(mapTypeRef(ref)).toBe('string[]');
|
|
26
|
+
it('maps date-time to Date', () => {
|
|
27
|
+
const ref: TypeRef = { kind: 'primitive', type: 'string', format: 'date-time' };
|
|
28
|
+
expect(mapTypeRef(ref)).toBe('Date');
|
|
32
29
|
});
|
|
33
30
|
|
|
34
|
-
it('maps model
|
|
31
|
+
it('maps model ref to model name', () => {
|
|
35
32
|
const ref: TypeRef = { kind: 'model', name: 'Organization' };
|
|
36
33
|
expect(mapTypeRef(ref)).toBe('Organization');
|
|
37
34
|
});
|
|
38
35
|
|
|
39
|
-
it('maps enum
|
|
40
|
-
const ref: TypeRef = { kind: 'enum', name: 'Status' };
|
|
36
|
+
it('maps enum ref to enum name', () => {
|
|
37
|
+
const ref: TypeRef = { kind: 'enum', name: 'Status', values: ['active', 'inactive'] };
|
|
41
38
|
expect(mapTypeRef(ref)).toBe('Status');
|
|
42
39
|
});
|
|
43
40
|
|
|
41
|
+
it('maps array of primitives', () => {
|
|
42
|
+
const ref: TypeRef = { kind: 'array', items: { kind: 'primitive', type: 'string' } };
|
|
43
|
+
expect(mapTypeRef(ref)).toBe('string[]');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('maps array of models', () => {
|
|
47
|
+
const ref: TypeRef = { kind: 'array', items: { kind: 'model', name: 'Org' } };
|
|
48
|
+
expect(mapTypeRef(ref)).toBe('Org[]');
|
|
49
|
+
});
|
|
50
|
+
|
|
44
51
|
it('maps nullable type', () => {
|
|
45
|
-
const ref: TypeRef = {
|
|
46
|
-
kind: 'nullable',
|
|
47
|
-
inner: { kind: 'primitive', type: 'string' },
|
|
48
|
-
};
|
|
52
|
+
const ref: TypeRef = { kind: 'nullable', inner: { kind: 'primitive', type: 'string' } };
|
|
49
53
|
expect(mapTypeRef(ref)).toBe('string | null');
|
|
50
54
|
});
|
|
51
55
|
|
|
@@ -64,32 +68,14 @@ describe('mapTypeRef', () => {
|
|
|
64
68
|
const ref: TypeRef = {
|
|
65
69
|
kind: 'union',
|
|
66
70
|
variants: [
|
|
67
|
-
{ kind: '
|
|
68
|
-
{ kind: '
|
|
71
|
+
{ kind: 'primitive', type: 'string' },
|
|
72
|
+
{ kind: 'primitive', type: 'string' },
|
|
69
73
|
],
|
|
70
74
|
};
|
|
71
|
-
expect(mapTypeRef(ref)).toBe('
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('maps map type', () => {
|
|
75
|
-
const ref: TypeRef = {
|
|
76
|
-
kind: 'map',
|
|
77
|
-
valueType: { kind: 'primitive', type: 'string' },
|
|
78
|
-
};
|
|
79
|
-
expect(mapTypeRef(ref)).toBe('Record<string, string>');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('maps literal string', () => {
|
|
83
|
-
const ref: TypeRef = { kind: 'literal', value: 'organization' };
|
|
84
|
-
expect(mapTypeRef(ref)).toBe("'organization'");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('maps literal number', () => {
|
|
88
|
-
const ref: TypeRef = { kind: 'literal', value: 42 };
|
|
89
|
-
expect(mapTypeRef(ref)).toBe('42');
|
|
75
|
+
expect(mapTypeRef(ref)).toBe('string');
|
|
90
76
|
});
|
|
91
77
|
|
|
92
|
-
it('parenthesizes
|
|
78
|
+
it('parenthesizes unions in arrays', () => {
|
|
93
79
|
const ref: TypeRef = {
|
|
94
80
|
kind: 'array',
|
|
95
81
|
items: {
|
|
@@ -102,37 +88,46 @@ describe('mapTypeRef', () => {
|
|
|
102
88
|
};
|
|
103
89
|
expect(mapTypeRef(ref)).toBe('(string | number)[]');
|
|
104
90
|
});
|
|
91
|
+
|
|
92
|
+
it('maps map type', () => {
|
|
93
|
+
const ref: TypeRef = { kind: 'map', valueType: { kind: 'primitive', type: 'string' } };
|
|
94
|
+
expect(mapTypeRef(ref)).toBe('Record<string, string>');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('maps string literal', () => {
|
|
98
|
+
const ref: TypeRef = { kind: 'literal', value: 'active' };
|
|
99
|
+
expect(mapTypeRef(ref)).toBe("'active'");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('maps number literal', () => {
|
|
103
|
+
const ref: TypeRef = { kind: 'literal', value: 42 };
|
|
104
|
+
expect(mapTypeRef(ref)).toBe('42');
|
|
105
|
+
});
|
|
105
106
|
});
|
|
106
107
|
|
|
107
108
|
describe('mapWireTypeRef', () => {
|
|
108
|
-
it('maps model
|
|
109
|
+
it('maps model ref with Response suffix', () => {
|
|
109
110
|
const ref: TypeRef = { kind: 'model', name: 'Organization' };
|
|
110
111
|
expect(mapWireTypeRef(ref)).toBe('OrganizationResponse');
|
|
111
112
|
});
|
|
112
113
|
|
|
113
114
|
it('maps array of models with Response suffix', () => {
|
|
114
|
-
const ref: TypeRef = {
|
|
115
|
-
|
|
116
|
-
items: { kind: 'model', name: 'OrganizationDomain' },
|
|
117
|
-
};
|
|
118
|
-
expect(mapWireTypeRef(ref)).toBe('OrganizationDomainResponse[]');
|
|
115
|
+
const ref: TypeRef = { kind: 'array', items: { kind: 'model', name: 'Org' } };
|
|
116
|
+
expect(mapWireTypeRef(ref)).toBe('OrgResponse[]');
|
|
119
117
|
});
|
|
120
118
|
|
|
121
|
-
it('
|
|
122
|
-
const ref: TypeRef = { kind: 'primitive', type: 'string' };
|
|
119
|
+
it('maps date-time as string in wire type', () => {
|
|
120
|
+
const ref: TypeRef = { kind: 'primitive', type: 'string', format: 'date-time' };
|
|
123
121
|
expect(mapWireTypeRef(ref)).toBe('string');
|
|
124
122
|
});
|
|
125
123
|
|
|
126
|
-
it('
|
|
127
|
-
const ref: TypeRef = { kind: 'enum', name: 'Status' };
|
|
124
|
+
it('maps enum ref unchanged', () => {
|
|
125
|
+
const ref: TypeRef = { kind: 'enum', name: 'Status', values: ['active'] };
|
|
128
126
|
expect(mapWireTypeRef(ref)).toBe('Status');
|
|
129
127
|
});
|
|
130
128
|
|
|
131
129
|
it('maps nullable model with Response suffix', () => {
|
|
132
|
-
const ref: TypeRef = {
|
|
133
|
-
|
|
134
|
-
inner: { kind: 'model', name: 'Organization' },
|
|
135
|
-
};
|
|
136
|
-
expect(mapWireTypeRef(ref)).toBe('OrganizationResponse | null');
|
|
130
|
+
const ref: TypeRef = { kind: 'nullable', inner: { kind: 'model', name: 'Org' } };
|
|
131
|
+
expect(mapWireTypeRef(ref)).toBe('OrgResponse | null');
|
|
137
132
|
});
|
|
138
133
|
});
|
package/test/node/utils.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { modelHasNewFields } from '../../src/node/utils.js';
|
|
3
2
|
import type { EmitterContext, ApiSpec, Model } from '@workos/oagen';
|
|
4
3
|
import { defaultSdkBehavior } from '@workos/oagen';
|
|
4
|
+
import { modelHasNewFields } from '../../src/node/utils.js';
|
|
5
5
|
|
|
6
6
|
const emptySpec: ApiSpec = {
|
|
7
7
|
name: 'Test',
|
|
@@ -20,121 +20,70 @@ const ctx: EmitterContext = {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
describe('modelHasNewFields', () => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
27
|
-
|
|
28
|
-
],
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
it('returns true when no apiSurface exists (Scenario B)', () => {
|
|
23
|
+
it('returns true when no apiSurface (Scenario B)', () => {
|
|
24
|
+
const model: Model = {
|
|
25
|
+
name: 'Organization',
|
|
26
|
+
fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
27
|
+
};
|
|
32
28
|
expect(modelHasNewFields(model, ctx)).toBe(true);
|
|
33
29
|
});
|
|
34
30
|
|
|
35
|
-
it('returns true when model
|
|
31
|
+
it('returns true when model not in baseline', () => {
|
|
32
|
+
const model: Model = {
|
|
33
|
+
name: 'NewModel',
|
|
34
|
+
fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
35
|
+
};
|
|
36
36
|
const ctxWithSurface: EmitterContext = {
|
|
37
37
|
...ctx,
|
|
38
|
-
apiSurface: {
|
|
39
|
-
language: 'node',
|
|
40
|
-
extractedFrom: 'test',
|
|
41
|
-
extractedAt: '2024-01-01',
|
|
42
|
-
classes: {},
|
|
43
|
-
interfaces: {},
|
|
44
|
-
typeAliases: {},
|
|
45
|
-
enums: {},
|
|
46
|
-
exports: {},
|
|
47
|
-
},
|
|
38
|
+
apiSurface: { interfaces: { Organization: { fields: {} } } } as any,
|
|
48
39
|
};
|
|
49
40
|
expect(modelHasNewFields(model, ctxWithSurface)).toBe(true);
|
|
50
41
|
});
|
|
51
42
|
|
|
52
|
-
it('returns false when all fields
|
|
53
|
-
const
|
|
54
|
-
...ctx,
|
|
55
|
-
apiSurface: {
|
|
56
|
-
language: 'node',
|
|
57
|
-
extractedFrom: 'test',
|
|
58
|
-
extractedAt: '2024-01-01',
|
|
59
|
-
classes: {},
|
|
60
|
-
typeAliases: {},
|
|
61
|
-
enums: {},
|
|
62
|
-
exports: {},
|
|
63
|
-
interfaces: {
|
|
64
|
-
Organization: {
|
|
65
|
-
name: 'Organization',
|
|
66
|
-
fields: {
|
|
67
|
-
id: { name: 'id', type: 'string', optional: false },
|
|
68
|
-
name: { name: 'name', type: 'string', optional: false },
|
|
69
|
-
},
|
|
70
|
-
extends: [],
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
expect(modelHasNewFields(model, ctxWithBaseline)).toBe(false);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('returns true when model has one new field not in baseline', () => {
|
|
79
|
-
const modelWithNewField: Model = {
|
|
43
|
+
it('returns false when all fields in baseline', () => {
|
|
44
|
+
const model: Model = {
|
|
80
45
|
name: 'Organization',
|
|
81
46
|
fields: [
|
|
82
47
|
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
83
48
|
{ name: 'name', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
84
|
-
{ name: 'slug', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
85
49
|
],
|
|
86
50
|
};
|
|
87
|
-
const
|
|
51
|
+
const ctxWithSurface: EmitterContext = {
|
|
88
52
|
...ctx,
|
|
89
53
|
apiSurface: {
|
|
90
|
-
language: 'node',
|
|
91
|
-
extractedFrom: 'test',
|
|
92
|
-
extractedAt: '2024-01-01',
|
|
93
|
-
classes: {},
|
|
94
|
-
typeAliases: {},
|
|
95
|
-
enums: {},
|
|
96
|
-
exports: {},
|
|
97
54
|
interfaces: {
|
|
98
55
|
Organization: {
|
|
99
|
-
name: 'Organization',
|
|
100
56
|
fields: {
|
|
101
|
-
id: {
|
|
102
|
-
name: {
|
|
57
|
+
id: { type: 'string', optional: false },
|
|
58
|
+
name: { type: 'string', optional: false },
|
|
103
59
|
},
|
|
104
|
-
extends: [],
|
|
105
60
|
},
|
|
106
61
|
},
|
|
107
|
-
},
|
|
62
|
+
} as any,
|
|
108
63
|
};
|
|
109
|
-
expect(modelHasNewFields(
|
|
64
|
+
expect(modelHasNewFields(model, ctxWithSurface)).toBe(false);
|
|
110
65
|
});
|
|
111
66
|
|
|
112
|
-
it('
|
|
113
|
-
const
|
|
67
|
+
it('returns true when new field added', () => {
|
|
68
|
+
const model: Model = {
|
|
114
69
|
name: 'Organization',
|
|
115
|
-
fields: [
|
|
70
|
+
fields: [
|
|
71
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
72
|
+
{ name: 'new_field', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
73
|
+
],
|
|
116
74
|
};
|
|
117
|
-
const
|
|
75
|
+
const ctxWithSurface: EmitterContext = {
|
|
118
76
|
...ctx,
|
|
119
77
|
apiSurface: {
|
|
120
|
-
language: 'node',
|
|
121
|
-
extractedFrom: 'test',
|
|
122
|
-
extractedAt: '2024-01-01',
|
|
123
|
-
classes: {},
|
|
124
|
-
typeAliases: {},
|
|
125
|
-
enums: {},
|
|
126
|
-
exports: {},
|
|
127
78
|
interfaces: {
|
|
128
79
|
Organization: {
|
|
129
|
-
name: 'Organization',
|
|
130
80
|
fields: {
|
|
131
|
-
|
|
81
|
+
id: { type: 'string', optional: false },
|
|
132
82
|
},
|
|
133
|
-
extends: [],
|
|
134
83
|
},
|
|
135
84
|
},
|
|
136
|
-
},
|
|
85
|
+
} as any,
|
|
137
86
|
};
|
|
138
|
-
expect(modelHasNewFields(
|
|
87
|
+
expect(modelHasNewFields(model, ctxWithSurface)).toBe(true);
|
|
139
88
|
});
|
|
140
89
|
});
|