@workos/oagen-emitters 0.3.0 → 0.5.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 (128) hide show
  1. package/.github/workflows/ci.yml +1 -1
  2. package/.github/workflows/lint.yml +1 -1
  3. package/.github/workflows/release-please.yml +2 -2
  4. package/.github/workflows/release.yml +1 -1
  5. package/.husky/pre-push +11 -0
  6. package/.node-version +1 -1
  7. package/.release-please-manifest.json +1 -1
  8. package/CHANGELOG.md +15 -0
  9. package/README.md +35 -224
  10. package/dist/index.d.mts +12 -1
  11. package/dist/index.d.mts.map +1 -1
  12. package/dist/index.mjs +2 -12737
  13. package/dist/plugin-BSop9f9z.mjs +21471 -0
  14. package/dist/plugin-BSop9f9z.mjs.map +1 -0
  15. package/dist/plugin.d.mts +7 -0
  16. package/dist/plugin.d.mts.map +1 -0
  17. package/dist/plugin.mjs +2 -0
  18. package/docs/sdk-architecture/dotnet.md +336 -0
  19. package/oagen.config.ts +5 -343
  20. package/package.json +10 -34
  21. package/smoke/sdk-dotnet.ts +45 -12
  22. package/src/dotnet/client.ts +89 -0
  23. package/src/dotnet/enums.ts +323 -0
  24. package/src/dotnet/fixtures.ts +236 -0
  25. package/src/dotnet/index.ts +248 -0
  26. package/src/dotnet/manifest.ts +36 -0
  27. package/src/dotnet/models.ts +320 -0
  28. package/src/dotnet/naming.ts +368 -0
  29. package/src/dotnet/resources.ts +943 -0
  30. package/src/dotnet/tests.ts +713 -0
  31. package/src/dotnet/type-map.ts +228 -0
  32. package/src/dotnet/wrappers.ts +197 -0
  33. package/src/go/client.ts +35 -3
  34. package/src/go/enums.ts +4 -0
  35. package/src/go/index.ts +15 -7
  36. package/src/go/models.ts +6 -1
  37. package/src/go/naming.ts +5 -17
  38. package/src/go/resources.ts +534 -73
  39. package/src/go/tests.ts +39 -3
  40. package/src/go/type-map.ts +8 -3
  41. package/src/go/wrappers.ts +79 -21
  42. package/src/index.ts +15 -0
  43. package/src/kotlin/client.ts +58 -0
  44. package/src/kotlin/enums.ts +189 -0
  45. package/src/kotlin/index.ts +92 -0
  46. package/src/kotlin/manifest.ts +55 -0
  47. package/src/kotlin/models.ts +486 -0
  48. package/src/kotlin/naming.ts +229 -0
  49. package/src/kotlin/overrides.ts +25 -0
  50. package/src/kotlin/resources.ts +998 -0
  51. package/src/kotlin/tests.ts +1133 -0
  52. package/src/kotlin/type-map.ts +123 -0
  53. package/src/kotlin/wrappers.ts +168 -0
  54. package/src/node/client.ts +84 -7
  55. package/src/node/field-plan.ts +12 -14
  56. package/src/node/fixtures.ts +39 -3
  57. package/src/node/index.ts +1 -0
  58. package/src/node/models.ts +281 -37
  59. package/src/node/resources.ts +319 -95
  60. package/src/node/tests.ts +108 -29
  61. package/src/node/type-map.ts +1 -31
  62. package/src/node/utils.ts +96 -6
  63. package/src/node/wrappers.ts +31 -1
  64. package/src/php/client.ts +11 -3
  65. package/src/php/models.ts +0 -33
  66. package/src/php/naming.ts +2 -21
  67. package/src/php/resources.ts +275 -19
  68. package/src/php/tests.ts +118 -18
  69. package/src/php/type-map.ts +16 -2
  70. package/src/php/wrappers.ts +7 -2
  71. package/src/plugin.ts +50 -0
  72. package/src/python/client.ts +50 -32
  73. package/src/python/enums.ts +35 -10
  74. package/src/python/index.ts +35 -27
  75. package/src/python/models.ts +139 -2
  76. package/src/python/naming.ts +2 -22
  77. package/src/python/resources.ts +234 -17
  78. package/src/python/tests.ts +260 -16
  79. package/src/python/type-map.ts +16 -2
  80. package/src/ruby/client.ts +238 -0
  81. package/src/ruby/enums.ts +149 -0
  82. package/src/ruby/index.ts +93 -0
  83. package/src/ruby/manifest.ts +35 -0
  84. package/src/ruby/models.ts +360 -0
  85. package/src/ruby/naming.ts +187 -0
  86. package/src/ruby/rbi.ts +313 -0
  87. package/src/ruby/resources.ts +799 -0
  88. package/src/ruby/tests.ts +459 -0
  89. package/src/ruby/type-map.ts +97 -0
  90. package/src/ruby/wrappers.ts +161 -0
  91. package/src/shared/model-utils.ts +357 -16
  92. package/src/shared/naming-utils.ts +83 -0
  93. package/src/shared/non-spec-services.ts +13 -0
  94. package/src/shared/resolved-ops.ts +75 -1
  95. package/src/shared/wrapper-utils.ts +12 -1
  96. package/test/dotnet/client.test.ts +121 -0
  97. package/test/dotnet/enums.test.ts +193 -0
  98. package/test/dotnet/errors.test.ts +9 -0
  99. package/test/dotnet/manifest.test.ts +82 -0
  100. package/test/dotnet/models.test.ts +258 -0
  101. package/test/dotnet/resources.test.ts +387 -0
  102. package/test/dotnet/tests.test.ts +202 -0
  103. package/test/entrypoint.test.ts +89 -0
  104. package/test/go/client.test.ts +6 -6
  105. package/test/go/resources.test.ts +156 -7
  106. package/test/kotlin/models.test.ts +135 -0
  107. package/test/kotlin/resources.test.ts +210 -0
  108. package/test/kotlin/tests.test.ts +176 -0
  109. package/test/node/client.test.ts +74 -0
  110. package/test/node/models.test.ts +134 -1
  111. package/test/node/resources.test.ts +343 -34
  112. package/test/node/utils.test.ts +140 -0
  113. package/test/php/client.test.ts +2 -1
  114. package/test/php/models.test.ts +5 -4
  115. package/test/php/resources.test.ts +103 -0
  116. package/test/php/tests.test.ts +67 -0
  117. package/test/plugin.test.ts +50 -0
  118. package/test/python/client.test.ts +56 -0
  119. package/test/python/models.test.ts +99 -0
  120. package/test/python/resources.test.ts +294 -0
  121. package/test/python/tests.test.ts +91 -0
  122. package/test/ruby/client.test.ts +81 -0
  123. package/test/ruby/resources.test.ts +386 -0
  124. package/test/shared/resolved-ops.test.ts +122 -0
  125. package/tsdown.config.ts +1 -1
  126. package/dist/index.mjs.map +0 -1
  127. package/scripts/generate-php.js +0 -13
  128. package/scripts/git-push-with-published-oagen.sh +0 -21
@@ -614,4 +614,298 @@ describe('generateResources', () => {
614
614
  // Deprecated query param without description
615
615
  expect(content).toContain('legacy_param: (deprecated)');
616
616
  });
617
+
618
+ it('generates parameter group dataclasses, union kwargs, and isinstance dispatch', () => {
619
+ const models: Model[] = [
620
+ {
621
+ name: 'Widget',
622
+ fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
623
+ },
624
+ ];
625
+
626
+ const services: Service[] = [
627
+ {
628
+ name: 'Widgets',
629
+ operations: [
630
+ {
631
+ name: 'listWidgets',
632
+ httpMethod: 'get',
633
+ path: '/widgets',
634
+ pathParams: [],
635
+ queryParams: [
636
+ {
637
+ name: 'limit',
638
+ type: { kind: 'primitive', type: 'integer' },
639
+ required: false,
640
+ },
641
+ {
642
+ name: 'after',
643
+ type: { kind: 'primitive', type: 'string' },
644
+ required: false,
645
+ },
646
+ {
647
+ name: 'order',
648
+ type: { kind: 'primitive', type: 'string' },
649
+ required: false,
650
+ },
651
+ {
652
+ name: 'parent_resource_id',
653
+ type: { kind: 'primitive', type: 'string' },
654
+ required: false,
655
+ },
656
+ {
657
+ name: 'parent_resource_type_slug',
658
+ type: { kind: 'primitive', type: 'string' },
659
+ required: false,
660
+ },
661
+ {
662
+ name: 'parent_resource_external_id',
663
+ type: { kind: 'primitive', type: 'string' },
664
+ required: false,
665
+ },
666
+ ],
667
+ headerParams: [],
668
+ response: { kind: 'model', name: 'WidgetList' },
669
+ errors: [],
670
+ injectIdempotencyKey: false,
671
+ pagination: {
672
+ strategy: 'cursor',
673
+ param: 'after',
674
+ dataPath: 'data',
675
+ itemType: { kind: 'model', name: 'Widget' },
676
+ },
677
+ parameterGroups: [
678
+ {
679
+ name: 'parent_resource',
680
+ optional: false,
681
+ variants: [
682
+ {
683
+ name: 'by_id',
684
+ parameters: [
685
+ {
686
+ name: 'parent_resource_id',
687
+ type: { kind: 'primitive', type: 'string' },
688
+ required: true,
689
+ },
690
+ ],
691
+ },
692
+ {
693
+ name: 'by_external_id',
694
+ parameters: [
695
+ {
696
+ name: 'parent_resource_type_slug',
697
+ type: { kind: 'primitive', type: 'string' },
698
+ required: true,
699
+ },
700
+ {
701
+ name: 'parent_resource_external_id',
702
+ type: { kind: 'primitive', type: 'string' },
703
+ required: true,
704
+ },
705
+ ],
706
+ },
707
+ ],
708
+ },
709
+ ],
710
+ },
711
+ ],
712
+ },
713
+ ];
714
+
715
+ const ctxWithServices: EmitterContext = {
716
+ ...ctx,
717
+ spec: { ...emptySpec, services, models },
718
+ };
719
+
720
+ const files = generateResources(services, ctxWithServices);
721
+ expect(files.length).toBe(1);
722
+ const content = files[0].content;
723
+
724
+ // dataclass import should be present
725
+ expect(content).toContain('from dataclasses import dataclass');
726
+
727
+ // Variant dataclass definitions
728
+ expect(content).toContain('@dataclass');
729
+ expect(content).toContain('class ParentResourceById:');
730
+ expect(content).toContain(' parent_resource_id: str');
731
+ expect(content).toContain('class ParentResourceByExternalId:');
732
+ expect(content).toContain(' parent_resource_type_slug: str');
733
+ expect(content).toContain(' parent_resource_external_id: str');
734
+
735
+ // Method signature should have the union kwarg, not individual grouped params
736
+ expect(content).toContain('parent_resource: Union[ParentResourceById, ParentResourceByExternalId],');
737
+ // Grouped params should NOT appear as individual kwargs
738
+ expect(content).not.toMatch(/^\s+parent_resource_id: str,$/m);
739
+ expect(content).not.toMatch(/^\s+parent_resource_type_slug: str,$/m);
740
+ expect(content).not.toMatch(/^\s+parent_resource_external_id: str,$/m);
741
+
742
+ // isinstance dispatch in method body
743
+ expect(content).toContain('if isinstance(parent_resource, ParentResourceById):');
744
+ expect(content).toContain('params["parent_resource_id"] = parent_resource.parent_resource_id');
745
+ expect(content).toContain('elif isinstance(parent_resource, ParentResourceByExternalId):');
746
+ expect(content).toContain('params["parent_resource_type_slug"] = parent_resource.parent_resource_type_slug');
747
+ expect(content).toContain('params["parent_resource_external_id"] = parent_resource.parent_resource_external_id');
748
+
749
+ // Docstring should document the group parameter
750
+ expect(content).toContain(
751
+ 'parent_resource: Identifies the parent resource. One of: ParentResourceById, ParentResourceByExternalId.',
752
+ );
753
+ });
754
+
755
+ it('generates optional parameter group with Optional[Union[...]] = None', () => {
756
+ const models: Model[] = [
757
+ {
758
+ name: 'Thing',
759
+ fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
760
+ },
761
+ ];
762
+
763
+ const services: Service[] = [
764
+ {
765
+ name: 'Things',
766
+ operations: [
767
+ {
768
+ name: 'getThing',
769
+ httpMethod: 'get',
770
+ path: '/things/{id}',
771
+ pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
772
+ queryParams: [
773
+ {
774
+ name: 'scope_id',
775
+ type: { kind: 'primitive', type: 'string' },
776
+ required: false,
777
+ },
778
+ {
779
+ name: 'scope_name',
780
+ type: { kind: 'primitive', type: 'string' },
781
+ required: false,
782
+ },
783
+ ],
784
+ headerParams: [],
785
+ response: { kind: 'model', name: 'Thing' },
786
+ errors: [],
787
+ injectIdempotencyKey: false,
788
+ parameterGroups: [
789
+ {
790
+ name: 'scope',
791
+ optional: true,
792
+ variants: [
793
+ {
794
+ name: 'by_id',
795
+ parameters: [
796
+ {
797
+ name: 'scope_id',
798
+ type: { kind: 'primitive', type: 'string' },
799
+ required: true,
800
+ },
801
+ ],
802
+ },
803
+ {
804
+ name: 'by_name',
805
+ parameters: [
806
+ {
807
+ name: 'scope_name',
808
+ type: { kind: 'primitive', type: 'string' },
809
+ required: true,
810
+ },
811
+ ],
812
+ },
813
+ ],
814
+ },
815
+ ],
816
+ },
817
+ ],
818
+ },
819
+ ];
820
+
821
+ const ctxWithServices: EmitterContext = {
822
+ ...ctx,
823
+ spec: { ...emptySpec, services, models },
824
+ };
825
+
826
+ const files = generateResources(services, ctxWithServices);
827
+ const content = files[0].content;
828
+
829
+ // Optional group should use Optional[Union[...]] = None
830
+ expect(content).toContain('scope: Optional[Union[ScopeById, ScopeByName]] = None,');
831
+
832
+ // Dataclass definitions
833
+ expect(content).toContain('class ScopeById:');
834
+ expect(content).toContain(' scope_id: str');
835
+ expect(content).toContain('class ScopeByName:');
836
+ expect(content).toContain(' scope_name: str');
837
+
838
+ // isinstance dispatch in the non-paginated GET body
839
+ expect(content).toContain('if isinstance(scope, ScopeById):');
840
+ expect(content).toContain('params["scope_id"] = scope.scope_id');
841
+ expect(content).toContain('elif isinstance(scope, ScopeByName):');
842
+ expect(content).toContain('params["scope_name"] = scope.scope_name');
843
+ });
844
+
845
+ it('uses body model field types for parameter group dataclasses', () => {
846
+ const models: Model[] = [
847
+ {
848
+ name: 'OrganizationMembership',
849
+ fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
850
+ },
851
+ {
852
+ name: 'CreateOrganizationMembershipRequest',
853
+ fields: [
854
+ { name: 'user_id', type: { kind: 'primitive', type: 'string' }, required: true },
855
+ { name: 'organization_id', type: { kind: 'primitive', type: 'string' }, required: true },
856
+ { name: 'role_slug', type: { kind: 'primitive', type: 'string' }, required: false },
857
+ {
858
+ name: 'role_slugs',
859
+ type: { kind: 'array', items: { kind: 'primitive', type: 'string' } },
860
+ required: false,
861
+ },
862
+ ],
863
+ },
864
+ ];
865
+
866
+ const services: Service[] = [
867
+ {
868
+ name: 'UserManagement',
869
+ operations: [
870
+ {
871
+ name: 'createOrganizationMembership',
872
+ httpMethod: 'post',
873
+ path: '/user_management/organization_memberships',
874
+ pathParams: [],
875
+ queryParams: [],
876
+ headerParams: [],
877
+ requestBody: { kind: 'model', name: 'CreateOrganizationMembershipRequest' },
878
+ response: { kind: 'model', name: 'OrganizationMembership' },
879
+ errors: [],
880
+ injectIdempotencyKey: false,
881
+ parameterGroups: [
882
+ {
883
+ name: 'role',
884
+ optional: true,
885
+ variants: [
886
+ {
887
+ name: 'single',
888
+ parameters: [{ name: 'role_slug', type: { kind: 'primitive', type: 'string' }, required: false }],
889
+ },
890
+ {
891
+ name: 'multiple',
892
+ parameters: [{ name: 'role_slugs', type: { kind: 'primitive', type: 'string' }, required: false }],
893
+ },
894
+ ],
895
+ },
896
+ ],
897
+ },
898
+ ],
899
+ },
900
+ ];
901
+
902
+ const ctxWithServices: EmitterContext = {
903
+ ...ctx,
904
+ spec: { ...emptySpec, services, models },
905
+ };
906
+
907
+ const files = generateResources(services, ctxWithServices);
908
+ expect(files[0].content).toContain('class RoleMultiple:');
909
+ expect(files[0].content).toContain(' role_slugs: List[str]');
910
+ });
617
911
  });
@@ -36,6 +36,7 @@ const services: Service[] = [
36
36
  queryParams: [],
37
37
  headerParams: [],
38
38
  response: { kind: 'primitive', type: 'unknown' },
39
+ successResponses: [{ statusCode: 202, type: { kind: 'primitive', type: 'unknown' } }],
39
40
  errors: [],
40
41
  injectIdempotencyKey: false,
41
42
  },
@@ -77,6 +78,7 @@ describe('generateTests', () => {
77
78
  expect(content).toContain('class TestOrganizations:');
78
79
  expect(content).toContain('def test_get_organization(');
79
80
  expect(content).toContain('def test_delete_organization(');
81
+ expect(content).toContain('httpx_mock.add_response(status_code=202, content=b"\\n")');
80
82
  expect(content).toContain('assert result is None');
81
83
  expect(content).toContain('isinstance(result, Organization)');
82
84
  });
@@ -107,6 +109,93 @@ describe('generateTests', () => {
107
109
  expect(data).toHaveProperty('name');
108
110
  });
109
111
 
112
+ it('generates discriminator dispatch tests for dispatcher models', () => {
113
+ const discriminatorModel: any = {
114
+ name: 'EventSchema',
115
+ fields: [],
116
+ discriminator: {
117
+ property: 'event',
118
+ mapping: {
119
+ 'user.created': 'UserCreated',
120
+ 'dsync.user.created': 'DsyncUserCreated',
121
+ },
122
+ },
123
+ };
124
+
125
+ const discModels: Model[] = [
126
+ discriminatorModel,
127
+ {
128
+ name: 'UserCreated',
129
+ fields: [
130
+ { name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
131
+ { name: 'event', type: { kind: 'literal', value: 'user.created' }, required: true },
132
+ { name: 'data', type: { kind: 'primitive', type: 'unknown' }, required: true },
133
+ ],
134
+ },
135
+ {
136
+ name: 'DsyncUserCreated',
137
+ fields: [
138
+ { name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
139
+ { name: 'event', type: { kind: 'literal', value: 'dsync.user.created' }, required: true },
140
+ { name: 'data', type: { kind: 'primitive', type: 'unknown' }, required: true },
141
+ ],
142
+ },
143
+ ];
144
+
145
+ const discServices: Service[] = [
146
+ {
147
+ name: 'Events',
148
+ operations: [
149
+ {
150
+ name: 'listEvents',
151
+ httpMethod: 'get',
152
+ path: '/events',
153
+ pathParams: [],
154
+ queryParams: [],
155
+ headerParams: [],
156
+ response: { kind: 'model', name: 'EventSchema' },
157
+ errors: [],
158
+ injectIdempotencyKey: false,
159
+ pagination: {
160
+ strategy: 'cursor',
161
+ param: 'after',
162
+ dataPath: 'data',
163
+ itemType: { kind: 'model', name: 'EventSchema' },
164
+ },
165
+ },
166
+ ],
167
+ },
168
+ ];
169
+
170
+ const discSpec: ApiSpec = {
171
+ ...spec,
172
+ models: discModels,
173
+ services: discServices,
174
+ enums: [],
175
+ };
176
+
177
+ const files = generateTests(discSpec, { ...ctx, spec: discSpec });
178
+ const roundTripTest = files.find((f) => f.path === 'tests/test_models_round_trip.py');
179
+ expect(roundTripTest).toBeDefined();
180
+
181
+ const content = roundTripTest!.content;
182
+ expect(content).toContain('class TestDiscriminatorDispatch:');
183
+ expect(content).toContain('def test_event_schema_dispatches_known_variant(self)');
184
+ expect(content).toContain('def test_event_schema_returns_unknown_for_unrecognized_type(self)');
185
+ expect(content).toContain('isinstance(result, EventSchemaUnknown)');
186
+ expect(content).toContain('def test_event_schema_raises_on_missing_discriminator(self)');
187
+ expect(content).toContain('def test_event_schema_raises_on_none_discriminator(self)');
188
+ expect(content).toContain('pytest.raises(Exception)');
189
+
190
+ // Service test should exercise discriminated union dispatch through pagination
191
+ const serviceTest = files.find((f) => f.path === 'tests/test_events.py');
192
+ expect(serviceTest).toBeDefined();
193
+ const svcContent = serviceTest!.content;
194
+ expect(svcContent).toContain('load_fixture("dsync_user_created.json")');
195
+ expect(svcContent).toContain('isinstance(page.data[0], DsyncUserCreated)');
196
+ expect(svcContent).toContain('assert len(page.data) == 1');
197
+ });
198
+
110
199
  it('generates model edge-case and query/pagination regression tests', () => {
111
200
  const edgeModels: Model[] = [
112
201
  {
@@ -188,6 +277,8 @@ describe('generateTests', () => {
188
277
  const roundTripTest = files.find((f) => f.path === 'tests/test_models_round_trip.py');
189
278
 
190
279
  expect(serviceTest).toBeDefined();
280
+ expect(serviceTest!.content).toContain('assert len(page.data) == 1');
281
+ expect(serviceTest!.content).toContain('assert isinstance(page.data[0], Organization)');
191
282
  expect(serviceTest!.content).toContain('def test_list_organizations_empty_page(');
192
283
  expect(serviceTest!.content).toContain('def test_list_organizations_encodes_query_params(');
193
284
  expect(serviceTest!.content).toContain('assert request.url.params["email"] == "value email/test"');
@@ -0,0 +1,81 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import type { EmitterContext, ApiSpec, Service, Model } from '@workos/oagen';
3
+ import { defaultSdkBehavior } from '@workos/oagen';
4
+ import { generateClient } from '../../src/ruby/client.js';
5
+
6
+ const models: Model[] = [
7
+ {
8
+ name: 'Organization',
9
+ fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
10
+ },
11
+ ];
12
+
13
+ const services: Service[] = [
14
+ {
15
+ name: 'Organizations',
16
+ operations: [
17
+ {
18
+ name: 'listOrganizations',
19
+ httpMethod: 'get',
20
+ path: '/organizations',
21
+ pathParams: [],
22
+ queryParams: [],
23
+ headerParams: [],
24
+ response: { kind: 'model', name: 'Organization' },
25
+ errors: [],
26
+ injectIdempotencyKey: false,
27
+ },
28
+ ],
29
+ },
30
+ ];
31
+
32
+ const emptySpec: ApiSpec = {
33
+ name: 'Test',
34
+ version: '1.0.0',
35
+ baseUrl: 'https://api.example.com',
36
+ services,
37
+ models,
38
+ enums: [],
39
+ sdk: defaultSdkBehavior(),
40
+ };
41
+
42
+ const ctx: EmitterContext = {
43
+ namespace: 'workos',
44
+ namespacePascal: 'WorkOS',
45
+ spec: emptySpec,
46
+ };
47
+
48
+ describe('generateClient (ruby)', () => {
49
+ it('generates inflections, main entry, and client files', () => {
50
+ const result = generateClient(emptySpec, ctx);
51
+
52
+ expect(result).toHaveLength(3);
53
+ expect(result[0].path).toBe('lib/workos/inflections.rb');
54
+ expect(result[1].path).toBe('lib/workos.rb');
55
+ expect(result[2].path).toBe('lib/workos/client.rb');
56
+ });
57
+
58
+ it('ignores inflections.rb in Zeitwerk loader', () => {
59
+ const result = generateClient(emptySpec, ctx);
60
+ const mainEntry = result[1].content;
61
+
62
+ expect(mainEntry).toContain('loader.ignore("#{__dir__}/workos/inflections.rb")');
63
+ });
64
+
65
+ it('ignores errors.rb in Zeitwerk loader', () => {
66
+ const result = generateClient(emptySpec, ctx);
67
+ const mainEntry = result[1].content;
68
+
69
+ expect(mainEntry).toContain('loader.ignore("#{__dir__}/workos/errors.rb")');
70
+ });
71
+
72
+ it('requires inflections before loader.setup', () => {
73
+ const result = generateClient(emptySpec, ctx);
74
+ const mainEntry = result[1].content;
75
+
76
+ const requireIdx = mainEntry.indexOf("require_relative 'workos/inflections'");
77
+ const setupIdx = mainEntry.indexOf('loader.setup');
78
+ expect(requireIdx).toBeGreaterThan(-1);
79
+ expect(setupIdx).toBeGreaterThan(requireIdx);
80
+ });
81
+ });