@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.
- package/.github/workflows/ci.yml +27 -0
- package/.github/workflows/release.yml +97 -0
- package/.mcp.json +9 -0
- package/APITOOL.md +195 -0
- package/BACKLOG.md +62 -0
- package/CHANGELOG.md +88 -0
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/bun.lock +291 -0
- package/docs/GLOSSARY.md +182 -0
- package/docs/INDEX.md +21 -0
- package/docs/agent.md +135 -0
- package/docs/archive/APITOOL-pre-M22.md +831 -0
- package/docs/archive/BACKLOG-AI-NATIVE.md +56 -0
- package/docs/archive/M1-M2-parser-runner.md +216 -0
- package/docs/archive/M4-M7-reporter-cli.md +179 -0
- package/docs/archive/M5-M7-storage-junit.md +300 -0
- package/docs/archive/M6-webui.md +339 -0
- package/docs/ci.md +274 -0
- package/docs/generation-issues.md +67 -0
- package/generated/.env.yaml +3 -0
- package/install.ps1 +80 -0
- package/install.sh +113 -0
- package/package.json +46 -0
- package/scripts/run-mocked-tests.ts +45 -0
- package/seed-demo.ts +53 -0
- package/self-tests/auth.yaml +18 -0
- package/self-tests/collections-crud.yaml +46 -0
- package/self-tests/environments-crud.yaml +48 -0
- package/self-tests/export.yaml +32 -0
- package/self-tests/runs.yaml +16 -0
- package/src/bun-types.d.ts +5 -0
- package/src/cli/commands/add-api.ts +51 -0
- package/src/cli/commands/ai-generate.ts +106 -0
- package/src/cli/commands/chat.ts +43 -0
- package/src/cli/commands/ci-init.ts +126 -0
- package/src/cli/commands/collections.ts +41 -0
- package/src/cli/commands/coverage.ts +65 -0
- package/src/cli/commands/doctor.ts +127 -0
- package/src/cli/commands/envs.ts +218 -0
- package/src/cli/commands/init.ts +84 -0
- package/src/cli/commands/mcp.ts +16 -0
- package/src/cli/commands/run.ts +137 -0
- package/src/cli/commands/runs.ts +108 -0
- package/src/cli/commands/serve.ts +22 -0
- package/src/cli/commands/update.ts +142 -0
- package/src/cli/commands/validate.ts +18 -0
- package/src/cli/index.ts +500 -0
- package/src/cli/output.ts +24 -0
- package/src/cli/runtime.ts +7 -0
- package/src/core/agent/agent-loop.ts +116 -0
- package/src/core/agent/context-manager.ts +41 -0
- package/src/core/agent/system-prompt.ts +33 -0
- package/src/core/agent/tools/diagnose-failure.ts +51 -0
- package/src/core/agent/tools/explore-api.ts +40 -0
- package/src/core/agent/tools/index.ts +48 -0
- package/src/core/agent/tools/manage-environment.ts +40 -0
- package/src/core/agent/tools/query-results.ts +40 -0
- package/src/core/agent/tools/run-tests.ts +38 -0
- package/src/core/agent/tools/send-request.ts +44 -0
- package/src/core/agent/tools/validate-tests.ts +23 -0
- package/src/core/agent/types.ts +22 -0
- package/src/core/generator/ai/ai-generator.ts +61 -0
- package/src/core/generator/ai/llm-client.ts +159 -0
- package/src/core/generator/ai/output-parser.ts +307 -0
- package/src/core/generator/ai/prompt-builder.ts +153 -0
- package/src/core/generator/ai/types.ts +56 -0
- package/src/core/generator/coverage-scanner.ts +87 -0
- package/src/core/generator/data-factory.ts +115 -0
- package/src/core/generator/index.ts +10 -0
- package/src/core/generator/openapi-reader.ts +142 -0
- package/src/core/generator/schema-utils.ts +52 -0
- package/src/core/generator/serializer.ts +189 -0
- package/src/core/generator/types.ts +47 -0
- package/src/core/parser/filter.ts +14 -0
- package/src/core/parser/index.ts +21 -0
- package/src/core/parser/schema.ts +175 -0
- package/src/core/parser/types.ts +50 -0
- package/src/core/parser/variables.ts +146 -0
- package/src/core/parser/yaml-parser.ts +85 -0
- package/src/core/reporter/console.ts +175 -0
- package/src/core/reporter/index.ts +23 -0
- package/src/core/reporter/json.ts +9 -0
- package/src/core/reporter/junit.ts +78 -0
- package/src/core/reporter/types.ts +12 -0
- package/src/core/runner/assertions.ts +172 -0
- package/src/core/runner/execute-run.ts +75 -0
- package/src/core/runner/executor.ts +150 -0
- package/src/core/runner/http-client.ts +69 -0
- package/src/core/runner/index.ts +12 -0
- package/src/core/runner/types.ts +48 -0
- package/src/core/setup-api.ts +97 -0
- package/src/core/utils.ts +9 -0
- package/src/db/queries.ts +868 -0
- package/src/db/schema.ts +215 -0
- package/src/mcp/server.ts +47 -0
- package/src/mcp/tools/ci-init.ts +57 -0
- package/src/mcp/tools/coverage-analysis.ts +58 -0
- package/src/mcp/tools/explore-api.ts +84 -0
- package/src/mcp/tools/generate-missing-tests.ts +80 -0
- package/src/mcp/tools/generate-tests-guide.ts +353 -0
- package/src/mcp/tools/manage-environment.ts +123 -0
- package/src/mcp/tools/manage-server.ts +87 -0
- package/src/mcp/tools/query-db.ts +141 -0
- package/src/mcp/tools/run-tests.ts +66 -0
- package/src/mcp/tools/save-test-suite.ts +164 -0
- package/src/mcp/tools/send-request.ts +53 -0
- package/src/mcp/tools/setup-api.ts +49 -0
- package/src/mcp/tools/validate-tests.ts +42 -0
- package/src/tui/chat-ui.ts +150 -0
- package/src/web/routes/api.ts +234 -0
- package/src/web/routes/dashboard.ts +348 -0
- package/src/web/routes/runs.ts +64 -0
- package/src/web/schemas.ts +121 -0
- package/src/web/server.ts +134 -0
- package/src/web/static/htmx.min.js +1 -0
- package/src/web/static/style.css +265 -0
- package/src/web/views/layout.ts +46 -0
- package/src/web/views/results.ts +209 -0
- package/tests/agent/agent-loop.test.ts +61 -0
- package/tests/agent/context-manager.test.ts +59 -0
- package/tests/agent/system-prompt.test.ts +42 -0
- package/tests/agent/tools/diagnose-failure.test.ts +85 -0
- package/tests/agent/tools/explore-api.test.ts +59 -0
- package/tests/agent/tools/manage-environment.test.ts +78 -0
- package/tests/agent/tools/query-results.test.ts +77 -0
- package/tests/agent/tools/run-tests.test.ts +89 -0
- package/tests/agent/tools/send-request.test.ts +78 -0
- package/tests/agent/tools/validate-tests.test.ts +59 -0
- package/tests/ai/ai-generator.integration.test.ts +131 -0
- package/tests/ai/llm-client.test.ts +145 -0
- package/tests/ai/output-parser.test.ts +132 -0
- package/tests/ai/prompt-builder.test.ts +67 -0
- package/tests/ai/types.test.ts +55 -0
- package/tests/cli/args.test.ts +63 -0
- package/tests/cli/chat.test.ts +38 -0
- package/tests/cli/ci-init.test.ts +112 -0
- package/tests/cli/commands.test.ts +316 -0
- package/tests/cli/coverage.test.ts +58 -0
- package/tests/cli/doctor.test.ts +39 -0
- package/tests/cli/envs.test.ts +181 -0
- package/tests/cli/init.test.ts +80 -0
- package/tests/cli/runs.test.ts +94 -0
- package/tests/cli/safe-run.test.ts +103 -0
- package/tests/cli/update.test.ts +32 -0
- package/tests/core/generator/schema-utils.test.ts +108 -0
- package/tests/core/parser/nested-assertions.test.ts +80 -0
- package/tests/core/runner/root-body-assertions.test.ts +70 -0
- package/tests/db/chat-queries.test.ts +88 -0
- package/tests/db/chat-schema.test.ts +37 -0
- package/tests/db/environments.test.ts +131 -0
- package/tests/db/queries.test.ts +409 -0
- package/tests/db/schema.test.ts +141 -0
- package/tests/fixtures/.env.yaml +3 -0
- package/tests/fixtures/auth-token-test.yaml +8 -0
- package/tests/fixtures/bail/suite-a.yaml +6 -0
- package/tests/fixtures/bail/suite-b.yaml +6 -0
- package/tests/fixtures/crud.yaml +35 -0
- package/tests/fixtures/invalid-missing-name.yaml +5 -0
- package/tests/fixtures/invalid-no-method.yaml +6 -0
- package/tests/fixtures/petstore-auth.json +295 -0
- package/tests/fixtures/petstore-simple.json +151 -0
- package/tests/fixtures/post-only.yaml +12 -0
- package/tests/fixtures/simple.yaml +6 -0
- package/tests/fixtures/valid/.env.yaml +1 -0
- package/tests/fixtures/valid/a.yaml +5 -0
- package/tests/fixtures/valid/b.yml +5 -0
- package/tests/generator/coverage-scanner.test.ts +129 -0
- package/tests/generator/data-factory.test.ts +133 -0
- package/tests/generator/openapi-reader.test.ts +131 -0
- package/tests/integration/auth-flow.test.ts +217 -0
- package/tests/mcp/coverage-analysis.test.ts +64 -0
- package/tests/mcp/explore-api-schemas.test.ts +105 -0
- package/tests/mcp/explore-api.test.ts +49 -0
- package/tests/mcp/generate-missing-tests.test.ts +69 -0
- package/tests/mcp/manage-environment.test.ts +89 -0
- package/tests/mcp/save-test-suite.test.ts +116 -0
- package/tests/mcp/send-request.test.ts +79 -0
- package/tests/mcp/setup-api.test.ts +106 -0
- package/tests/mcp/tools.test.ts +248 -0
- package/tests/parser/schema.test.ts +134 -0
- package/tests/parser/variables.test.ts +227 -0
- package/tests/parser/yaml-parser.test.ts +69 -0
- package/tests/reporter/console.test.ts +256 -0
- package/tests/reporter/json.test.ts +98 -0
- package/tests/reporter/junit.test.ts +284 -0
- package/tests/runner/assertions.test.ts +262 -0
- package/tests/runner/executor.test.ts +310 -0
- package/tests/runner/http-client.test.ts +138 -0
- package/tests/web/routes.test.ts +160 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
> **Исторический snapshot** на момент реализации M5+JUnit. Актуальная документация — [APITOOL.md](../../APITOOL.md)
|
|
2
|
+
|
|
3
|
+
# M5: Storage (SQLite) + JUnit Reporter
|
|
4
|
+
|
|
5
|
+
Автоматическое сохранение истории прогонов в SQLite и JUnit XML для CI-систем.
|
|
6
|
+
|
|
7
|
+
## Быстрый старт
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Запуск с сохранением в БД (по умолчанию)
|
|
11
|
+
bun src/cli/index.ts run examples/
|
|
12
|
+
|
|
13
|
+
# JUnit XML для CI
|
|
14
|
+
bun src/cli/index.ts run tests/ --report junit > results.xml
|
|
15
|
+
|
|
16
|
+
# Без сохранения в БД
|
|
17
|
+
bun src/cli/index.ts run tests/ --no-db
|
|
18
|
+
|
|
19
|
+
# Своя БД
|
|
20
|
+
bun src/cli/index.ts run tests/ --db /path/to/my.db
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## JUnit Reporter
|
|
26
|
+
|
|
27
|
+
### Использование
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bun src/cli/index.ts run tests/ --report junit
|
|
31
|
+
bun src/cli/index.ts run tests/ --report junit > results.xml
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Формат вывода
|
|
35
|
+
|
|
36
|
+
```xml
|
|
37
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
38
|
+
<testsuites tests="5" failures="1" errors="0" time="2.340">
|
|
39
|
+
<testsuite name="Users CRUD" tests="5" failures="1" errors="0" skipped="0" time="2.340">
|
|
40
|
+
<testcase name="Create user" time="0.450"/>
|
|
41
|
+
<testcase name="Get user" time="0.120"/>
|
|
42
|
+
<testcase name="Update user" time="0.310">
|
|
43
|
+
<failure message="equals 200: expected 200, got 500">equals 200: expected 200, got 500</failure>
|
|
44
|
+
</testcase>
|
|
45
|
+
<testcase name="Verify deleted" time="0.000">
|
|
46
|
+
<skipped/>
|
|
47
|
+
</testcase>
|
|
48
|
+
<testcase name="Broken endpoint" time="0.050">
|
|
49
|
+
<error message="Connection refused">Connection refused</error>
|
|
50
|
+
</testcase>
|
|
51
|
+
</testsuite>
|
|
52
|
+
</testsuites>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Маппинг статусов
|
|
56
|
+
|
|
57
|
+
| Статус шага | XML-элемент |
|
|
58
|
+
|-------------|-------------|
|
|
59
|
+
| `pass` | `<testcase .../>` (без дочерних элементов) |
|
|
60
|
+
| `skip` | `<testcase ...><skipped/></testcase>` |
|
|
61
|
+
| `fail` | `<testcase ...><failure message="...">...</failure></testcase>` |
|
|
62
|
+
| `error` | `<testcase ...><error message="...">...</error></testcase>` |
|
|
63
|
+
|
|
64
|
+
**Атрибут `time`** — в секундах с тремя знаками после запятой: `(ms / 1000).toFixed(3)`.
|
|
65
|
+
|
|
66
|
+
**XML-экранирование** применяется ко всем строковым значениям: `& < > " '`.
|
|
67
|
+
|
|
68
|
+
### Программное использование
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { junitReporter } from "./src/core/reporter/junit.ts";
|
|
72
|
+
import type { TestRunResult } from "./src/core/runner/types.ts";
|
|
73
|
+
|
|
74
|
+
const results: TestRunResult[] = /* ... */;
|
|
75
|
+
junitReporter.report(results); // пишет XML в stdout
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Storage (SQLite)
|
|
81
|
+
|
|
82
|
+
### Поведение по умолчанию
|
|
83
|
+
|
|
84
|
+
При каждом `apitool run` создаётся или обновляется файл `apitool.db` в текущей директории. Файл создаётся автоматически при первом запуске.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
apitool run tests/ # сохраняет в ./apitool.db
|
|
88
|
+
apitool run tests/ --no-db # пропустить сохранение
|
|
89
|
+
apitool run tests/ --db ci.db # сохранить в ci.db
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Флаг `--db` принимает абсолютный или относительный путь.
|
|
93
|
+
|
|
94
|
+
### Схема БД
|
|
95
|
+
|
|
96
|
+
```sql
|
|
97
|
+
CREATE TABLE runs (
|
|
98
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
99
|
+
started_at TEXT NOT NULL, -- ISO 8601
|
|
100
|
+
finished_at TEXT,
|
|
101
|
+
total INTEGER NOT NULL DEFAULT 0,
|
|
102
|
+
passed INTEGER NOT NULL DEFAULT 0,
|
|
103
|
+
failed INTEGER NOT NULL DEFAULT 0,
|
|
104
|
+
skipped INTEGER NOT NULL DEFAULT 0,
|
|
105
|
+
trigger TEXT DEFAULT 'manual',
|
|
106
|
+
commit_sha TEXT,
|
|
107
|
+
branch TEXT,
|
|
108
|
+
environment TEXT,
|
|
109
|
+
duration_ms INTEGER
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
CREATE TABLE results (
|
|
113
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
114
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
115
|
+
suite_name TEXT NOT NULL,
|
|
116
|
+
test_name TEXT NOT NULL,
|
|
117
|
+
status TEXT NOT NULL, -- pass | fail | skip | error
|
|
118
|
+
duration_ms INTEGER NOT NULL,
|
|
119
|
+
request_method TEXT,
|
|
120
|
+
request_url TEXT,
|
|
121
|
+
request_body TEXT,
|
|
122
|
+
response_status INTEGER,
|
|
123
|
+
response_body TEXT, -- хранится только при fail/error
|
|
124
|
+
error_message TEXT,
|
|
125
|
+
assertions TEXT -- JSON: AssertionResult[]
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
CREATE TABLE environments (
|
|
129
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
130
|
+
name TEXT NOT NULL UNIQUE,
|
|
131
|
+
variables TEXT NOT NULL -- JSON: Record<string, string>
|
|
132
|
+
);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Примечание:** `response_body` сохраняется только для шагов со статусом `fail` или `error`, чтобы экономить место.
|
|
136
|
+
|
|
137
|
+
### Миграции
|
|
138
|
+
|
|
139
|
+
Версия схемы хранится в `PRAGMA user_version`. При каждом открытии БД применяются только недостающие миграции в транзакции.
|
|
140
|
+
|
|
141
|
+
### Настройки производительности
|
|
142
|
+
|
|
143
|
+
При открытии БД автоматически устанавливаются:
|
|
144
|
+
- `PRAGMA journal_mode = WAL` — concurrent reads без блокировки
|
|
145
|
+
- `PRAGMA foreign_keys = ON` — проверка ссылочной целостности
|
|
146
|
+
|
|
147
|
+
### Программное использование
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { getDb, closeDb } from "./src/db/schema.ts";
|
|
151
|
+
import {
|
|
152
|
+
createRun, finalizeRun, saveResults,
|
|
153
|
+
getRunById, listRuns, deleteRun,
|
|
154
|
+
upsertEnvironment, getEnvironment, listEnvironments,
|
|
155
|
+
} from "./src/db/queries.ts";
|
|
156
|
+
|
|
157
|
+
// Открыть БД (синглтон)
|
|
158
|
+
const db = getDb(); // apitool.db в cwd
|
|
159
|
+
const db2 = getDb("my.db"); // конкретный файл
|
|
160
|
+
|
|
161
|
+
// Сохранить прогон
|
|
162
|
+
const runId = createRun({ started_at: new Date().toISOString(), environment: "staging" });
|
|
163
|
+
// ... выполнить тесты ...
|
|
164
|
+
finalizeRun(runId, results);
|
|
165
|
+
saveResults(runId, results);
|
|
166
|
+
|
|
167
|
+
// Чтение истории
|
|
168
|
+
const runs = listRuns(10, 0); // последние 10 прогонов
|
|
169
|
+
const run = getRunById(1);
|
|
170
|
+
const steps = getResultsByRunId(1); // шаги с десериализованными assertions
|
|
171
|
+
|
|
172
|
+
// Управление окружениями
|
|
173
|
+
upsertEnvironment("staging", { BASE_URL: "https://staging.example.com" });
|
|
174
|
+
const vars = getEnvironment("staging");
|
|
175
|
+
const names = listEnvironments();
|
|
176
|
+
|
|
177
|
+
// В тестах — закрыть синглтон
|
|
178
|
+
closeDb();
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### API queries.ts
|
|
182
|
+
|
|
183
|
+
| Функция | Описание |
|
|
184
|
+
|---------|----------|
|
|
185
|
+
| `createRun(opts)` → `number` | Создать запись run, вернуть id |
|
|
186
|
+
| `finalizeRun(runId, results[])` | Обновить totals и finished_at |
|
|
187
|
+
| `saveResults(runId, results[])` | Bulk-insert шагов в транзакции |
|
|
188
|
+
| `getRunById(id)` → `RunRecord \| null` | Запись run по id |
|
|
189
|
+
| `getResultsByRunId(id)` → `StoredStepResult[]` | Шаги run с assertions из JSON |
|
|
190
|
+
| `listRuns(limit?, offset?)` → `RunSummary[]` | Список прогонов по дате DESC |
|
|
191
|
+
| `deleteRun(id)` → `boolean` | Удалить run и его результаты |
|
|
192
|
+
| `upsertEnvironment(name, vars)` | Создать или обновить окружение |
|
|
193
|
+
| `getEnvironment(name)` → `Record<string, string> \| null` | Переменные окружения |
|
|
194
|
+
| `listEnvironments()` → `string[]` | Имена всех окружений |
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Новые флаги CLI
|
|
199
|
+
|
|
200
|
+
### `run` — обновлённая сигнатура
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
apitool run <path> [options]
|
|
204
|
+
|
|
205
|
+
--report <format> console | json | junit (default: console)
|
|
206
|
+
--no-db Не создавать/обновлять apitool.db
|
|
207
|
+
--db <path> Путь к файлу БД (default: apitool.db в cwd)
|
|
208
|
+
--env <name> Файл окружения .env.<name>.yaml
|
|
209
|
+
--timeout <ms> Таймаут запроса
|
|
210
|
+
--bail Остановиться после первого упавшего suite
|
|
211
|
+
--auth-token <token> Auth-токен, доступен как {{auth_token}} переменная
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Примеры
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# Стандартный прогон: вывод в консоль + сохранение в apitool.db
|
|
218
|
+
apitool run tests/
|
|
219
|
+
|
|
220
|
+
# CI: JUnit XML + своя БД
|
|
221
|
+
apitool run tests/ --report junit --db ci/history.db > junit.xml
|
|
222
|
+
|
|
223
|
+
# Dry-run без БД
|
|
224
|
+
apitool run tests/ --no-db
|
|
225
|
+
|
|
226
|
+
# Staging-окружение
|
|
227
|
+
apitool run tests/ --env staging --db runs/staging.db
|
|
228
|
+
|
|
229
|
+
# Только JUnit, без записи в БД
|
|
230
|
+
apitool run tests/ --report junit --no-db > results.xml
|
|
231
|
+
|
|
232
|
+
# CI с внешним токеном
|
|
233
|
+
apitool run tests/ --auth-token "$CI_AUTH_TOKEN" --report junit > results.xml
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Архитектура
|
|
239
|
+
|
|
240
|
+
### Pipeline команды `run` (обновлённый)
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
parse(path)
|
|
244
|
+
→ loadEnvironment(env, dir)
|
|
245
|
+
→ [if --auth-token] env.auth_token = token
|
|
246
|
+
→ --timeout override
|
|
247
|
+
→ runSuites() / runSuites() + bail
|
|
248
|
+
→ reporter.report(results) # console | json | junit
|
|
249
|
+
→ [if !--no-db] getDb(dbPath)
|
|
250
|
+
createRun(opts)
|
|
251
|
+
finalizeRun(runId, results)
|
|
252
|
+
saveResults(runId, results)
|
|
253
|
+
→ exit code (0 | 1 | 2)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Сохранение в БД — **non-fatal**: при ошибке выводится предупреждение, exit code не меняется.
|
|
257
|
+
|
|
258
|
+
### Файловая структура
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
src/
|
|
262
|
+
├── db/
|
|
263
|
+
│ ├── schema.ts # getDb(), closeDb(), миграции
|
|
264
|
+
│ └── queries.ts # CRUD: runs, results, environments
|
|
265
|
+
└── core/reporter/
|
|
266
|
+
├── junit.ts # JUnit XML reporter
|
|
267
|
+
├── types.ts # ReporterName: "console"|"json"|"junit"
|
|
268
|
+
└── index.ts # getReporter() factory
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Reporter factory
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import { getReporter } from "./src/core/reporter/index.ts";
|
|
275
|
+
|
|
276
|
+
const reporter = getReporter("junit"); // "console" | "json" | "junit"
|
|
277
|
+
reporter.report(results);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Тесты
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
bun test tests/reporter/junit.test.ts # JUnit reporter (22 tests)
|
|
286
|
+
bun test tests/db/schema.test.ts # SQLite schema (10 tests)
|
|
287
|
+
bun test tests/db/queries.test.ts # DB queries (20 tests)
|
|
288
|
+
bun test tests/cli/commands.test.ts # CLI commands (16 tests)
|
|
289
|
+
|
|
290
|
+
bun test tests/ # все unit-тесты
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Покрытие
|
|
294
|
+
|
|
295
|
+
| Файл | Тесты |
|
|
296
|
+
|------|-------|
|
|
297
|
+
| `reporter/junit.ts` | XML-структура, time-форматирование, XML-экранирование, все 4 статуса |
|
|
298
|
+
| `db/schema.ts` | Создание файла, синглтон, таблицы, индексы, WAL, FK, версия, идемпотентность |
|
|
299
|
+
| `db/queries.ts` | createRun, finalizeRun, saveResults, getResultsBy RunId, listRuns, deleteRun, environments |
|
|
300
|
+
| `cli/commands/run.ts` | `--no-db` пропускает БД, `--db` использует указанный путь, junit reporter в pipeline, `--auth-token` парсинг и инъекция |
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
> **Исторический snapshot** на момент реализации M6. Актуальная документация — [APITOOL.md](../../APITOOL.md)
|
|
2
|
+
|
|
3
|
+
# M6: WebUI (Hono + HTMX)
|
|
4
|
+
|
|
5
|
+
HTML-дашборд для просмотра истории прогонов, метрик и исследования API.
|
|
6
|
+
|
|
7
|
+
## Быстрый старт
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Установить зависимость (уже добавлена)
|
|
11
|
+
bun add hono
|
|
12
|
+
|
|
13
|
+
# Запустить с OpenAPI спекой (URL или файл)
|
|
14
|
+
bun src/cli/index.ts serve --port 8080 --openapi https://petstore3.swagger.io/api/v3/openapi.json
|
|
15
|
+
|
|
16
|
+
# Запустить только с дашбордом (без Explorer)
|
|
17
|
+
bun src/cli/index.ts serve --port 8080
|
|
18
|
+
|
|
19
|
+
# Своя БД
|
|
20
|
+
bun src/cli/index.ts serve --port 8080 --db history.db
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Открыть http://localhost:8080 в браузере.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## CLI: `apitool serve`
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
apitool serve [options]
|
|
31
|
+
|
|
32
|
+
--port <port> Порт сервера (default: 8080)
|
|
33
|
+
--host <host> Хост сервера (default: 0.0.0.0)
|
|
34
|
+
--openapi <spec> Путь к OpenAPI спеке или URL для Explorer
|
|
35
|
+
--db <path> Путь к SQLite БД (default: apitool.db)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Страницы
|
|
41
|
+
|
|
42
|
+
| Route | Описание |
|
|
43
|
+
|-------|----------|
|
|
44
|
+
| `GET /` | Dashboard: карточки метрик, SVG trend chart, коллекции, последние прогоны, медленные/flaky тесты |
|
|
45
|
+
| `GET /metrics` | HTML-фрагмент метрик (для HTMX auto-refresh) |
|
|
46
|
+
| `GET /runs` | Список прогонов с фильтрацией (статус, environment, дата, поиск) и пагинацией (20 на страницу) |
|
|
47
|
+
| `GET /runs/:id` | Детали прогона: шаги по suite, expand failed, кнопки Export JUnit XML / JSON |
|
|
48
|
+
| `GET /explorer` | Дерево API из OpenAPI спеки, multi-auth panel, "Try it" формы |
|
|
49
|
+
| `GET /static/style.css` | CSS стили |
|
|
50
|
+
| `POST /api/run` | Запуск тестов из WebUI |
|
|
51
|
+
| `POST /api/try` | Единичный HTTP-запрос из Explorer (с auth injection) |
|
|
52
|
+
| `POST /api/authorize` | Proxy login для Bearer auth |
|
|
53
|
+
| `GET /api/export/:runId/junit` | Скачать JUnit XML отчёт (Content-Disposition: attachment) |
|
|
54
|
+
| `GET /api/export/:runId/json` | Скачать JSON отчёт (Content-Disposition: attachment) |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Dashboard (`GET /`)
|
|
59
|
+
|
|
60
|
+
Карточки:
|
|
61
|
+
- **Total Runs** — количество завершённых прогонов
|
|
62
|
+
- **Total Tests** — сумма всех тестов по всем прогонам
|
|
63
|
+
- **Pass Rate** — `SUM(passed) * 100 / SUM(total)` (%)
|
|
64
|
+
- **Avg Duration** — среднее время прогона
|
|
65
|
+
|
|
66
|
+
**Pass Rate Trend Chart** (между карточками и коллекциями):
|
|
67
|
+
- Inline SVG (viewBox 700x200, width=100%), area fill + polyline
|
|
68
|
+
- Grid lines at 0/25/50/75/100%, Y-axis labels
|
|
69
|
+
- Circle dots с `<title>` tooltips (run ID, pass rate, date)
|
|
70
|
+
- X-axis: 3 date labels (first, middle, last)
|
|
71
|
+
- Использует CSS variables — dark mode автоматически
|
|
72
|
+
- Guard: < 2 data points → "Not enough data" текст
|
|
73
|
+
- Shared модуль `src/web/views/trend-chart.ts` — переиспользуется на dashboard и collection page
|
|
74
|
+
|
|
75
|
+
**Collection page** (`/collections/:id`) также показывает собственный тренд-график (только прогоны этой коллекции) между метриками и секцией Test Suites.
|
|
76
|
+
|
|
77
|
+
Таблицы:
|
|
78
|
+
- **Recent Runs** (последние 5) — ID, дата, total/pass/fail/skip, duration, badge
|
|
79
|
+
- **Slowest Tests** (top 5) — suite, test, средняя длительность
|
|
80
|
+
- **Flaky Tests** (top 5) — тесты с разными статусами в последних 20 прогонах
|
|
81
|
+
|
|
82
|
+
Auto-refresh: `hx-trigger="every 10s"` на блоке метрик.
|
|
83
|
+
|
|
84
|
+
### SQL-запросы (добавлены в `src/db/queries.ts`)
|
|
85
|
+
|
|
86
|
+
| Функция | Описание |
|
|
87
|
+
|---------|----------|
|
|
88
|
+
| `getDashboardStats()` → `DashboardStats` | Агрегаты: totalRuns, totalTests, overallPassRate, avgDuration |
|
|
89
|
+
| `getPassRateTrend(limit)` → `PassRateTrendPoint[]` | Pass rate по последним N прогонам (глобальный) |
|
|
90
|
+
| `getCollectionPassRateTrend(collectionId, limit)` → `PassRateTrendPoint[]` | Pass rate по прогонам конкретной коллекции |
|
|
91
|
+
| `getSlowestTests(limit)` → `SlowestTest[]` | AVG(duration_ms) GROUP BY suite+test, ORDER BY DESC |
|
|
92
|
+
| `getFlakyTests(runsBack, limit)` → `FlakyTest[]` | Тесты с COUNT(DISTINCT status) > 1 |
|
|
93
|
+
| `countRuns(filters?)` → `number` | Количество записей в runs (с фильтрами) |
|
|
94
|
+
| `getDistinctEnvironments()` → `string[]` | Уникальные environment из runs |
|
|
95
|
+
| `buildRunFilterSQL(filters)` → `{where, params}` | WHERE clause builder для RunFilters |
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Runs (`GET /runs`, `GET /runs/:id`)
|
|
100
|
+
|
|
101
|
+
### Список прогонов
|
|
102
|
+
|
|
103
|
+
**Filter bar** (HTMX form, `hx-push-url`):
|
|
104
|
+
- Status: `<select>` — All / Has Failures / All Passed
|
|
105
|
+
- Environment: `<select>` из `getDistinctEnvironments()`
|
|
106
|
+
- Date from/to: `<input type="date">`
|
|
107
|
+
- Test name: `<input type="text">` (LIKE search)
|
|
108
|
+
- Кнопки: Filter, Clear
|
|
109
|
+
|
|
110
|
+
Таблица с колонками: ID | Date | Total | Pass | Fail | Skip | Duration | Status badge.
|
|
111
|
+
|
|
112
|
+
Пагинация: `?page=N`, 20 записей на страницу. Фильтры сохраняются в URL query params через `buildQueryString()`. HTMX `hx-get` + `hx-push-url` для SPA-навигации.
|
|
113
|
+
|
|
114
|
+
DB-функции:
|
|
115
|
+
- `RunFilters` interface: `{ status?, environment?, date_from?, date_to?, test_name? }`
|
|
116
|
+
- `buildRunFilterSQL(filters)` — WHERE clause с parameterized placeholders
|
|
117
|
+
- `listRuns(limit, offset, filters?)` — backward-compatible optional param
|
|
118
|
+
- `countRuns(filters?)` — backward-compatible
|
|
119
|
+
- `getDistinctEnvironments()` — `SELECT DISTINCT environment FROM runs`
|
|
120
|
+
|
|
121
|
+
### Детали прогона
|
|
122
|
+
|
|
123
|
+
- Шапка: ID, дата, environment, duration, totals (pass/fail/skip)
|
|
124
|
+
- **Export buttons**: "Export JUnit XML" / "Export JSON" — plain `<a download>` links to `/api/export/:runId/...`
|
|
125
|
+
- Шаги сгруппированы по suite
|
|
126
|
+
- Каждый шаг: badge (✓/✗/○), имя, duration
|
|
127
|
+
- Клик на failed/error шаг раскрывает панель с:
|
|
128
|
+
- Request method + URL
|
|
129
|
+
- Error message (если error)
|
|
130
|
+
- Список assertions (pass/fail с actual значениями)
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Explorer (`GET /explorer`)
|
|
135
|
+
|
|
136
|
+
### Без спеки
|
|
137
|
+
|
|
138
|
+
Если `--openapi` не указан — страница с сообщением и инструкцией.
|
|
139
|
+
|
|
140
|
+
### С OpenAPI спекой
|
|
141
|
+
|
|
142
|
+
- Спека загружается при старте сервера (файл или URL)
|
|
143
|
+
- Эндпоинты сгруппированы по `tags[0]`
|
|
144
|
+
- Каждый эндпоинт показывает: method badge (цветной), path, summary
|
|
145
|
+
- Клик раскрывает панель:
|
|
146
|
+
- **Parameters** — таблица (name, in, required, type)
|
|
147
|
+
- **Request Body** — JSON-схема (если есть)
|
|
148
|
+
- **Responses** — таблица (status, description)
|
|
149
|
+
- **Try it** — форма для отправки запроса
|
|
150
|
+
|
|
151
|
+
### Base URL из спеки
|
|
152
|
+
|
|
153
|
+
Поле `servers` из OpenAPI спеки подставляется автоматически (как в Swagger UI):
|
|
154
|
+
- 1 сервер → input с заполненным значением
|
|
155
|
+
- Несколько серверов → dropdown для выбора
|
|
156
|
+
- Нет серверов → пустое поле
|
|
157
|
+
|
|
158
|
+
### Authorize Panel (multi-scheme)
|
|
159
|
+
|
|
160
|
+
При наличии `securitySchemes` в OpenAPI спеке отображается панель авторизации с поддержкой нескольких схем одновременно.
|
|
161
|
+
|
|
162
|
+
**Credential Store** — глобальный объект `window.__authCredentials`, хранящий активные авторизации по имени схемы. HTMX-хук `htmx:configRequest` автоматически инжектит все активные credentials в каждый `/api/try` запрос.
|
|
163
|
+
|
|
164
|
+
**Поддерживаемые типы:**
|
|
165
|
+
|
|
166
|
+
| Тип | UI | Поведение |
|
|
167
|
+
|-----|-----|-----------|
|
|
168
|
+
| `http/bearer` | Поле для прямого токена + login-proxy (если `loginPath`) | `Authorization: Bearer <token>` |
|
|
169
|
+
| `http/basic` | Username + password | Клиентский `btoa(user:pass)` → `Authorization: Basic <encoded>` |
|
|
170
|
+
| `apiKey` | Поле для значения, badge с расположением | Header или query param по спеке |
|
|
171
|
+
| `oauth2`, `openIdConnect` | Имя + "Not yet supported" | — |
|
|
172
|
+
|
|
173
|
+
**Per-scheme статус:** каждая схема показывает badge "Active" после применения. Глобальный счётчик показывает количество активных схем.
|
|
174
|
+
|
|
175
|
+
**Функции:**
|
|
176
|
+
- `applyBearerDirect(name)` — прямой токен
|
|
177
|
+
- `doLoginProxy(name, loginPath)` — proxy через `/api/authorize`
|
|
178
|
+
- `applyApiKey(name, location, keyName)` — header или query
|
|
179
|
+
- `applyBasic(name)` — `btoa(user:pass)`
|
|
180
|
+
|
|
181
|
+
### POST /api/authorize
|
|
182
|
+
|
|
183
|
+
Proxy для Bearer auth через login endpoint.
|
|
184
|
+
|
|
185
|
+
**Body (JSON):**
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"base_url": "http://localhost:3000",
|
|
189
|
+
"path": "/auth/login",
|
|
190
|
+
"username": "admin",
|
|
191
|
+
"password": "secret"
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Поведение:**
|
|
196
|
+
1. Отправляет POST `base_url + path` с `{username, password}`
|
|
197
|
+
2. Ищет `token` или `access_token` в JSON-ответе
|
|
198
|
+
3. Возвращает `{token}` или `{error}`
|
|
199
|
+
|
|
200
|
+
### Try it (`POST /api/try`)
|
|
201
|
+
|
|
202
|
+
Форма собирает:
|
|
203
|
+
- Base URL (из servers или вручную)
|
|
204
|
+
- Path parameters → подставляются в URL `{param}`
|
|
205
|
+
- Query parameters → добавляются к URL
|
|
206
|
+
- Header parameters → отправляются как заголовки
|
|
207
|
+
- Auth credentials → инжектятся через HTMX-хук из credential store
|
|
208
|
+
- Body (JSON) → отправляется как request body
|
|
209
|
+
|
|
210
|
+
Ответ вставляется через HTMX: статус (цветной), заголовки (collapsible), тело (pretty JSON).
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## API (`POST /api/run`, `POST /api/try`)
|
|
215
|
+
|
|
216
|
+
### POST /api/run
|
|
217
|
+
|
|
218
|
+
Запуск тестов из WebUI.
|
|
219
|
+
|
|
220
|
+
**Body (JSON):**
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"path": "tests/",
|
|
224
|
+
"env": "staging"
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Поведение:**
|
|
229
|
+
1. `parse(path)` → `loadEnvironment()` → `runSuite()` для каждого suite
|
|
230
|
+
2. Результаты сохраняются в БД (trigger: "webui")
|
|
231
|
+
3. Возвращает `HX-Redirect: /runs/:id` для перенаправления
|
|
232
|
+
|
|
233
|
+
### POST /api/try
|
|
234
|
+
|
|
235
|
+
Единичный HTTP-запрос из Explorer.
|
|
236
|
+
|
|
237
|
+
**Поддерживает два формата входа:**
|
|
238
|
+
- `application/x-www-form-urlencoded` (от HTMX форм)
|
|
239
|
+
- `application/json`
|
|
240
|
+
|
|
241
|
+
**Возвращает HTML-фрагмент** (для вставки через HTMX):
|
|
242
|
+
- Статус с цветом (2xx=зелёный, 4xx=оранжевый, 5xx=красный)
|
|
243
|
+
- Headers (в `<details>`)
|
|
244
|
+
- Body (pretty-printed JSON если возможно)
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
### Export (`GET /api/export/:runId/junit`, `GET /api/export/:runId/json`)
|
|
249
|
+
|
|
250
|
+
Скачивание результатов прогона в формате JUnit XML или JSON.
|
|
251
|
+
|
|
252
|
+
**Реализация:**
|
|
253
|
+
- `reconstructResults(runId)` — helper в `api.ts`, пересобирает `TestRunResult[]` из `getRunById()` + `getResultsByRunId()`, группирует по `suite_name`, маппит `StoredStepResult` → `StepResult`
|
|
254
|
+
- `generateJunitXml(results)` — извлечена из `junitReporter.report()` в `junit.ts` (CLI поведение не изменилось)
|
|
255
|
+
- Response: `Content-Disposition: attachment; filename="run-N-*.ext"`, соответствующий Content-Type
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Архитектура
|
|
260
|
+
|
|
261
|
+
### Файловая структура
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
src/web/
|
|
265
|
+
├── server.ts # Hono app, static serving, startServer()
|
|
266
|
+
├── routes/
|
|
267
|
+
│ ├── dashboard.ts # GET /, GET /metrics
|
|
268
|
+
│ ├── collections.ts # GET /collections/:id, POST/DELETE /api/collections
|
|
269
|
+
│ ├── runs.ts # GET /runs (с фильтрами), GET /runs/:id
|
|
270
|
+
│ ├── explorer.ts # GET /explorer
|
|
271
|
+
│ └── api.ts # POST /api/run, POST /api/try, GET /api/export
|
|
272
|
+
├── views/
|
|
273
|
+
│ ├── layout.ts # HTML layout, escapeHtml()
|
|
274
|
+
│ └── trend-chart.ts # Shared SVG trend chart component
|
|
275
|
+
└── static/
|
|
276
|
+
└── style.css # CSS (dark/light, responsive)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Стилизация
|
|
280
|
+
|
|
281
|
+
Один CSS файл без фреймворков:
|
|
282
|
+
- CSS variables для цветов (pass/fail/skip/error)
|
|
283
|
+
- `prefers-color-scheme: dark` для автоматической тёмной темы
|
|
284
|
+
- Responsive через `max-width: 1100px`
|
|
285
|
+
- Компоненты: cards, tables, badges, progress bar, forms
|
|
286
|
+
|
|
287
|
+
### HTMX-паттерны
|
|
288
|
+
|
|
289
|
+
- `hx-get` + `hx-target="main"` + `hx-push-url="true"` — SPA-навигация без перезагрузки
|
|
290
|
+
- `hx-trigger="every 10s"` — авто-обновление метрик
|
|
291
|
+
- `hx-post` + `hx-target="#response-N"` — отправка Try it формы
|
|
292
|
+
- `HX-Request: true` header — сервер возвращает фрагмент вместо полной страницы
|
|
293
|
+
|
|
294
|
+
### Программное использование
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import { createApp } from "./src/web/server.ts";
|
|
298
|
+
import { startServer } from "./src/web/server.ts";
|
|
299
|
+
|
|
300
|
+
// Для тестов — без поднятия сервера
|
|
301
|
+
const app = createApp({ endpoints: [], specPath: null, servers: [], securitySchemes: [], loginPath: null });
|
|
302
|
+
const response = await app.request("/");
|
|
303
|
+
|
|
304
|
+
// Запуск сервера
|
|
305
|
+
await startServer({ port: 8080, openapiSpec: "api.yaml" });
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Тесты
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
bun test tests/web/routes.test.ts # 11 тестов роутов
|
|
314
|
+
bun test tests/web/explorer.test.ts # 14 тестов Explorer
|
|
315
|
+
bun test tests/db/queries.test.ts # dashboard-метрики (8 тестов)
|
|
316
|
+
|
|
317
|
+
bun test tests/web/ tests/db/ # все web + db тесты
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Покрытие
|
|
321
|
+
|
|
322
|
+
| Файл | Тесты |
|
|
323
|
+
|------|-------|
|
|
324
|
+
| `web/routes.test.ts` | Dashboard 200, metrics fragment, runs list, run details, 404/400, static CSS, HTMX fragments, pagination |
|
|
325
|
+
| `web/explorer.test.ts` | No-spec message, endpoint tree, tag grouping, parameters, request body, pre-filled server URL, HTMX fragment, bearer auth panel, API Key rendering, Basic auth rendering, multi-scheme, oauth2 unsupported, bearer without loginPath |
|
|
326
|
+
| `db/queries.test.ts` | getDashboardStats (zeros, aggregates), getPassRateTrend, getSlowestTests, getFlakyTests, countRuns |
|
|
327
|
+
|
|
328
|
+
Hono позволяет тестировать без поднятия HTTP-сервера: `app.request(path)` возвращает стандартный `Response`.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Зависимости
|
|
333
|
+
|
|
334
|
+
| Пакет | Версия | Назначение |
|
|
335
|
+
|-------|--------|------------|
|
|
336
|
+
| `hono` | 4.12.2 | HTTP-сервер, роутинг |
|
|
337
|
+
| HTMX | 2.0.4 (CDN) | Интерактивность без JS-фреймворка |
|
|
338
|
+
|
|
339
|
+
Нет сборщиков, бандлеров, node_modules для фронта. HTMX подключается через CDN `<script>`.
|