@apidiff/core 1.0.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 (249) hide show
  1. package/dist/ast/hash.d.ts +2 -0
  2. package/dist/ast/hash.d.ts.map +1 -0
  3. package/dist/ast/index.d.ts +3 -0
  4. package/dist/ast/index.d.ts.map +1 -0
  5. package/dist/ast/traverse.d.ts +13 -0
  6. package/dist/ast/traverse.d.ts.map +1 -0
  7. package/dist/config/index.d.ts +9 -0
  8. package/dist/config/index.d.ts.map +1 -0
  9. package/dist/diff/auth-differ.d.ts +4 -0
  10. package/dist/diff/auth-differ.d.ts.map +1 -0
  11. package/dist/diff/endpoint-differ.d.ts +3 -0
  12. package/dist/diff/endpoint-differ.d.ts.map +1 -0
  13. package/dist/diff/index.d.ts +3 -0
  14. package/dist/diff/index.d.ts.map +1 -0
  15. package/dist/diff/schema-differ.d.ts +3 -0
  16. package/dist/diff/schema-differ.d.ts.map +1 -0
  17. package/dist/diff/server-differ.d.ts +3 -0
  18. package/dist/diff/server-differ.d.ts.map +1 -0
  19. package/dist/index.cjs +2902 -0
  20. package/dist/index.d.ts +15 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +2855 -0
  23. package/dist/loader/file-loader.d.ts +5 -0
  24. package/dist/loader/file-loader.d.ts.map +1 -0
  25. package/dist/loader/git-loader.d.ts +5 -0
  26. package/dist/loader/git-loader.d.ts.map +1 -0
  27. package/dist/loader/index.d.ts +7 -0
  28. package/dist/loader/index.d.ts.map +1 -0
  29. package/dist/loader/url-loader.d.ts +5 -0
  30. package/dist/loader/url-loader.d.ts.map +1 -0
  31. package/dist/output/html.d.ts +3 -0
  32. package/dist/output/html.d.ts.map +1 -0
  33. package/dist/output/index.d.ts +3 -0
  34. package/dist/output/index.d.ts.map +1 -0
  35. package/dist/output/json.d.ts +3 -0
  36. package/dist/output/json.d.ts.map +1 -0
  37. package/dist/output/markdown.d.ts +3 -0
  38. package/dist/output/markdown.d.ts.map +1 -0
  39. package/dist/output/terminal.d.ts +3 -0
  40. package/dist/output/terminal.d.ts.map +1 -0
  41. package/dist/parsers/base.d.ts +8 -0
  42. package/dist/parsers/base.d.ts.map +1 -0
  43. package/dist/parsers/graphql/index.d.ts +13 -0
  44. package/dist/parsers/graphql/index.d.ts.map +1 -0
  45. package/dist/parsers/index.d.ts +7 -0
  46. package/dist/parsers/index.d.ts.map +1 -0
  47. package/dist/parsers/openapi2/index.d.ts +9 -0
  48. package/dist/parsers/openapi2/index.d.ts.map +1 -0
  49. package/dist/parsers/openapi3/index.d.ts +9 -0
  50. package/dist/parsers/openapi3/index.d.ts.map +1 -0
  51. package/dist/parsers/openapi3/ref-resolver.d.ts +2 -0
  52. package/dist/parsers/openapi3/ref-resolver.d.ts.map +1 -0
  53. package/dist/parsers/openapi3/schema-normalizer.d.ts +3 -0
  54. package/dist/parsers/openapi3/schema-normalizer.d.ts.map +1 -0
  55. package/dist/parsers/openapi3/security-normalizer.d.ts +3 -0
  56. package/dist/parsers/openapi3/security-normalizer.d.ts.map +1 -0
  57. package/dist/parsers/protobuf/index.d.ts +14 -0
  58. package/dist/parsers/protobuf/index.d.ts.map +1 -0
  59. package/dist/rules/auth/oauth-scope-removed.d.ts +9 -0
  60. package/dist/rules/auth/oauth-scope-removed.d.ts.map +1 -0
  61. package/dist/rules/auth/security-added.d.ts +9 -0
  62. package/dist/rules/auth/security-added.d.ts.map +1 -0
  63. package/dist/rules/auth/security-removed.d.ts +9 -0
  64. package/dist/rules/auth/security-removed.d.ts.map +1 -0
  65. package/dist/rules/auth/security-scheme-type-changed.d.ts +9 -0
  66. package/dist/rules/auth/security-scheme-type-changed.d.ts.map +1 -0
  67. package/dist/rules/base.d.ts +19 -0
  68. package/dist/rules/base.d.ts.map +1 -0
  69. package/dist/rules/endpoint/endpoint-added.d.ts +9 -0
  70. package/dist/rules/endpoint/endpoint-added.d.ts.map +1 -0
  71. package/dist/rules/endpoint/endpoint-deprecated.d.ts +9 -0
  72. package/dist/rules/endpoint/endpoint-deprecated.d.ts.map +1 -0
  73. package/dist/rules/endpoint/endpoint-removed.d.ts +9 -0
  74. package/dist/rules/endpoint/endpoint-removed.d.ts.map +1 -0
  75. package/dist/rules/endpoint/http-method-changed.d.ts +9 -0
  76. package/dist/rules/endpoint/http-method-changed.d.ts.map +1 -0
  77. package/dist/rules/endpoint/path-changed.d.ts +9 -0
  78. package/dist/rules/endpoint/path-changed.d.ts.map +1 -0
  79. package/dist/rules/index.d.ts +4 -0
  80. package/dist/rules/index.d.ts.map +1 -0
  81. package/dist/rules/meta/server-removed.d.ts +9 -0
  82. package/dist/rules/meta/server-removed.d.ts.map +1 -0
  83. package/dist/rules/param/param-added.d.ts +9 -0
  84. package/dist/rules/param/param-added.d.ts.map +1 -0
  85. package/dist/rules/param/param-deprecated.d.ts +9 -0
  86. package/dist/rules/param/param-deprecated.d.ts.map +1 -0
  87. package/dist/rules/param/param-enum-value-added.d.ts +9 -0
  88. package/dist/rules/param/param-enum-value-added.d.ts.map +1 -0
  89. package/dist/rules/param/param-enum-value-removed.d.ts +9 -0
  90. package/dist/rules/param/param-enum-value-removed.d.ts.map +1 -0
  91. package/dist/rules/param/param-location-changed.d.ts +9 -0
  92. package/dist/rules/param/param-location-changed.d.ts.map +1 -0
  93. package/dist/rules/param/param-removed.d.ts +9 -0
  94. package/dist/rules/param/param-removed.d.ts.map +1 -0
  95. package/dist/rules/param/param-required-added.d.ts +9 -0
  96. package/dist/rules/param/param-required-added.d.ts.map +1 -0
  97. package/dist/rules/param/param-required-true-to-false.d.ts +9 -0
  98. package/dist/rules/param/param-required-true-to-false.d.ts.map +1 -0
  99. package/dist/rules/param/param-type-changed.d.ts +9 -0
  100. package/dist/rules/param/param-type-changed.d.ts.map +1 -0
  101. package/dist/rules/request/request-body-added-required.d.ts +9 -0
  102. package/dist/rules/request/request-body-added-required.d.ts.map +1 -0
  103. package/dist/rules/request/request-body-removed.d.ts +9 -0
  104. package/dist/rules/request/request-body-removed.d.ts.map +1 -0
  105. package/dist/rules/request/request-content-type-added.d.ts +9 -0
  106. package/dist/rules/request/request-content-type-added.d.ts.map +1 -0
  107. package/dist/rules/request/request-content-type-removed.d.ts +9 -0
  108. package/dist/rules/request/request-content-type-removed.d.ts.map +1 -0
  109. package/dist/rules/request/request-field-added-required.d.ts +9 -0
  110. package/dist/rules/request/request-field-added-required.d.ts.map +1 -0
  111. package/dist/rules/request/request-field-removed.d.ts +9 -0
  112. package/dist/rules/request/request-field-removed.d.ts.map +1 -0
  113. package/dist/rules/request/request-field-type-changed.d.ts +9 -0
  114. package/dist/rules/request/request-field-type-changed.d.ts.map +1 -0
  115. package/dist/rules/request/request-required-false-to-true.d.ts +9 -0
  116. package/dist/rules/request/request-required-false-to-true.d.ts.map +1 -0
  117. package/dist/rules/response/response-field-added.d.ts +9 -0
  118. package/dist/rules/response/response-field-added.d.ts.map +1 -0
  119. package/dist/rules/response/response-field-removed.d.ts +9 -0
  120. package/dist/rules/response/response-field-removed.d.ts.map +1 -0
  121. package/dist/rules/response/response-field-type-changed.d.ts +9 -0
  122. package/dist/rules/response/response-field-type-changed.d.ts.map +1 -0
  123. package/dist/rules/response/response-header-added-required.d.ts +9 -0
  124. package/dist/rules/response/response-header-added-required.d.ts.map +1 -0
  125. package/dist/rules/response/response-header-removed.d.ts +9 -0
  126. package/dist/rules/response/response-header-removed.d.ts.map +1 -0
  127. package/dist/rules/response/response-media-type-added.d.ts +9 -0
  128. package/dist/rules/response/response-media-type-added.d.ts.map +1 -0
  129. package/dist/rules/response/response-media-type-removed.d.ts +9 -0
  130. package/dist/rules/response/response-media-type-removed.d.ts.map +1 -0
  131. package/dist/rules/response/response-status-added.d.ts +9 -0
  132. package/dist/rules/response/response-status-added.d.ts.map +1 -0
  133. package/dist/rules/response/response-status-removed.d.ts +9 -0
  134. package/dist/rules/response/response-status-removed.d.ts.map +1 -0
  135. package/dist/types/ast.d.ts +140 -0
  136. package/dist/types/ast.d.ts.map +1 -0
  137. package/dist/types/config.d.ts +17 -0
  138. package/dist/types/config.d.ts.map +1 -0
  139. package/dist/types/diff.d.ts +43 -0
  140. package/dist/types/diff.d.ts.map +1 -0
  141. package/dist/types/errors.d.ts +21 -0
  142. package/dist/types/errors.d.ts.map +1 -0
  143. package/dist/types/index.d.ts +6 -0
  144. package/dist/types/index.d.ts.map +1 -0
  145. package/dist/types/semantic.d.ts +45 -0
  146. package/dist/types/semantic.d.ts.map +1 -0
  147. package/package.json +31 -0
  148. package/src/ast/hash.ts +26 -0
  149. package/src/ast/index.ts +2 -0
  150. package/src/ast/traverse.ts +59 -0
  151. package/src/config/index.ts +44 -0
  152. package/src/diff/auth-differ.ts +72 -0
  153. package/src/diff/endpoint-differ.ts +144 -0
  154. package/src/diff/index.ts +13 -0
  155. package/src/diff/schema-differ.ts +70 -0
  156. package/src/diff/server-differ.ts +22 -0
  157. package/src/index.ts +92 -0
  158. package/src/loader/file-loader.ts +19 -0
  159. package/src/loader/git-loader.ts +22 -0
  160. package/src/loader/index.ts +32 -0
  161. package/src/loader/url-loader.ts +25 -0
  162. package/src/output/html.ts +177 -0
  163. package/src/output/index.ts +16 -0
  164. package/src/output/json.ts +5 -0
  165. package/src/output/markdown.ts +29 -0
  166. package/src/output/terminal.ts +37 -0
  167. package/src/parsers/base.ts +8 -0
  168. package/src/parsers/graphql/index.ts +181 -0
  169. package/src/parsers/index.ts +61 -0
  170. package/src/parsers/openapi2/index.ts +218 -0
  171. package/src/parsers/openapi3/index.ts +223 -0
  172. package/src/parsers/openapi3/ref-resolver.ts +101 -0
  173. package/src/parsers/openapi3/schema-normalizer.ts +52 -0
  174. package/src/parsers/openapi3/security-normalizer.ts +18 -0
  175. package/src/parsers/protobuf/index.ts +208 -0
  176. package/src/rules/auth/oauth-scope-removed.ts +34 -0
  177. package/src/rules/auth/security-added.ts +32 -0
  178. package/src/rules/auth/security-removed.ts +47 -0
  179. package/src/rules/auth/security-scheme-type-changed.ts +30 -0
  180. package/src/rules/base.ts +29 -0
  181. package/src/rules/endpoint/endpoint-added.ts +26 -0
  182. package/src/rules/endpoint/endpoint-deprecated.ts +29 -0
  183. package/src/rules/endpoint/endpoint-removed.ts +26 -0
  184. package/src/rules/endpoint/http-method-changed.ts +29 -0
  185. package/src/rules/endpoint/path-changed.ts +29 -0
  186. package/src/rules/index.ts +83 -0
  187. package/src/rules/meta/server-removed.ts +26 -0
  188. package/src/rules/param/param-added.ts +52 -0
  189. package/src/rules/param/param-deprecated.ts +34 -0
  190. package/src/rules/param/param-enum-value-added.ts +32 -0
  191. package/src/rules/param/param-enum-value-removed.ts +32 -0
  192. package/src/rules/param/param-location-changed.ts +43 -0
  193. package/src/rules/param/param-removed.ts +52 -0
  194. package/src/rules/param/param-required-added.ts +34 -0
  195. package/src/rules/param/param-required-true-to-false.ts +34 -0
  196. package/src/rules/param/param-type-changed.ts +32 -0
  197. package/src/rules/request/request-body-added-required.ts +33 -0
  198. package/src/rules/request/request-body-removed.ts +30 -0
  199. package/src/rules/request/request-content-type-added.ts +31 -0
  200. package/src/rules/request/request-content-type-removed.ts +31 -0
  201. package/src/rules/request/request-field-added-required.ts +41 -0
  202. package/src/rules/request/request-field-removed.ts +35 -0
  203. package/src/rules/request/request-field-type-changed.ts +37 -0
  204. package/src/rules/request/request-required-false-to-true.ts +56 -0
  205. package/src/rules/response/response-field-added.ts +34 -0
  206. package/src/rules/response/response-field-removed.ts +34 -0
  207. package/src/rules/response/response-field-type-changed.ts +37 -0
  208. package/src/rules/response/response-header-added-required.ts +47 -0
  209. package/src/rules/response/response-header-removed.ts +32 -0
  210. package/src/rules/response/response-media-type-added.ts +32 -0
  211. package/src/rules/response/response-media-type-removed.ts +32 -0
  212. package/src/rules/response/response-status-added.ts +31 -0
  213. package/src/rules/response/response-status-removed.ts +31 -0
  214. package/src/types/ast.ts +164 -0
  215. package/src/types/config.ts +31 -0
  216. package/src/types/diff.ts +49 -0
  217. package/src/types/errors.ts +26 -0
  218. package/src/types/index.ts +5 -0
  219. package/src/types/semantic.ts +60 -0
  220. package/test/integration/stripe-v1.yaml +36 -0
  221. package/test/integration/stripe-v2.yaml +35 -0
  222. package/tests/ast.test.ts +60 -0
  223. package/tests/config.test.ts +43 -0
  224. package/tests/diff/auth-differ.test.ts +55 -0
  225. package/tests/diff/endpoint-differ.test.ts +42 -0
  226. package/tests/diff/schema-differ.test.ts +46 -0
  227. package/tests/diff/server-differ.test.ts +23 -0
  228. package/tests/diff.test.ts +116 -0
  229. package/tests/fixtures/openapi3/auth-added/v1.yaml +11 -0
  230. package/tests/fixtures/openapi3/auth-added/v2.yaml +18 -0
  231. package/tests/integration.test.ts +21 -0
  232. package/tests/loader-more.test.ts +75 -0
  233. package/tests/loader.test.ts +61 -0
  234. package/tests/output.test.ts +58 -0
  235. package/tests/parsers/openapi3-coverage.test.ts +77 -0
  236. package/tests/parsers/openapi3.test.ts +41 -0
  237. package/tests/parsers/parsers-other.test.ts +135 -0
  238. package/tests/parsers/ref-resolver.test.ts +78 -0
  239. package/tests/parsers/schema-normalizer.test.ts +30 -0
  240. package/tests/rules/auth-meta.test.ts +87 -0
  241. package/tests/rules/base.test.ts +44 -0
  242. package/tests/rules/endpoint.test.ts +122 -0
  243. package/tests/rules/param.test.ts +242 -0
  244. package/tests/rules/request.test.ts +147 -0
  245. package/tests/rules/response.test.ts +161 -0
  246. package/tests/rules/rules-coverage.test.ts +64 -0
  247. package/tests/types-errors.test.ts +22 -0
  248. package/tsconfig.json +8 -0
  249. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,242 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ParamRemovedRule } from '../../src/rules/param/param-removed.js';
3
+ import { ParamAddedRule } from '../../src/rules/param/param-added.js';
4
+ import { ParamDeprecatedRule } from '../../src/rules/param/param-deprecated.js';
5
+ import { ParamTypeChangedRule } from '../../src/rules/param/param-type-changed.js';
6
+ import { ParamRequiredAddedRule } from '../../src/rules/param/param-required-added.js';
7
+ import { ParamLocationChangedRule } from '../../src/rules/param/param-location-changed.js';
8
+ import { ParamEnumValueRemovedRule } from '../../src/rules/param/param-enum-value-removed.js';
9
+ import { ParamEnumValueAddedRule } from '../../src/rules/param/param-enum-value-added.js';
10
+ import { ParamRequiredTrueToFalseRule } from '../../src/rules/param/param-required-true-to-false.js';
11
+ import type { DiffSet, RuleContext } from '../../src/types/index.js';
12
+
13
+ describe('Parameter Rules', () => {
14
+ const context: RuleContext = {
15
+ config: { failOn: 'breaking', disabledRules: [], ignorePaths: [], output: { format: 'terminal' } },
16
+ oldSpec: { meta: { title: 'v1', version: '1.0.0', format: 'openapi3', rawVersion: '3.0.0' }, servers: [], endpoints: [], components: { schemas: {}, securitySchemes: {}, parameters: {}, responses: {}, headers: {}, requestBodies: {} }, security: [] },
17
+ newSpec: { meta: { title: 'v2', version: '2.0.0', format: 'openapi3', rawVersion: '3.0.0' }, servers: [], endpoints: [], components: { schemas: {}, securitySchemes: {}, parameters: {}, responses: {}, headers: {}, requestBodies: {} }, security: [] },
18
+ };
19
+
20
+ const dummyEndpoint = { id: 'GET:/test', path: '/test', method: 'GET' as const, summary: '', description: '', tags: [], deprecated: false, security: [], parameters: [], responses: [] };
21
+
22
+ it('PARAM_REMOVED triggers on required param removal', () => {
23
+ const diff: DiffSet = {
24
+ endpointDiffs: [
25
+ {
26
+ type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint,
27
+ fieldChanges: [
28
+ { fieldPath: ['parameters', 'testParam:query'], changeType: 'removed', oldValue: { name: 'testParam', in: 'query', required: true, schema: { type: 'string' } } }
29
+ ]
30
+ }
31
+ ],
32
+ componentDiffs: []
33
+ };
34
+
35
+ const rule = new ParamRemovedRule();
36
+ const changes = rule.apply(diff, context);
37
+ expect(changes).toHaveLength(1);
38
+ expect(changes[0].ruleId).toBe('PARAM_REMOVED');
39
+ expect(changes[0].severity).toBe('breaking');
40
+ });
41
+
42
+ it('PARAM_OPTIONAL_REMOVED triggers on optional param removal', () => {
43
+ const diff: DiffSet = {
44
+ endpointDiffs: [
45
+ {
46
+ type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint,
47
+ fieldChanges: [
48
+ { fieldPath: ['parameters', 'testParam:query'], changeType: 'removed', oldValue: { name: 'testParam', in: 'query', required: false, schema: { type: 'string' } } }
49
+ ]
50
+ }
51
+ ],
52
+ componentDiffs: []
53
+ };
54
+
55
+ const rule = new ParamRemovedRule();
56
+ const changes = rule.apply(diff, context);
57
+ expect(changes).toHaveLength(1);
58
+ expect(changes[0].ruleId).toBe('PARAM_OPTIONAL_REMOVED');
59
+ expect(changes[0].severity).toBe('warning');
60
+ });
61
+
62
+ it('PARAM_ADDED_REQUIRED triggers on required param added', () => {
63
+ const diff: DiffSet = {
64
+ endpointDiffs: [
65
+ {
66
+ type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint,
67
+ fieldChanges: [
68
+ { fieldPath: ['parameters', 'testParam:query'], changeType: 'added', newValue: { name: 'testParam', in: 'query', required: true, schema: { type: 'string' } } }
69
+ ]
70
+ }
71
+ ],
72
+ componentDiffs: []
73
+ };
74
+
75
+ const rule = new ParamAddedRule();
76
+ const changes = rule.apply(diff, context);
77
+ expect(changes).toHaveLength(1);
78
+ expect(changes[0].ruleId).toBe('PARAM_ADDED_REQUIRED');
79
+ expect(changes[0].severity).toBe('breaking');
80
+ });
81
+
82
+ it('PARAM_ADDED triggers on optional param added', () => {
83
+ const diff: DiffSet = {
84
+ endpointDiffs: [
85
+ {
86
+ type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint,
87
+ fieldChanges: [
88
+ { fieldPath: ['parameters', 'testParam:query'], changeType: 'added', newValue: { name: 'testParam', in: 'query', required: false, schema: { type: 'string' } } }
89
+ ]
90
+ }
91
+ ],
92
+ componentDiffs: []
93
+ };
94
+
95
+ const rule = new ParamAddedRule();
96
+ const changes = rule.apply(diff, context);
97
+ expect(changes).toHaveLength(1);
98
+ expect(changes[0].ruleId).toBe('PARAM_ADDED');
99
+ expect(changes[0].severity).toBe('info');
100
+ });
101
+
102
+ it('PARAM_DEPRECATED triggers on deprecated change', () => {
103
+ const diff: DiffSet = {
104
+ endpointDiffs: [
105
+ {
106
+ type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint,
107
+ fieldChanges: [
108
+ { fieldPath: ['parameters', 'testParam:query', 'deprecated'], changeType: 'changed', oldValue: false, newValue: true }
109
+ ]
110
+ }
111
+ ],
112
+ componentDiffs: []
113
+ };
114
+
115
+ const rule = new ParamDeprecatedRule();
116
+ const changes = rule.apply(diff, context);
117
+ expect(changes).toHaveLength(1);
118
+ expect(changes[0].ruleId).toBe('PARAM_DEPRECATED');
119
+ expect(changes[0].severity).toBe('warning');
120
+ });
121
+
122
+ it('PARAM_TYPE_CHANGED triggers on schema type change', () => {
123
+ const diff: DiffSet = {
124
+ endpointDiffs: [
125
+ {
126
+ type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint,
127
+ fieldChanges: [
128
+ { fieldPath: ['parameters', 'testParam:query', 'schema', 'type'], changeType: 'changed', oldValue: 'string', newValue: 'number' }
129
+ ]
130
+ }
131
+ ],
132
+ componentDiffs: []
133
+ };
134
+
135
+ const rule = new ParamTypeChangedRule();
136
+ const changes = rule.apply(diff, context);
137
+ expect(changes).toHaveLength(1);
138
+ expect(changes[0].ruleId).toBe('PARAM_TYPE_CHANGED');
139
+ expect(changes[0].severity).toBe('breaking');
140
+ });
141
+
142
+ it('PARAM_REQUIRED_FALSE_TO_TRUE triggers when optional becomes required', () => {
143
+ const diff: DiffSet = {
144
+ endpointDiffs: [
145
+ {
146
+ type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint,
147
+ fieldChanges: [
148
+ { fieldPath: ['parameters', 'testParam:query', 'required'], changeType: 'changed', oldValue: false, newValue: true }
149
+ ]
150
+ }
151
+ ],
152
+ componentDiffs: []
153
+ };
154
+
155
+ const rule = new ParamRequiredAddedRule();
156
+ const changes = rule.apply(diff, context);
157
+ expect(changes).toHaveLength(1);
158
+ expect(changes[0].ruleId).toBe('PARAM_REQUIRED_FALSE_TO_TRUE');
159
+ expect(changes[0].severity).toBe('breaking');
160
+ });
161
+
162
+ it('PARAM_LOCATION_CHANGED triggers when parameter moves', () => {
163
+ const diff: DiffSet = {
164
+ endpointDiffs: [
165
+ {
166
+ type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint,
167
+ fieldChanges: [
168
+ { fieldPath: ['parameters', 'testParam:query'], changeType: 'removed', oldValue: { name: 'testParam', in: 'query' } },
169
+ { fieldPath: ['parameters', 'testParam:header'], changeType: 'added', newValue: { name: 'testParam', in: 'header' } }
170
+ ]
171
+ }
172
+ ],
173
+ componentDiffs: []
174
+ };
175
+
176
+ const rule = new ParamLocationChangedRule();
177
+ const changes = rule.apply(diff, context);
178
+ expect(changes).toHaveLength(1);
179
+ expect(changes[0].ruleId).toBe('PARAM_LOCATION_CHANGED');
180
+ expect(changes[0].severity).toBe('breaking');
181
+ });
182
+
183
+ it('PARAM_ENUM_VALUE_REMOVED triggers when enum is removed', () => {
184
+ const diff: DiffSet = {
185
+ endpointDiffs: [
186
+ {
187
+ type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint,
188
+ fieldChanges: [
189
+ { fieldPath: ['parameters', 'testParam:query', 'schema', 'enum', 'oldVal'], changeType: 'removed', oldValue: 'oldVal' }
190
+ ]
191
+ }
192
+ ],
193
+ componentDiffs: []
194
+ };
195
+
196
+ const rule = new ParamEnumValueRemovedRule();
197
+ const changes = rule.apply(diff, context);
198
+ expect(changes).toHaveLength(1);
199
+ expect(changes[0].ruleId).toBe('PARAM_ENUM_VALUE_REMOVED');
200
+ expect(changes[0].severity).toBe('breaking');
201
+ });
202
+
203
+ it('PARAM_ENUM_VALUE_ADDED triggers when enum is added', () => {
204
+ const diff: DiffSet = {
205
+ endpointDiffs: [
206
+ {
207
+ type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint,
208
+ fieldChanges: [
209
+ { fieldPath: ['parameters', 'testParam:query', 'schema', 'enum', 'newVal'], changeType: 'added', newValue: 'newVal' }
210
+ ]
211
+ }
212
+ ],
213
+ componentDiffs: []
214
+ };
215
+
216
+ const rule = new ParamEnumValueAddedRule();
217
+ const changes = rule.apply(diff, context);
218
+ expect(changes).toHaveLength(1);
219
+ expect(changes[0].ruleId).toBe('PARAM_ENUM_VALUE_ADDED');
220
+ expect(changes[0].severity).toBe('info');
221
+ });
222
+
223
+ it('PARAM_REQUIRED_TRUE_TO_FALSE triggers when required becomes optional', () => {
224
+ const diff: DiffSet = {
225
+ endpointDiffs: [
226
+ {
227
+ type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint,
228
+ fieldChanges: [
229
+ { fieldPath: ['parameters', 'testParam:query', 'required'], changeType: 'changed', oldValue: true, newValue: false }
230
+ ]
231
+ }
232
+ ],
233
+ componentDiffs: []
234
+ };
235
+
236
+ const rule = new ParamRequiredTrueToFalseRule();
237
+ const changes = rule.apply(diff, context);
238
+ expect(changes).toHaveLength(1);
239
+ expect(changes[0].ruleId).toBe('PARAM_REQUIRED_TRUE_TO_FALSE');
240
+ expect(changes[0].severity).toBe('info');
241
+ });
242
+ });
@@ -0,0 +1,147 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { RequestBodyAddedRequiredRule } from '../../src/rules/request/request-body-added-required.js';
3
+ import { RequestBodyRemovedRule } from '../../src/rules/request/request-body-removed.js';
4
+ import { RequestContentTypeAddedRule } from '../../src/rules/request/request-content-type-added.js';
5
+ import { RequestContentTypeRemovedRule } from '../../src/rules/request/request-content-type-removed.js';
6
+ import { RequestFieldRemovedRule } from '../../src/rules/request/request-field-removed.js';
7
+ import { RequestFieldAddedRequiredRule } from '../../src/rules/request/request-field-added-required.js';
8
+ import { RequestFieldTypeChangedRule } from '../../src/rules/request/request-field-type-changed.js';
9
+ import { RequestRequiredFalseToTrueRule } from '../../src/rules/request/request-required-false-to-true.js';
10
+ import type { DiffSet, RuleContext } from '../../src/types/index.js';
11
+
12
+ describe('Request Rules', () => {
13
+ const context: RuleContext = {
14
+ config: { failOn: 'breaking', disabledRules: [], ignorePaths: [], output: { format: 'terminal' } },
15
+ oldSpec: { meta: { title: 'v1', version: '1.0.0', format: 'openapi3', rawVersion: '3.0.0' }, servers: [], endpoints: [], components: { schemas: {}, securitySchemes: {}, parameters: {}, responses: {}, headers: {}, requestBodies: {} }, security: [] },
16
+ newSpec: { meta: { title: 'v2', version: '2.0.0', format: 'openapi3', rawVersion: '3.0.0' }, servers: [], endpoints: [], components: { schemas: {}, securitySchemes: {}, parameters: {}, responses: {}, headers: {}, requestBodies: {} }, security: [] },
17
+ };
18
+
19
+ const dummyEndpoint = { id: 'POST:/test', path: '/test', method: 'POST' as const, summary: '', description: '', tags: [], deprecated: false, security: [], parameters: [], responses: [] };
20
+
21
+ it('REQUEST_BODY_ADDED_REQUIRED triggers when required body is added', () => {
22
+ const diff: DiffSet = {
23
+ endpointDiffs: [
24
+ { type: 'changed', endpointId: 'POST:/test', path: '/test', method: 'POST', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
25
+ { fieldPath: ['requestBody'], changeType: 'added', newValue: { required: true, content: {} } }
26
+ ]}
27
+ ], componentDiffs: []
28
+ };
29
+ const rule = new RequestBodyAddedRequiredRule();
30
+ const changes = rule.apply(diff, context);
31
+ expect(changes).toHaveLength(1);
32
+ expect(changes[0].ruleId).toBe('REQUEST_BODY_ADDED_REQUIRED');
33
+ });
34
+
35
+ it('REQUEST_BODY_REMOVED triggers when body is removed', () => {
36
+ const diff: DiffSet = {
37
+ endpointDiffs: [
38
+ { type: 'changed', endpointId: 'POST:/test', path: '/test', method: 'POST', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
39
+ { fieldPath: ['requestBody'], changeType: 'removed', oldValue: { required: false, content: {} } }
40
+ ]}
41
+ ], componentDiffs: []
42
+ };
43
+ const rule = new RequestBodyRemovedRule();
44
+ const changes = rule.apply(diff, context);
45
+ expect(changes).toHaveLength(1);
46
+ expect(changes[0].ruleId).toBe('REQUEST_BODY_REMOVED');
47
+ });
48
+
49
+ it('REQUEST_CONTENT_TYPE_ADDED triggers when content type is added', () => {
50
+ const diff: DiffSet = {
51
+ endpointDiffs: [
52
+ { type: 'changed', endpointId: 'POST:/test', path: '/test', method: 'POST', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
53
+ { fieldPath: ['requestBody', 'content', 'application/json'], changeType: 'added', newValue: { schema: {} } }
54
+ ]}
55
+ ], componentDiffs: []
56
+ };
57
+ const rule = new RequestContentTypeAddedRule();
58
+ const changes = rule.apply(diff, context);
59
+ expect(changes).toHaveLength(1);
60
+ expect(changes[0].ruleId).toBe('REQUEST_CONTENT_TYPE_ADDED');
61
+ });
62
+
63
+ it('REQUEST_CONTENT_TYPE_REMOVED triggers when content type is removed', () => {
64
+ const diff: DiffSet = {
65
+ endpointDiffs: [
66
+ { type: 'changed', endpointId: 'POST:/test', path: '/test', method: 'POST', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
67
+ { fieldPath: ['requestBody', 'content', 'application/json'], changeType: 'removed', oldValue: { schema: {} } }
68
+ ]}
69
+ ], componentDiffs: []
70
+ };
71
+ const rule = new RequestContentTypeRemovedRule();
72
+ const changes = rule.apply(diff, context);
73
+ expect(changes).toHaveLength(1);
74
+ expect(changes[0].ruleId).toBe('REQUEST_CONTENT_TYPE_REMOVED');
75
+ });
76
+
77
+ it('REQUEST_FIELD_REMOVED triggers when field is removed', () => {
78
+ const diff: DiffSet = {
79
+ endpointDiffs: [
80
+ { type: 'changed', endpointId: 'POST:/test', path: '/test', method: 'POST', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
81
+ { fieldPath: ['requestBody', 'content', 'application/json', 'schema', 'properties', 'testField'], changeType: 'removed', oldValue: { type: 'string' } }
82
+ ]}
83
+ ], componentDiffs: []
84
+ };
85
+ const rule = new RequestFieldRemovedRule();
86
+ const changes = rule.apply(diff, context);
87
+ expect(changes).toHaveLength(1);
88
+ expect(changes[0].ruleId).toBe('REQUEST_FIELD_REMOVED');
89
+ });
90
+
91
+ it('REQUEST_FIELD_ADDED_REQUIRED triggers when required field is added', () => {
92
+ const diff: DiffSet = {
93
+ endpointDiffs: [
94
+ { type: 'changed', endpointId: 'POST:/test', path: '/test', method: 'POST', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
95
+ { fieldPath: ['requestBody', 'content', 'application/json', 'schema', 'required', 'testField'], changeType: 'added', newValue: 'testField' },
96
+ { fieldPath: ['requestBody', 'content', 'application/json', 'schema', 'properties', 'testField'], changeType: 'added', newValue: { type: 'string' } }
97
+ ]}
98
+ ], componentDiffs: []
99
+ };
100
+ const rule = new RequestFieldAddedRequiredRule();
101
+ const changes = rule.apply(diff, context);
102
+ expect(changes).toHaveLength(1);
103
+ expect(changes[0].ruleId).toBe('REQUEST_FIELD_ADDED_REQUIRED');
104
+ });
105
+
106
+ it('REQUEST_FIELD_TYPE_CHANGED triggers when type changes', () => {
107
+ const diff: DiffSet = {
108
+ endpointDiffs: [
109
+ { type: 'changed', endpointId: 'POST:/test', path: '/test', method: 'POST', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
110
+ { fieldPath: ['requestBody', 'content', 'application/json', 'schema', 'properties', 'testField', 'type'], changeType: 'changed', oldValue: 'string', newValue: 'number' }
111
+ ]}
112
+ ], componentDiffs: []
113
+ };
114
+ const rule = new RequestFieldTypeChangedRule();
115
+ const changes = rule.apply(diff, context);
116
+ expect(changes).toHaveLength(1);
117
+ expect(changes[0].ruleId).toBe('REQUEST_FIELD_TYPE_CHANGED');
118
+ });
119
+
120
+ it('REQUEST_REQUIRED_FALSE_TO_TRUE triggers when body becomes required', () => {
121
+ const diff: DiffSet = {
122
+ endpointDiffs: [
123
+ { type: 'changed', endpointId: 'POST:/test', path: '/test', method: 'POST', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
124
+ { fieldPath: ['requestBody', 'required'], changeType: 'changed', oldValue: false, newValue: true }
125
+ ]}
126
+ ], componentDiffs: []
127
+ };
128
+ const rule = new RequestRequiredFalseToTrueRule();
129
+ const changes = rule.apply(diff, context);
130
+ expect(changes).toHaveLength(1);
131
+ expect(changes[0].ruleId).toBe('REQUEST_REQUIRED_FALSE_TO_TRUE');
132
+ });
133
+
134
+ it('REQUEST_REQUIRED_FALSE_TO_TRUE triggers when existing field becomes required', () => {
135
+ const diff: DiffSet = {
136
+ endpointDiffs: [
137
+ { type: 'changed', endpointId: 'POST:/test', path: '/test', method: 'POST', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
138
+ { fieldPath: ['requestBody', 'content', 'application/json', 'schema', 'required', 'testField'], changeType: 'added', newValue: 'testField' }
139
+ ]}
140
+ ], componentDiffs: []
141
+ };
142
+ const rule = new RequestRequiredFalseToTrueRule();
143
+ const changes = rule.apply(diff, context);
144
+ expect(changes).toHaveLength(1);
145
+ expect(changes[0].ruleId).toBe('REQUEST_REQUIRED_FALSE_TO_TRUE');
146
+ });
147
+ });
@@ -0,0 +1,161 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ResponseFieldRemovedRule } from '../../src/rules/response/response-field-removed.js';
3
+ import { ResponseFieldAddedRule } from '../../src/rules/response/response-field-added.js';
4
+ import { ResponseFieldTypeChangedRule } from '../../src/rules/response/response-field-type-changed.js';
5
+ import { ResponseStatusRemovedRule } from '../../src/rules/response/response-status-removed.js';
6
+ import { ResponseStatusAddedRule } from '../../src/rules/response/response-status-added.js';
7
+ import { ResponseMediaTypeRemovedRule } from '../../src/rules/response/response-media-type-removed.js';
8
+ import { ResponseMediaTypeAddedRule } from '../../src/rules/response/response-media-type-added.js';
9
+ import { ResponseHeaderRemovedRule } from '../../src/rules/response/response-header-removed.js';
10
+ import { ResponseHeaderAddedRequiredRule } from '../../src/rules/response/response-header-added-required.js';
11
+ import type { DiffSet, RuleContext } from '../../src/types/index.js';
12
+
13
+ describe('Response Rules', () => {
14
+ const context: RuleContext = {
15
+ config: { failOn: 'breaking', disabledRules: [], ignorePaths: [], output: { format: 'terminal' } },
16
+ oldSpec: { meta: { title: 'v1', version: '1.0.0', format: 'openapi3', rawVersion: '3.0.0' }, servers: [], endpoints: [], components: { schemas: {}, securitySchemes: {}, parameters: {}, responses: {}, headers: {}, requestBodies: {} }, security: [] },
17
+ newSpec: { meta: { title: 'v2', version: '2.0.0', format: 'openapi3', rawVersion: '3.0.0' }, servers: [], endpoints: [], components: { schemas: {}, securitySchemes: {}, parameters: {}, responses: {}, headers: {}, requestBodies: {} }, security: [] },
18
+ };
19
+
20
+ const dummyEndpoint = { id: 'GET:/test', path: '/test', method: 'GET' as const, summary: '', description: '', tags: [], deprecated: false, security: [], parameters: [], responses: [] };
21
+
22
+ it('RESPONSE_FIELD_REMOVED triggers when field is removed', () => {
23
+ const diff: DiffSet = {
24
+ endpointDiffs: [
25
+ { type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
26
+ { fieldPath: ['responses', '200', 'content', 'application/json', 'schema', 'properties', 'testField'], changeType: 'removed', oldValue: { type: 'string' } }
27
+ ]}
28
+ ], componentDiffs: []
29
+ };
30
+ const rule = new ResponseFieldRemovedRule();
31
+ const changes = rule.apply(diff, context);
32
+ expect(changes).toHaveLength(1);
33
+ expect(changes[0].ruleId).toBe('RESPONSE_FIELD_REMOVED');
34
+ });
35
+
36
+ it('RESPONSE_FIELD_ADDED triggers when field is added', () => {
37
+ const diff: DiffSet = {
38
+ endpointDiffs: [
39
+ { type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
40
+ { fieldPath: ['responses', '200', 'content', 'application/json', 'schema', 'properties', 'testField'], changeType: 'added', newValue: { type: 'string' } }
41
+ ]}
42
+ ], componentDiffs: []
43
+ };
44
+ const rule = new ResponseFieldAddedRule();
45
+ const changes = rule.apply(diff, context);
46
+ expect(changes).toHaveLength(1);
47
+ expect(changes[0].ruleId).toBe('RESPONSE_FIELD_ADDED');
48
+ });
49
+
50
+ it('RESPONSE_FIELD_TYPE_CHANGED triggers when type changes', () => {
51
+ const diff: DiffSet = {
52
+ endpointDiffs: [
53
+ { type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
54
+ { fieldPath: ['responses', '200', 'content', 'application/json', 'schema', 'properties', 'testField', 'type'], changeType: 'changed', oldValue: 'string', newValue: 'number' }
55
+ ]}
56
+ ], componentDiffs: []
57
+ };
58
+ const rule = new ResponseFieldTypeChangedRule();
59
+ const changes = rule.apply(diff, context);
60
+ expect(changes).toHaveLength(1);
61
+ expect(changes[0].ruleId).toBe('RESPONSE_FIELD_TYPE_CHANGED');
62
+ });
63
+
64
+ it('RESPONSE_STATUS_REMOVED triggers when status code is removed', () => {
65
+ const diff: DiffSet = {
66
+ endpointDiffs: [
67
+ { type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
68
+ { fieldPath: ['responses', '201'], changeType: 'removed', oldValue: {} }
69
+ ]}
70
+ ], componentDiffs: []
71
+ };
72
+ const rule = new ResponseStatusRemovedRule();
73
+ const changes = rule.apply(diff, context);
74
+ expect(changes).toHaveLength(1);
75
+ expect(changes[0].ruleId).toBe('RESPONSE_STATUS_REMOVED');
76
+ });
77
+
78
+ it('RESPONSE_STATUS_ADDED triggers when status code is added', () => {
79
+ const diff: DiffSet = {
80
+ endpointDiffs: [
81
+ { type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
82
+ { fieldPath: ['responses', '201'], changeType: 'added', newValue: {} }
83
+ ]}
84
+ ], componentDiffs: []
85
+ };
86
+ const rule = new ResponseStatusAddedRule();
87
+ const changes = rule.apply(diff, context);
88
+ expect(changes).toHaveLength(1);
89
+ expect(changes[0].ruleId).toBe('RESPONSE_STATUS_ADDED');
90
+ });
91
+
92
+ it('RESPONSE_MEDIA_TYPE_REMOVED triggers when media type is removed', () => {
93
+ const diff: DiffSet = {
94
+ endpointDiffs: [
95
+ { type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
96
+ { fieldPath: ['responses', '200', 'content', 'application/json'], changeType: 'removed', oldValue: {} }
97
+ ]}
98
+ ], componentDiffs: []
99
+ };
100
+ const rule = new ResponseMediaTypeRemovedRule();
101
+ const changes = rule.apply(diff, context);
102
+ expect(changes).toHaveLength(1);
103
+ expect(changes[0].ruleId).toBe('RESPONSE_MEDIA_TYPE_REMOVED');
104
+ });
105
+
106
+ it('RESPONSE_MEDIA_TYPE_ADDED triggers when media type is added', () => {
107
+ const diff: DiffSet = {
108
+ endpointDiffs: [
109
+ { type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
110
+ { fieldPath: ['responses', '200', 'content', 'application/json'], changeType: 'added', newValue: {} }
111
+ ]}
112
+ ], componentDiffs: []
113
+ };
114
+ const rule = new ResponseMediaTypeAddedRule();
115
+ const changes = rule.apply(diff, context);
116
+ expect(changes).toHaveLength(1);
117
+ expect(changes[0].ruleId).toBe('RESPONSE_MEDIA_TYPE_ADDED');
118
+ });
119
+
120
+ it('RESPONSE_HEADER_REMOVED triggers when header is removed', () => {
121
+ const diff: DiffSet = {
122
+ endpointDiffs: [
123
+ { type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
124
+ { fieldPath: ['responses', '200', 'headers', 'X-Rate-Limit'], changeType: 'removed', oldValue: {} }
125
+ ]}
126
+ ], componentDiffs: []
127
+ };
128
+ const rule = new ResponseHeaderRemovedRule();
129
+ const changes = rule.apply(diff, context);
130
+ expect(changes).toHaveLength(1);
131
+ expect(changes[0].ruleId).toBe('RESPONSE_HEADER_REMOVED');
132
+ });
133
+
134
+ it('RESPONSE_HEADER_ADDED_REQUIRED triggers when required header is added', () => {
135
+ const diff: DiffSet = {
136
+ endpointDiffs: [
137
+ { type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
138
+ { fieldPath: ['responses', '200', 'headers', 'X-Rate-Limit'], changeType: 'added', newValue: { required: true } }
139
+ ]}
140
+ ], componentDiffs: []
141
+ };
142
+ const rule = new ResponseHeaderAddedRequiredRule();
143
+ const changes = rule.apply(diff, context);
144
+ expect(changes).toHaveLength(1);
145
+ expect(changes[0].ruleId).toBe('RESPONSE_HEADER_ADDED_REQUIRED');
146
+ });
147
+
148
+ it('RESPONSE_HEADER_ADDED_REQUIRED triggers when optional header becomes required', () => {
149
+ const diff: DiffSet = {
150
+ endpointDiffs: [
151
+ { type: 'changed', endpointId: 'GET:/test', path: '/test', method: 'GET', oldEndpoint: dummyEndpoint, newEndpoint: dummyEndpoint, fieldChanges: [
152
+ { fieldPath: ['responses', '200', 'headers', 'X-Rate-Limit', 'required'], changeType: 'changed', oldValue: false, newValue: true }
153
+ ]}
154
+ ], componentDiffs: []
155
+ };
156
+ const rule = new ResponseHeaderAddedRequiredRule();
157
+ const changes = rule.apply(diff, context);
158
+ expect(changes).toHaveLength(1);
159
+ expect(changes[0].ruleId).toBe('RESPONSE_HEADER_ADDED_REQUIRED');
160
+ });
161
+ });
@@ -0,0 +1,64 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { BUILT_IN_RULES, runRules } from '../../src/rules/index.js';
3
+ import type { DiffSet, RuleContext, Endpoint } from '../../src/types/index.js';
4
+
5
+ describe('Rules Early Return Coverage', () => {
6
+ it('covers early returns and ignored endpoints for all rules', () => {
7
+ const oldE: Endpoint = {
8
+ id: 'test',
9
+ method: 'GET',
10
+ path: '/ignored',
11
+ security: [],
12
+ parameters: [],
13
+ responses: [],
14
+ tags: [],
15
+ deprecated: false,
16
+ extensions: {}
17
+ };
18
+
19
+ const newE: Endpoint = { ...oldE };
20
+
21
+ const context: RuleContext = {
22
+ config: {
23
+ severity: 'info',
24
+ disabledRules: [],
25
+ ignorePaths: ['/ignored']
26
+ },
27
+ oldAst: { meta: {} as any, endpoints: [], components: {} as any, servers: [], security: [] },
28
+ newAst: { meta: {} as any, endpoints: [], components: {} as any, servers: [], security: [] },
29
+ sourcePath: ''
30
+ };
31
+
32
+ const diffSet: DiffSet = {
33
+ endpointDiffs: [
34
+ // Ignored path
35
+ { type: 'changed', endpointId: 'test1', method: 'GET', path: '/ignored', oldEndpoint: oldE, newEndpoint: newE, fieldChanges: [] },
36
+ // Not changed
37
+ { type: 'added', endpointId: 'test2', method: 'GET', path: '/test', newEndpoint: newE, fieldChanges: [] },
38
+ { type: 'removed', endpointId: 'test3', method: 'GET', path: '/test', oldEndpoint: oldE, fieldChanges: [] },
39
+ // Changed but irrelevant field change
40
+ {
41
+ type: 'changed',
42
+ endpointId: 'test4',
43
+ method: 'GET',
44
+ path: '/test',
45
+ oldEndpoint: oldE,
46
+ newEndpoint: newE,
47
+ fieldChanges: [
48
+ { fieldPath: ['unknown'], changeType: 'changed', oldValue: 'a', newValue: 'b' },
49
+ { fieldPath: ['parameters', 'p:query'], changeType: 'removed', oldValue: {} },
50
+ { fieldPath: ['parameters', 'p:header'], changeType: 'added', newValue: {} }
51
+ ]
52
+ }
53
+ ],
54
+ serverDiffs: [],
55
+ securityDiffs: []
56
+ };
57
+
58
+ // Run all rules against this diffSet
59
+ const changes = runRules(diffSet, context);
60
+
61
+ // Some endpoints may produce changes depending on the rule, but most will early return
62
+ expect(changes).toBeInstanceOf(Array);
63
+ });
64
+ });
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ApidiffError, LoadError, ParseError, RefError, ConfigError, FormatError } from '../src/types/errors.js';
3
+
4
+ describe('Errors', () => {
5
+ it('instantiates correctly', () => {
6
+ const parseErr = new ParseError('Test error', 10, 20, 'file.yaml', new Error('cause'));
7
+ expect(parseErr.name).toBe('ParseError');
8
+ expect(parseErr.message).toBe('Test error');
9
+ expect(parseErr.line).toBe(10);
10
+ expect(parseErr.col).toBe(20);
11
+ expect(parseErr.filePath).toBe('file.yaml');
12
+
13
+ const refErr = new RefError('Test ref error', '#/test');
14
+ expect(refErr.name).toBe('RefError');
15
+ expect(refErr.message).toBe('Test ref error');
16
+ expect(refErr.ref).toBe('#/test');
17
+
18
+ expect(new LoadError('load').name).toBe('LoadError');
19
+ expect(new ConfigError('config').name).toBe('ConfigError');
20
+ expect(new FormatError('format').name).toBe('FormatError');
21
+ });
22
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src/**/*"]
8
+ }