@workos/oagen-emitters 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/.husky/pre-commit +1 -0
  2. package/.release-please-manifest.json +1 -1
  3. package/CHANGELOG.md +15 -0
  4. package/README.md +129 -0
  5. package/dist/index.d.mts +13 -1
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +14549 -3385
  8. package/dist/index.mjs.map +1 -1
  9. package/docs/sdk-architecture/dotnet.md +336 -0
  10. package/docs/sdk-architecture/go.md +338 -0
  11. package/docs/sdk-architecture/php.md +315 -0
  12. package/docs/sdk-architecture/python.md +511 -0
  13. package/oagen.config.ts +328 -2
  14. package/package.json +9 -5
  15. package/scripts/generate-php.js +13 -0
  16. package/scripts/git-push-with-published-oagen.sh +21 -0
  17. package/smoke/sdk-dotnet.ts +45 -12
  18. package/smoke/sdk-go.ts +116 -42
  19. package/smoke/sdk-php.ts +28 -26
  20. package/smoke/sdk-python.ts +5 -2
  21. package/src/dotnet/client.ts +89 -0
  22. package/src/dotnet/enums.ts +323 -0
  23. package/src/dotnet/fixtures.ts +236 -0
  24. package/src/dotnet/index.ts +246 -0
  25. package/src/dotnet/manifest.ts +36 -0
  26. package/src/dotnet/models.ts +344 -0
  27. package/src/dotnet/naming.ts +330 -0
  28. package/src/dotnet/resources.ts +622 -0
  29. package/src/dotnet/tests.ts +693 -0
  30. package/src/dotnet/type-map.ts +201 -0
  31. package/src/dotnet/wrappers.ts +186 -0
  32. package/src/go/client.ts +141 -0
  33. package/src/go/enums.ts +196 -0
  34. package/src/go/fixtures.ts +212 -0
  35. package/src/go/index.ts +84 -0
  36. package/src/go/manifest.ts +36 -0
  37. package/src/go/models.ts +254 -0
  38. package/src/go/naming.ts +179 -0
  39. package/src/go/resources.ts +827 -0
  40. package/src/go/tests.ts +751 -0
  41. package/src/go/type-map.ts +82 -0
  42. package/src/go/wrappers.ts +261 -0
  43. package/src/index.ts +4 -0
  44. package/src/kotlin/client.ts +53 -0
  45. package/src/kotlin/enums.ts +162 -0
  46. package/src/kotlin/index.ts +92 -0
  47. package/src/kotlin/manifest.ts +55 -0
  48. package/src/kotlin/models.ts +395 -0
  49. package/src/kotlin/naming.ts +223 -0
  50. package/src/kotlin/overrides.ts +25 -0
  51. package/src/kotlin/resources.ts +667 -0
  52. package/src/kotlin/tests.ts +1019 -0
  53. package/src/kotlin/type-map.ts +123 -0
  54. package/src/kotlin/wrappers.ts +168 -0
  55. package/src/node/client.ts +128 -115
  56. package/src/node/enums.ts +9 -0
  57. package/src/node/errors.ts +37 -232
  58. package/src/node/field-plan.ts +726 -0
  59. package/src/node/fixtures.ts +9 -1
  60. package/src/node/index.ts +3 -9
  61. package/src/node/models.ts +178 -21
  62. package/src/node/naming.ts +49 -111
  63. package/src/node/resources.ts +527 -397
  64. package/src/node/sdk-errors.ts +41 -0
  65. package/src/node/tests.ts +69 -19
  66. package/src/node/type-map.ts +4 -2
  67. package/src/node/utils.ts +13 -71
  68. package/src/node/wrappers.ts +151 -0
  69. package/src/php/client.ts +179 -0
  70. package/src/php/enums.ts +67 -0
  71. package/src/php/errors.ts +9 -0
  72. package/src/php/fixtures.ts +181 -0
  73. package/src/php/index.ts +96 -0
  74. package/src/php/manifest.ts +36 -0
  75. package/src/php/models.ts +310 -0
  76. package/src/php/naming.ts +279 -0
  77. package/src/php/resources.ts +636 -0
  78. package/src/php/tests.ts +609 -0
  79. package/src/php/type-map.ts +90 -0
  80. package/src/php/utils.ts +18 -0
  81. package/src/php/wrappers.ts +152 -0
  82. package/src/python/client.ts +345 -0
  83. package/src/python/enums.ts +313 -0
  84. package/src/python/fixtures.ts +196 -0
  85. package/src/python/index.ts +95 -0
  86. package/src/python/manifest.ts +38 -0
  87. package/src/python/models.ts +688 -0
  88. package/src/python/naming.ts +189 -0
  89. package/src/python/resources.ts +1322 -0
  90. package/src/python/tests.ts +1335 -0
  91. package/src/python/type-map.ts +93 -0
  92. package/src/python/wrappers.ts +191 -0
  93. package/src/shared/model-utils.ts +472 -0
  94. package/src/shared/naming-utils.ts +154 -0
  95. package/src/shared/non-spec-services.ts +54 -0
  96. package/src/shared/resolved-ops.ts +109 -0
  97. package/src/shared/wrapper-utils.ts +70 -0
  98. package/test/dotnet/client.test.ts +121 -0
  99. package/test/dotnet/enums.test.ts +193 -0
  100. package/test/dotnet/errors.test.ts +9 -0
  101. package/test/dotnet/manifest.test.ts +82 -0
  102. package/test/dotnet/models.test.ts +260 -0
  103. package/test/dotnet/resources.test.ts +255 -0
  104. package/test/dotnet/tests.test.ts +202 -0
  105. package/test/go/client.test.ts +92 -0
  106. package/test/go/enums.test.ts +132 -0
  107. package/test/go/errors.test.ts +9 -0
  108. package/test/go/models.test.ts +265 -0
  109. package/test/go/resources.test.ts +408 -0
  110. package/test/go/tests.test.ts +143 -0
  111. package/test/kotlin/models.test.ts +135 -0
  112. package/test/kotlin/tests.test.ts +176 -0
  113. package/test/node/client.test.ts +92 -12
  114. package/test/node/enums.test.ts +2 -0
  115. package/test/node/errors.test.ts +2 -41
  116. package/test/node/models.test.ts +2 -0
  117. package/test/node/naming.test.ts +23 -0
  118. package/test/node/resources.test.ts +315 -84
  119. package/test/node/serializers.test.ts +3 -1
  120. package/test/node/type-map.test.ts +11 -0
  121. package/test/php/client.test.ts +95 -0
  122. package/test/php/enums.test.ts +173 -0
  123. package/test/php/errors.test.ts +9 -0
  124. package/test/php/models.test.ts +497 -0
  125. package/test/php/resources.test.ts +682 -0
  126. package/test/php/tests.test.ts +185 -0
  127. package/test/python/client.test.ts +200 -0
  128. package/test/python/enums.test.ts +228 -0
  129. package/test/python/errors.test.ts +16 -0
  130. package/test/python/manifest.test.ts +74 -0
  131. package/test/python/models.test.ts +716 -0
  132. package/test/python/resources.test.ts +617 -0
  133. package/test/python/tests.test.ts +202 -0
  134. package/src/node/common.ts +0 -273
  135. package/src/node/config.ts +0 -71
  136. package/src/node/serializers.ts +0 -746
@@ -0,0 +1,260 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateModels } from '../../src/dotnet/models.js';
3
+ import { primeEnumAliases } from '../../src/dotnet/enums.js';
4
+ import type { EmitterContext, ApiSpec, Model, Service } from '@workos/oagen';
5
+ import { defaultSdkBehavior } from '@workos/oagen';
6
+
7
+ const emptySpec: ApiSpec = {
8
+ name: 'Test',
9
+ version: '1.0.0',
10
+ baseUrl: '',
11
+ services: [],
12
+ models: [],
13
+ enums: [],
14
+ sdk: defaultSdkBehavior(),
15
+ };
16
+
17
+ const ctx: EmitterContext = {
18
+ namespace: 'workos',
19
+ namespacePascal: 'WorkOS',
20
+ spec: emptySpec,
21
+ };
22
+
23
+ describe('dotnet/models', () => {
24
+ it('returns empty for no models', () => {
25
+ expect(generateModels([], ctx)).toEqual([]);
26
+ });
27
+
28
+ it('generates a C# class with JSON attributes', () => {
29
+ const service: Service = {
30
+ name: 'Organizations',
31
+ operations: [
32
+ {
33
+ name: 'getOrganization',
34
+ httpMethod: 'get',
35
+ path: '/organizations/{id}',
36
+ pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
37
+ queryParams: [],
38
+ headerParams: [],
39
+ response: { kind: 'model', name: 'Organization' },
40
+ errors: [],
41
+ injectIdempotencyKey: false,
42
+ },
43
+ ],
44
+ };
45
+
46
+ const models: Model[] = [
47
+ {
48
+ name: 'Organization',
49
+ fields: [
50
+ { name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
51
+ { name: 'name', type: { kind: 'primitive', type: 'string' }, required: true },
52
+ {
53
+ name: 'created_at',
54
+ type: { kind: 'primitive', type: 'string', format: 'date-time' },
55
+ required: true,
56
+ },
57
+ {
58
+ name: 'external_id',
59
+ type: { kind: 'nullable', inner: { kind: 'primitive', type: 'string' } },
60
+ required: false,
61
+ },
62
+ ],
63
+ },
64
+ ];
65
+
66
+ primeEnumAliases([]);
67
+ const files = generateModels(models, {
68
+ ...ctx,
69
+ spec: { ...emptySpec, services: [service], models },
70
+ });
71
+
72
+ expect(files.length).toBeGreaterThanOrEqual(1);
73
+
74
+ const modelFile = files.find((f) => f.path === 'Entities/Organization.cs')!;
75
+ expect(modelFile).toBeDefined();
76
+
77
+ const content = modelFile.content;
78
+ // Namespace
79
+ expect(content).toContain('namespace WorkOS');
80
+ // Class definition
81
+ expect(content).toContain('public class Organization');
82
+
83
+ // Required fields with JSON attributes
84
+ expect(content).toContain('[JsonProperty("id")]');
85
+ expect(content).toContain('public string Id');
86
+ expect(content).toContain('[JsonProperty("name")]');
87
+ expect(content).toContain('public string Name');
88
+
89
+ // DateTime field
90
+ expect(content).toContain('DateTimeOffset');
91
+ expect(content).toContain('[JsonProperty("created_at")]');
92
+
93
+ // Optional/nullable field
94
+ expect(content).toContain('[JsonProperty("external_id")]');
95
+ });
96
+
97
+ it('skips list wrapper and list metadata models', () => {
98
+ const models: Model[] = [
99
+ {
100
+ name: 'Organization',
101
+ fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
102
+ },
103
+ {
104
+ name: 'OrganizationList',
105
+ fields: [
106
+ {
107
+ name: 'data',
108
+ type: { kind: 'array', items: { kind: 'model', name: 'Organization' } },
109
+ required: true,
110
+ },
111
+ {
112
+ name: 'list_metadata',
113
+ type: { kind: 'model', name: 'ListMetadata' },
114
+ required: true,
115
+ },
116
+ ],
117
+ },
118
+ {
119
+ name: 'ListMetadata',
120
+ fields: [
121
+ { name: 'before', type: { kind: 'primitive', type: 'string' }, required: false },
122
+ { name: 'after', type: { kind: 'primitive', type: 'string' }, required: false },
123
+ ],
124
+ },
125
+ ];
126
+
127
+ primeEnumAliases([]);
128
+ const files = generateModels(models, {
129
+ ...ctx,
130
+ spec: { ...emptySpec, models },
131
+ });
132
+ const filePaths = files.map((f) => f.path);
133
+
134
+ // Should generate Organization but NOT OrganizationList or ListMetadata
135
+ expect(filePaths.some((p) => p.includes('Organization.cs') && !p.includes('List'))).toBe(true);
136
+ expect(filePaths.some((p) => p.includes('OrganizationList.cs'))).toBe(false);
137
+ expect(filePaths.some((p) => p.includes('ListMetadata.cs'))).toBe(false);
138
+ });
139
+
140
+ it('deduplicates structurally identical models', () => {
141
+ const models: Model[] = [
142
+ {
143
+ name: 'OrganizationDomain',
144
+ fields: [
145
+ { name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
146
+ { name: 'domain', type: { kind: 'primitive', type: 'string' }, required: true },
147
+ ],
148
+ },
149
+ {
150
+ name: 'OrganizationDomainStandAlone',
151
+ fields: [
152
+ { name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
153
+ { name: 'domain', type: { kind: 'primitive', type: 'string' }, required: true },
154
+ ],
155
+ },
156
+ ];
157
+
158
+ primeEnumAliases([]);
159
+ const files = generateModels(models, {
160
+ ...ctx,
161
+ spec: { ...emptySpec, models },
162
+ });
163
+
164
+ // Canonical model should have a full class
165
+ const canonicalFile = files.find(
166
+ (f) => f.path.includes('OrganizationDomain.cs') && !f.path.includes('StandAlone'),
167
+ )!;
168
+ expect(canonicalFile).toBeDefined();
169
+ expect(canonicalFile.content).toContain('public class OrganizationDomain');
170
+
171
+ // Alias model should be a subclass of canonical
172
+ const aliasFile = files.find((f) => f.path.includes('OrganizationDomainStandAlone.cs'))!;
173
+ expect(aliasFile).toBeDefined();
174
+ expect(aliasFile.content).toContain('OrganizationDomain');
175
+ });
176
+
177
+ it('emits [System.Obsolete] for deprecated fields', () => {
178
+ const models: Model[] = [
179
+ {
180
+ name: 'Organization',
181
+ fields: [
182
+ { name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
183
+ {
184
+ name: 'old_field',
185
+ type: { kind: 'primitive', type: 'string' },
186
+ required: true,
187
+ deprecated: true,
188
+ description: 'Legacy field',
189
+ },
190
+ ],
191
+ },
192
+ ];
193
+
194
+ primeEnumAliases([]);
195
+ const files = generateModels(models, {
196
+ ...ctx,
197
+ spec: { ...emptySpec, models },
198
+ });
199
+ const modelFile = files.find((f) => f.path.includes('Organization.cs'))!;
200
+
201
+ expect(modelFile.content).toContain('[System.Obsolete');
202
+ });
203
+
204
+ it('handles map fields', () => {
205
+ const models: Model[] = [
206
+ {
207
+ name: 'Organization',
208
+ fields: [
209
+ { name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
210
+ {
211
+ name: 'metadata',
212
+ type: { kind: 'map', valueType: { kind: 'primitive', type: 'string' } },
213
+ required: false,
214
+ },
215
+ ],
216
+ },
217
+ ];
218
+
219
+ primeEnumAliases([]);
220
+ const files = generateModels(models, {
221
+ ...ctx,
222
+ spec: { ...emptySpec, models },
223
+ });
224
+ const modelFile = files.find((f) => f.path.includes('Organization.cs'))!;
225
+
226
+ expect(modelFile.content).toContain('Dictionary<string,');
227
+ });
228
+
229
+ it('handles array fields with model refs', () => {
230
+ const models: Model[] = [
231
+ {
232
+ name: 'Organization',
233
+ fields: [
234
+ { name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
235
+ {
236
+ name: 'domains',
237
+ type: { kind: 'array', items: { kind: 'model', name: 'OrganizationDomain' } },
238
+ required: true,
239
+ },
240
+ ],
241
+ },
242
+ {
243
+ name: 'OrganizationDomain',
244
+ fields: [
245
+ { name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
246
+ { name: 'domain', type: { kind: 'primitive', type: 'string' }, required: true },
247
+ ],
248
+ },
249
+ ];
250
+
251
+ primeEnumAliases([]);
252
+ const files = generateModels(models, {
253
+ ...ctx,
254
+ spec: { ...emptySpec, models },
255
+ });
256
+ const orgFile = files.find((f) => f.path.includes('Organization.cs') && !f.path.includes('Domain'))!;
257
+ expect(orgFile).toBeDefined();
258
+ expect(orgFile.content).toContain('List<OrganizationDomain>');
259
+ });
260
+ });
@@ -0,0 +1,255 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateResources } from '../../src/dotnet/resources.js';
3
+ import { primeEnumAliases } from '../../src/dotnet/enums.js';
4
+ import type { EmitterContext, ApiSpec, Service, Model } from '@workos/oagen';
5
+ import { defaultSdkBehavior } from '@workos/oagen';
6
+
7
+ const emptySpec: ApiSpec = {
8
+ name: 'Test',
9
+ version: '1.0.0',
10
+ baseUrl: '',
11
+ services: [],
12
+ models: [],
13
+ enums: [],
14
+ sdk: defaultSdkBehavior(),
15
+ };
16
+
17
+ const ctx: EmitterContext = {
18
+ namespace: 'workos',
19
+ namespacePascal: 'WorkOS',
20
+ spec: emptySpec,
21
+ };
22
+
23
+ describe('dotnet/resources', () => {
24
+ it('returns empty for no services', () => {
25
+ primeEnumAliases([]);
26
+ expect(generateResources([], ctx)).toEqual([]);
27
+ });
28
+
29
+ it('generates a service class with methods', () => {
30
+ const models: Model[] = [
31
+ {
32
+ name: 'Organization',
33
+ fields: [
34
+ { name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
35
+ { name: 'name', type: { kind: 'primitive', type: 'string' }, required: true },
36
+ ],
37
+ },
38
+ ];
39
+
40
+ const services: Service[] = [
41
+ {
42
+ name: 'Organizations',
43
+ operations: [
44
+ {
45
+ name: 'getOrganization',
46
+ httpMethod: 'get',
47
+ path: '/organizations/{id}',
48
+ pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
49
+ queryParams: [],
50
+ headerParams: [],
51
+ response: { kind: 'model', name: 'Organization' },
52
+ errors: [],
53
+ injectIdempotencyKey: false,
54
+ },
55
+ {
56
+ name: 'deleteOrganization',
57
+ httpMethod: 'delete',
58
+ path: '/organizations/{id}',
59
+ pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
60
+ queryParams: [],
61
+ headerParams: [],
62
+ response: { kind: 'primitive', type: 'unknown' },
63
+ errors: [],
64
+ injectIdempotencyKey: false,
65
+ },
66
+ ],
67
+ },
68
+ ];
69
+
70
+ primeEnumAliases([]);
71
+ const ctxWithServices: EmitterContext = {
72
+ ...ctx,
73
+ spec: { ...emptySpec, services, models },
74
+ };
75
+
76
+ const files = generateResources(services, ctxWithServices);
77
+ expect(files.length).toBeGreaterThanOrEqual(1);
78
+
79
+ const serviceFile = files.find((f) => f.path.includes('OrganizationsService.cs'))!;
80
+ expect(serviceFile).toBeDefined();
81
+
82
+ const content = serviceFile.content;
83
+ // Namespace and class
84
+ expect(content).toContain('namespace WorkOS');
85
+ expect(content).toContain('public class OrganizationsService : Service');
86
+
87
+ // GET method
88
+ expect(content).toContain('GetAsync');
89
+ expect(content).toContain('async Task');
90
+
91
+ // DELETE method
92
+ expect(content).toContain('DeleteAsync');
93
+ });
94
+
95
+ it('generates options classes for operations with params', () => {
96
+ const models: Model[] = [
97
+ {
98
+ name: 'Organization',
99
+ fields: [
100
+ { name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
101
+ { name: 'name', type: { kind: 'primitive', type: 'string' }, required: true },
102
+ ],
103
+ },
104
+ {
105
+ name: 'CreateOrganizationRequest',
106
+ fields: [{ name: 'name', type: { kind: 'primitive', type: 'string' }, required: true }],
107
+ },
108
+ ];
109
+
110
+ const services: Service[] = [
111
+ {
112
+ name: 'Organizations',
113
+ operations: [
114
+ {
115
+ name: 'createOrganization',
116
+ httpMethod: 'post',
117
+ path: '/organizations',
118
+ pathParams: [],
119
+ queryParams: [],
120
+ headerParams: [],
121
+ requestBody: { kind: 'model', name: 'CreateOrganizationRequest' },
122
+ response: { kind: 'model', name: 'Organization' },
123
+ errors: [],
124
+ injectIdempotencyKey: false,
125
+ },
126
+ ],
127
+ },
128
+ ];
129
+
130
+ primeEnumAliases([]);
131
+ const ctxWithServices: EmitterContext = {
132
+ ...ctx,
133
+ spec: { ...emptySpec, services, models },
134
+ };
135
+
136
+ const files = generateResources(services, ctxWithServices);
137
+ const optionsFile = files.find((f) => f.path.includes('Options.cs'))!;
138
+ expect(optionsFile).toBeDefined();
139
+
140
+ const content = optionsFile.content;
141
+ expect(content).toContain('Options');
142
+ expect(content).toContain('[JsonProperty("name")]');
143
+ expect(content).toContain('public string Name');
144
+ });
145
+
146
+ it('generates paginated list method with auto-pagination', () => {
147
+ const models: Model[] = [
148
+ {
149
+ name: 'Organization',
150
+ fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
151
+ },
152
+ {
153
+ name: 'OrganizationList',
154
+ fields: [
155
+ {
156
+ name: 'data',
157
+ type: { kind: 'array', items: { kind: 'model', name: 'Organization' } },
158
+ required: true,
159
+ },
160
+ {
161
+ name: 'list_metadata',
162
+ type: { kind: 'model', name: 'ListMetadata' },
163
+ required: true,
164
+ },
165
+ ],
166
+ },
167
+ ];
168
+
169
+ const services: Service[] = [
170
+ {
171
+ name: 'Organizations',
172
+ operations: [
173
+ {
174
+ name: 'listOrganizations',
175
+ httpMethod: 'get',
176
+ path: '/organizations',
177
+ pathParams: [],
178
+ queryParams: [
179
+ { name: 'limit', type: { kind: 'primitive', type: 'integer' }, required: false },
180
+ { name: 'after', type: { kind: 'primitive', type: 'string' }, required: false },
181
+ ],
182
+ headerParams: [],
183
+ response: { kind: 'model', name: 'OrganizationList' },
184
+ errors: [],
185
+ injectIdempotencyKey: false,
186
+ pagination: {
187
+ strategy: 'cursor',
188
+ param: 'after',
189
+ dataPath: 'data',
190
+ itemType: { kind: 'model', name: 'Organization' },
191
+ },
192
+ },
193
+ ],
194
+ },
195
+ ];
196
+
197
+ primeEnumAliases([]);
198
+ const ctxWithServices: EmitterContext = {
199
+ ...ctx,
200
+ spec: { ...emptySpec, services, models },
201
+ };
202
+
203
+ const files = generateResources(services, ctxWithServices);
204
+ const serviceFile = files.find((f) => f.path.includes('OrganizationsService.cs'))!;
205
+ const content = serviceFile.content;
206
+
207
+ // List method (public method name omits Async suffix; return type is async Task)
208
+ expect(content).toContain('async Task<WorkOSList<Organization>>');
209
+ expect(content).toContain('List(');
210
+
211
+ // Auto-pagination method
212
+ expect(content).toContain('ListAutoPagingAsync');
213
+ expect(content).toContain('IAsyncEnumerable<Organization>');
214
+ });
215
+
216
+ it('generates deprecated operations with Obsolete attribute', () => {
217
+ const models: Model[] = [
218
+ {
219
+ name: 'Organization',
220
+ fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
221
+ },
222
+ ];
223
+
224
+ const services: Service[] = [
225
+ {
226
+ name: 'Organizations',
227
+ operations: [
228
+ {
229
+ name: 'getOrganization',
230
+ httpMethod: 'get',
231
+ path: '/organizations/{id}',
232
+ pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
233
+ queryParams: [],
234
+ headerParams: [],
235
+ response: { kind: 'model', name: 'Organization' },
236
+ errors: [],
237
+ injectIdempotencyKey: false,
238
+ deprecated: true,
239
+ },
240
+ ],
241
+ },
242
+ ];
243
+
244
+ primeEnumAliases([]);
245
+ const ctxWithServices: EmitterContext = {
246
+ ...ctx,
247
+ spec: { ...emptySpec, services, models },
248
+ };
249
+
250
+ const files = generateResources(services, ctxWithServices);
251
+ const serviceFile = files.find((f) => f.path.includes('OrganizationsService.cs'))!;
252
+
253
+ expect(serviceFile.content).toContain('[System.Obsolete');
254
+ });
255
+ });