@kirrosh/zond 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +648 -0
- package/README.md +58 -6
- package/package.json +9 -6
- package/src/cli/argv.ts +122 -0
- package/src/cli/commands/add-api.ts +134 -0
- package/src/cli/commands/api/annotate/idempotency.ts +59 -0
- package/src/cli/commands/api/annotate/index.ts +525 -0
- package/src/cli/commands/api/annotate/lifecycle.ts +74 -0
- package/src/cli/commands/api/annotate/overlay.ts +206 -0
- package/src/cli/commands/api/annotate/pagination.ts +60 -0
- package/src/cli/commands/api/annotate/prompts.ts +183 -0
- package/src/cli/commands/api/annotate/readback.ts +58 -0
- package/src/cli/commands/api/annotate/resources.ts +91 -0
- package/src/cli/commands/api/annotate/seed-bodies.ts +61 -0
- package/src/cli/commands/audit.ts +480 -0
- package/src/cli/commands/bootstrap.ts +710 -0
- package/src/cli/commands/catalog.ts +35 -0
- package/src/cli/commands/check.ts +348 -0
- package/src/cli/commands/checks.ts +756 -0
- package/src/cli/commands/ci-init.ts +43 -0
- package/src/cli/commands/clean.ts +212 -0
- package/src/cli/commands/cleanup.ts +262 -0
- package/src/cli/commands/completions.ts +16 -0
- package/src/cli/commands/coverage.ts +605 -132
- package/src/cli/commands/db.ts +178 -7
- package/src/cli/commands/describe.ts +37 -2
- package/src/cli/commands/discover.ts +1236 -0
- package/src/cli/commands/doctor.ts +607 -0
- package/src/cli/commands/fixtures.ts +402 -0
- package/src/cli/commands/generate.ts +420 -46
- package/src/cli/commands/init/bootstrap.ts +30 -1
- package/src/cli/commands/{init.ts → init/index.ts} +99 -5
- package/src/cli/commands/init/skills.ts +56 -3
- package/src/cli/commands/init/templates/agents.md +65 -61
- package/src/cli/commands/init/templates/skills/zond-checks.md +397 -0
- package/src/cli/commands/init/templates/skills/zond-triage.md +210 -0
- package/src/cli/commands/init/templates/skills/zond.md +592 -125
- package/src/cli/commands/init/templates/zond-config.yml +8 -9
- package/src/cli/commands/prepare-fixtures.ts +135 -0
- package/src/cli/commands/probe/mass-assignment.ts +503 -0
- package/src/cli/commands/probe/security.ts +454 -0
- package/src/cli/commands/probe/static.ts +255 -0
- package/src/cli/commands/probe/webhooks.ts +161 -0
- package/src/cli/commands/probe.ts +459 -0
- package/src/cli/commands/reference.ts +87 -0
- package/src/cli/commands/refresh-api.ts +169 -0
- package/src/cli/commands/remove-api.ts +150 -0
- package/src/cli/commands/report-bundle.ts +318 -0
- package/src/cli/commands/report.ts +241 -0
- package/src/cli/commands/request.ts +379 -4
- package/src/cli/commands/run.ts +842 -53
- package/src/cli/commands/session.ts +244 -0
- package/src/cli/commands/use.ts +18 -1
- package/src/cli/index.ts +20 -3
- package/src/cli/json-envelope.ts +112 -3
- package/src/cli/json-schemas.ts +263 -0
- package/src/cli/program.ts +198 -635
- package/src/cli/resolve.ts +105 -0
- package/src/cli/status-filter.ts +124 -0
- package/src/cli/util/api-context.ts +85 -0
- package/src/cli/version.ts +5 -0
- package/src/core/anti-fp/bootstrap.ts +34 -0
- package/src/core/anti-fp/index.ts +33 -0
- package/src/core/anti-fp/registry.ts +44 -0
- package/src/core/anti-fp/rules/baseline-echo.ts +74 -0
- package/src/core/anti-fp/rules/schemathesis/body_negation_becomes_valid.ts +52 -0
- package/src/core/anti-fp/rules/schemathesis/coverage_phase_boundary_positive.ts +38 -0
- package/src/core/anti-fp/rules/schemathesis/has_unverifiable_mutations.ts +35 -0
- package/src/core/anti-fp/rules/schemathesis/index.ts +24 -0
- package/src/core/anti-fp/rules/schemathesis/string_type_mutation_becomes_valid.ts +53 -0
- package/src/core/anti-fp/rules/subscription-gated/index.ts +11 -0
- package/src/core/anti-fp/rules/subscription-gated/paid-plan-403.ts +75 -0
- package/src/core/anti-fp/types.ts +68 -0
- package/src/core/checks/checks/_crud-helpers.ts +133 -0
- package/src/core/checks/checks/_negative_mutator.ts +133 -0
- package/src/core/checks/checks/_readback-helpers.ts +133 -0
- package/src/core/checks/checks/content_type_conformance.ts +39 -0
- package/src/core/checks/checks/cross_call_references.ts +134 -0
- package/src/core/checks/checks/ensure_resource_availability.ts +62 -0
- package/src/core/checks/checks/idempotency_replay.ts +246 -0
- package/src/core/checks/checks/ignored_auth.ts +211 -0
- package/src/core/checks/checks/index.ts +65 -0
- package/src/core/checks/checks/lifecycle_transitions.ts +273 -0
- package/src/core/checks/checks/missing_required_header.ts +40 -0
- package/src/core/checks/checks/negative_data_rejection.ts +45 -0
- package/src/core/checks/checks/not_a_server_error.ts +27 -0
- package/src/core/checks/checks/open_cors_on_sensitive.ts +131 -0
- package/src/core/checks/checks/pagination_invariants.ts +238 -0
- package/src/core/checks/checks/positive_data_acceptance.ts +36 -0
- package/src/core/checks/checks/rate_limit_headers_absent.ts +77 -0
- package/src/core/checks/checks/response_headers_conformance.ts +74 -0
- package/src/core/checks/checks/response_schema_conformance.ts +30 -0
- package/src/core/checks/checks/status_code_conformance.ts +61 -0
- package/src/core/checks/checks/unsupported_method.ts +63 -0
- package/src/core/checks/checks/use_after_free.ts +78 -0
- package/src/core/checks/index.ts +30 -0
- package/src/core/checks/mode.ts +79 -0
- package/src/core/checks/recommended-action.ts +64 -0
- package/src/core/checks/registry.ts +78 -0
- package/src/core/checks/runner.ts +874 -0
- package/src/core/checks/sarif.ts +230 -0
- package/src/core/checks/stateful.ts +121 -0
- package/src/core/checks/types.ts +189 -0
- package/src/core/classifier/recommended-action.ts +222 -0
- package/src/core/context/current.ts +22 -6
- package/src/core/context/session.ts +78 -0
- package/src/core/coverage/loader.ts +185 -0
- package/src/core/coverage/reasons.ts +300 -0
- package/src/core/diagnostics/db-analysis.ts +151 -11
- package/src/core/diagnostics/failure-class.ts +120 -0
- package/src/core/diagnostics/failure-hints.ts +212 -9
- package/src/core/diagnostics/spec-pointer.ts +99 -0
- package/src/core/diagnostics/suggested-fixes.ts +156 -0
- package/src/core/exporter/case-study/index.ts +270 -0
- package/src/core/exporter/curl.ts +40 -0
- package/src/core/exporter/exporter.ts +48 -0
- package/src/core/exporter/html-report/escape.ts +24 -0
- package/src/core/exporter/html-report/index.ts +479 -0
- package/src/core/exporter/html-report/script.ts +100 -0
- package/src/core/exporter/html-report/styles.ts +408 -0
- package/src/core/generator/chunker.ts +42 -16
- package/src/core/generator/coverage-phase.ts +0 -0
- package/src/core/generator/create-body.ts +89 -0
- package/src/core/generator/data-factory.ts +445 -19
- package/src/core/generator/describe.ts +1 -1
- package/src/core/generator/fixtures-builder.ts +325 -0
- package/src/core/generator/index.ts +7 -5
- package/src/core/generator/openapi-reader.ts +37 -3
- package/src/core/generator/path-param-disambig.ts +114 -0
- package/src/core/generator/resources-builder.ts +648 -0
- package/src/core/generator/schema-utils.ts +11 -3
- package/src/core/generator/serializer.ts +103 -13
- package/src/core/generator/suite-generator.ts +419 -111
- package/src/core/generator/types.ts +8 -0
- package/src/core/identity/identity-file.ts +129 -0
- package/src/core/lint/affects.ts +28 -0
- package/src/core/lint/config.ts +96 -0
- package/src/core/lint/format.ts +42 -0
- package/src/core/lint/index.ts +94 -0
- package/src/core/lint/reporter.ts +128 -0
- package/src/core/lint/rules/consistency.ts +158 -0
- package/src/core/lint/rules/heuristics.ts +97 -0
- package/src/core/lint/rules/strictness.ts +109 -0
- package/src/core/lint/types.ts +96 -0
- package/src/core/lint/walker.ts +248 -0
- package/src/core/meta/meta-store.ts +6 -73
- package/src/core/output/README.md +91 -0
- package/src/core/output/index.ts +13 -0
- package/src/core/output/run.ts +126 -0
- package/src/core/output/types.ts +129 -0
- package/src/core/parser/env-interpolation.ts +104 -0
- package/src/core/parser/filter.ts +57 -0
- package/src/core/parser/schema.ts +129 -4
- package/src/core/parser/types.ts +19 -1
- package/src/core/parser/variables.ts +0 -0
- package/src/core/parser/yaml-parser.ts +58 -12
- package/src/core/probe/bootstrap.ts +34 -0
- package/src/core/probe/dry-run-envelope.ts +57 -0
- package/src/core/probe/mass-assignment-probe-class.ts +198 -0
- package/src/core/probe/mass-assignment-probe.ts +1122 -0
- package/src/core/probe/mass-assignment-template.ts +212 -0
- package/src/core/probe/method-probe.ts +43 -76
- package/src/core/probe/method-shared.ts +69 -0
- package/src/core/probe/negative-probe.ts +183 -149
- package/src/core/probe/orphan-tracker.ts +188 -0
- package/src/core/probe/path-discovery.ts +440 -0
- package/src/core/probe/probe-harness.ts +120 -0
- package/src/core/probe/registry.ts +89 -0
- package/src/core/probe/runner.ts +136 -0
- package/src/core/probe/security-probe-class.ts +201 -0
- package/src/core/probe/security-probe.ts +1453 -0
- package/src/core/probe/shared.ts +505 -0
- package/src/core/probe/static-probe-class.ts +125 -0
- package/src/core/probe/types.ts +165 -0
- package/src/core/probe/verdict-aggregator.ts +33 -0
- package/src/core/probe/webhooks-probe.ts +284 -0
- package/src/core/reporter/console.ts +41 -2
- package/src/core/reporter/index.ts +2 -3
- package/src/core/reporter/json.ts +11 -1
- package/src/core/reporter/junit.ts +27 -12
- package/src/core/reporter/ndjson.ts +37 -0
- package/src/core/reporter/types.ts +3 -0
- package/src/core/runner/assertions.ts +58 -1
- package/src/core/runner/async-pool.ts +108 -0
- package/src/core/runner/auth-path.ts +8 -0
- package/src/core/runner/ci-context.ts +72 -0
- package/src/core/runner/executor.ts +264 -20
- package/src/core/runner/form-encode.ts +51 -0
- package/src/core/runner/http-client.ts +75 -2
- package/src/core/runner/learn-drift.ts +293 -0
- package/src/core/runner/preflight-vars.ts +149 -0
- package/src/core/runner/progress-tracker.ts +73 -0
- package/src/core/runner/rate-limiter.ts +89 -17
- package/src/core/runner/run-kind.ts +39 -0
- package/src/core/runner/schema-validator.ts +312 -0
- package/src/core/runner/send-request.ts +153 -20
- package/src/core/runner/types.ts +38 -0
- package/src/core/secrets/registry.ts +164 -0
- package/src/core/secrets/secrets-file.ts +115 -0
- package/src/core/selectors/operation-filter.ts +144 -0
- package/src/core/setup-api.ts +415 -16
- package/src/core/severity/category.ts +94 -0
- package/src/core/severity/index.ts +121 -0
- package/src/core/spec/layers.ts +154 -0
- package/src/core/util/format-eta.ts +21 -0
- package/src/core/utils.ts +5 -1
- package/src/core/workspace/config.ts +129 -0
- package/src/core/workspace/manifest.ts +283 -0
- package/src/core/workspace/output-rotation.ts +62 -0
- package/src/core/workspace/triage-path.ts +87 -0
- package/src/db/lint-runs.ts +47 -0
- package/src/db/migrate.ts +126 -0
- package/src/db/migrations/0001_run_kind.sql +25 -0
- package/src/db/migrations/sql.d.ts +4 -0
- package/src/db/queries/collections.ts +133 -0
- package/src/db/queries/coverage.ts +9 -0
- package/src/db/queries/dashboard.ts +59 -0
- package/src/db/queries/results.ts +128 -0
- package/src/db/queries/runs.ts +235 -0
- package/src/db/queries/sessions.ts +42 -0
- package/src/db/queries/settings.ts +28 -0
- package/src/db/queries/types.ts +172 -0
- package/src/db/queries.ts +72 -802
- package/src/db/schema.ts +178 -50
- package/src/cli/commands/export.ts +0 -144
- package/src/cli/commands/guide.ts +0 -127
- package/src/cli/commands/init/templates/skills/scenarios.md +0 -97
- package/src/cli/commands/probe-methods.ts +0 -108
- package/src/cli/commands/probe-validation.ts +0 -124
- package/src/cli/commands/serve.ts +0 -114
- package/src/cli/commands/sync.ts +0 -268
- package/src/cli/commands/update.ts +0 -189
- package/src/cli/commands/validate.ts +0 -34
- package/src/core/diagnostics/render-md.ts +0 -112
- package/src/core/exporter/postman.ts +0 -963
- package/src/core/generator/guide-builder.ts +0 -253
- package/src/core/meta/types.ts +0 -19
- package/src/core/parser/index.ts +0 -21
- package/src/core/runner/execute-run.ts +0 -132
- package/src/core/runner/index.ts +0 -12
- package/src/core/sync/spec-differ.ts +0 -38
- package/src/web/data/collection-state.ts +0 -362
- package/src/web/routes/api.ts +0 -314
- package/src/web/routes/dashboard.ts +0 -350
- package/src/web/routes/runs.ts +0 -64
- package/src/web/schemas.ts +0 -121
- package/src/web/server.ts +0 -134
- package/src/web/static/htmx.min.cjs +0 -1
- package/src/web/static/style.css +0 -1148
- package/src/web/views/endpoints-tab.ts +0 -174
- package/src/web/views/explorer-tab.ts +0 -402
- package/src/web/views/health-strip.ts +0 -92
- package/src/web/views/layout.ts +0 -48
- package/src/web/views/results.ts +0 -210
- package/src/web/views/runs-tab.ts +0 -126
- package/src/web/views/suites-tab.ts +0 -181
package/src/db/schema.ts
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
|
-
import { resolve } from "path";
|
|
3
|
-
import { existsSync } from "fs";
|
|
2
|
+
import { dirname, resolve } from "path";
|
|
3
|
+
import { existsSync, mkdirSync } from "fs";
|
|
4
4
|
import { findWorkspaceRoot } from "../core/workspace/root.ts";
|
|
5
|
+
import { applyMigrations } from "./migrate.ts";
|
|
5
6
|
|
|
6
7
|
let _db: Database | null = null;
|
|
7
8
|
let _dbPath: string | null = null;
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Default DB path lives under `<workspace>/.zond/zond.db` to keep runtime
|
|
12
|
+
* artifacts out of the project root. For back-compat we still recognise a
|
|
13
|
+
* legacy `<workspace>/zond.db` if it exists — old workspaces keep working
|
|
14
|
+
* without migration.
|
|
15
|
+
*/
|
|
16
|
+
function defaultDbPath(): string {
|
|
17
|
+
const root = findWorkspaceRoot().root;
|
|
18
|
+
const legacy = resolve(root, "zond.db");
|
|
19
|
+
if (existsSync(legacy)) return legacy;
|
|
20
|
+
return resolve(root, ".zond", "zond.db");
|
|
21
|
+
}
|
|
22
|
+
|
|
9
23
|
export function getDb(dbPath?: string): Database {
|
|
10
|
-
const path = dbPath
|
|
11
|
-
? resolve(dbPath)
|
|
12
|
-
: (_dbPath ?? resolve(findWorkspaceRoot().root, "zond.db"));
|
|
24
|
+
const path = dbPath ? resolve(dbPath) : (_dbPath ?? defaultDbPath());
|
|
13
25
|
|
|
14
26
|
// If cached connection exists, verify the file still exists
|
|
15
27
|
if (_db && _dbPath === path && existsSync(path)) return _db;
|
|
@@ -20,19 +32,68 @@ export function getDb(dbPath?: string): Database {
|
|
|
20
32
|
_db = null;
|
|
21
33
|
_dbPath = null;
|
|
22
34
|
}
|
|
35
|
+
// SQLite won't auto-create parent dirs; ensure `.zond/` (or any custom
|
|
36
|
+
// path's parent) exists before opening the file.
|
|
37
|
+
const parent = dirname(path);
|
|
38
|
+
if (!existsSync(parent)) mkdirSync(parent, { recursive: true });
|
|
39
|
+
|
|
23
40
|
const db = new Database(path, { create: true });
|
|
24
41
|
|
|
25
42
|
// Performance and integrity settings
|
|
26
43
|
db.exec("PRAGMA journal_mode = WAL");
|
|
27
44
|
db.exec("PRAGMA foreign_keys = ON");
|
|
45
|
+
// ARV-163: concurrent zond processes (e.g. `probe security` in one terminal
|
|
46
|
+
// + `checks run` in another) collide on the write-lock and surface as
|
|
47
|
+
// "database is locked". Letting SQLite spin at the C-level for up to 5s
|
|
48
|
+
// resolves the overwhelming majority without any per-call retry logic.
|
|
49
|
+
// `synchronous=NORMAL` is safe under WAL and cuts fsync overhead in the
|
|
50
|
+
// bulk-insert path (saveResults). withDbRetry() in this file remains the
|
|
51
|
+
// belt-and-suspenders for the rare long-running transactions.
|
|
52
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
53
|
+
db.exec("PRAGMA synchronous = NORMAL");
|
|
28
54
|
|
|
29
55
|
runMigrations(db);
|
|
30
56
|
|
|
57
|
+
// ARV-127: file-based migrations sit on top of the legacy PRAGMA
|
|
58
|
+
// path. On an existing DB the runner pre-seeds `schema_migrations`
|
|
59
|
+
// so the v9→v10 inline migration (now mirrored in
|
|
60
|
+
// src/db/migrations/0001_run_kind.sql) is treated as applied.
|
|
61
|
+
applyMigrations(db);
|
|
62
|
+
|
|
31
63
|
_db = db;
|
|
32
64
|
_dbPath = path;
|
|
33
65
|
return db;
|
|
34
66
|
}
|
|
35
67
|
|
|
68
|
+
/**
|
|
69
|
+
* ARV-163: retry wrapper for SQLite write paths that may collide with
|
|
70
|
+
* concurrent zond processes. `PRAGMA busy_timeout` already absorbs short
|
|
71
|
+
* contention at the C level — this wrapper only catches the residual cases
|
|
72
|
+
* where the lock holder runs longer than the timeout (e.g. a big
|
|
73
|
+
* `saveResults` bulk insert during a parallel `probe security` cleanup).
|
|
74
|
+
*
|
|
75
|
+
* Detects bun:sqlite's "database is locked" / "SQLITE_BUSY" message shapes;
|
|
76
|
+
* other errors propagate immediately. Backoff: 100, 200, 400, 800ms capped at
|
|
77
|
+
* 4 attempts (≈1.5s total) so we never silently stall a CLI command.
|
|
78
|
+
*/
|
|
79
|
+
export function withDbRetry<T>(label: string, fn: () => T): T {
|
|
80
|
+
const delaysMs = [100, 200, 400, 800];
|
|
81
|
+
let lastError: unknown;
|
|
82
|
+
for (let attempt = 0; attempt <= delaysMs.length; attempt++) {
|
|
83
|
+
try {
|
|
84
|
+
return fn();
|
|
85
|
+
} catch (err) {
|
|
86
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
87
|
+
if (!/database is locked|SQLITE_BUSY/i.test(msg)) throw err;
|
|
88
|
+
lastError = err;
|
|
89
|
+
if (attempt === delaysMs.length) break;
|
|
90
|
+
Bun.sleepSync(delaysMs[attempt]!);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
94
|
+
throw new Error(`SQLite still locked after ${delaysMs.length + 1} attempts (${label}): ${msg}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
36
97
|
export function closeDb(): void {
|
|
37
98
|
if (_db) {
|
|
38
99
|
try { _db.close(); } catch {}
|
|
@@ -41,7 +102,7 @@ export function closeDb(): void {
|
|
|
41
102
|
}
|
|
42
103
|
}
|
|
43
104
|
|
|
44
|
-
|
|
105
|
+
function resetDb(): void {
|
|
45
106
|
if (_db) { try { _db.close(); } catch {} }
|
|
46
107
|
_db = null;
|
|
47
108
|
_dbPath = null;
|
|
@@ -51,7 +112,7 @@ export function resetDb(): void {
|
|
|
51
112
|
// Schema
|
|
52
113
|
// ──────────────────────────────────────────────
|
|
53
114
|
|
|
54
|
-
const SCHEMA_VERSION =
|
|
115
|
+
const SCHEMA_VERSION = 10;
|
|
55
116
|
|
|
56
117
|
const SCHEMA = `
|
|
57
118
|
CREATE TABLE IF NOT EXISTS runs (
|
|
@@ -67,7 +128,13 @@ const SCHEMA = `
|
|
|
67
128
|
branch TEXT,
|
|
68
129
|
environment TEXT,
|
|
69
130
|
duration_ms INTEGER,
|
|
70
|
-
collection_id INTEGER REFERENCES collections(id)
|
|
131
|
+
collection_id INTEGER REFERENCES collections(id),
|
|
132
|
+
session_id TEXT,
|
|
133
|
+
tags TEXT,
|
|
134
|
+
-- ARV-55: classify a run once at INSERT time so coverage / diagnose
|
|
135
|
+
-- queries don't have to re-derive "is this a probe-only run?" from
|
|
136
|
+
-- the results' suite_file paths.
|
|
137
|
+
run_kind TEXT NOT NULL DEFAULT 'regular' CHECK (run_kind IN ('regular','probe','check'))
|
|
71
138
|
);
|
|
72
139
|
|
|
73
140
|
CREATE TABLE IF NOT EXISTS results (
|
|
@@ -86,7 +153,12 @@ const SCHEMA = `
|
|
|
86
153
|
assertions TEXT,
|
|
87
154
|
captures TEXT,
|
|
88
155
|
response_headers TEXT,
|
|
89
|
-
suite_file TEXT
|
|
156
|
+
suite_file TEXT,
|
|
157
|
+
provenance TEXT,
|
|
158
|
+
failure_class TEXT,
|
|
159
|
+
failure_class_reason TEXT,
|
|
160
|
+
spec_pointer TEXT,
|
|
161
|
+
spec_excerpt TEXT
|
|
90
162
|
);
|
|
91
163
|
|
|
92
164
|
CREATE TABLE IF NOT EXISTS collections (
|
|
@@ -98,44 +170,6 @@ const SCHEMA = `
|
|
|
98
170
|
base_dir TEXT
|
|
99
171
|
);
|
|
100
172
|
|
|
101
|
-
CREATE TABLE IF NOT EXISTS ai_generations (
|
|
102
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
103
|
-
collection_id INTEGER REFERENCES collections(id),
|
|
104
|
-
prompt TEXT NOT NULL,
|
|
105
|
-
model TEXT NOT NULL,
|
|
106
|
-
provider TEXT NOT NULL,
|
|
107
|
-
generated_yaml TEXT,
|
|
108
|
-
output_path TEXT,
|
|
109
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
110
|
-
error_message TEXT,
|
|
111
|
-
prompt_tokens INTEGER,
|
|
112
|
-
completion_tokens INTEGER,
|
|
113
|
-
duration_ms INTEGER,
|
|
114
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
CREATE TABLE IF NOT EXISTS chat_sessions (
|
|
118
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
119
|
-
title TEXT,
|
|
120
|
-
provider TEXT NOT NULL,
|
|
121
|
-
model TEXT NOT NULL,
|
|
122
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
123
|
-
last_active TEXT NOT NULL DEFAULT (datetime('now'))
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
127
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
128
|
-
session_id INTEGER NOT NULL REFERENCES chat_sessions(id),
|
|
129
|
-
role TEXT NOT NULL,
|
|
130
|
-
content TEXT NOT NULL,
|
|
131
|
-
tool_name TEXT,
|
|
132
|
-
tool_args TEXT,
|
|
133
|
-
tool_result TEXT,
|
|
134
|
-
input_tokens INTEGER,
|
|
135
|
-
output_tokens INTEGER,
|
|
136
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
137
|
-
);
|
|
138
|
-
|
|
139
173
|
CREATE TABLE IF NOT EXISTS settings (
|
|
140
174
|
key TEXT PRIMARY KEY,
|
|
141
175
|
value TEXT NOT NULL
|
|
@@ -143,13 +177,27 @@ const SCHEMA = `
|
|
|
143
177
|
|
|
144
178
|
CREATE INDEX IF NOT EXISTS idx_runs_started ON runs(started_at DESC);
|
|
145
179
|
CREATE INDEX IF NOT EXISTS idx_runs_collection ON runs(collection_id);
|
|
180
|
+
CREATE INDEX IF NOT EXISTS idx_runs_session ON runs(session_id, started_at DESC);
|
|
146
181
|
CREATE INDEX IF NOT EXISTS idx_results_run ON results(run_id);
|
|
147
182
|
CREATE INDEX IF NOT EXISTS idx_results_status ON results(status);
|
|
148
183
|
CREATE INDEX IF NOT EXISTS idx_results_name ON results(suite_name, test_name);
|
|
149
184
|
CREATE INDEX IF NOT EXISTS idx_collections_name ON collections(name);
|
|
150
|
-
|
|
151
|
-
CREATE
|
|
152
|
-
|
|
185
|
+
|
|
186
|
+
CREATE TABLE IF NOT EXISTS lint_runs (
|
|
187
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
188
|
+
spec_path TEXT NOT NULL,
|
|
189
|
+
started_at TEXT NOT NULL,
|
|
190
|
+
finished_at TEXT,
|
|
191
|
+
total INTEGER NOT NULL DEFAULT 0,
|
|
192
|
+
high_count INTEGER NOT NULL DEFAULT 0,
|
|
193
|
+
medium_count INTEGER NOT NULL DEFAULT 0,
|
|
194
|
+
low_count INTEGER NOT NULL DEFAULT 0,
|
|
195
|
+
endpoint_count INTEGER NOT NULL DEFAULT 0,
|
|
196
|
+
config_json TEXT,
|
|
197
|
+
issues_json TEXT
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
CREATE INDEX IF NOT EXISTS idx_lint_runs_spec ON lint_runs(spec_path, started_at DESC);
|
|
153
201
|
`;
|
|
154
202
|
|
|
155
203
|
function runMigrations(db: Database): void {
|
|
@@ -165,6 +213,86 @@ function runMigrations(db: Database): void {
|
|
|
165
213
|
// Migration v1→v2: add suite_file column to results
|
|
166
214
|
db.exec("ALTER TABLE results ADD COLUMN suite_file TEXT");
|
|
167
215
|
}
|
|
216
|
+
if (ver >= 2 && ver < 3) {
|
|
217
|
+
// Migration v2→v3: add provenance column (test source metadata)
|
|
218
|
+
db.exec("ALTER TABLE results ADD COLUMN provenance TEXT");
|
|
219
|
+
}
|
|
220
|
+
if (ver >= 3 && ver < 4) {
|
|
221
|
+
// Migration v3→v4: add failure classification columns
|
|
222
|
+
db.exec("ALTER TABLE results ADD COLUMN failure_class TEXT");
|
|
223
|
+
db.exec("ALTER TABLE results ADD COLUMN failure_class_reason TEXT");
|
|
224
|
+
}
|
|
225
|
+
if (ver >= 4 && ver < 5) {
|
|
226
|
+
// Migration v4→v5: add spec_pointer + spec_excerpt (frozen OpenAPI evidence)
|
|
227
|
+
db.exec("ALTER TABLE results ADD COLUMN spec_pointer TEXT");
|
|
228
|
+
db.exec("ALTER TABLE results ADD COLUMN spec_excerpt TEXT");
|
|
229
|
+
}
|
|
230
|
+
if (ver >= 5 && ver < 6) {
|
|
231
|
+
// Migration v5→v6: add lint_runs table for `zond lint-spec` history.
|
|
232
|
+
db.exec(`
|
|
233
|
+
CREATE TABLE IF NOT EXISTS lint_runs (
|
|
234
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
235
|
+
spec_path TEXT NOT NULL,
|
|
236
|
+
started_at TEXT NOT NULL,
|
|
237
|
+
finished_at TEXT,
|
|
238
|
+
total INTEGER NOT NULL DEFAULT 0,
|
|
239
|
+
high_count INTEGER NOT NULL DEFAULT 0,
|
|
240
|
+
medium_count INTEGER NOT NULL DEFAULT 0,
|
|
241
|
+
low_count INTEGER NOT NULL DEFAULT 0,
|
|
242
|
+
endpoint_count INTEGER NOT NULL DEFAULT 0,
|
|
243
|
+
config_json TEXT,
|
|
244
|
+
issues_json TEXT
|
|
245
|
+
);
|
|
246
|
+
CREATE INDEX IF NOT EXISTS idx_lint_runs_spec ON lint_runs(spec_path, started_at DESC);
|
|
247
|
+
`);
|
|
248
|
+
}
|
|
249
|
+
if (ver >= 6 && ver < 7) {
|
|
250
|
+
// Migration v6→v7: add session_id column to runs for grouping CLI invocations
|
|
251
|
+
// (e.g. `zond hunt`, scripted post-init runs) into one campaign.
|
|
252
|
+
db.exec("ALTER TABLE runs ADD COLUMN session_id TEXT");
|
|
253
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_runs_session ON runs(session_id, started_at DESC)");
|
|
254
|
+
}
|
|
255
|
+
if (ver >= 7 && ver < 8) {
|
|
256
|
+
// Migration v7→v8: drop the unused AI/chat tables. They were a legacy
|
|
257
|
+
// experiment (in-app chat-driven YAML generation) that never shipped a
|
|
258
|
+
// user-facing surface and have no consumers in the codebase.
|
|
259
|
+
db.exec("DROP TABLE IF EXISTS chat_messages");
|
|
260
|
+
db.exec("DROP TABLE IF EXISTS chat_sessions");
|
|
261
|
+
db.exec("DROP TABLE IF EXISTS ai_generations");
|
|
262
|
+
}
|
|
263
|
+
if (ver >= 8 && ver < 9) {
|
|
264
|
+
// Migration v8→v9: tags column on runs (JSON array of strings — union
|
|
265
|
+
// of suite-level tags actually executed in the run, plus any explicit
|
|
266
|
+
// --tag filters). Powers `coverage --union tag:<name>` (TASK-274).
|
|
267
|
+
db.exec("ALTER TABLE runs ADD COLUMN tags TEXT");
|
|
268
|
+
}
|
|
269
|
+
if (ver >= 9 && ver < 10) {
|
|
270
|
+
// Migration v9→v10 (ARV-55): classify each historical run by suite
|
|
271
|
+
// kind so coverage's default query becomes a column compare. The
|
|
272
|
+
// CHECK constraint can't be added retroactively without a table
|
|
273
|
+
// rebuild — accept the looser column for legacy rows; new INSERTs
|
|
274
|
+
// go through `createRun()` which only emits known kinds.
|
|
275
|
+
db.exec("ALTER TABLE runs ADD COLUMN run_kind TEXT NOT NULL DEFAULT 'regular'");
|
|
276
|
+
// Backfill: derive kind per existing run from its stored results.
|
|
277
|
+
// `every` semantics mirror the runtime `detectRunKind` helper —
|
|
278
|
+
// pure-probe / pure-check vs anything else.
|
|
279
|
+
db.exec(`
|
|
280
|
+
UPDATE runs SET run_kind = 'probe'
|
|
281
|
+
WHERE id IN (
|
|
282
|
+
SELECT r.id FROM runs r
|
|
283
|
+
WHERE EXISTS (SELECT 1 FROM results WHERE run_id = r.id AND suite_file IS NOT NULL AND suite_file LIKE '%probes/%')
|
|
284
|
+
AND NOT EXISTS (SELECT 1 FROM results WHERE run_id = r.id AND suite_file IS NOT NULL AND suite_file NOT LIKE '%probes/%')
|
|
285
|
+
)
|
|
286
|
+
`);
|
|
287
|
+
db.exec(`
|
|
288
|
+
UPDATE runs SET run_kind = 'check'
|
|
289
|
+
WHERE id IN (
|
|
290
|
+
SELECT r.id FROM runs r
|
|
291
|
+
WHERE EXISTS (SELECT 1 FROM results WHERE run_id = r.id AND suite_file IS NOT NULL AND suite_file LIKE '%checks/%')
|
|
292
|
+
AND NOT EXISTS (SELECT 1 FROM results WHERE run_id = r.id AND suite_file IS NOT NULL AND suite_file NOT LIKE '%checks/%')
|
|
293
|
+
)
|
|
294
|
+
`);
|
|
295
|
+
}
|
|
168
296
|
db.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
|
|
169
297
|
})();
|
|
170
298
|
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { dirname, basename, join } from "path";
|
|
2
|
-
import { parse } from "../../core/parser/yaml-parser.ts";
|
|
3
|
-
import {
|
|
4
|
-
buildCollection,
|
|
5
|
-
buildEnvironment,
|
|
6
|
-
deriveCollectionName,
|
|
7
|
-
} from "../../core/exporter/postman.ts";
|
|
8
|
-
import { printError, printSuccess, printWarning } from "../output.ts";
|
|
9
|
-
import { jsonOk, jsonError, printJson } from "../json-envelope.ts";
|
|
10
|
-
|
|
11
|
-
export interface ExportOptions {
|
|
12
|
-
testsPath: string;
|
|
13
|
-
output: string;
|
|
14
|
-
env?: string;
|
|
15
|
-
collectionName?: string;
|
|
16
|
-
json?: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function exportCommand(options: ExportOptions): Promise<number> {
|
|
20
|
-
// 1. Parse test suites
|
|
21
|
-
let suites;
|
|
22
|
-
try {
|
|
23
|
-
suites = await parse(options.testsPath);
|
|
24
|
-
} catch (err) {
|
|
25
|
-
const msg = `Failed to parse tests: ${(err as Error).message}`;
|
|
26
|
-
if (options.json) {
|
|
27
|
-
printJson(jsonError("export postman", [msg]));
|
|
28
|
-
} else {
|
|
29
|
-
printError(msg);
|
|
30
|
-
}
|
|
31
|
-
return 2;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (suites.length === 0) {
|
|
35
|
-
const msg = "No test suites found";
|
|
36
|
-
if (options.json) {
|
|
37
|
-
printJson(jsonError("export postman", [msg]));
|
|
38
|
-
} else {
|
|
39
|
-
printError(msg);
|
|
40
|
-
}
|
|
41
|
-
return 1;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// 2. Derive collection name
|
|
45
|
-
const collectionName =
|
|
46
|
-
options.collectionName ?? deriveCollectionName(options.testsPath);
|
|
47
|
-
|
|
48
|
-
// 3. Build collection
|
|
49
|
-
const { collection, warnings } = buildCollection(suites, collectionName);
|
|
50
|
-
|
|
51
|
-
// Count total items across all folders
|
|
52
|
-
const totalItems = collection.item.reduce((sum, folder) => sum + folder.item.length, 0);
|
|
53
|
-
|
|
54
|
-
// 4. Write collection file
|
|
55
|
-
try {
|
|
56
|
-
await Bun.write(options.output, JSON.stringify(collection, null, 2));
|
|
57
|
-
} catch (err) {
|
|
58
|
-
const msg = `Failed to write collection file: ${(err as Error).message}`;
|
|
59
|
-
if (options.json) {
|
|
60
|
-
printJson(jsonError("export postman", [msg], warnings));
|
|
61
|
-
} else {
|
|
62
|
-
printError(msg);
|
|
63
|
-
}
|
|
64
|
-
return 2;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// 5. Optional env export
|
|
68
|
-
let envOutput: string | undefined;
|
|
69
|
-
if (options.env) {
|
|
70
|
-
let envVars: Record<string, string>;
|
|
71
|
-
try {
|
|
72
|
-
const text = await Bun.file(options.env).text();
|
|
73
|
-
const parsed = Bun.YAML.parse(text);
|
|
74
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
75
|
-
throw new Error("Environment file must be a YAML object");
|
|
76
|
-
}
|
|
77
|
-
// Convert all values to strings
|
|
78
|
-
envVars = {};
|
|
79
|
-
for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
|
|
80
|
-
envVars[k] = String(v);
|
|
81
|
-
}
|
|
82
|
-
} catch (err) {
|
|
83
|
-
const msg = `Failed to read env file: ${(err as Error).message}`;
|
|
84
|
-
if (options.json) {
|
|
85
|
-
printJson(jsonError("export postman", [msg], warnings));
|
|
86
|
-
} else {
|
|
87
|
-
printError(msg);
|
|
88
|
-
}
|
|
89
|
-
return 2;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Derive env name: e.g. ".env.staging.yaml" → "staging", ".env.yaml" → collectionName
|
|
93
|
-
const envBasename = basename(options.env);
|
|
94
|
-
const envNameMatch = envBasename.match(/^\.?env\.(.+?)\.ya?ml$/);
|
|
95
|
-
const envName = envNameMatch ? envNameMatch[1]! : collectionName;
|
|
96
|
-
|
|
97
|
-
const environment = buildEnvironment(envVars, envName);
|
|
98
|
-
|
|
99
|
-
// Output path: same directory as output, same base name with .postman_environment.json
|
|
100
|
-
const outBase = basename(options.output).replace(/\.postman\.json$/, "").replace(/\.json$/, "");
|
|
101
|
-
const outDir = dirname(options.output);
|
|
102
|
-
envOutput = join(outDir, `${outBase}.postman_environment.json`);
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
await Bun.write(envOutput, JSON.stringify(environment, null, 2));
|
|
106
|
-
} catch (err) {
|
|
107
|
-
const msg = `Failed to write environment file: ${(err as Error).message}`;
|
|
108
|
-
if (options.json) {
|
|
109
|
-
printJson(jsonError("export postman", [msg], warnings));
|
|
110
|
-
} else {
|
|
111
|
-
printError(msg);
|
|
112
|
-
}
|
|
113
|
-
return 2;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// 6. Output result
|
|
118
|
-
if (options.json) {
|
|
119
|
-
printJson(
|
|
120
|
-
jsonOk(
|
|
121
|
-
"export postman",
|
|
122
|
-
{
|
|
123
|
-
output: options.output,
|
|
124
|
-
suites: suites.length,
|
|
125
|
-
items: totalItems,
|
|
126
|
-
...(envOutput !== undefined ? { envOutput } : {}),
|
|
127
|
-
},
|
|
128
|
-
warnings
|
|
129
|
-
)
|
|
130
|
-
);
|
|
131
|
-
} else {
|
|
132
|
-
for (const w of warnings) {
|
|
133
|
-
printWarning(w);
|
|
134
|
-
}
|
|
135
|
-
printSuccess(
|
|
136
|
-
`Exported ${suites.length} suite(s) / ${totalItems} request(s) → ${options.output}`
|
|
137
|
-
);
|
|
138
|
-
if (envOutput) {
|
|
139
|
-
printSuccess(`Environment exported → ${envOutput}`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return 0;
|
|
144
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
readOpenApiSpec,
|
|
3
|
-
extractEndpoints,
|
|
4
|
-
extractSecuritySchemes,
|
|
5
|
-
scanCoveredEndpoints,
|
|
6
|
-
filterUncoveredEndpoints,
|
|
7
|
-
} from "../../core/generator/index.ts";
|
|
8
|
-
import { compressEndpointsWithSchemas, buildGenerationGuide } from "../../core/generator/guide-builder.ts";
|
|
9
|
-
import { planChunks, filterByTag } from "../../core/generator/chunker.ts";
|
|
10
|
-
import { findCollectionBySpec } from "../../db/queries.ts";
|
|
11
|
-
import { printError } from "../output.ts";
|
|
12
|
-
import { jsonOk, jsonError, printJson } from "../json-envelope.ts";
|
|
13
|
-
|
|
14
|
-
export interface GuideOptions {
|
|
15
|
-
specPath: string;
|
|
16
|
-
testsDir?: string;
|
|
17
|
-
tag?: string;
|
|
18
|
-
json?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export async function guideCommand(options: GuideOptions): Promise<number> {
|
|
22
|
-
try {
|
|
23
|
-
const doc = await readOpenApiSpec(options.specPath);
|
|
24
|
-
let endpoints = extractEndpoints(doc);
|
|
25
|
-
const securitySchemes = extractSecuritySchemes(doc);
|
|
26
|
-
const baseUrl = ((doc as any).servers?.[0]?.url) as string | undefined;
|
|
27
|
-
const title = (doc as any).info?.title as string | undefined;
|
|
28
|
-
|
|
29
|
-
let outputDir = options.testsDir;
|
|
30
|
-
if (!outputDir) {
|
|
31
|
-
try {
|
|
32
|
-
const collection = findCollectionBySpec(options.specPath);
|
|
33
|
-
outputDir = collection?.test_path ?? "./tests/";
|
|
34
|
-
} catch {
|
|
35
|
-
outputDir = "./tests/";
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
let coverageInfo: { covered: number; total: number; percentage: number } | undefined;
|
|
40
|
-
if (options.testsDir) {
|
|
41
|
-
const totalBefore = endpoints.length;
|
|
42
|
-
const covered = await scanCoveredEndpoints(options.testsDir);
|
|
43
|
-
const uncovered = filterUncoveredEndpoints(endpoints, covered);
|
|
44
|
-
const coveredCount = totalBefore - uncovered.length;
|
|
45
|
-
const percentage = totalBefore > 0 ? Math.round((coveredCount / totalBefore) * 100) : 100;
|
|
46
|
-
coverageInfo = { covered: coveredCount, total: totalBefore, percentage };
|
|
47
|
-
endpoints = uncovered;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (endpoints.length === 0) {
|
|
51
|
-
if (options.json) {
|
|
52
|
-
printJson(jsonOk("guide", { fullyCovered: true, ...coverageInfo }));
|
|
53
|
-
} else {
|
|
54
|
-
console.log("All endpoints are covered.");
|
|
55
|
-
}
|
|
56
|
-
return 0;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (options.tag) {
|
|
60
|
-
endpoints = filterByTag(endpoints, options.tag);
|
|
61
|
-
if (endpoints.length === 0) {
|
|
62
|
-
const msg = `No endpoints found for tag "${options.tag}"`;
|
|
63
|
-
if (options.json) printJson(jsonError("guide", [msg]));
|
|
64
|
-
else printError(msg);
|
|
65
|
-
return 1;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const plan = planChunks(endpoints);
|
|
70
|
-
|
|
71
|
-
if (plan.needsChunking && !options.tag) {
|
|
72
|
-
if (options.json) {
|
|
73
|
-
printJson(jsonOk("guide", {
|
|
74
|
-
mode: "plan",
|
|
75
|
-
title: title ?? "API",
|
|
76
|
-
totalEndpoints: plan.totalEndpoints,
|
|
77
|
-
chunks: plan.chunks,
|
|
78
|
-
...(coverageInfo ? { coverage: coverageInfo } : {}),
|
|
79
|
-
}));
|
|
80
|
-
} else {
|
|
81
|
-
console.log(`API has ${plan.totalEndpoints} endpoints across ${plan.chunks.length} tags.`);
|
|
82
|
-
console.log("Generate per-tag with --tag <name>:\n");
|
|
83
|
-
for (const chunk of plan.chunks) {
|
|
84
|
-
console.log(` --tag ${chunk.tag} (${chunk.count} endpoints)`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return 0;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const coverageHeader = coverageInfo
|
|
91
|
-
? `## Coverage: ${coverageInfo.covered}/${coverageInfo.total} endpoints covered (${coverageInfo.percentage}%). Generating tests for ${endpoints.length} uncovered endpoints:`
|
|
92
|
-
: undefined;
|
|
93
|
-
|
|
94
|
-
const apiContext = compressEndpointsWithSchemas(endpoints, securitySchemes);
|
|
95
|
-
const guide = buildGenerationGuide({
|
|
96
|
-
title: options.tag ? `${title ?? "API"} — tag: ${options.tag}` : (title ?? "API"),
|
|
97
|
-
baseUrl,
|
|
98
|
-
apiContext,
|
|
99
|
-
outputDir,
|
|
100
|
-
securitySchemes,
|
|
101
|
-
endpointCount: endpoints.length,
|
|
102
|
-
coverageHeader,
|
|
103
|
-
includeFormat: true,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
if (options.json) {
|
|
107
|
-
printJson(jsonOk("guide", {
|
|
108
|
-
title: title ?? "API",
|
|
109
|
-
endpointCount: endpoints.length,
|
|
110
|
-
outputDir,
|
|
111
|
-
guide,
|
|
112
|
-
...(coverageInfo ? { coverage: coverageInfo } : {}),
|
|
113
|
-
}));
|
|
114
|
-
} else {
|
|
115
|
-
console.log(guide);
|
|
116
|
-
}
|
|
117
|
-
return 0;
|
|
118
|
-
} catch (err) {
|
|
119
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
120
|
-
if (options.json) {
|
|
121
|
-
printJson(jsonError("guide", [message]));
|
|
122
|
-
} else {
|
|
123
|
-
printError(message);
|
|
124
|
-
}
|
|
125
|
-
return 2;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: zond-scenarios
|
|
3
|
-
description: |
|
|
4
|
-
Author multi-step API scenario tests (user journeys) with zond. Use when asked to:
|
|
5
|
-
write a scenario, model a user flow, replay a UI flow via API, chain requests with
|
|
6
|
-
captures, set up test data via API, test a workflow end-to-end. Activates on:
|
|
7
|
-
"user scenario", "API workflow", "multi-step test", "login then ...", "create then ...".
|
|
8
|
-
allowed-tools: [Read, Write, Bash(zond *), Bash(bunx zond *)]
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# zond — API Scenario Tests
|
|
12
|
-
|
|
13
|
-
CLI-only skill. Scenarios are **hand-written** YAML chaining multiple requests
|
|
14
|
-
with captures (no `zond generate` for scenarios — `generate` only emits per-endpoint suites).
|
|
15
|
-
|
|
16
|
-
## Critical rules
|
|
17
|
-
- **NEVER** run `zond generate` to produce scenarios — write them manually from `.api-catalog.yaml`.
|
|
18
|
-
- **NEVER** read OpenAPI/Swagger specs directly — use `zond catalog` or `zond describe`.
|
|
19
|
-
- **NEVER** invent endpoints — only use entries present in `.api-catalog.yaml`.
|
|
20
|
-
- **Captures are file-scoped** — variables defined in one file do not leak into others
|
|
21
|
-
unless the producing suite is marked `setup: true`.
|
|
22
|
-
- Tag every scenario `[scenario, <flow-name>]`. Run by `--tag scenario` or `--tag <flow>,setup`.
|
|
23
|
-
- Keep one user journey per file; chain steps within that file.
|
|
24
|
-
|
|
25
|
-
## Workflow
|
|
26
|
-
```bash
|
|
27
|
-
# 1. Make sure the catalog is current
|
|
28
|
-
zond catalog <spec> --output apis/<name>/tests
|
|
29
|
-
|
|
30
|
-
# 2. Read the catalog to pick endpoints (NOT the raw spec)
|
|
31
|
-
cat apis/<name>/tests/.api-catalog.yaml
|
|
32
|
-
|
|
33
|
-
# 3. Author the scenario YAML (see structure below)
|
|
34
|
-
|
|
35
|
-
# 4. Validate + run
|
|
36
|
-
zond validate apis/<name>/tests/scenarios/<flow>.yaml
|
|
37
|
-
zond run apis/<name>/tests/scenarios/<flow>.yaml --json
|
|
38
|
-
|
|
39
|
-
# 5. Diagnose failures
|
|
40
|
-
zond db diagnose <run-id> --json
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Scenario YAML — minimal structure
|
|
44
|
-
```yaml
|
|
45
|
-
name: user_signup_to_first_purchase
|
|
46
|
-
tags: [scenario, signup_purchase]
|
|
47
|
-
steps:
|
|
48
|
-
- name: register
|
|
49
|
-
request:
|
|
50
|
-
method: POST
|
|
51
|
-
url: "{{base_url}}/auth/register"
|
|
52
|
-
body: { email: "{{generate.email}}", password: "{{generate.password}}" }
|
|
53
|
-
expect: { status: 201 }
|
|
54
|
-
capture:
|
|
55
|
-
user_id: "$.id"
|
|
56
|
-
auth_token: "$.token"
|
|
57
|
-
|
|
58
|
-
- name: create_cart
|
|
59
|
-
request:
|
|
60
|
-
method: POST
|
|
61
|
-
url: "{{base_url}}/carts"
|
|
62
|
-
headers: { Authorization: "Bearer {{auth_token}}" }
|
|
63
|
-
expect: { status: 201 }
|
|
64
|
-
capture: { cart_id: "$.id" }
|
|
65
|
-
|
|
66
|
-
- name: checkout
|
|
67
|
-
request:
|
|
68
|
-
method: POST
|
|
69
|
-
url: "{{base_url}}/carts/{{cart_id}}/checkout"
|
|
70
|
-
headers: { Authorization: "Bearer {{auth_token}}" }
|
|
71
|
-
expect: { status: 200 }
|
|
72
|
-
|
|
73
|
-
- name: cleanup
|
|
74
|
-
always: true # runs even if earlier steps failed
|
|
75
|
-
request:
|
|
76
|
-
method: DELETE
|
|
77
|
-
url: "{{base_url}}/users/{{user_id}}"
|
|
78
|
-
headers: { Authorization: "Bearer {{auth_token}}" }
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Key building blocks (full reference in `ZOND.md`):
|
|
82
|
-
- `capture: { var: "$.json.path" }` — JSONPath extraction into scenario-local vars.
|
|
83
|
-
- `expect.status` / `expect.json` / `expect.headers` — assertions.
|
|
84
|
-
- `{{generate.email}}`, `{{generate.uuid}}`, `{{generate.int(1,100)}}` — value generators.
|
|
85
|
-
- `always: true` on a step — guaranteed cleanup (runs on prior failure).
|
|
86
|
-
- `setup: true` at the suite level — captures propagate to other suites in the run.
|
|
87
|
-
|
|
88
|
-
## Sharing auth across scenarios
|
|
89
|
-
Put login in `apis/<name>/tests/setup.yaml` with `setup: true`; scenarios reference
|
|
90
|
-
`{{auth_token}}` directly. Run with `--tag <flow>,setup`.
|
|
91
|
-
|
|
92
|
-
## When to hand off
|
|
93
|
-
- Need broad endpoint coverage, bug hunting, or run diagnosis → `zond`.
|
|
94
|
-
- This skill is **only** for hand-written multi-step flows / fixture creation.
|
|
95
|
-
|
|
96
|
-
For full YAML structure (assertions, flow control, generators, conditional steps),
|
|
97
|
-
see the YAML format section of `ZOND.md` at the repo root.
|