@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
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
import type { EndpointInfo, SecuritySchemeInfo } from "./types.ts";
|
|
2
|
-
import { compressSchema, formatParam, isAnySchema } from "./schema-utils.ts";
|
|
3
|
-
|
|
4
|
-
export function compressEndpointsWithSchemas(
|
|
5
|
-
endpoints: EndpointInfo[],
|
|
6
|
-
securitySchemes: SecuritySchemeInfo[],
|
|
7
|
-
): string {
|
|
8
|
-
const lines: string[] = [];
|
|
9
|
-
|
|
10
|
-
if (securitySchemes.length > 0) {
|
|
11
|
-
lines.push("SECURITY SCHEMES:");
|
|
12
|
-
for (const s of securitySchemes) {
|
|
13
|
-
let desc = ` ${s.name}: ${s.type}`;
|
|
14
|
-
if (s.scheme) desc += ` (${s.scheme})`;
|
|
15
|
-
if (s.bearerFormat) desc += ` [${s.bearerFormat}]`;
|
|
16
|
-
if (s.in && s.apiKeyName) desc += ` (${s.apiKeyName} in ${s.in})`;
|
|
17
|
-
lines.push(desc);
|
|
18
|
-
}
|
|
19
|
-
lines.push("");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
lines.push("ENDPOINTS:");
|
|
23
|
-
for (const ep of endpoints) {
|
|
24
|
-
const summary = ep.summary ? ` — ${ep.summary}` : "";
|
|
25
|
-
const security = ep.security.length > 0 ? ` [auth: ${ep.security.join(", ")}]` : "";
|
|
26
|
-
lines.push(`\n${ep.method} ${ep.path}${summary}${security}`);
|
|
27
|
-
|
|
28
|
-
// Parameters
|
|
29
|
-
const pathParams = ep.parameters.filter(p => p.in === "path");
|
|
30
|
-
const queryParams = ep.parameters.filter(p => p.in === "query");
|
|
31
|
-
const headerParams = ep.parameters.filter(p => p.in === "header");
|
|
32
|
-
if (pathParams.length > 0) {
|
|
33
|
-
lines.push(` Path params: ${pathParams.map(p => formatParam(p)).join(", ")}`);
|
|
34
|
-
}
|
|
35
|
-
if (queryParams.length > 0) {
|
|
36
|
-
lines.push(` Query params: ${queryParams.map(p => formatParam(p)).join(", ")}`);
|
|
37
|
-
}
|
|
38
|
-
if (headerParams.length > 0) {
|
|
39
|
-
lines.push(` Header params: ${headerParams.map(p => formatParam(p)).join(", ")}`);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Request body with full schema
|
|
43
|
-
if (ep.requestBodySchema) {
|
|
44
|
-
const contentType = ep.requestBodyContentType ?? "application/json";
|
|
45
|
-
const anyBody = isAnySchema(ep.requestBodySchema);
|
|
46
|
-
const bodyLine = anyBody
|
|
47
|
-
? `any # ⚠️ spec defines body as 'any' — actual required fields unknown, test may need manual adjustment`
|
|
48
|
-
: compressSchema(ep.requestBodySchema);
|
|
49
|
-
lines.push(` Request body (${contentType}): ${bodyLine}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Responses with schemas
|
|
53
|
-
for (const resp of ep.responses) {
|
|
54
|
-
const schemaStr = resp.schema ? ` → ${compressSchema(resp.schema)}` : "";
|
|
55
|
-
lines.push(` ${resp.statusCode}: ${resp.description}${schemaStr}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return lines.join("\n");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const YAML_FORMAT_CHEATSHEET = `
|
|
63
|
-
## YAML Test Format Reference
|
|
64
|
-
|
|
65
|
-
### Suite structure
|
|
66
|
-
\`\`\`yaml
|
|
67
|
-
name: Suite Name # required
|
|
68
|
-
base_url: "{{base_url}}" # or hardcoded URL
|
|
69
|
-
tags: [smoke] # optional: smoke | crud | destructive | auth
|
|
70
|
-
tests:
|
|
71
|
-
- name: Get all items # required
|
|
72
|
-
GET: /items # method as YAML key, path as value
|
|
73
|
-
query: { limit: 10 }
|
|
74
|
-
expect:
|
|
75
|
-
status: 200
|
|
76
|
-
_body: { type: array, length_gt: 0 }
|
|
77
|
-
\`\`\`
|
|
78
|
-
|
|
79
|
-
### Path parameters
|
|
80
|
-
Inline the value directly — there is NO \`params\` field:
|
|
81
|
-
\`\`\`yaml
|
|
82
|
-
- name: Get item by ID
|
|
83
|
-
GET: /items/1 # hardcoded
|
|
84
|
-
- name: Get captured item
|
|
85
|
-
GET: /items/{{item_id}} # from prior capture
|
|
86
|
-
\`\`\`
|
|
87
|
-
|
|
88
|
-
### Assertion operators (use inside expect)
|
|
89
|
-
| Operator | Example |
|
|
90
|
-
|----------|---------|
|
|
91
|
-
| equals (default) | \`status: 200\` |
|
|
92
|
-
| not_equals | \`status: { not_equals: 500 }\` |
|
|
93
|
-
| contains | \`name: { contains: "john" }\` |
|
|
94
|
-
| not_contains | \`error: { not_contains: "fatal" }\` |
|
|
95
|
-
| exists / not_exists | \`id: { exists: true }\` |
|
|
96
|
-
| gt / gte / lt / lte | \`count: { gte: 1 }\` |
|
|
97
|
-
| matches (regex) | \`email: { matches: "^.+@.+$" }\` |
|
|
98
|
-
| type | \`items: { type: array }\` |
|
|
99
|
-
| length | \`items: { length: 5 }\` |
|
|
100
|
-
| length_gt/gte/lt/lte | \`items: { length_gt: 0 }\` |
|
|
101
|
-
|
|
102
|
-
### Body assertions
|
|
103
|
-
- \`_body\` — assert on root response body: \`_body: { type: array }\`
|
|
104
|
-
- Combine operators in one key: \`_body: { type: array, length_gt: 0 }\`
|
|
105
|
-
- Dot-notation for nested: \`data.user.id: { exists: true }\`
|
|
106
|
-
- Array element: \`items.0.name: { exists: true }\`
|
|
107
|
-
- YAML keys must be unique — do NOT repeat \`_body\` twice
|
|
108
|
-
|
|
109
|
-
### Request body — IMPORTANT
|
|
110
|
-
Use \`json:\` for JSON request bodies. Do NOT use \`body:\` — it is not a valid key.
|
|
111
|
-
\`\`\`yaml
|
|
112
|
-
- name: Create resource
|
|
113
|
-
POST: /resources
|
|
114
|
-
json: { name: "test", email: "a@b.com" } # correct — use json:
|
|
115
|
-
# body: { ... } # WRONG — body: is not supported
|
|
116
|
-
expect:
|
|
117
|
-
status: 201
|
|
118
|
-
id: { exists: true }
|
|
119
|
-
\`\`\`
|
|
120
|
-
For form-encoded: use \`form:\` instead of \`json:\`.
|
|
121
|
-
|
|
122
|
-
### Built-in generators
|
|
123
|
-
\`{{$uuid}}\`, \`{{$randomInt}}\`, \`{{$timestamp}}\`, \`{{$isoTimestamp}}\`, \`{{$randomName}}\`, \`{{$randomEmail}}\`, \`{{$randomString}}\`, \`{{$randomUrl}}\` (uri/url), \`{{$randomFqdn}}\` (hostname), \`{{$randomIpv4}}\` (ipv4), \`{{$randomDate}}\` (date), \`{{$randomIsoDate}}\` (date-time)
|
|
124
|
-
|
|
125
|
-
### Variable capture & interpolation
|
|
126
|
-
\`\`\`yaml
|
|
127
|
-
- name: Create item
|
|
128
|
-
POST: /items
|
|
129
|
-
json: { name: "test-{{$uuid}}" }
|
|
130
|
-
capture:
|
|
131
|
-
created_id: id # saves response.id
|
|
132
|
-
expect:
|
|
133
|
-
status: 201
|
|
134
|
-
|
|
135
|
-
- name: Get created item
|
|
136
|
-
GET: /items/{{created_id}}
|
|
137
|
-
expect:
|
|
138
|
-
status: 200
|
|
139
|
-
id: { equals: "{{created_id}}" }
|
|
140
|
-
\`\`\`
|
|
141
|
-
|
|
142
|
-
### Array assertions
|
|
143
|
-
\`\`\`yaml
|
|
144
|
-
items:
|
|
145
|
-
each: # every element must match
|
|
146
|
-
status: { not_equals: "deleted" }
|
|
147
|
-
id: { type: integer }
|
|
148
|
-
|
|
149
|
-
items:
|
|
150
|
-
contains_item: # at least one element matches
|
|
151
|
-
name: { contains: "test" }
|
|
152
|
-
|
|
153
|
-
ids:
|
|
154
|
-
set_equals: [1, 2, 3] # same elements, order-independent
|
|
155
|
-
\`\`\`
|
|
156
|
-
|
|
157
|
-
### Flow control
|
|
158
|
-
\`\`\`yaml
|
|
159
|
-
# skip_if — skip step when condition is true (after variable substitution)
|
|
160
|
-
- name: Delete only if exists
|
|
161
|
-
DELETE: /items/{{item_id}}
|
|
162
|
-
skip_if: "{{item_id}} == 0"
|
|
163
|
-
expect:
|
|
164
|
-
status: 204
|
|
165
|
-
|
|
166
|
-
# retry_until — repeat request until condition met
|
|
167
|
-
- name: Wait for processing
|
|
168
|
-
GET: /jobs/{{job_id}}
|
|
169
|
-
retry_until:
|
|
170
|
-
condition: "{{status}} == completed"
|
|
171
|
-
max_attempts: 5
|
|
172
|
-
delay_ms: 1000
|
|
173
|
-
expect:
|
|
174
|
-
status: 200
|
|
175
|
-
|
|
176
|
-
# for_each — repeat step for each item in array
|
|
177
|
-
- name: Delete item
|
|
178
|
-
DELETE: /items/{{id}}
|
|
179
|
-
for_each:
|
|
180
|
-
var: id
|
|
181
|
-
in: "{{item_ids}}"
|
|
182
|
-
expect:
|
|
183
|
-
status: [200, 204]
|
|
184
|
-
|
|
185
|
-
# set — transform variables without HTTP request
|
|
186
|
-
- name: Extract IDs
|
|
187
|
-
set:
|
|
188
|
-
all_ids: { map_field: ["{{items}}", "id"] }
|
|
189
|
-
count: { length: "{{items}}" }
|
|
190
|
-
first_item: { first: "{{items}}" }
|
|
191
|
-
merged: { concat: ["{{list_a}}", "{{list_b}}"] }
|
|
192
|
-
\`\`\`
|
|
193
|
-
|
|
194
|
-
### Coverage matching
|
|
195
|
-
Use spec paths with \`{param}\` placeholders in the path for coverage to match:
|
|
196
|
-
- Spec says \`GET /products/{id}\` → write \`GET: /products/1\` (hardcode the value)
|
|
197
|
-
- Coverage scanner matches test paths against spec paths automatically
|
|
198
|
-
|
|
199
|
-
### Suite variable isolation — IMPORTANT
|
|
200
|
-
Each suite runs in its own variable scope. Captured variables (via \`capture:\`) do NOT propagate between suites.
|
|
201
|
-
If multiple suites need auth, each suite must either:
|
|
202
|
-
- Include its own login step with \`capture: auth_token\`
|
|
203
|
-
- Or use \`auth_token\` from \`.env.yaml\` (pre-configured, no capture needed)
|
|
204
|
-
|
|
205
|
-
Do NOT create a separate "setup" suite expecting other suites to use its captures.
|
|
206
|
-
|
|
207
|
-
### ETag / Conditional Requests
|
|
208
|
-
If-Match and If-None-Match require escaped quotes around the ETag value:
|
|
209
|
-
\`\`\`yaml
|
|
210
|
-
- name: Update with ETag
|
|
211
|
-
PUT: /items/{{item_id}}
|
|
212
|
-
headers:
|
|
213
|
-
If-Match: "\\"{{etag}}\\""
|
|
214
|
-
json: { name: "updated" }
|
|
215
|
-
expect:
|
|
216
|
-
status: 200
|
|
217
|
-
\`\`\`
|
|
218
|
-
|
|
219
|
-
### CRITICAL: Never mask server errors
|
|
220
|
-
- If an endpoint returns 500 — do NOT change expect to \`status: 500\`. Keep \`status: 200\` and let the test fail.
|
|
221
|
-
- A failing test = signal about an API bug. The goal is NOT "all tests green" but "tests reflect expected behavior".
|
|
222
|
-
- Fixing tests means fixing test logic (wrong path, missing auth, bad body), NOT accepting error responses as expected.
|
|
223
|
-
- Legitimate error expectations: 404 for missing resources, 400/422 for invalid input, 401 for no auth — these are negative tests by design.
|
|
224
|
-
`;
|
|
225
|
-
|
|
226
|
-
export interface GuideOptions {
|
|
227
|
-
title: string;
|
|
228
|
-
baseUrl?: string;
|
|
229
|
-
apiContext: string;
|
|
230
|
-
outputDir: string;
|
|
231
|
-
securitySchemes: SecuritySchemeInfo[];
|
|
232
|
-
endpointCount: number;
|
|
233
|
-
coverageHeader?: string;
|
|
234
|
-
includeFormat?: boolean;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export function buildGenerationGuide(opts: GuideOptions): string {
|
|
238
|
-
const hasAuth = opts.securitySchemes.length > 0;
|
|
239
|
-
|
|
240
|
-
const securitySummary = hasAuth
|
|
241
|
-
? `Security: ${opts.securitySchemes.map(s => `${s.name} (${s.type}${s.scheme ? `/${s.scheme}` : ""})`).join(", ")}`
|
|
242
|
-
: "Security: none";
|
|
243
|
-
|
|
244
|
-
const formatSection = opts.includeFormat !== false ? YAML_FORMAT_CHEATSHEET : "";
|
|
245
|
-
|
|
246
|
-
return `# Test Generation Guide for ${opts.title}
|
|
247
|
-
${opts.coverageHeader ? `\n${opts.coverageHeader}\n` : ""}
|
|
248
|
-
## API Specification (${opts.endpointCount} endpoints)
|
|
249
|
-
${opts.baseUrl ? `Base URL: ${opts.baseUrl}` : "Base URL: use {{base_url}} environment variable"}
|
|
250
|
-
${securitySummary}
|
|
251
|
-
|
|
252
|
-
${opts.apiContext}${formatSection}`;
|
|
253
|
-
}
|
package/src/core/meta/types.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export interface FileMeta {
|
|
2
|
-
generatedAt: string;
|
|
3
|
-
zondVersion: string;
|
|
4
|
-
suiteType: "smoke" | "crud" | "auth" | "sanity" | "unsafe";
|
|
5
|
-
tag?: string;
|
|
6
|
-
/** Normalized endpoint keys, e.g. ["GET /users", "POST /users/{*}"] */
|
|
7
|
-
endpoints: string[];
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface ZondMeta {
|
|
11
|
-
/** Version of zond that last wrote this metadata */
|
|
12
|
-
zondVersion: string;
|
|
13
|
-
/** ISO timestamp of last sync/generate */
|
|
14
|
-
lastSyncedAt: string;
|
|
15
|
-
/** SHA-256 hex of spec content at time of last generation */
|
|
16
|
-
specHash: string;
|
|
17
|
-
/** Per-file metadata, keyed by filename (e.g. "smoke-users.yaml") */
|
|
18
|
-
files: Record<string, FileMeta>;
|
|
19
|
-
}
|
package/src/core/parser/index.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
HttpMethod,
|
|
3
|
-
AssertionRule,
|
|
4
|
-
TestStepExpect,
|
|
5
|
-
TestStep,
|
|
6
|
-
SuiteConfig,
|
|
7
|
-
TestSuite,
|
|
8
|
-
Environment,
|
|
9
|
-
} from "./types.ts";
|
|
10
|
-
|
|
11
|
-
export { validateSuite, DEFAULT_CONFIG } from "./schema.ts";
|
|
12
|
-
export {
|
|
13
|
-
GENERATORS,
|
|
14
|
-
substituteString,
|
|
15
|
-
substituteDeep,
|
|
16
|
-
substituteStep,
|
|
17
|
-
extractVariableReferences,
|
|
18
|
-
loadEnvironment,
|
|
19
|
-
} from "./variables.ts";
|
|
20
|
-
export { parse, parseFile, parseDirectory } from "./yaml-parser.ts";
|
|
21
|
-
export { filterSuitesByTags } from "./filter.ts";
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { parse } from "../parser/yaml-parser.ts";
|
|
2
|
-
import { loadEnvironment } from "../parser/variables.ts";
|
|
3
|
-
import { filterSuitesByTags } from "../parser/filter.ts";
|
|
4
|
-
import { runSuite } from "./executor.ts";
|
|
5
|
-
import { getDb } from "../../db/schema.ts";
|
|
6
|
-
import { createRun, finalizeRun, saveResults, findCollectionByTestPath } from "../../db/queries.ts";
|
|
7
|
-
import { dirname, resolve } from "path";
|
|
8
|
-
import { stat } from "node:fs/promises";
|
|
9
|
-
import type { TestRunResult } from "./types.ts";
|
|
10
|
-
|
|
11
|
-
export const AUTH_PATH_RE = /\/(auth|login|signin|token|oauth)\b/i;
|
|
12
|
-
|
|
13
|
-
export interface ExecuteRunOptions {
|
|
14
|
-
testPath: string;
|
|
15
|
-
envName?: string;
|
|
16
|
-
trigger?: string; // "cli" | "webui" | "mcp"
|
|
17
|
-
dbPath?: string;
|
|
18
|
-
safe?: boolean;
|
|
19
|
-
tag?: string[];
|
|
20
|
-
envVars?: Record<string, string>;
|
|
21
|
-
dryRun?: boolean;
|
|
22
|
-
rerunFilter?: Set<string>; // "suite_name::test_name" keys to rerun
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface ExecuteRunResult {
|
|
26
|
-
runId: number;
|
|
27
|
-
results: TestRunResult[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function executeRun(options: ExecuteRunOptions): Promise<ExecuteRunResult> {
|
|
31
|
-
const { testPath, envName, trigger = "cli", dbPath, safe, tag } = options;
|
|
32
|
-
|
|
33
|
-
let suites = await parse(testPath);
|
|
34
|
-
if (suites.length === 0) {
|
|
35
|
-
throw new Error("No test files found");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Tag filter
|
|
39
|
-
if (tag && tag.length > 0) {
|
|
40
|
-
suites = filterSuitesByTags(suites, tag);
|
|
41
|
-
if (suites.length === 0) {
|
|
42
|
-
throw new Error("No suites match the specified tags");
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Rerun filter: keep only specific failed tests
|
|
47
|
-
if (options.rerunFilter && options.rerunFilter.size > 0) {
|
|
48
|
-
for (const suite of suites) {
|
|
49
|
-
suite.tests = suite.tests.filter(t => options.rerunFilter!.has(`${suite.name}::${t.name}`));
|
|
50
|
-
}
|
|
51
|
-
suites = suites.filter(s => s.tests.length > 0);
|
|
52
|
-
if (suites.length === 0) {
|
|
53
|
-
throw new Error("No matching tests found for rerun filter");
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Safe mode: filter to GET + auth endpoints (same logic as run.ts)
|
|
58
|
-
if (safe) {
|
|
59
|
-
for (const suite of suites) {
|
|
60
|
-
suite.tests = suite.tests.filter(t => t.method === "GET" || !t.method || AUTH_PATH_RE.test(t.path));
|
|
61
|
-
}
|
|
62
|
-
suites = suites.filter(s => s.tests.length > 0);
|
|
63
|
-
if (suites.length === 0) {
|
|
64
|
-
throw new Error("No safe tests found. Nothing to run in safe mode.");
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const fileStat = await stat(testPath).catch(() => null);
|
|
69
|
-
const isDirectory = fileStat?.isDirectory() ?? false;
|
|
70
|
-
const envDir = isDirectory ? testPath : dirname(testPath);
|
|
71
|
-
|
|
72
|
-
getDb(dbPath);
|
|
73
|
-
const resolvedPath = resolve(testPath);
|
|
74
|
-
const collection = findCollectionByTestPath(resolvedPath)
|
|
75
|
-
?? (fileStat?.isFile() ? findCollectionByTestPath(resolve(dirname(testPath))) : null);
|
|
76
|
-
|
|
77
|
-
const effectiveEnvName = envName;
|
|
78
|
-
|
|
79
|
-
// Helper: load env with optional --env-var overrides merged on top
|
|
80
|
-
async function loadEnvWithOverrides(dir: string): Promise<Record<string, string>> {
|
|
81
|
-
const env = await loadEnvironment(effectiveEnvName, dir);
|
|
82
|
-
if (options.envVars && Object.keys(options.envVars).length > 0) {
|
|
83
|
-
Object.assign(env, options.envVars);
|
|
84
|
-
}
|
|
85
|
-
return env;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Phase 1: run setup suites first (sequentially), collect their captures
|
|
89
|
-
const setupSuites = suites.filter(s => s.setup);
|
|
90
|
-
const regularSuites = suites.filter(s => !s.setup);
|
|
91
|
-
const setupResults: Awaited<ReturnType<typeof runSuite>>[] = [];
|
|
92
|
-
const setupCaptures: Record<string, string> = {};
|
|
93
|
-
|
|
94
|
-
for (const suite of setupSuites) {
|
|
95
|
-
const suiteDir = suite.filePath ? dirname(suite.filePath) : envDir;
|
|
96
|
-
const env = await loadEnvWithOverrides(suiteDir);
|
|
97
|
-
const result = await runSuite(suite, env, options.dryRun);
|
|
98
|
-
setupResults.push(result);
|
|
99
|
-
for (const step of result.steps) {
|
|
100
|
-
for (const [k, v] of Object.entries(step.captures)) {
|
|
101
|
-
setupCaptures[k] = String(v);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Phase 2: run regular suites with env enriched by setup captures
|
|
107
|
-
let regularResults: Awaited<ReturnType<typeof runSuite>>[];
|
|
108
|
-
if (isDirectory) {
|
|
109
|
-
regularResults = await Promise.all(regularSuites.map(async (s) => {
|
|
110
|
-
const suiteDir = s.filePath ? dirname(s.filePath) : envDir;
|
|
111
|
-
const env = await loadEnvWithOverrides(suiteDir);
|
|
112
|
-
return runSuite(s, { ...env, ...setupCaptures }, options.dryRun);
|
|
113
|
-
}));
|
|
114
|
-
} else {
|
|
115
|
-
const env = await loadEnvWithOverrides(envDir);
|
|
116
|
-
const enrichedEnv = { ...env, ...setupCaptures };
|
|
117
|
-
regularResults = await Promise.all(regularSuites.map(s => runSuite(s, enrichedEnv, options.dryRun)));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const results = [...setupResults, ...regularResults];
|
|
121
|
-
|
|
122
|
-
const runId = createRun({
|
|
123
|
-
started_at: results[0]?.started_at ?? new Date().toISOString(),
|
|
124
|
-
environment: effectiveEnvName,
|
|
125
|
-
trigger,
|
|
126
|
-
collection_id: collection?.id,
|
|
127
|
-
});
|
|
128
|
-
finalizeRun(runId, results);
|
|
129
|
-
saveResults(runId, results);
|
|
130
|
-
|
|
131
|
-
return { runId, results };
|
|
132
|
-
}
|
package/src/core/runner/index.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
StepStatus,
|
|
3
|
-
HttpRequest,
|
|
4
|
-
HttpResponse,
|
|
5
|
-
AssertionResult,
|
|
6
|
-
StepResult,
|
|
7
|
-
TestRunResult,
|
|
8
|
-
} from "./types.ts";
|
|
9
|
-
|
|
10
|
-
export { executeRequest, type FetchOptions, DEFAULT_FETCH_OPTIONS } from "./http-client.ts";
|
|
11
|
-
export { checkAssertions, extractCaptures } from "./assertions.ts";
|
|
12
|
-
export { runSuite, runSuites } from "./executor.ts";
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { EndpointInfo } from "../generator/types.ts";
|
|
2
|
-
import { normalizePath } from "../generator/coverage-scanner.ts";
|
|
3
|
-
|
|
4
|
-
export interface SpecDiff {
|
|
5
|
-
/** Endpoints in current spec not present in previous snapshot */
|
|
6
|
-
newEndpoints: EndpointInfo[];
|
|
7
|
-
/** Endpoint keys from previous snapshot not present in current spec */
|
|
8
|
-
removedKeys: string[];
|
|
9
|
-
/** True if spec content hash changed (could be just description changes) */
|
|
10
|
-
specChanged: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Produce a normalized key for an endpoint: "GET /users/{*}" */
|
|
14
|
-
export function endpointKey(method: string, path: string): string {
|
|
15
|
-
return `${method.toUpperCase()} ${normalizePath(path)}`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Compare current endpoints against previously-known endpoint keys
|
|
20
|
-
* (stored as strings in .zond-meta.json).
|
|
21
|
-
*/
|
|
22
|
-
export function diffEndpoints(
|
|
23
|
-
prevKeys: string[],
|
|
24
|
-
currentEndpoints: EndpointInfo[],
|
|
25
|
-
): Omit<SpecDiff, "specChanged"> {
|
|
26
|
-
const prevSet = new Set(prevKeys);
|
|
27
|
-
const currentSet = new Set(
|
|
28
|
-
currentEndpoints.map((ep) => endpointKey(ep.method, ep.path)),
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
const newEndpoints = currentEndpoints.filter(
|
|
32
|
-
(ep) => !prevSet.has(endpointKey(ep.method, ep.path)),
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
const removedKeys = [...prevSet].filter((key) => !currentSet.has(key));
|
|
36
|
-
|
|
37
|
-
return { newEndpoints, removedKeys };
|
|
38
|
-
}
|