@workos/oagen-emitters 0.19.0 → 0.19.1
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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +7 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{plugin-BXDPA9pJ.mjs → plugin-DXIciTnN.mjs} +535 -96
- package/dist/plugin-DXIciTnN.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +1 -1
- package/src/dotnet/fixtures.ts +28 -7
- package/src/dotnet/index.ts +42 -1
- package/src/dotnet/tests.ts +1 -1
- package/src/go/enums.ts +91 -18
- package/src/go/fixtures.ts +25 -3
- package/src/go/flat-merge.ts +253 -0
- package/src/go/models.ts +85 -20
- package/src/go/tests.ts +4 -2
- package/src/kotlin/models.ts +36 -6
- package/src/kotlin/tests.ts +36 -1
- package/src/python/fixtures.ts +34 -6
- package/src/python/models.ts +118 -34
- package/src/python/tests.ts +28 -9
- package/src/ruby/tests.ts +35 -2
- package/src/rust/enums.ts +29 -15
- package/src/rust/fixtures.ts +12 -3
- package/src/rust/models.ts +26 -8
- package/src/rust/tests.ts +1 -1
- package/src/shared/resolved-ops.ts +57 -0
- package/test/dotnet/scoped-aggregates.test.ts +247 -0
- package/test/go/scoping.test.ts +324 -0
- package/test/kotlin/models.test.ts +74 -0
- package/test/kotlin/tests.test.ts +33 -0
- package/test/python/scoped-aggregates.test.ts +205 -0
- package/test/ruby/tests.test.ts +130 -0
- package/test/rust/fixtures.test.ts +13 -7
- package/dist/plugin-BXDPA9pJ.mjs.map +0 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import type { EmitterContext, ApiSpec, Service, Model, ResolvedOperation } from '@workos/oagen';
|
|
3
|
+
import { defaultSdkBehavior, toSnakeCase, toPascalCase, assignModelsToServices } from '@workos/oagen';
|
|
4
|
+
import { generateTests } from '../../src/ruby/tests.js';
|
|
5
|
+
import { fileName, buildMountDirMap } from '../../src/ruby/naming.js';
|
|
6
|
+
import { classifyUnassignedModel } from '../../src/ruby/models.js';
|
|
7
|
+
|
|
8
|
+
function makeSpec(services: Service[], models: Model[]): ApiSpec {
|
|
9
|
+
return {
|
|
10
|
+
name: 'Test',
|
|
11
|
+
version: '1.0.0',
|
|
12
|
+
baseUrl: 'https://api.workos.com',
|
|
13
|
+
services,
|
|
14
|
+
models,
|
|
15
|
+
enums: [],
|
|
16
|
+
sdk: defaultSdkBehavior(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildResolvedOps(services: Service[]): ResolvedOperation[] {
|
|
21
|
+
const ops: ResolvedOperation[] = [];
|
|
22
|
+
for (const service of services) {
|
|
23
|
+
const mountOn = toPascalCase(service.name);
|
|
24
|
+
for (const op of service.operations) {
|
|
25
|
+
ops.push({
|
|
26
|
+
operation: op,
|
|
27
|
+
service,
|
|
28
|
+
methodName: toSnakeCase(op.name),
|
|
29
|
+
mountOn,
|
|
30
|
+
defaults: {},
|
|
31
|
+
inferFromClient: [],
|
|
32
|
+
urlBuilder: false,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return ops;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Compute the per-model `.rb` path exactly as ruby/models.ts (and tests.ts) does. */
|
|
40
|
+
function modelFilePath(modelName: string, spec: ApiSpec, ctx: EmitterContext): string {
|
|
41
|
+
const modelToService = assignModelsToServices(spec.models as Model[], spec.services, ctx.modelHints);
|
|
42
|
+
const mountDirMap = buildMountDirMap(ctx);
|
|
43
|
+
const service = modelToService.get(modelName);
|
|
44
|
+
const dir = service
|
|
45
|
+
? (mountDirMap.get(service) ?? classifyUnassignedModel(modelName))
|
|
46
|
+
: classifyUnassignedModel(modelName);
|
|
47
|
+
return `lib/workos/${dir}/${fileName(modelName)}.rb`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const models: Model[] = [
|
|
51
|
+
// In-scope: selected service's model.
|
|
52
|
+
{ name: 'Organization', fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }] },
|
|
53
|
+
// On-disk: out-of-scope this run, but its file is recorded in the prior manifest.
|
|
54
|
+
{ name: 'Connection', fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }] },
|
|
55
|
+
// Brand-new out-of-scope: out-of-scope AND not in the prior manifest.
|
|
56
|
+
{ name: 'Directory', fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }] },
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const services: Service[] = [
|
|
60
|
+
{
|
|
61
|
+
name: 'Organizations',
|
|
62
|
+
operations: [
|
|
63
|
+
{
|
|
64
|
+
name: 'listOrganizations',
|
|
65
|
+
httpMethod: 'get',
|
|
66
|
+
path: '/organizations',
|
|
67
|
+
pathParams: [],
|
|
68
|
+
queryParams: [],
|
|
69
|
+
headerParams: [],
|
|
70
|
+
response: { kind: 'model', name: 'Organization' },
|
|
71
|
+
errors: [],
|
|
72
|
+
injectIdempotencyKey: false,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const spec = makeSpec(services, models);
|
|
79
|
+
|
|
80
|
+
function findRoundTripFile(files: { path: string; content: string }[]): { path: string; content: string } {
|
|
81
|
+
const file = files.find((f) => f.path === 'test/workos/test_model_round_trip.rb');
|
|
82
|
+
if (!file) throw new Error('round-trip test file not emitted');
|
|
83
|
+
return file;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
describe('ruby/tests model round-trip aggregate scoping', () => {
|
|
87
|
+
it('full run: round-trips every model', () => {
|
|
88
|
+
const ctx: EmitterContext = {
|
|
89
|
+
namespace: 'workos',
|
|
90
|
+
namespacePascal: 'WorkOS',
|
|
91
|
+
spec,
|
|
92
|
+
resolvedOperations: buildResolvedOps(services),
|
|
93
|
+
};
|
|
94
|
+
const content = findRoundTripFile(generateTests(spec, ctx)).content;
|
|
95
|
+
expect(content).toContain('WorkOS::Organization.new');
|
|
96
|
+
expect(content).toContain('WorkOS::Connection.new');
|
|
97
|
+
expect(content).toContain('WorkOS::Directory.new');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('scoped run: keeps in-scope + on-disk models, drops brand-new out-of-scope models', () => {
|
|
101
|
+
const onDiskPath = modelFilePath('Connection', spec, {
|
|
102
|
+
namespace: 'workos',
|
|
103
|
+
namespacePascal: 'WorkOS',
|
|
104
|
+
spec,
|
|
105
|
+
resolvedOperations: buildResolvedOps(services),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const ctx: EmitterContext = {
|
|
109
|
+
namespace: 'workos',
|
|
110
|
+
namespacePascal: 'WorkOS',
|
|
111
|
+
spec,
|
|
112
|
+
resolvedOperations: buildResolvedOps(services),
|
|
113
|
+
// Scoped to the Organizations service / Organization model only.
|
|
114
|
+
scopedServices: new Set(['Organizations']),
|
|
115
|
+
scopedModelNames: new Set(['Organization']),
|
|
116
|
+
// Connection's per-model file already exists on disk from a prior run.
|
|
117
|
+
priorTargetManifestPaths: new Set([onDiskPath]),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const content = findRoundTripFile(generateTests(spec, ctx)).content;
|
|
121
|
+
|
|
122
|
+
// In-scope model: kept.
|
|
123
|
+
expect(content).toContain('WorkOS::Organization.new');
|
|
124
|
+
// On-disk (prior manifest) model: retained even though out-of-scope.
|
|
125
|
+
expect(content).toContain('WorkOS::Connection.new');
|
|
126
|
+
// Brand-new out-of-scope model: NO round-trip test (would NameError).
|
|
127
|
+
expect(content).not.toContain('WorkOS::Directory.new');
|
|
128
|
+
expect(content).not.toContain('def test_directory_round_trip');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import type { ApiSpec, Enum, Model } from '@workos/oagen';
|
|
2
|
+
import type { ApiSpec, Enum, Model, EmitterContext } from '@workos/oagen';
|
|
3
3
|
import { defaultSdkBehavior } from '@workos/oagen';
|
|
4
4
|
import { exampleFromSpec, generateFixtures, generateModelFixture } from '../../src/rust/fixtures.js';
|
|
5
5
|
|
|
@@ -15,6 +15,12 @@ function spec(models: Model[], enums: Enum[] = []): ApiSpec {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// Minimal full-run context (no `scopedServices`): scoping is inert, so every
|
|
19
|
+
// fixture is emitted — matching these tests' intent of asserting content.
|
|
20
|
+
function ctx(s: ApiSpec): EmitterContext {
|
|
21
|
+
return { namespace: 'test', namespacePascal: 'Test', spec: s };
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
describe('rust/fixtures', () => {
|
|
19
25
|
it('prefers a spec `example` over the generated placeholder for a primitive field', () => {
|
|
20
26
|
const models: Model[] = [
|
|
@@ -36,7 +42,7 @@ describe('rust/fixtures', () => {
|
|
|
36
42
|
],
|
|
37
43
|
},
|
|
38
44
|
];
|
|
39
|
-
const files = generateFixtures(spec(models));
|
|
45
|
+
const files = generateFixtures(spec(models), ctx(spec(models)));
|
|
40
46
|
const file = files.find((f) => f.path === 'tests/fixtures/event.json')!;
|
|
41
47
|
expect(file).toBeDefined();
|
|
42
48
|
const parsed = JSON.parse(file.content);
|
|
@@ -59,7 +65,7 @@ describe('rust/fixtures', () => {
|
|
|
59
65
|
],
|
|
60
66
|
},
|
|
61
67
|
];
|
|
62
|
-
const files = generateFixtures(spec(models));
|
|
68
|
+
const files = generateFixtures(spec(models), ctx(spec(models)));
|
|
63
69
|
const file = files.find((f) => f.path === 'tests/fixtures/wrong.json')!;
|
|
64
70
|
const parsed = JSON.parse(file.content);
|
|
65
71
|
expect(parsed.count).toBe(0); // placeholder fallback
|
|
@@ -82,7 +88,7 @@ describe('rust/fixtures', () => {
|
|
|
82
88
|
],
|
|
83
89
|
},
|
|
84
90
|
];
|
|
85
|
-
const files = generateFixtures(spec(models));
|
|
91
|
+
const files = generateFixtures(spec(models), ctx(spec(models)));
|
|
86
92
|
const file = files.find((f) => f.path === 'tests/fixtures/org.json')!;
|
|
87
93
|
const parsed = JSON.parse(file.content);
|
|
88
94
|
expect(parsed.domains).toEqual(['example.com', 'foo.com']);
|
|
@@ -120,7 +126,7 @@ describe('rust/fixtures', () => {
|
|
|
120
126
|
],
|
|
121
127
|
},
|
|
122
128
|
];
|
|
123
|
-
const files = generateFixtures(spec(models));
|
|
129
|
+
const files = generateFixtures(spec(models), ctx(spec(models)));
|
|
124
130
|
const file = files.find((f) => f.path === 'tests/fixtures/outer.json')!;
|
|
125
131
|
const parsed = JSON.parse(file.content);
|
|
126
132
|
// The nested model is regenerated from its own fields' examples, not from
|
|
@@ -162,7 +168,7 @@ describe('rust/fixtures', () => {
|
|
|
162
168
|
],
|
|
163
169
|
},
|
|
164
170
|
];
|
|
165
|
-
const files = generateFixtures(spec(models, enums));
|
|
171
|
+
const files = generateFixtures(spec(models, enums), ctx(spec(models, enums)));
|
|
166
172
|
const good = JSON.parse(files.find((f) => f.path === 'tests/fixtures/good_ex.json')!.content);
|
|
167
173
|
const bad = JSON.parse(files.find((f) => f.path === 'tests/fixtures/bad_ex.json')!.content);
|
|
168
174
|
expect(good.status).toBe('pending'); // valid example wins
|
|
@@ -183,7 +189,7 @@ describe('rust/fixtures', () => {
|
|
|
183
189
|
],
|
|
184
190
|
},
|
|
185
191
|
];
|
|
186
|
-
const files = generateFixtures(spec(models));
|
|
192
|
+
const files = generateFixtures(spec(models), ctx(spec(models)));
|
|
187
193
|
const parsed = JSON.parse(files.find((f) => f.path === 'tests/fixtures/nullish.json')!.content);
|
|
188
194
|
expect(parsed.name).toBe('test_name');
|
|
189
195
|
});
|