@workos/oagen-emitters 0.12.0 → 0.12.2
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-C408Wh-o.mjs → plugin-eCuvoL1T.mjs} +3914 -2121
- package/dist/plugin-eCuvoL1T.mjs.map +1 -0
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +1 -1
- package/package.json +10 -10
- 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 +345 -20
- package/src/node/live-surface.ts +378 -0
- package/src/node/models.ts +540 -351
- package/src/node/naming.ts +119 -25
- package/src/node/node-overrides.ts +77 -0
- package/src/node/options.ts +41 -0
- package/src/node/resources.ts +455 -46
- package/src/node/sdk-errors.ts +0 -16
- package/src/node/tests.ts +108 -83
- package/src/node/type-map.ts +40 -18
- package/src/node/utils.ts +89 -102
- package/src/node/wrappers.ts +0 -20
- package/src/rust/fixtures.ts +87 -1
- package/src/rust/models.ts +17 -2
- package/src/rust/resources.ts +697 -62
- package/src/rust/tests.ts +540 -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 +376 -2036
- package/test/node/tests.test.ts +119 -0
- package/test/node/type-map.test.ts +49 -54
- package/test/node/utils.test.ts +29 -80
- package/test/rust/fixtures.test.ts +227 -0
- package/test/rust/models.test.ts +38 -0
- package/test/rust/resources.test.ts +505 -2
- package/test/rust/tests.test.ts +504 -0
- package/dist/plugin-C408Wh-o.mjs.map +0 -1
- package/test/node/serializers.test.ts +0 -444
package/test/node/enums.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
import type { EmitterContext, ApiSpec, Enum, Service } from '@workos/oagen';
|
|
2
|
+
import type { EmitterContext, ApiSpec, Enum } from '@workos/oagen';
|
|
4
3
|
import { defaultSdkBehavior } from '@workos/oagen';
|
|
4
|
+
import { generateEnums } from '../../src/node/enums.js';
|
|
5
5
|
|
|
6
6
|
const emptySpec: ApiSpec = {
|
|
7
7
|
name: 'Test',
|
|
@@ -24,33 +24,7 @@ describe('generateEnums', () => {
|
|
|
24
24
|
expect(generateEnums([], ctx)).toEqual([]);
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
it('generates
|
|
28
|
-
const service: Service = {
|
|
29
|
-
name: 'Organizations',
|
|
30
|
-
operations: [
|
|
31
|
-
{
|
|
32
|
-
name: 'getOrganization',
|
|
33
|
-
httpMethod: 'get',
|
|
34
|
-
path: '/organizations/{id}',
|
|
35
|
-
pathParams: [
|
|
36
|
-
{
|
|
37
|
-
name: 'id',
|
|
38
|
-
type: { kind: 'primitive', type: 'string' },
|
|
39
|
-
required: true,
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
queryParams: [],
|
|
43
|
-
headerParams: [],
|
|
44
|
-
response: {
|
|
45
|
-
kind: 'model',
|
|
46
|
-
name: 'Organization',
|
|
47
|
-
},
|
|
48
|
-
errors: [],
|
|
49
|
-
injectIdempotencyKey: false,
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
};
|
|
53
|
-
|
|
27
|
+
it('generates const-object enum with derived type alias', () => {
|
|
54
28
|
const enums: Enum[] = [
|
|
55
29
|
{
|
|
56
30
|
name: 'Status',
|
|
@@ -62,138 +36,93 @@ describe('generateEnums', () => {
|
|
|
62
36
|
},
|
|
63
37
|
];
|
|
64
38
|
|
|
65
|
-
|
|
66
|
-
const files = generateEnums(enums, {
|
|
67
|
-
...ctx,
|
|
68
|
-
spec: { ...emptySpec, services: [service] },
|
|
69
|
-
});
|
|
70
|
-
expect(files.length).toBe(1);
|
|
71
|
-
expect(files[0].content).toMatchInlineSnapshot(`
|
|
72
|
-
"export type Status =
|
|
73
|
-
| 'active'
|
|
74
|
-
| 'inactive'
|
|
75
|
-
| 'pending';"
|
|
76
|
-
`);
|
|
77
|
-
});
|
|
39
|
+
const result = generateEnums(enums, ctx);
|
|
78
40
|
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
{
|
|
89
|
-
name: 'id',
|
|
90
|
-
type: { kind: 'primitive', type: 'string' },
|
|
91
|
-
required: true,
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
queryParams: [],
|
|
95
|
-
headerParams: [],
|
|
96
|
-
response: { kind: 'enum', name: 'OrgStatus' },
|
|
97
|
-
errors: [],
|
|
98
|
-
injectIdempotencyKey: false,
|
|
99
|
-
},
|
|
100
|
-
],
|
|
101
|
-
};
|
|
41
|
+
expect(result).toHaveLength(1);
|
|
42
|
+
expect(result[0].content).toContain('export const Status = {');
|
|
43
|
+
expect(result[0].content).toContain("Active: 'active'");
|
|
44
|
+
expect(result[0].content).toContain("Inactive: 'inactive'");
|
|
45
|
+
expect(result[0].content).toContain("Pending: 'pending'");
|
|
46
|
+
expect(result[0].content).toContain('} as const;');
|
|
47
|
+
expect(result[0].content).toContain('export type Status =');
|
|
48
|
+
expect(result[0].content).toContain('(typeof Status)[keyof typeof Status]');
|
|
49
|
+
});
|
|
102
50
|
|
|
51
|
+
it('places enum in common when not referenced by service', () => {
|
|
103
52
|
const enums: Enum[] = [
|
|
104
53
|
{
|
|
105
|
-
name: '
|
|
106
|
-
values: [
|
|
107
|
-
{ name: 'ACTIVE', value: 'active' },
|
|
108
|
-
{ name: 'INACTIVE', value: 'inactive' },
|
|
109
|
-
],
|
|
54
|
+
name: 'Status',
|
|
55
|
+
values: [{ name: 'ACTIVE', value: 'active' }],
|
|
110
56
|
},
|
|
111
57
|
];
|
|
112
58
|
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
117
|
-
expect(files[0].path).toBe('src/organizations/interfaces/org-status.interface.ts');
|
|
59
|
+
const result = generateEnums(enums, ctx);
|
|
60
|
+
|
|
61
|
+
expect(result[0].path).toBe('src/common/interfaces/status.interface.ts');
|
|
118
62
|
});
|
|
119
63
|
|
|
120
|
-
it('
|
|
64
|
+
it('places enum in service directory when referenced', () => {
|
|
121
65
|
const enums: Enum[] = [
|
|
122
66
|
{
|
|
123
|
-
name: '
|
|
124
|
-
values: [
|
|
125
|
-
{ name: 'FAILED', value: 'failed' },
|
|
126
|
-
{ name: 'PENDING', value: 'pending' },
|
|
127
|
-
{ name: 'VERIFIED', value: 'verified' },
|
|
128
|
-
{ name: 'LEGACY_VERIFIED', value: 'legacy_verified' },
|
|
129
|
-
{ name: 'UNVERIFIED', value: 'unverified' },
|
|
130
|
-
],
|
|
67
|
+
name: 'OrgStatus',
|
|
68
|
+
values: [{ name: 'ACTIVE', value: 'active' }],
|
|
131
69
|
},
|
|
132
70
|
];
|
|
133
71
|
|
|
134
|
-
const
|
|
135
|
-
...
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
72
|
+
const specWithServices: ApiSpec = {
|
|
73
|
+
...emptySpec,
|
|
74
|
+
enums,
|
|
75
|
+
services: [
|
|
76
|
+
{
|
|
77
|
+
name: 'Organizations',
|
|
78
|
+
operations: [
|
|
79
|
+
{
|
|
80
|
+
name: 'listOrganizations',
|
|
81
|
+
httpMethod: 'get',
|
|
82
|
+
path: '/organizations',
|
|
83
|
+
pathParams: [],
|
|
84
|
+
queryParams: [
|
|
85
|
+
{
|
|
86
|
+
name: 'status',
|
|
87
|
+
type: { kind: 'enum', name: 'OrgStatus', values: ['active'] },
|
|
88
|
+
required: false,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
headerParams: [],
|
|
92
|
+
response: { kind: 'primitive', type: 'unknown' },
|
|
93
|
+
errors: [],
|
|
94
|
+
injectIdempotencyKey: false,
|
|
150
95
|
},
|
|
151
|
-
|
|
96
|
+
],
|
|
152
97
|
},
|
|
153
|
-
|
|
154
|
-
},
|
|
98
|
+
],
|
|
155
99
|
};
|
|
156
100
|
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
expect(content).toContain("Failed = 'failed',");
|
|
162
|
-
expect(content).toContain("Pending = 'pending',");
|
|
163
|
-
expect(content).toContain("Verified = 'verified',");
|
|
101
|
+
const ctxWithServices: EmitterContext = {
|
|
102
|
+
...ctx,
|
|
103
|
+
spec: specWithServices,
|
|
104
|
+
};
|
|
164
105
|
|
|
165
|
-
|
|
166
|
-
expect(content).toContain("LegacyVerified = 'legacy_verified',");
|
|
167
|
-
expect(content).toContain("Unverified = 'unverified',");
|
|
106
|
+
const result = generateEnums(enums, ctxWithServices);
|
|
168
107
|
|
|
169
|
-
|
|
170
|
-
expect(content).not.toContain('legacyverified');
|
|
108
|
+
expect(result[0].path).toBe('src/organizations/interfaces/org-status.interface.ts');
|
|
171
109
|
});
|
|
172
110
|
|
|
173
111
|
it('renders @deprecated on enum values', () => {
|
|
174
112
|
const enums: Enum[] = [
|
|
175
113
|
{
|
|
176
|
-
name: '
|
|
114
|
+
name: 'Method',
|
|
177
115
|
values: [
|
|
178
116
|
{ name: 'ACTIVE', value: 'active' },
|
|
179
|
-
{
|
|
180
|
-
|
|
181
|
-
value: 'legacy',
|
|
182
|
-
description: 'No longer supported.',
|
|
183
|
-
deprecated: true,
|
|
184
|
-
},
|
|
185
|
-
{ name: 'OLD', value: 'old', deprecated: true },
|
|
117
|
+
{ name: 'OLD', value: 'old', deprecated: true, description: 'No longer supported.' },
|
|
118
|
+
{ name: 'BARE', value: 'bare', deprecated: true },
|
|
186
119
|
],
|
|
187
120
|
},
|
|
188
121
|
];
|
|
189
122
|
|
|
190
|
-
const
|
|
191
|
-
const content = files[0].content;
|
|
192
|
-
|
|
193
|
-
// Value with description + deprecated gets multiline JSDoc
|
|
194
|
-
expect(content).toContain(' /**\n * No longer supported.\n * @deprecated\n */');
|
|
123
|
+
const result = generateEnums(enums, ctx);
|
|
195
124
|
|
|
196
|
-
|
|
197
|
-
expect(content).toContain('
|
|
125
|
+
expect(result[0].content).toContain('No longer supported.\n * @deprecated');
|
|
126
|
+
expect(result[0].content).toContain('/** @deprecated */');
|
|
198
127
|
});
|
|
199
128
|
});
|
package/test/node/errors.test.ts
CHANGED
|
@@ -2,8 +2,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
2
2
|
import { generateErrors } from '../../src/node/errors.js';
|
|
3
3
|
|
|
4
4
|
describe('generateErrors', () => {
|
|
5
|
-
it('returns empty array without context
|
|
6
|
-
|
|
7
|
-
expect(files).toEqual([]);
|
|
5
|
+
it('returns empty array without context', () => {
|
|
6
|
+
expect(generateErrors()).toEqual([]);
|
|
8
7
|
});
|
|
9
8
|
});
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } 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
|
+
|
|
7
|
+
import { buildLiveSurface, emptyLiveSurface, pathExists, shouldSkipPath } from '../../src/node/live-surface.js';
|
|
8
|
+
|
|
9
|
+
let tmpRoot: string;
|
|
10
|
+
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'live-surface-'));
|
|
13
|
+
const src = path.join(tmpRoot, 'src');
|
|
14
|
+
fs.mkdirSync(path.join(src, 'organizations', 'interfaces'), { recursive: true });
|
|
15
|
+
fs.mkdirSync(path.join(src, 'common', 'utils'), { recursive: true });
|
|
16
|
+
fs.mkdirSync(path.join(src, 'webhooks'), { recursive: true });
|
|
17
|
+
|
|
18
|
+
// Plain hand-written class file (no header, no marker)
|
|
19
|
+
fs.writeFileSync(
|
|
20
|
+
path.join(src, 'organizations', 'organizations.ts'),
|
|
21
|
+
[
|
|
22
|
+
"import { WorkOS } from '../workos.js';",
|
|
23
|
+
'export class Organizations {',
|
|
24
|
+
' constructor(private workos: WorkOS) {}',
|
|
25
|
+
' async listOrganizations(options?: { limit?: number }) {',
|
|
26
|
+
' return this.workos.get("/organizations", options);',
|
|
27
|
+
' }',
|
|
28
|
+
' async getOrganization(id: string) {',
|
|
29
|
+
' return this.workos.get(`/organizations/${id}`);',
|
|
30
|
+
' }',
|
|
31
|
+
'}',
|
|
32
|
+
].join('\n'),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
fs.writeFileSync(
|
|
36
|
+
path.join(src, 'organizations', 'interfaces', 'organization.interface.ts'),
|
|
37
|
+
[
|
|
38
|
+
'export interface Organization {',
|
|
39
|
+
' id: string;',
|
|
40
|
+
' name: string;',
|
|
41
|
+
' createdAt: string;',
|
|
42
|
+
'}',
|
|
43
|
+
'',
|
|
44
|
+
'export interface OrganizationResponse {',
|
|
45
|
+
' id: string;',
|
|
46
|
+
' name: string;',
|
|
47
|
+
" 'created_at': string;",
|
|
48
|
+
'}',
|
|
49
|
+
'',
|
|
50
|
+
'export type Status = "active" | "suspended";',
|
|
51
|
+
].join('\n'),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Auto-generated file (header present, no ignore marker)
|
|
55
|
+
fs.mkdirSync(path.join(src, 'admin-portal'), { recursive: true });
|
|
56
|
+
fs.writeFileSync(
|
|
57
|
+
path.join(src, 'admin-portal', 'admin-portal.ts'),
|
|
58
|
+
[
|
|
59
|
+
'// This file is auto-generated by oagen. Do not edit.',
|
|
60
|
+
"import { WorkOS } from '../workos.js';",
|
|
61
|
+
'export class AdminPortal {',
|
|
62
|
+
' constructor(private workos: WorkOS) {}',
|
|
63
|
+
' async generateLink(opts: { adminEmails?: string[] }) { return opts; }',
|
|
64
|
+
'}',
|
|
65
|
+
].join('\n'),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Const-object enum (workos-node house style) with acronym casing.
|
|
69
|
+
fs.mkdirSync(path.join(src, 'common', 'interfaces'), { recursive: true });
|
|
70
|
+
fs.writeFileSync(
|
|
71
|
+
path.join(src, 'common', 'interfaces', 'generate-link-intent.interface.ts'),
|
|
72
|
+
[
|
|
73
|
+
'// This file is auto-generated by oagen. Do not edit.',
|
|
74
|
+
'',
|
|
75
|
+
'export const GenerateLinkIntent = {',
|
|
76
|
+
" SSO: 'sso',",
|
|
77
|
+
" DSync: 'dsync',",
|
|
78
|
+
" AuditLogs: 'audit_logs',",
|
|
79
|
+
'} as const;',
|
|
80
|
+
'export type GenerateLinkIntent = (typeof GenerateLinkIntent)[keyof typeof GenerateLinkIntent];',
|
|
81
|
+
].join('\n'),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Protected file
|
|
85
|
+
fs.writeFileSync(
|
|
86
|
+
path.join(src, 'webhooks', 'webhooks.ts'),
|
|
87
|
+
[
|
|
88
|
+
'// @oagen-ignore-file',
|
|
89
|
+
'export class Webhooks {',
|
|
90
|
+
' verifySignature(payload: string) { return true; }',
|
|
91
|
+
'}',
|
|
92
|
+
].join('\n'),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
fs.writeFileSync(
|
|
96
|
+
path.join(src, 'common', 'utils', 'pagination.ts'),
|
|
97
|
+
[
|
|
98
|
+
'// @oagen-ignore-file',
|
|
99
|
+
'export function autoPaginate<T>(input: T[]): T[] { return input; }',
|
|
100
|
+
'export async function fetchPage(url: string) { return { data: [], after: null }; }',
|
|
101
|
+
].join('\n'),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// A spec file that should be ignored
|
|
105
|
+
fs.writeFileSync(
|
|
106
|
+
path.join(src, 'organizations', 'organizations.spec.ts'),
|
|
107
|
+
["import { Organizations } from './organizations.js';", "describe('x', () => { it('y', () => {}) })"].join('\n'),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
execFileSync('git', ['init'], { cwd: tmpRoot, stdio: 'ignore' });
|
|
111
|
+
execFileSync('git', ['add', 'src'], { cwd: tmpRoot, stdio: 'ignore' });
|
|
112
|
+
fs.mkdirSync(path.join(src, 'connect'), { recursive: true });
|
|
113
|
+
fs.writeFileSync(path.join(src, 'connect', 'connect.ts'), 'export class Connect {}');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
afterAll(() => {
|
|
117
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('buildLiveSurface', () => {
|
|
121
|
+
it('returns an empty surface when src/ is missing', () => {
|
|
122
|
+
const empty = fs.mkdtempSync(path.join(os.tmpdir(), 'no-src-'));
|
|
123
|
+
try {
|
|
124
|
+
const surface = buildLiveSurface(empty);
|
|
125
|
+
expect(surface.files.size).toBe(0);
|
|
126
|
+
expect(surface.classes.size).toBe(0);
|
|
127
|
+
expect(surface.interfaces.size).toBe(0);
|
|
128
|
+
} finally {
|
|
129
|
+
fs.rmSync(empty, { recursive: true, force: true });
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('records files relative to root with POSIX separators', () => {
|
|
134
|
+
const surface = buildLiveSurface(tmpRoot);
|
|
135
|
+
expect(surface.files.has('src/organizations/organizations.ts')).toBe(true);
|
|
136
|
+
expect(surface.files.has('src/organizations/interfaces/organization.interface.ts')).toBe(true);
|
|
137
|
+
expect(surface.files.has('src/webhooks/webhooks.ts')).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('distinguishes git-tracked baseline files from untracked junk', () => {
|
|
141
|
+
const surface = buildLiveSurface(tmpRoot);
|
|
142
|
+
expect(surface.trackedFiles.has('src/organizations/organizations.ts')).toBe(true);
|
|
143
|
+
expect(surface.trackedFiles.has('src/connect/connect.ts')).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('does not parse declarations from untracked files when git baseline exists', () => {
|
|
147
|
+
const surface = buildLiveSurface(tmpRoot);
|
|
148
|
+
expect(surface.classes.has('Connect')).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('detects @oagen-ignore-file markers', () => {
|
|
152
|
+
const surface = buildLiveSurface(tmpRoot);
|
|
153
|
+
expect(surface.protectedFiles.has('src/webhooks/webhooks.ts')).toBe(true);
|
|
154
|
+
expect(surface.protectedFiles.has('src/common/utils/pagination.ts')).toBe(true);
|
|
155
|
+
expect(surface.protectedFiles.has('src/organizations/organizations.ts')).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('detects auto-generated header', () => {
|
|
159
|
+
const surface = buildLiveSurface(tmpRoot);
|
|
160
|
+
expect(surface.autogenFiles.has('src/admin-portal/admin-portal.ts')).toBe(true);
|
|
161
|
+
expect(surface.autogenFiles.has('src/organizations/organizations.ts')).toBe(false);
|
|
162
|
+
// Protected files are NOT also recorded as autogen even if their content
|
|
163
|
+
// happens to mention the phrase — the marker takes precedence.
|
|
164
|
+
expect(surface.autogenFiles.has('src/webhooks/webhooks.ts')).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('extracts exported class names with their methods', () => {
|
|
168
|
+
const surface = buildLiveSurface(tmpRoot);
|
|
169
|
+
const orgs = surface.classes.get('Organizations');
|
|
170
|
+
expect(orgs).toBeDefined();
|
|
171
|
+
expect(orgs?.filePath).toBe('src/organizations/organizations.ts');
|
|
172
|
+
expect(orgs?.methods.has('listOrganizations')).toBe(true);
|
|
173
|
+
expect(orgs?.methods.has('getOrganization')).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('extracts protected class declarations as well', () => {
|
|
177
|
+
const surface = buildLiveSurface(tmpRoot);
|
|
178
|
+
expect(surface.classes.get('Webhooks')?.filePath).toBe('src/webhooks/webhooks.ts');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('extracts exported interface names with their fields', () => {
|
|
182
|
+
const surface = buildLiveSurface(tmpRoot);
|
|
183
|
+
const org = surface.interfaces.get('Organization');
|
|
184
|
+
expect(org?.filePath).toBe('src/organizations/interfaces/organization.interface.ts');
|
|
185
|
+
expect(org?.fields.has('id')).toBe(true);
|
|
186
|
+
expect(org?.fields.has('name')).toBe(true);
|
|
187
|
+
expect(org?.fields.has('createdAt')).toBe(true);
|
|
188
|
+
|
|
189
|
+
const wire = surface.interfaces.get('OrganizationResponse');
|
|
190
|
+
expect(wire?.fields.has('created_at')).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('records exported type aliases', () => {
|
|
194
|
+
const surface = buildLiveSurface(tmpRoot);
|
|
195
|
+
expect(surface.interfaces.has('Status')).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('records exported functions', () => {
|
|
199
|
+
const surface = buildLiveSurface(tmpRoot);
|
|
200
|
+
expect(surface.functions.get('autoPaginate')).toBe('src/common/utils/pagination.ts');
|
|
201
|
+
expect(surface.functions.get('fetchPage')).toBe('src/common/utils/pagination.ts');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('extracts const-object enum members with acronym casing preserved', () => {
|
|
205
|
+
const surface = buildLiveSurface(tmpRoot);
|
|
206
|
+
const members = surface.constObjectEnums.get('GenerateLinkIntent');
|
|
207
|
+
expect(members).toBeDefined();
|
|
208
|
+
expect(members?.get('sso')).toBe('SSO');
|
|
209
|
+
expect(members?.get('dsync')).toBe('DSync');
|
|
210
|
+
expect(members?.get('audit_logs')).toBe('AuditLogs');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('skips .spec.ts files when extracting symbols', () => {
|
|
214
|
+
const surface = buildLiveSurface(tmpRoot);
|
|
215
|
+
// The spec file is recorded in files but its symbols are not parsed (no class extraction).
|
|
216
|
+
expect(surface.files.has('src/organizations/organizations.spec.ts')).toBe(true);
|
|
217
|
+
// No class declarations exist in the spec file anyway, so no false-positive check needed.
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('helpers', () => {
|
|
222
|
+
it('shouldSkipPath returns true for protected files', () => {
|
|
223
|
+
const surface = buildLiveSurface(tmpRoot);
|
|
224
|
+
expect(shouldSkipPath(surface, 'src/webhooks/webhooks.ts')).toBe(true);
|
|
225
|
+
expect(shouldSkipPath(surface, 'src/organizations/organizations.ts')).toBe(false);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('pathExists reflects walked file set', () => {
|
|
229
|
+
const surface = buildLiveSurface(tmpRoot);
|
|
230
|
+
expect(pathExists(surface, 'src/organizations/organizations.ts')).toBe(true);
|
|
231
|
+
expect(pathExists(surface, 'src/missing/file.ts')).toBe(false);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('emptyLiveSurface produces a usable zero state', () => {
|
|
235
|
+
const surface = emptyLiveSurface();
|
|
236
|
+
expect(surface.files.size).toBe(0);
|
|
237
|
+
expect(shouldSkipPath(surface, 'anything')).toBe(false);
|
|
238
|
+
expect(pathExists(surface, 'anything')).toBe(false);
|
|
239
|
+
});
|
|
240
|
+
});
|