@kirrosh/apitool 0.4.3

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 (191) hide show
  1. package/.github/workflows/ci.yml +27 -0
  2. package/.github/workflows/release.yml +97 -0
  3. package/.mcp.json +9 -0
  4. package/APITOOL.md +195 -0
  5. package/BACKLOG.md +62 -0
  6. package/CHANGELOG.md +88 -0
  7. package/LICENSE +21 -0
  8. package/README.md +105 -0
  9. package/bun.lock +291 -0
  10. package/docs/GLOSSARY.md +182 -0
  11. package/docs/INDEX.md +21 -0
  12. package/docs/agent.md +135 -0
  13. package/docs/archive/APITOOL-pre-M22.md +831 -0
  14. package/docs/archive/BACKLOG-AI-NATIVE.md +56 -0
  15. package/docs/archive/M1-M2-parser-runner.md +216 -0
  16. package/docs/archive/M4-M7-reporter-cli.md +179 -0
  17. package/docs/archive/M5-M7-storage-junit.md +300 -0
  18. package/docs/archive/M6-webui.md +339 -0
  19. package/docs/ci.md +274 -0
  20. package/docs/generation-issues.md +67 -0
  21. package/generated/.env.yaml +3 -0
  22. package/install.ps1 +80 -0
  23. package/install.sh +113 -0
  24. package/package.json +46 -0
  25. package/scripts/run-mocked-tests.ts +45 -0
  26. package/seed-demo.ts +53 -0
  27. package/self-tests/auth.yaml +18 -0
  28. package/self-tests/collections-crud.yaml +46 -0
  29. package/self-tests/environments-crud.yaml +48 -0
  30. package/self-tests/export.yaml +32 -0
  31. package/self-tests/runs.yaml +16 -0
  32. package/src/bun-types.d.ts +5 -0
  33. package/src/cli/commands/add-api.ts +51 -0
  34. package/src/cli/commands/ai-generate.ts +106 -0
  35. package/src/cli/commands/chat.ts +43 -0
  36. package/src/cli/commands/ci-init.ts +126 -0
  37. package/src/cli/commands/collections.ts +41 -0
  38. package/src/cli/commands/coverage.ts +65 -0
  39. package/src/cli/commands/doctor.ts +127 -0
  40. package/src/cli/commands/envs.ts +218 -0
  41. package/src/cli/commands/init.ts +84 -0
  42. package/src/cli/commands/mcp.ts +16 -0
  43. package/src/cli/commands/run.ts +137 -0
  44. package/src/cli/commands/runs.ts +108 -0
  45. package/src/cli/commands/serve.ts +22 -0
  46. package/src/cli/commands/update.ts +142 -0
  47. package/src/cli/commands/validate.ts +18 -0
  48. package/src/cli/index.ts +500 -0
  49. package/src/cli/output.ts +24 -0
  50. package/src/cli/runtime.ts +7 -0
  51. package/src/core/agent/agent-loop.ts +116 -0
  52. package/src/core/agent/context-manager.ts +41 -0
  53. package/src/core/agent/system-prompt.ts +33 -0
  54. package/src/core/agent/tools/diagnose-failure.ts +51 -0
  55. package/src/core/agent/tools/explore-api.ts +40 -0
  56. package/src/core/agent/tools/index.ts +48 -0
  57. package/src/core/agent/tools/manage-environment.ts +40 -0
  58. package/src/core/agent/tools/query-results.ts +40 -0
  59. package/src/core/agent/tools/run-tests.ts +38 -0
  60. package/src/core/agent/tools/send-request.ts +44 -0
  61. package/src/core/agent/tools/validate-tests.ts +23 -0
  62. package/src/core/agent/types.ts +22 -0
  63. package/src/core/generator/ai/ai-generator.ts +61 -0
  64. package/src/core/generator/ai/llm-client.ts +159 -0
  65. package/src/core/generator/ai/output-parser.ts +307 -0
  66. package/src/core/generator/ai/prompt-builder.ts +153 -0
  67. package/src/core/generator/ai/types.ts +56 -0
  68. package/src/core/generator/coverage-scanner.ts +87 -0
  69. package/src/core/generator/data-factory.ts +115 -0
  70. package/src/core/generator/index.ts +10 -0
  71. package/src/core/generator/openapi-reader.ts +142 -0
  72. package/src/core/generator/schema-utils.ts +52 -0
  73. package/src/core/generator/serializer.ts +189 -0
  74. package/src/core/generator/types.ts +47 -0
  75. package/src/core/parser/filter.ts +14 -0
  76. package/src/core/parser/index.ts +21 -0
  77. package/src/core/parser/schema.ts +175 -0
  78. package/src/core/parser/types.ts +50 -0
  79. package/src/core/parser/variables.ts +146 -0
  80. package/src/core/parser/yaml-parser.ts +85 -0
  81. package/src/core/reporter/console.ts +175 -0
  82. package/src/core/reporter/index.ts +23 -0
  83. package/src/core/reporter/json.ts +9 -0
  84. package/src/core/reporter/junit.ts +78 -0
  85. package/src/core/reporter/types.ts +12 -0
  86. package/src/core/runner/assertions.ts +172 -0
  87. package/src/core/runner/execute-run.ts +75 -0
  88. package/src/core/runner/executor.ts +150 -0
  89. package/src/core/runner/http-client.ts +69 -0
  90. package/src/core/runner/index.ts +12 -0
  91. package/src/core/runner/types.ts +48 -0
  92. package/src/core/setup-api.ts +97 -0
  93. package/src/core/utils.ts +9 -0
  94. package/src/db/queries.ts +868 -0
  95. package/src/db/schema.ts +215 -0
  96. package/src/mcp/server.ts +47 -0
  97. package/src/mcp/tools/ci-init.ts +57 -0
  98. package/src/mcp/tools/coverage-analysis.ts +58 -0
  99. package/src/mcp/tools/explore-api.ts +84 -0
  100. package/src/mcp/tools/generate-missing-tests.ts +80 -0
  101. package/src/mcp/tools/generate-tests-guide.ts +353 -0
  102. package/src/mcp/tools/manage-environment.ts +123 -0
  103. package/src/mcp/tools/manage-server.ts +87 -0
  104. package/src/mcp/tools/query-db.ts +141 -0
  105. package/src/mcp/tools/run-tests.ts +66 -0
  106. package/src/mcp/tools/save-test-suite.ts +164 -0
  107. package/src/mcp/tools/send-request.ts +53 -0
  108. package/src/mcp/tools/setup-api.ts +49 -0
  109. package/src/mcp/tools/validate-tests.ts +42 -0
  110. package/src/tui/chat-ui.ts +150 -0
  111. package/src/web/routes/api.ts +234 -0
  112. package/src/web/routes/dashboard.ts +348 -0
  113. package/src/web/routes/runs.ts +64 -0
  114. package/src/web/schemas.ts +121 -0
  115. package/src/web/server.ts +134 -0
  116. package/src/web/static/htmx.min.js +1 -0
  117. package/src/web/static/style.css +265 -0
  118. package/src/web/views/layout.ts +46 -0
  119. package/src/web/views/results.ts +209 -0
  120. package/tests/agent/agent-loop.test.ts +61 -0
  121. package/tests/agent/context-manager.test.ts +59 -0
  122. package/tests/agent/system-prompt.test.ts +42 -0
  123. package/tests/agent/tools/diagnose-failure.test.ts +85 -0
  124. package/tests/agent/tools/explore-api.test.ts +59 -0
  125. package/tests/agent/tools/manage-environment.test.ts +78 -0
  126. package/tests/agent/tools/query-results.test.ts +77 -0
  127. package/tests/agent/tools/run-tests.test.ts +89 -0
  128. package/tests/agent/tools/send-request.test.ts +78 -0
  129. package/tests/agent/tools/validate-tests.test.ts +59 -0
  130. package/tests/ai/ai-generator.integration.test.ts +131 -0
  131. package/tests/ai/llm-client.test.ts +145 -0
  132. package/tests/ai/output-parser.test.ts +132 -0
  133. package/tests/ai/prompt-builder.test.ts +67 -0
  134. package/tests/ai/types.test.ts +55 -0
  135. package/tests/cli/args.test.ts +63 -0
  136. package/tests/cli/chat.test.ts +38 -0
  137. package/tests/cli/ci-init.test.ts +112 -0
  138. package/tests/cli/commands.test.ts +316 -0
  139. package/tests/cli/coverage.test.ts +58 -0
  140. package/tests/cli/doctor.test.ts +39 -0
  141. package/tests/cli/envs.test.ts +181 -0
  142. package/tests/cli/init.test.ts +80 -0
  143. package/tests/cli/runs.test.ts +94 -0
  144. package/tests/cli/safe-run.test.ts +103 -0
  145. package/tests/cli/update.test.ts +32 -0
  146. package/tests/core/generator/schema-utils.test.ts +108 -0
  147. package/tests/core/parser/nested-assertions.test.ts +80 -0
  148. package/tests/core/runner/root-body-assertions.test.ts +70 -0
  149. package/tests/db/chat-queries.test.ts +88 -0
  150. package/tests/db/chat-schema.test.ts +37 -0
  151. package/tests/db/environments.test.ts +131 -0
  152. package/tests/db/queries.test.ts +409 -0
  153. package/tests/db/schema.test.ts +141 -0
  154. package/tests/fixtures/.env.yaml +3 -0
  155. package/tests/fixtures/auth-token-test.yaml +8 -0
  156. package/tests/fixtures/bail/suite-a.yaml +6 -0
  157. package/tests/fixtures/bail/suite-b.yaml +6 -0
  158. package/tests/fixtures/crud.yaml +35 -0
  159. package/tests/fixtures/invalid-missing-name.yaml +5 -0
  160. package/tests/fixtures/invalid-no-method.yaml +6 -0
  161. package/tests/fixtures/petstore-auth.json +295 -0
  162. package/tests/fixtures/petstore-simple.json +151 -0
  163. package/tests/fixtures/post-only.yaml +12 -0
  164. package/tests/fixtures/simple.yaml +6 -0
  165. package/tests/fixtures/valid/.env.yaml +1 -0
  166. package/tests/fixtures/valid/a.yaml +5 -0
  167. package/tests/fixtures/valid/b.yml +5 -0
  168. package/tests/generator/coverage-scanner.test.ts +129 -0
  169. package/tests/generator/data-factory.test.ts +133 -0
  170. package/tests/generator/openapi-reader.test.ts +131 -0
  171. package/tests/integration/auth-flow.test.ts +217 -0
  172. package/tests/mcp/coverage-analysis.test.ts +64 -0
  173. package/tests/mcp/explore-api-schemas.test.ts +105 -0
  174. package/tests/mcp/explore-api.test.ts +49 -0
  175. package/tests/mcp/generate-missing-tests.test.ts +69 -0
  176. package/tests/mcp/manage-environment.test.ts +89 -0
  177. package/tests/mcp/save-test-suite.test.ts +116 -0
  178. package/tests/mcp/send-request.test.ts +79 -0
  179. package/tests/mcp/setup-api.test.ts +106 -0
  180. package/tests/mcp/tools.test.ts +248 -0
  181. package/tests/parser/schema.test.ts +134 -0
  182. package/tests/parser/variables.test.ts +227 -0
  183. package/tests/parser/yaml-parser.test.ts +69 -0
  184. package/tests/reporter/console.test.ts +256 -0
  185. package/tests/reporter/json.test.ts +98 -0
  186. package/tests/reporter/junit.test.ts +284 -0
  187. package/tests/runner/assertions.test.ts +262 -0
  188. package/tests/runner/executor.test.ts +310 -0
  189. package/tests/runner/http-client.test.ts +138 -0
  190. package/tests/web/routes.test.ts +160 -0
  191. package/tsconfig.json +31 -0
@@ -0,0 +1,6 @@
1
+ name: Suite A
2
+ tests:
3
+ - name: "Test A"
4
+ GET: /a
5
+ expect:
6
+ status: 200
@@ -0,0 +1,6 @@
1
+ name: Suite B
2
+ tests:
3
+ - name: "Test B"
4
+ GET: /b
5
+ expect:
6
+ status: 200
@@ -0,0 +1,35 @@
1
+ name: Users CRUD
2
+ base_url: "{{base}}"
3
+ headers:
4
+ Authorization: "Bearer {{token}}"
5
+ Content-Type: application/json
6
+
7
+ config:
8
+ timeout: 10000
9
+ retries: 1
10
+
11
+ tests:
12
+ - name: "Create user"
13
+ POST: /users
14
+ json:
15
+ name: "{{$randomName}}"
16
+ email: "{{$randomEmail}}"
17
+ expect:
18
+ status: 201
19
+ body:
20
+ id: { capture: user_id, type: integer }
21
+ name: { type: string }
22
+ duration: 2000
23
+
24
+ - name: "Get created user"
25
+ GET: /users/{{user_id}}
26
+ expect:
27
+ status: 200
28
+ body:
29
+ id: { equals: "{{user_id}}" }
30
+ email: { matches: ".+@.+" }
31
+
32
+ - name: "Delete user"
33
+ DELETE: /users/{{user_id}}
34
+ expect:
35
+ status: 204
@@ -0,0 +1,5 @@
1
+ tests:
2
+ - name: "Test"
3
+ GET: /health
4
+ expect:
5
+ status: 200
@@ -0,0 +1,6 @@
1
+ name: Bad Suite
2
+ tests:
3
+ - name: "No method"
4
+ path: /health
5
+ expect:
6
+ status: 200
@@ -0,0 +1,295 @@
1
+ {
2
+ "openapi": "3.0.0",
3
+ "info": {
4
+ "title": "Test Petstore",
5
+ "version": "1.0.0"
6
+ },
7
+ "servers": [{ "url": "http://localhost:3000", "description": "Local test server" }],
8
+ "components": {
9
+ "securitySchemes": {
10
+ "bearerAuth": {
11
+ "type": "http",
12
+ "scheme": "bearer",
13
+ "bearerFormat": "JWT"
14
+ }
15
+ },
16
+ "schemas": {},
17
+ "parameters": {}
18
+ },
19
+ "paths": {
20
+ "/auth/login": {
21
+ "post": {
22
+ "operationId": "login",
23
+ "summary": "Authenticate and get a JWT token",
24
+ "tags": ["auth"],
25
+ "requestBody": {
26
+ "required": true,
27
+ "content": {
28
+ "application/json": {
29
+ "schema": {
30
+ "type": "object",
31
+ "properties": {
32
+ "username": { "type": "string", "example": "admin" },
33
+ "password": { "type": "string", "example": "admin" }
34
+ },
35
+ "required": ["username", "password"]
36
+ }
37
+ }
38
+ }
39
+ },
40
+ "responses": {
41
+ "200": {
42
+ "description": "Login successful",
43
+ "content": {
44
+ "application/json": {
45
+ "schema": {
46
+ "type": "object",
47
+ "properties": {
48
+ "token": { "type": "string", "example": "eyJhbGciOiJIUzI1NiIs..." }
49
+ },
50
+ "required": ["token"]
51
+ }
52
+ }
53
+ }
54
+ },
55
+ "401": {
56
+ "description": "Invalid credentials",
57
+ "content": {
58
+ "application/json": {
59
+ "schema": {
60
+ "type": "object",
61
+ "properties": {
62
+ "error": { "type": "string", "example": "Not found" }
63
+ },
64
+ "required": ["error"]
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ },
72
+ "/pets": {
73
+ "get": {
74
+ "operationId": "listPets",
75
+ "summary": "List all pets",
76
+ "tags": ["pets"],
77
+ "security": [{ "bearerAuth": [] }],
78
+ "responses": {
79
+ "200": {
80
+ "description": "A list of pets",
81
+ "content": {
82
+ "application/json": {
83
+ "schema": {
84
+ "type": "array",
85
+ "items": {
86
+ "type": "object",
87
+ "properties": {
88
+ "id": { "type": "integer", "example": 1 },
89
+ "name": { "type": "string", "example": "Buddy" },
90
+ "tag": { "type": "string", "example": "dog" },
91
+ "status": { "type": "string", "enum": ["available", "pending", "sold"], "example": "available" }
92
+ },
93
+ "required": ["id", "name", "status"]
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+ },
101
+ "post": {
102
+ "operationId": "createPet",
103
+ "summary": "Create a pet",
104
+ "tags": ["pets"],
105
+ "security": [{ "bearerAuth": [] }],
106
+ "requestBody": {
107
+ "required": true,
108
+ "content": {
109
+ "application/json": {
110
+ "schema": {
111
+ "type": "object",
112
+ "properties": {
113
+ "name": { "type": "string", "example": "Buddy" },
114
+ "tag": { "type": "string", "example": "dog" },
115
+ "status": { "type": "string", "enum": ["available", "pending", "sold"], "default": "available", "example": "available" }
116
+ },
117
+ "required": ["name"]
118
+ }
119
+ }
120
+ }
121
+ },
122
+ "responses": {
123
+ "201": {
124
+ "description": "Pet created",
125
+ "content": {
126
+ "application/json": {
127
+ "schema": {
128
+ "type": "object",
129
+ "properties": {
130
+ "id": { "type": "integer", "example": 1 },
131
+ "name": { "type": "string", "example": "Buddy" },
132
+ "tag": { "type": "string", "example": "dog" },
133
+ "status": { "type": "string", "enum": ["available", "pending", "sold"], "example": "available" }
134
+ },
135
+ "required": ["id", "name", "status"]
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ }
142
+ },
143
+ "/pets/{id}": {
144
+ "get": {
145
+ "operationId": "getPet",
146
+ "summary": "Get a pet by ID",
147
+ "tags": ["pets"],
148
+ "security": [{ "bearerAuth": [] }],
149
+ "parameters": [
150
+ { "schema": { "type": "string", "example": "1" }, "required": true, "name": "id", "in": "path" }
151
+ ],
152
+ "responses": {
153
+ "200": {
154
+ "description": "A pet",
155
+ "content": {
156
+ "application/json": {
157
+ "schema": {
158
+ "type": "object",
159
+ "properties": {
160
+ "id": { "type": "integer", "example": 1 },
161
+ "name": { "type": "string", "example": "Buddy" },
162
+ "tag": { "type": "string", "example": "dog" },
163
+ "status": { "type": "string", "enum": ["available", "pending", "sold"], "example": "available" }
164
+ },
165
+ "required": ["id", "name", "status"]
166
+ }
167
+ }
168
+ }
169
+ },
170
+ "404": {
171
+ "description": "Pet not found",
172
+ "content": {
173
+ "application/json": {
174
+ "schema": {
175
+ "type": "object",
176
+ "properties": {
177
+ "error": { "type": "string", "example": "Not found" }
178
+ },
179
+ "required": ["error"]
180
+ }
181
+ }
182
+ }
183
+ }
184
+ }
185
+ },
186
+ "put": {
187
+ "operationId": "updatePet",
188
+ "summary": "Update a pet",
189
+ "tags": ["pets"],
190
+ "security": [{ "bearerAuth": [] }],
191
+ "parameters": [
192
+ { "schema": { "type": "string", "example": "1" }, "required": true, "name": "id", "in": "path" }
193
+ ],
194
+ "requestBody": {
195
+ "required": true,
196
+ "content": {
197
+ "application/json": {
198
+ "schema": {
199
+ "type": "object",
200
+ "properties": {
201
+ "name": { "type": "string", "example": "Buddy" },
202
+ "tag": { "type": "string", "example": "dog" },
203
+ "status": { "type": "string", "enum": ["available", "pending", "sold"], "default": "available", "example": "available" }
204
+ },
205
+ "required": ["name"]
206
+ }
207
+ }
208
+ }
209
+ },
210
+ "responses": {
211
+ "200": {
212
+ "description": "Pet updated",
213
+ "content": {
214
+ "application/json": {
215
+ "schema": {
216
+ "type": "object",
217
+ "properties": {
218
+ "id": { "type": "integer", "example": 1 },
219
+ "name": { "type": "string", "example": "Buddy" },
220
+ "tag": { "type": "string", "example": "dog" },
221
+ "status": { "type": "string", "enum": ["available", "pending", "sold"], "example": "available" }
222
+ },
223
+ "required": ["id", "name", "status"]
224
+ }
225
+ }
226
+ }
227
+ },
228
+ "404": {
229
+ "description": "Pet not found",
230
+ "content": {
231
+ "application/json": {
232
+ "schema": {
233
+ "type": "object",
234
+ "properties": {
235
+ "error": { "type": "string", "example": "Not found" }
236
+ },
237
+ "required": ["error"]
238
+ }
239
+ }
240
+ }
241
+ }
242
+ }
243
+ },
244
+ "delete": {
245
+ "operationId": "deletePet",
246
+ "summary": "Delete a pet",
247
+ "tags": ["pets"],
248
+ "security": [{ "bearerAuth": [] }],
249
+ "parameters": [
250
+ { "schema": { "type": "string", "example": "1" }, "required": true, "name": "id", "in": "path" }
251
+ ],
252
+ "responses": {
253
+ "204": { "description": "Pet deleted" },
254
+ "404": {
255
+ "description": "Pet not found",
256
+ "content": {
257
+ "application/json": {
258
+ "schema": {
259
+ "type": "object",
260
+ "properties": {
261
+ "error": { "type": "string", "example": "Not found" }
262
+ },
263
+ "required": ["error"]
264
+ }
265
+ }
266
+ }
267
+ }
268
+ }
269
+ }
270
+ },
271
+ "/health": {
272
+ "get": {
273
+ "operationId": "healthCheck",
274
+ "summary": "Health check endpoint",
275
+ "responses": {
276
+ "200": {
277
+ "description": "Service is healthy",
278
+ "content": {
279
+ "application/json": {
280
+ "schema": {
281
+ "type": "object",
282
+ "properties": {
283
+ "status": { "type": "string", "example": "ok" },
284
+ "uptime": { "type": "number", "example": 12345 }
285
+ },
286
+ "required": ["status", "uptime"]
287
+ }
288
+ }
289
+ }
290
+ }
291
+ }
292
+ }
293
+ }
294
+ }
295
+ }
@@ -0,0 +1,151 @@
1
+ {
2
+ "openapi": "3.0.0",
3
+ "info": {
4
+ "title": "Simple Petstore",
5
+ "version": "1.0.0"
6
+ },
7
+ "servers": [{ "url": "http://localhost:3000" }],
8
+ "paths": {
9
+ "/pets": {
10
+ "get": {
11
+ "operationId": "listPets",
12
+ "summary": "List all pets",
13
+ "tags": ["pets"],
14
+ "parameters": [
15
+ {
16
+ "name": "limit",
17
+ "in": "query",
18
+ "required": false,
19
+ "schema": { "type": "integer" }
20
+ }
21
+ ],
22
+ "responses": {
23
+ "200": {
24
+ "description": "A list of pets",
25
+ "content": {
26
+ "application/json": {
27
+ "schema": {
28
+ "type": "array",
29
+ "items": {
30
+ "type": "object",
31
+ "properties": {
32
+ "id": { "type": "integer" },
33
+ "name": { "type": "string" },
34
+ "species": { "type": "string" }
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+ },
43
+ "post": {
44
+ "operationId": "createPet",
45
+ "summary": "Create a pet",
46
+ "tags": ["pets"],
47
+ "requestBody": {
48
+ "required": true,
49
+ "content": {
50
+ "application/json": {
51
+ "schema": {
52
+ "type": "object",
53
+ "required": ["name", "species"],
54
+ "properties": {
55
+ "name": { "type": "string" },
56
+ "species": { "type": "string" }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ },
62
+ "responses": {
63
+ "201": {
64
+ "description": "Created pet",
65
+ "content": {
66
+ "application/json": {
67
+ "schema": {
68
+ "type": "object",
69
+ "properties": {
70
+ "id": { "type": "integer" },
71
+ "name": { "type": "string" },
72
+ "species": { "type": "string" }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ },
78
+ "409": {
79
+ "description": "Pet with this name already exists",
80
+ "content": {
81
+ "application/json": {
82
+ "schema": {
83
+ "type": "object",
84
+ "properties": {
85
+ "error": { "type": "string" }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
93
+ },
94
+ "/pets/{petId}": {
95
+ "get": {
96
+ "operationId": "getPet",
97
+ "summary": "Get a pet by ID",
98
+ "tags": ["pets"],
99
+ "parameters": [
100
+ {
101
+ "name": "petId",
102
+ "in": "path",
103
+ "required": true,
104
+ "schema": { "type": "integer" }
105
+ }
106
+ ],
107
+ "responses": {
108
+ "200": {
109
+ "description": "A pet",
110
+ "content": {
111
+ "application/json": {
112
+ "schema": {
113
+ "type": "object",
114
+ "properties": {
115
+ "id": { "type": "integer" },
116
+ "name": { "type": "string" },
117
+ "species": { "type": "string" }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ },
123
+ "404": {
124
+ "description": "Pet not found"
125
+ }
126
+ }
127
+ },
128
+ "delete": {
129
+ "operationId": "deletePet",
130
+ "summary": "Delete a pet",
131
+ "tags": ["pets"],
132
+ "parameters": [
133
+ {
134
+ "name": "petId",
135
+ "in": "path",
136
+ "required": true,
137
+ "schema": { "type": "integer" }
138
+ }
139
+ ],
140
+ "responses": {
141
+ "204": {
142
+ "description": "Pet deleted"
143
+ },
144
+ "404": {
145
+ "description": "Pet not found"
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+ }
@@ -0,0 +1,12 @@
1
+ name: Write-only Suite
2
+ tests:
3
+ - name: "Create item"
4
+ POST: /items
5
+ json:
6
+ name: "Test Item"
7
+ expect:
8
+ status: 201
9
+ - name: "Delete item"
10
+ DELETE: /items/1
11
+ expect:
12
+ status: 204
@@ -0,0 +1,6 @@
1
+ name: Health Check
2
+ tests:
3
+ - name: "API is alive"
4
+ GET: /health
5
+ expect:
6
+ status: 200
@@ -0,0 +1 @@
1
+ base: http://localhost
@@ -0,0 +1,5 @@
1
+ name: A
2
+ tests:
3
+ - name: A
4
+ GET: /a
5
+ expect: {}
@@ -0,0 +1,5 @@
1
+ name: B
2
+ tests:
3
+ - name: B
4
+ POST: /b
5
+ expect: {}
@@ -0,0 +1,129 @@
1
+ import { describe, test, expect, beforeAll, afterAll } from "bun:test";
2
+ import { scanCoveredEndpoints, filterUncoveredEndpoints } from "../../src/core/generator/coverage-scanner.ts";
3
+ import type { EndpointInfo } from "../../src/core/generator/types.ts";
4
+ import { tmpdir } from "os";
5
+ import { join } from "path";
6
+ import { rm, mkdir } from "fs/promises";
7
+
8
+ const tmpDir = join(tmpdir(), `apitool-coverage-${Date.now()}`);
9
+
10
+ beforeAll(async () => {
11
+ await mkdir(tmpDir, { recursive: true });
12
+ });
13
+
14
+ afterAll(async () => {
15
+ await rm(tmpDir, { recursive: true, force: true }).catch(() => {});
16
+ });
17
+
18
+ describe("scanCoveredEndpoints", () => {
19
+ test("extracts method+path from YAML test files", async () => {
20
+ const yamlContent = `
21
+ name: Users CRUD
22
+ base_url: http://localhost:3000
23
+ tests:
24
+ - name: Create user
25
+ POST: /users
26
+ expect:
27
+ status: 201
28
+ - name: Get user
29
+ GET: /users/{{user_id}}
30
+ expect:
31
+ status: 200
32
+ - name: Delete user
33
+ DELETE: /users/{{user_id}}
34
+ expect:
35
+ status: 204
36
+ `;
37
+ await Bun.write(join(tmpDir, "users.yaml"), yamlContent);
38
+
39
+ const covered = await scanCoveredEndpoints(tmpDir);
40
+
41
+ expect(covered).toHaveLength(3);
42
+ expect(covered.find((c) => c.method === "POST")?.path).toBe("/users");
43
+ expect(covered.find((c) => c.method === "GET")?.path).toBe("/users/{*}");
44
+ expect(covered.find((c) => c.method === "DELETE")?.path).toBe("/users/{*}");
45
+ });
46
+
47
+ test("handles nested directories", async () => {
48
+ const subDir = join(tmpDir, "nested");
49
+ await mkdir(subDir, { recursive: true });
50
+ await Bun.write(join(subDir, "health.yaml"), `
51
+ name: Health
52
+ tests:
53
+ - name: Health check
54
+ GET: /health
55
+ expect:
56
+ status: 200
57
+ `);
58
+
59
+ const covered = await scanCoveredEndpoints(tmpDir);
60
+ const healthEndpoint = covered.find((c) => c.path === "/health");
61
+ expect(healthEndpoint).toBeDefined();
62
+ expect(healthEndpoint?.method).toBe("GET");
63
+ });
64
+
65
+ test("returns empty array for empty directory", async () => {
66
+ const emptyDir = join(tmpDir, "empty");
67
+ await mkdir(emptyDir, { recursive: true });
68
+ const covered = await scanCoveredEndpoints(emptyDir);
69
+ expect(covered).toHaveLength(0);
70
+ });
71
+ });
72
+
73
+ describe("filterUncoveredEndpoints", () => {
74
+ const makeEndpoint = (method: string, path: string): EndpointInfo => ({
75
+ method,
76
+ path,
77
+ tags: [],
78
+ parameters: [],
79
+ responseContentTypes: [],
80
+ responses: [],
81
+ security: [],
82
+ });
83
+
84
+ test("filters out covered endpoints", () => {
85
+ const all = [
86
+ makeEndpoint("GET", "/users"),
87
+ makeEndpoint("POST", "/users"),
88
+ makeEndpoint("GET", "/users/{id}"),
89
+ makeEndpoint("DELETE", "/users/{id}"),
90
+ makeEndpoint("GET", "/health"),
91
+ ];
92
+
93
+ const covered = [
94
+ { method: "GET", path: "/users", file: "users.yaml" },
95
+ { method: "POST", path: "/users", file: "users.yaml" },
96
+ { method: "GET", path: "/users/{*}", file: "users.yaml" },
97
+ ];
98
+
99
+ const uncovered = filterUncoveredEndpoints(all, covered);
100
+
101
+ expect(uncovered).toHaveLength(2);
102
+ expect(uncovered[0]!.method).toBe("DELETE");
103
+ expect(uncovered[0]!.path).toBe("/users/{id}");
104
+ expect(uncovered[1]!.method).toBe("GET");
105
+ expect(uncovered[1]!.path).toBe("/health");
106
+ });
107
+
108
+ test("returns all endpoints when nothing covered", () => {
109
+ const all = [
110
+ makeEndpoint("GET", "/users"),
111
+ makeEndpoint("POST", "/users"),
112
+ ];
113
+
114
+ const uncovered = filterUncoveredEndpoints(all, []);
115
+ expect(uncovered).toHaveLength(2);
116
+ });
117
+
118
+ test("returns empty when all covered", () => {
119
+ const all = [
120
+ makeEndpoint("GET", "/health"),
121
+ ];
122
+ const covered = [
123
+ { method: "GET", path: "/health", file: "health.yaml" },
124
+ ];
125
+
126
+ const uncovered = filterUncoveredEndpoints(all, covered);
127
+ expect(uncovered).toHaveLength(0);
128
+ });
129
+ });