@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
package/docs/ci.md ADDED
@@ -0,0 +1,274 @@
1
+ # CI/CD Integration
2
+
3
+ Run apitool API tests automatically in your CI/CD pipeline.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Generate CI workflow for your project
9
+ apitool ci init # auto-detect platform
10
+ apitool ci init --github # GitHub Actions
11
+ apitool ci init --gitlab # GitLab CI
12
+ ```
13
+
14
+ ## GitHub Actions
15
+
16
+ ```yaml
17
+ name: API Tests
18
+ on:
19
+ push:
20
+ branches: [main]
21
+ pull_request:
22
+ schedule:
23
+ - cron: "0 */6 * * *"
24
+ workflow_dispatch:
25
+
26
+ permissions:
27
+ contents: read
28
+ checks: write
29
+ pull-requests: write
30
+
31
+ jobs:
32
+ test:
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+
37
+ - name: Install apitool
38
+ run: curl -fsSL https://raw.githubusercontent.com/kirrosh/apitool/master/install.sh | sh
39
+
40
+ - name: Run tests
41
+ run: |
42
+ mkdir -p test-results
43
+ apitool run apis/ --report junit --no-db > test-results/junit.xml
44
+ continue-on-error: true
45
+
46
+ - name: Publish test results
47
+ uses: EnricoMi/publish-unit-test-result-action@v2
48
+ if: always()
49
+ with:
50
+ files: test-results/junit.xml
51
+
52
+ - uses: actions/upload-artifact@v4
53
+ if: always()
54
+ with:
55
+ name: test-results
56
+ path: test-results/junit.xml
57
+ ```
58
+
59
+ > **Note:** `continue-on-error: true` ensures junit.xml is always written and published, even when tests fail (exit code 1). The `publish-unit-test-result-action` will set the check status based on test results.
60
+
61
+ ## GitLab CI
62
+
63
+ ```yaml
64
+ api-tests:
65
+ image: ubuntu:latest
66
+ before_script:
67
+ - apt-get update -qq && apt-get install -y -qq curl
68
+ - curl -fsSL https://raw.githubusercontent.com/kirrosh/apitool/master/install.sh | sh
69
+ script:
70
+ - mkdir -p test-results
71
+ - apitool run apis/ --report junit --no-db > test-results/junit.xml
72
+ allow_failure:
73
+ exit_codes: 1
74
+ artifacts:
75
+ when: always
76
+ reports:
77
+ junit: test-results/junit.xml
78
+ ```
79
+
80
+ ## Jenkins
81
+
82
+ ```groovy
83
+ pipeline {
84
+ agent any
85
+
86
+ stages {
87
+ stage('Install') {
88
+ steps {
89
+ sh 'curl -fsSL https://raw.githubusercontent.com/kirrosh/apitool/master/install.sh | sh'
90
+ }
91
+ }
92
+ stage('Test') {
93
+ steps {
94
+ sh 'mkdir -p test-results'
95
+ sh 'apitool run apis/ --report junit --no-db > test-results/junit.xml || true'
96
+ }
97
+ post {
98
+ always {
99
+ junit 'test-results/junit.xml'
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ ## Generic Shell Script
108
+
109
+ Works with any CI system (CircleCI, Travis, Drone, etc.):
110
+
111
+ ```bash
112
+ #!/bin/bash
113
+ set -uo pipefail
114
+
115
+ # Install apitool
116
+ curl -fsSL https://raw.githubusercontent.com/kirrosh/apitool/master/install.sh | sh
117
+
118
+ # Run tests with JUnit output
119
+ mkdir -p test-results
120
+ apitool run apis/ --report junit --no-db > test-results/junit.xml
121
+ EXIT_CODE=$?
122
+
123
+ # Exit code: 0 = all passed, 1 = failures, 2 = error
124
+ exit $EXIT_CODE
125
+ ```
126
+
127
+ ## Environment Variables
128
+
129
+ `--env <name>` loads `.env.<name>.yaml` from the **test path directory** (`dirname` of the path passed to `apitool run`).
130
+
131
+ For example:
132
+ - `apitool run apis/petstore/tests/ --env ci` → looks for `apis/petstore/tests/.env.ci.yaml`
133
+ - `apitool run apis/ --env ci` → looks for `.env.ci.yaml` in current directory (parent of `apis/`)
134
+
135
+ If your env files live next to test files in subdirectories, run each API separately:
136
+
137
+ ```bash
138
+ apitool run apis/petstore/tests/ --env default --report junit --no-db
139
+ ```
140
+
141
+ Or place a `.env.yaml` (no name) in the repo root for shared variables.
142
+
143
+ ### Secrets in CI
144
+
145
+ Pass secrets as environment variables and reference them in `.env.ci.yaml`:
146
+
147
+ ```yaml
148
+ # .env.ci.yaml (in repo root or test directory)
149
+ base_url: https://api.staging.example.com
150
+ api_key: ${{ API_KEY }}
151
+ auth_token: ${{ AUTH_TOKEN }}
152
+ ```
153
+
154
+ #### GitHub Actions
155
+
156
+ ```yaml
157
+ - name: Run tests
158
+ env:
159
+ API_KEY: ${{ secrets.API_KEY }}
160
+ AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }}
161
+ run: apitool run apis/ --env ci --report junit --no-db > test-results/junit.xml
162
+ ```
163
+
164
+ #### GitLab CI
165
+
166
+ ```yaml
167
+ api-tests:
168
+ variables:
169
+ API_KEY: $API_KEY # Set in GitLab CI/CD settings
170
+ script:
171
+ - apitool run apis/ --env ci --report junit --no-db > test-results/junit.xml
172
+ ```
173
+
174
+ ### Auth token shortcut
175
+
176
+ For simple bearer token auth, use `--auth-token` instead of an env file:
177
+
178
+ ```bash
179
+ apitool run apis/ --auth-token "$AUTH_TOKEN" --report junit --no-db
180
+ ```
181
+
182
+ ## Triggers
183
+
184
+ The generated workflow runs on push, PR, schedule, and manual dispatch by default. You can also trigger tests from external events.
185
+
186
+ ### Schedule (cron)
187
+
188
+ Already included in the template. Adjust the cron expression:
189
+
190
+ ```yaml
191
+ schedule:
192
+ - cron: "0 */6 * * *" # every 6 hours
193
+ - cron: "0 9 * * 1-5" # weekdays at 9am UTC
194
+ - cron: "*/30 * * * *" # every 30 minutes
195
+ ```
196
+
197
+ ### Trigger from another repo (GitHub)
198
+
199
+ Use `repository_dispatch` to trigger API tests when your backend repo deploys:
200
+
201
+ **In the test repo workflow** (already included in template):
202
+ ```yaml
203
+ on:
204
+ repository_dispatch:
205
+ types: [api-updated]
206
+ ```
207
+
208
+ **In the backend repo** — add a step after deploy:
209
+ ```yaml
210
+ # .github/workflows/deploy.yml (backend repo)
211
+ - name: Trigger API tests
212
+ run: |
213
+ curl -X POST \
214
+ -H "Authorization: token ${{ secrets.TEST_REPO_PAT }}" \
215
+ -H "Accept: application/vnd.github.v3+json" \
216
+ https://api.github.com/repos/OWNER/apitool-tests/dispatches \
217
+ -d '{"event_type": "api-updated", "client_payload": {"env": "staging"}}'
218
+ ```
219
+
220
+ Or with `gh` CLI:
221
+ ```bash
222
+ gh api repos/OWNER/apitool-tests/dispatches \
223
+ -f event_type=api-updated \
224
+ -f 'client_payload[env]=staging'
225
+ ```
226
+
227
+ > **Note:** Requires a Personal Access Token (PAT) with `repo` scope stored as a secret in the backend repo.
228
+
229
+ ### Trigger from another repo (GitLab)
230
+
231
+ Use GitLab pipeline triggers:
232
+
233
+ ```bash
234
+ curl -X POST \
235
+ --form "ref=main" \
236
+ --form "token=$TRIGGER_TOKEN" \
237
+ "https://gitlab.com/api/v4/projects/PROJECT_ID/trigger/pipeline"
238
+ ```
239
+
240
+ Add the trigger token in GitLab: Settings → CI/CD → Pipeline triggers.
241
+
242
+ ### Webhook from external service
243
+
244
+ Any service that can send HTTP POST requests can trigger tests:
245
+
246
+ **GitHub:** Use `repository_dispatch` (see above)
247
+
248
+ **GitLab:** Use pipeline trigger tokens
249
+
250
+ **Generic (any CI):** Use the CI platform's API to trigger a build. Example with GitHub CLI:
251
+
252
+ ```bash
253
+ gh workflow run api-tests.yml --repo OWNER/apitool-tests
254
+ ```
255
+
256
+ ## Exit Codes
257
+
258
+ | Code | Meaning |
259
+ |------|---------|
260
+ | 0 | All tests passed |
261
+ | 1 | One or more tests failed |
262
+ | 2 | Configuration or runtime error |
263
+
264
+ ## Key Flags for CI
265
+
266
+ | Flag | Description |
267
+ |------|-------------|
268
+ | `--report junit` | Output JUnit XML for CI integration |
269
+ | `--no-db` | Skip writing to local SQLite database |
270
+ | `--env <name>` | Load `.env.<name>.yaml` from test path directory |
271
+ | `--bail` | Stop on first suite failure |
272
+ | `--safe` | Run only GET tests (read-only mode) |
273
+ | `--tag <tag>` | Filter suites by tag |
274
+ | `--auth-token <token>` | Inject bearer token as `{{auth_token}}` |
@@ -0,0 +1,67 @@
1
+ # Проблемы при первичной генерации тестов
2
+
3
+ ## 1. `writeSuites` возвращал пустой массив при incremental generation
4
+
5
+ **Проблема:** Функция `writeSuites` возвращала только новые файлы. При повторной генерации (incremental mode) существующие файлы пропускались и не попадали в результат — MCP-инструмент `generate_tests` возвращал `files: []`, хотя файлы на диске есть.
6
+
7
+ **Исправление:** `writeSuites` теперь возвращает `{ written: string[], skipped: string[] }`. MCP и CLI используют оба списка для полного отчёта.
8
+
9
+ **Файлы:** `src/core/generator/serializer.ts` (ранее skeleton.ts)
10
+
11
+ ---
12
+
13
+ ## 2. `getDb()` не восстанавливался после удаления файла БД
14
+
15
+ **Проблема:** Синглтон `_db` кешировал соединение. Если файл `apitool.db` удалялся (при пересоздании проекта), последующие вызовы `getDb()` возвращали "disk I/O error" — соединение было протухшим.
16
+
17
+ **Исправление:** `getDb()` теперь проверяет `existsSync(path)` перед возвратом кешированного соединения. Если файл удалён — соединение пересоздаётся.
18
+
19
+ **Файлы:** `src/db/schema.ts`
20
+
21
+ ---
22
+
23
+ ## 3. MCP `generate_tests` не создавал коллекцию и окружение в БД
24
+
25
+ **Проблема:** Блок создания коллекции/окружения падал в `catch` из-за проблемы #2 (протухшее DB-соединение). Ошибка тихо проглатывалась.
26
+
27
+ **Исправление:** Исправлен `getDb()` (проблема #2). Добавлено логирование ошибок в `catch` вместо тихого игнорирования.
28
+
29
+ **Примечание:** MCP tool `generate_tests` удалён в M21. AI-генерация доступна через `apitool ai-generate` и WebUI.
30
+
31
+ **Файлы:** `src/db/schema.ts`
32
+
33
+ ---
34
+
35
+ ## 4. Относительный `base_url` из спецификации ломал Explorer и тесты
36
+
37
+ **Проблема:** Swagger-спецификация содержит `servers[0].url = "/docgen2/docgen-ui-service/"` — относительный URL. При Try it в Explorer этот URL конкатенировался с path, давая невалидный URL для `fetch()`. В тестах (runner) аналогично — URL без хоста невалиден.
38
+
39
+ **Исправление:**
40
+ - Explorer: если `base_url` относительный — поле остаётся пустым с placeholder `https://your-host/...`
41
+ - Explorer: если в окружении БД есть абсолютный `base_url` — подставляется автоматически
42
+ - `/api/try`: добавлена валидация — возвращает понятную ошибку, если URL не абсолютный
43
+
44
+ **Файлы:** `src/web/routes/explorer.ts`, `src/web/routes/api.ts`, `src/web/server.ts`
45
+
46
+ ---
47
+
48
+ ## 5. HTMX загружался с CDN (unpkg.com)
49
+
50
+ **Проблема:** HTMX подгружался с `https://unpkg.com/htmx.org@2.0.4`. В корпоративных сетях CDN может быть заблокирован → кнопка "Try it" и все HTMX-элементы не работают (JS не загрузился).
51
+
52
+ **Исправление:** HTMX скачан локально в `src/web/static/htmx.min.js` и сервируется с `/static/htmx.min.js`.
53
+
54
+ **Файлы:** `src/web/static/htmx.min.js`, `src/web/server.ts`, `src/web/views/layout.ts`
55
+
56
+ ---
57
+
58
+ ## 6. Self-signed сертификаты блокировали запросы
59
+
60
+ **Проблема:** Внутренние API используют self-signed сертификаты. `fetch()` в Bun по умолчанию отклоняет такие соединения с ошибкой `self signed certificate in certificate chain`.
61
+
62
+ **Исправление:** Добавлен `tls: { rejectUnauthorized: false }` во все `fetch()`:
63
+ - Explorer Try it (`/api/try`)
64
+ - Explorer authorize proxy (`/api/authorize`)
65
+ - Test runner (`http-client.ts`)
66
+
67
+ **Файлы:** `src/web/routes/api.ts`, `src/core/runner/http-client.ts`
@@ -0,0 +1,3 @@
1
+ base_url: "http://localhost:3000"
2
+ auth_username: admin
3
+ auth_password: admin
package/install.ps1 ADDED
@@ -0,0 +1,80 @@
1
+ # apitool installer for Windows - downloads the latest release binary
2
+ # Usage: iwr https://raw.githubusercontent.com/kirrosh/apitool/master/install.ps1 | iex
3
+
4
+ $ErrorActionPreference = "Stop"
5
+
6
+ $REPO = "kirrosh/apitool"
7
+ $TARGET = "win-x64"
8
+ $ARTIFACT = "apitool-$TARGET.zip"
9
+
10
+ Write-Host "Detected platform: $TARGET" -ForegroundColor Cyan
11
+
12
+ # Get latest release tag
13
+ Write-Host "Fetching latest release..." -ForegroundColor Yellow
14
+ $RELEASE_URL = "https://api.github.com/repos/$REPO/releases/latest"
15
+ $TAG = (Invoke-RestMethod $RELEASE_URL).tag_name
16
+
17
+ if (-not $TAG) {
18
+ Write-Host "Error: Could not determine latest release tag" -ForegroundColor Red
19
+ exit 1
20
+ }
21
+ Write-Host "Latest release: $TAG" -ForegroundColor Green
22
+
23
+ # Download binary
24
+ $DOWNLOAD_URL = "https://github.com/$REPO/releases/download/$TAG/$ARTIFACT"
25
+ Write-Host "Downloading $DOWNLOAD_URL ..." -ForegroundColor Yellow
26
+
27
+ $TEMP_DIR = [System.IO.Path]::GetTempPath()
28
+ $DOWNLOAD_PATH = Join-Path $TEMP_DIR $ARTIFACT
29
+
30
+ try {
31
+ Invoke-WebRequest -Uri $DOWNLOAD_URL -OutFile $DOWNLOAD_PATH -UseBasicParsing
32
+ } catch {
33
+ Write-Host "Error: Could not download release" -ForegroundColor Red
34
+ Write-Host $_.Exception.Message
35
+ exit 1
36
+ }
37
+
38
+ # Extract
39
+ $EXTRACT_DIR = Join-Path $TEMP_DIR "apitool-install"
40
+ if (Test-Path $EXTRACT_DIR) {
41
+ Remove-Item $EXTRACT_DIR -Recurse -Force
42
+ }
43
+ New-Item -ItemType Directory -Path $EXTRACT_DIR | Out-Null
44
+
45
+ Write-Host "Extracting..." -ForegroundColor Yellow
46
+ Expand-Archive -Path $DOWNLOAD_PATH -DestinationPath $EXTRACT_DIR -Force
47
+
48
+ $BINARY = Join-Path $EXTRACT_DIR "apitool.exe"
49
+ if (-not (Test-Path $BINARY)) {
50
+ Write-Host "Error: Binary not found in archive" -ForegroundColor Red
51
+ exit 1
52
+ }
53
+
54
+ # Install binary
55
+ $INSTALL_DIR = "$env:LOCALAPPDATA\apitool"
56
+ if (-not (Test-Path $INSTALL_DIR)) {
57
+ New-Item -ItemType Directory -Path $INSTALL_DIR -Force | Out-Null
58
+ }
59
+
60
+ Copy-Item $BINARY $INSTALL_DIR -Force
61
+ $FINAL_PATH = Join-Path $INSTALL_DIR "apitool.exe"
62
+
63
+ Write-Host "Installed to $FINAL_PATH" -ForegroundColor Green
64
+
65
+ # Add to PATH if not already there
66
+ $PATH_ENTRY = $INSTALL_DIR
67
+ $currentPath = [System.Environment]::GetEnvironmentVariable("Path", "User")
68
+
69
+ if ($currentPath -notlike "*$PATH_ENTRY*") {
70
+ [System.Environment]::SetEnvironmentVariable("Path", "$currentPath;$PATH_ENTRY", "User")
71
+ Write-Host "Added $PATH_ENTRY to User PATH" -ForegroundColor Green
72
+ Write-Host "Note: You may need to restart your terminal for changes to take effect" -ForegroundColor Yellow
73
+ } else {
74
+ Write-Host "$PATH_ENTRY already in PATH" -ForegroundColor Cyan
75
+ }
76
+
77
+ # Verify
78
+ & $FINAL_PATH --version
79
+ Write-Host "Done!" -ForegroundColor Green
80
+ Write-Host "Run 'apitool init' to set up a new project." -ForegroundColor Cyan
package/install.sh ADDED
@@ -0,0 +1,113 @@
1
+ #!/bin/sh
2
+ # apitool installer — downloads the latest release binary for your platform
3
+ # Usage: curl -fsSL https://raw.githubusercontent.com/kirrosh/apitool/master/install.sh | sh
4
+
5
+ set -e
6
+
7
+ REPO="kirrosh/apitool"
8
+
9
+ # Detect OS
10
+ OS=$(uname -s)
11
+ case "$OS" in
12
+ Linux) PLATFORM="linux" ;;
13
+ Darwin) PLATFORM="darwin" ;;
14
+ *)
15
+ echo "Error: Unsupported OS: $OS"
16
+ echo "For Windows, download the zip from https://github.com/$REPO/releases/latest"
17
+ exit 1
18
+ ;;
19
+ esac
20
+
21
+ # Detect architecture
22
+ ARCH=$(uname -m)
23
+ case "$ARCH" in
24
+ x86_64|amd64) ARCH_SUFFIX="x64" ;;
25
+ arm64|aarch64) ARCH_SUFFIX="arm64" ;;
26
+ *)
27
+ echo "Error: Unsupported architecture: $ARCH"
28
+ exit 1
29
+ ;;
30
+ esac
31
+
32
+ TARGET="${PLATFORM}-${ARCH_SUFFIX}"
33
+ echo "Detected platform: $TARGET"
34
+
35
+ # Get latest release tag
36
+ echo "Fetching latest release..."
37
+ RELEASE_URL="https://api.github.com/repos/$REPO/releases/latest"
38
+ TAG=$(curl -fsSL "$RELEASE_URL" | grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"//;s/".*//')
39
+
40
+ if [ -z "$TAG" ]; then
41
+ echo "Error: Could not determine latest release tag"
42
+ exit 1
43
+ fi
44
+ echo "Latest release: $TAG"
45
+
46
+ # Download binary
47
+ ARTIFACT="apitool-${TARGET}.tar.gz"
48
+ DOWNLOAD_URL="https://github.com/$REPO/releases/download/$TAG/$ARTIFACT"
49
+ echo "Downloading $DOWNLOAD_URL ..."
50
+
51
+ TMPDIR=$(mktemp -d)
52
+ trap 'rm -rf "$TMPDIR"' EXIT
53
+
54
+ curl -fsSL "$DOWNLOAD_URL" -o "$TMPDIR/$ARTIFACT"
55
+ tar -xzf "$TMPDIR/$ARTIFACT" -C "$TMPDIR"
56
+
57
+ # Install binary
58
+ BINARY="$TMPDIR/apitool"
59
+ if [ ! -f "$BINARY" ]; then
60
+ echo "Error: Binary not found in archive"
61
+ exit 1
62
+ fi
63
+ chmod +x "$BINARY"
64
+
65
+ # Choose install directory
66
+ INSTALL_DIR="/usr/local/bin"
67
+ if [ ! -w "$INSTALL_DIR" ]; then
68
+ # Try with sudo first
69
+ if command -v sudo >/dev/null 2>&1; then
70
+ echo "Need sudo to install to $INSTALL_DIR"
71
+ sudo cp "$BINARY" "$INSTALL_DIR/apitool"
72
+ sudo chmod +x "$INSTALL_DIR/apitool"
73
+ else
74
+ INSTALL_DIR="$HOME/.local/bin"
75
+ mkdir -p "$INSTALL_DIR"
76
+ cp "$BINARY" "$INSTALL_DIR/apitool"
77
+ echo "Installed to $INSTALL_DIR"
78
+
79
+ # Add to PATH in shell profile if not already there
80
+ case ":$PATH:" in
81
+ *":$INSTALL_DIR:"*) ;;
82
+ *)
83
+ PROFILE=""
84
+ if [ -f "$HOME/.zshrc" ]; then
85
+ PROFILE="$HOME/.zshrc"
86
+ elif [ -f "$HOME/.bashrc" ]; then
87
+ PROFILE="$HOME/.bashrc"
88
+ elif [ -f "$HOME/.profile" ]; then
89
+ PROFILE="$HOME/.profile"
90
+ fi
91
+
92
+ if [ -n "$PROFILE" ]; then
93
+ echo "" >> "$PROFILE"
94
+ echo "# apitool" >> "$PROFILE"
95
+ echo "export PATH=\"\$HOME/.local/bin:\$PATH\"" >> "$PROFILE"
96
+ echo "Added $INSTALL_DIR to PATH in $PROFILE"
97
+ echo "Run: source $PROFILE (or open a new terminal)"
98
+ else
99
+ echo "Warning: $INSTALL_DIR is not in your PATH. Add it with:"
100
+ echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
101
+ fi
102
+ ;;
103
+ esac
104
+ fi
105
+ else
106
+ cp "$BINARY" "$INSTALL_DIR/apitool"
107
+ fi
108
+
109
+ echo "Installed to $INSTALL_DIR/apitool"
110
+
111
+ # Verify
112
+ "$INSTALL_DIR/apitool" --version
113
+ echo "Done! Run 'apitool init' to set up a new project."
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@kirrosh/apitool",
3
+ "version": "0.4.3",
4
+ "description": "API testing platform — define tests in YAML, run from CLI or WebUI, generate from OpenAPI specs",
5
+ "license": "MIT",
6
+ "module": "index.ts",
7
+ "type": "module",
8
+ "keywords": [
9
+ "api",
10
+ "testing",
11
+ "openapi",
12
+ "yaml",
13
+ "cli",
14
+ "rest",
15
+ "http"
16
+ ],
17
+ "bin": {
18
+ "apitool": "src/cli/index.ts"
19
+ },
20
+ "scripts": {
21
+ "apitool": "bun run src/cli/index.ts",
22
+ "test": "bun run test:unit && bun run test:mocked",
23
+ "test:unit": "bun test tests/db/ tests/parser/ tests/runner/ tests/generator/ tests/cli/args.test.ts tests/cli/chat.test.ts tests/cli/commands.test.ts tests/cli/doctor.test.ts tests/cli/envs.test.ts tests/cli/generate-env.test.ts tests/cli/init.test.ts tests/cli/runs.test.ts tests/cli/safe-run.test.ts tests/cli/update.test.ts tests/integration/ tests/web/",
24
+ "test:mocked": "bun run scripts/run-mocked-tests.ts",
25
+ "test:ai": "bun test tests/ai/",
26
+ "check": "tsc --noEmit --project tsconfig.json",
27
+ "build": "bun build --compile src/cli/index.ts --outfile apitool"
28
+ },
29
+ "devDependencies": {
30
+ "@types/bun": "latest"
31
+ },
32
+ "peerDependencies": {
33
+ "typescript": "^5"
34
+ },
35
+ "dependencies": {
36
+ "@ai-sdk/anthropic": "^2",
37
+ "@ai-sdk/openai": "^2",
38
+ "@hono/zod-openapi": "^1.2.2",
39
+ "@modelcontextprotocol/sdk": "^1.27.1",
40
+ "@readme/openapi-parser": "^5.5.0",
41
+ "ai": "^6",
42
+ "hono": "^4.12.2",
43
+ "openapi-types": "^12.1.3",
44
+ "zod": "^4.3.6"
45
+ }
46
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Runs each mock.module() test file in a separate Bun subprocess
3
+ * to avoid Bun's module cache pollution bug (bun#7823, bun#12823).
4
+ */
5
+
6
+ const MOCKED_FILES = [
7
+ "tests/agent/tools/diagnose-failure.test.ts",
8
+ "tests/agent/tools/explore-api.test.ts",
9
+ "tests/agent/tools/generate-tests.test.ts",
10
+ "tests/agent/tools/manage-environment.test.ts",
11
+ "tests/agent/tools/query-results.test.ts",
12
+ "tests/agent/tools/run-tests.test.ts",
13
+ "tests/agent/tools/send-request.test.ts",
14
+ "tests/agent/tools/validate-tests.test.ts",
15
+ "tests/mcp/coverage-analysis.test.ts",
16
+ "tests/mcp/explore-api.test.ts",
17
+ "tests/mcp/send-request.test.ts",
18
+ "tests/cli/request.test.ts",
19
+ "tests/cli/coverage.test.ts",
20
+ ];
21
+
22
+ const CONCURRENCY = 4;
23
+ let failed = 0;
24
+
25
+ async function runFile(file: string): Promise<boolean> {
26
+ const proc = Bun.spawn(["bun", "test", file], {
27
+ stdout: "inherit",
28
+ stderr: "inherit",
29
+ env: { ...process.env, FORCE_COLOR: "1" },
30
+ });
31
+ const code = await proc.exited;
32
+ return code === 0;
33
+ }
34
+
35
+ // Run in batches of CONCURRENCY
36
+ for (let i = 0; i < MOCKED_FILES.length; i += CONCURRENCY) {
37
+ const batch = MOCKED_FILES.slice(i, i + CONCURRENCY);
38
+ const results = await Promise.all(batch.map(runFile));
39
+ failed += results.filter((ok) => !ok).length;
40
+ }
41
+
42
+ if (failed > 0) {
43
+ console.error(`\n${failed} mocked test file(s) failed.`);
44
+ process.exit(1);
45
+ }
package/seed-demo.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { getDb } from "./src/db/schema.ts";
2
+ import { createRun, finalizeRun, saveResults } from "./src/db/queries.ts";
3
+ import type { TestRunResult } from "./src/core/runner/types.ts";
4
+
5
+ getDb();
6
+
7
+ function makeRun(dayOffset: number, passRate: number): TestRunResult[] {
8
+ const d = new Date();
9
+ d.setDate(d.getDate() - dayOffset);
10
+ const total = 10;
11
+ const passed = Math.round(total * passRate);
12
+ const failed = total - passed;
13
+
14
+ const steps = [];
15
+ for (let i = 0; i < total; i++) {
16
+ const status = i < passed ? "pass" : "fail";
17
+ steps.push({
18
+ name: `Test step ${i + 1}`,
19
+ status: status as any,
20
+ duration_ms: 50 + Math.round(Math.random() * 500),
21
+ request: { method: "GET", url: `http://api.example.com/endpoint-${i}`, headers: {} },
22
+ response: { status: status === "pass" ? 200 : 500, headers: {}, body: "{}", duration_ms: 50 + Math.round(Math.random() * 500) },
23
+ assertions: [{ field: "status", rule: `equals ${status === "pass" ? 200 : 204}`, passed: status === "pass", actual: status === "pass" ? 200 : 500, expected: status === "pass" ? 200 : 204 }],
24
+ captures: {},
25
+ });
26
+ }
27
+
28
+ return [{
29
+ suite_name: dayOffset % 2 === 0 ? "Users API" : "Payments API",
30
+ started_at: d.toISOString(),
31
+ finished_at: new Date(d.getTime() + 1500).toISOString(),
32
+ total,
33
+ passed,
34
+ failed,
35
+ skipped: 0,
36
+ steps,
37
+ }];
38
+ }
39
+
40
+ // Create 8 runs with varying pass rates
41
+ const rates = [1.0, 0.9, 0.8, 1.0, 0.7, 1.0, 0.9, 0.6];
42
+ for (let i = 0; i < rates.length; i++) {
43
+ const results = makeRun(rates.length - i, rates[i]!);
44
+ const runId = createRun({
45
+ started_at: results[0]!.started_at,
46
+ environment: i % 2 === 0 ? "staging" : "production",
47
+ trigger: "manual",
48
+ });
49
+ finalizeRun(runId, results);
50
+ saveResults(runId, results);
51
+ }
52
+
53
+ console.log("Seeded 8 demo runs into apitool.db");