@kirrosh/zond 0.21.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.
Files changed (256) hide show
  1. package/CHANGELOG.md +758 -3
  2. package/README.md +78 -15
  3. package/package.json +17 -10
  4. package/src/cli/argv.ts +122 -0
  5. package/src/cli/commands/add-api.ts +134 -0
  6. package/src/cli/commands/api/annotate/idempotency.ts +59 -0
  7. package/src/cli/commands/api/annotate/index.ts +525 -0
  8. package/src/cli/commands/api/annotate/lifecycle.ts +74 -0
  9. package/src/cli/commands/api/annotate/overlay.ts +206 -0
  10. package/src/cli/commands/api/annotate/pagination.ts +60 -0
  11. package/src/cli/commands/api/annotate/prompts.ts +183 -0
  12. package/src/cli/commands/api/annotate/readback.ts +58 -0
  13. package/src/cli/commands/api/annotate/resources.ts +91 -0
  14. package/src/cli/commands/api/annotate/seed-bodies.ts +61 -0
  15. package/src/cli/commands/audit.ts +480 -0
  16. package/src/cli/commands/bootstrap.ts +710 -0
  17. package/src/cli/commands/catalog.ts +35 -0
  18. package/src/cli/commands/check.ts +348 -0
  19. package/src/cli/commands/checks.ts +756 -0
  20. package/src/cli/commands/ci-init.ts +55 -6
  21. package/src/cli/commands/clean.ts +212 -0
  22. package/src/cli/commands/cleanup.ts +262 -0
  23. package/src/cli/commands/completions.ts +192 -0
  24. package/src/cli/commands/coverage.ts +605 -132
  25. package/src/cli/commands/db.ts +180 -8
  26. package/src/cli/commands/describe.ts +37 -2
  27. package/src/cli/commands/discover.ts +1236 -0
  28. package/src/cli/commands/doctor.ts +607 -0
  29. package/src/cli/commands/fixtures.ts +402 -0
  30. package/src/cli/commands/generate.ts +420 -47
  31. package/src/cli/commands/init/agents-md.ts +61 -0
  32. package/src/cli/commands/init/bootstrap.ts +108 -0
  33. package/src/cli/commands/init/index.ts +244 -0
  34. package/src/cli/commands/init/skills.ts +98 -0
  35. package/src/cli/commands/init/templates/agents.md +77 -0
  36. package/src/cli/commands/init/templates/markdown.d.ts +4 -0
  37. package/src/cli/commands/init/templates/skills/zond-checks.md +397 -0
  38. package/src/cli/commands/init/templates/skills/zond-triage.md +210 -0
  39. package/src/cli/commands/init/templates/skills/zond.md +651 -0
  40. package/src/cli/commands/init/templates/zond-config.yml +14 -0
  41. package/src/cli/commands/prepare-fixtures.ts +135 -0
  42. package/src/cli/commands/probe/mass-assignment.ts +503 -0
  43. package/src/cli/commands/probe/security.ts +454 -0
  44. package/src/cli/commands/probe/static.ts +255 -0
  45. package/src/cli/commands/probe/webhooks.ts +161 -0
  46. package/src/cli/commands/probe.ts +459 -0
  47. package/src/cli/commands/reference.ts +87 -0
  48. package/src/cli/commands/refresh-api.ts +169 -0
  49. package/src/cli/commands/remove-api.ts +150 -0
  50. package/src/cli/commands/report-bundle.ts +318 -0
  51. package/src/cli/commands/report.ts +241 -0
  52. package/src/cli/commands/request.ts +379 -4
  53. package/src/cli/commands/run.ts +911 -33
  54. package/src/cli/commands/session.ts +244 -0
  55. package/src/cli/commands/use.ts +74 -0
  56. package/src/cli/index.ts +36 -607
  57. package/src/cli/json-envelope.ts +112 -3
  58. package/src/cli/json-schemas.ts +263 -0
  59. package/src/cli/program.ts +218 -0
  60. package/src/cli/resolve.ts +105 -0
  61. package/src/cli/status-filter.ts +124 -0
  62. package/src/cli/util/api-context.ts +85 -0
  63. package/src/cli/version.ts +8 -0
  64. package/src/core/anti-fp/bootstrap.ts +34 -0
  65. package/src/core/anti-fp/index.ts +33 -0
  66. package/src/core/anti-fp/registry.ts +44 -0
  67. package/src/core/anti-fp/rules/baseline-echo.ts +74 -0
  68. package/src/core/anti-fp/rules/schemathesis/body_negation_becomes_valid.ts +52 -0
  69. package/src/core/anti-fp/rules/schemathesis/coverage_phase_boundary_positive.ts +38 -0
  70. package/src/core/anti-fp/rules/schemathesis/has_unverifiable_mutations.ts +35 -0
  71. package/src/core/anti-fp/rules/schemathesis/index.ts +24 -0
  72. package/src/core/anti-fp/rules/schemathesis/string_type_mutation_becomes_valid.ts +53 -0
  73. package/src/core/anti-fp/rules/subscription-gated/index.ts +11 -0
  74. package/src/core/anti-fp/rules/subscription-gated/paid-plan-403.ts +75 -0
  75. package/src/core/anti-fp/types.ts +68 -0
  76. package/src/core/checks/checks/_crud-helpers.ts +133 -0
  77. package/src/core/checks/checks/_negative_mutator.ts +133 -0
  78. package/src/core/checks/checks/_readback-helpers.ts +133 -0
  79. package/src/core/checks/checks/content_type_conformance.ts +39 -0
  80. package/src/core/checks/checks/cross_call_references.ts +134 -0
  81. package/src/core/checks/checks/ensure_resource_availability.ts +62 -0
  82. package/src/core/checks/checks/idempotency_replay.ts +246 -0
  83. package/src/core/checks/checks/ignored_auth.ts +211 -0
  84. package/src/core/checks/checks/index.ts +65 -0
  85. package/src/core/checks/checks/lifecycle_transitions.ts +273 -0
  86. package/src/core/checks/checks/missing_required_header.ts +40 -0
  87. package/src/core/checks/checks/negative_data_rejection.ts +45 -0
  88. package/src/core/checks/checks/not_a_server_error.ts +27 -0
  89. package/src/core/checks/checks/open_cors_on_sensitive.ts +131 -0
  90. package/src/core/checks/checks/pagination_invariants.ts +238 -0
  91. package/src/core/checks/checks/positive_data_acceptance.ts +36 -0
  92. package/src/core/checks/checks/rate_limit_headers_absent.ts +77 -0
  93. package/src/core/checks/checks/response_headers_conformance.ts +74 -0
  94. package/src/core/checks/checks/response_schema_conformance.ts +30 -0
  95. package/src/core/checks/checks/status_code_conformance.ts +61 -0
  96. package/src/core/checks/checks/unsupported_method.ts +63 -0
  97. package/src/core/checks/checks/use_after_free.ts +78 -0
  98. package/src/core/checks/index.ts +30 -0
  99. package/src/core/checks/mode.ts +79 -0
  100. package/src/core/checks/recommended-action.ts +64 -0
  101. package/src/core/checks/registry.ts +78 -0
  102. package/src/core/checks/runner.ts +874 -0
  103. package/src/core/checks/sarif.ts +230 -0
  104. package/src/core/checks/stateful.ts +121 -0
  105. package/src/core/checks/types.ts +189 -0
  106. package/src/core/classifier/recommended-action.ts +222 -0
  107. package/src/core/context/current.ts +51 -0
  108. package/src/core/context/session.ts +78 -0
  109. package/src/core/coverage/loader.ts +185 -0
  110. package/src/core/coverage/reasons.ts +300 -0
  111. package/src/core/diagnostics/db-analysis.ts +161 -12
  112. package/src/core/diagnostics/failure-class.ts +120 -0
  113. package/src/core/diagnostics/failure-hints.ts +212 -9
  114. package/src/core/diagnostics/spec-pointer.ts +99 -0
  115. package/src/core/diagnostics/suggested-fixes.ts +156 -0
  116. package/src/core/exporter/case-study/index.ts +270 -0
  117. package/src/core/exporter/curl.ts +40 -0
  118. package/src/core/exporter/exporter.ts +48 -0
  119. package/src/core/exporter/html-report/escape.ts +24 -0
  120. package/src/core/exporter/html-report/index.ts +479 -0
  121. package/src/core/exporter/html-report/script.ts +100 -0
  122. package/src/core/exporter/html-report/styles.ts +408 -0
  123. package/src/core/generator/chunker.ts +53 -15
  124. package/src/core/generator/coverage-phase.ts +0 -0
  125. package/src/core/generator/create-body.ts +89 -0
  126. package/src/core/generator/data-factory.ts +490 -33
  127. package/src/core/generator/describe.ts +1 -1
  128. package/src/core/generator/fixtures-builder.ts +325 -0
  129. package/src/core/generator/index.ts +7 -5
  130. package/src/core/generator/openapi-reader.ts +55 -3
  131. package/src/core/generator/path-param-disambig.ts +114 -0
  132. package/src/core/generator/resources-builder.ts +648 -0
  133. package/src/core/generator/schema-utils.ts +11 -3
  134. package/src/core/generator/serializer.ts +114 -15
  135. package/src/core/generator/suite-generator.ts +484 -77
  136. package/src/core/generator/types.ts +8 -0
  137. package/src/core/identity/identity-file.ts +129 -0
  138. package/src/core/lint/affects.ts +28 -0
  139. package/src/core/lint/config.ts +96 -0
  140. package/src/core/lint/format.ts +42 -0
  141. package/src/core/lint/index.ts +94 -0
  142. package/src/core/lint/reporter.ts +128 -0
  143. package/src/core/lint/rules/consistency.ts +158 -0
  144. package/src/core/lint/rules/heuristics.ts +97 -0
  145. package/src/core/lint/rules/strictness.ts +109 -0
  146. package/src/core/lint/types.ts +96 -0
  147. package/src/core/lint/walker.ts +248 -0
  148. package/src/core/meta/meta-store.ts +6 -73
  149. package/src/core/output/README.md +91 -0
  150. package/src/core/output/index.ts +13 -0
  151. package/src/core/output/run.ts +126 -0
  152. package/src/core/output/types.ts +129 -0
  153. package/src/core/parser/env-interpolation.ts +104 -0
  154. package/src/core/parser/filter.ts +57 -0
  155. package/src/core/parser/schema.ts +132 -5
  156. package/src/core/parser/types.ts +29 -2
  157. package/src/core/parser/variables.ts +0 -0
  158. package/src/core/parser/yaml-parser.ts +108 -13
  159. package/src/core/probe/bootstrap.ts +34 -0
  160. package/src/core/probe/dry-run-envelope.ts +57 -0
  161. package/src/core/probe/mass-assignment-probe-class.ts +198 -0
  162. package/src/core/probe/mass-assignment-probe.ts +1122 -0
  163. package/src/core/probe/mass-assignment-template.ts +212 -0
  164. package/src/core/probe/method-probe.ts +164 -0
  165. package/src/core/probe/method-shared.ts +69 -0
  166. package/src/core/probe/negative-probe.ts +691 -0
  167. package/src/core/probe/orphan-tracker.ts +188 -0
  168. package/src/core/probe/path-discovery.ts +440 -0
  169. package/src/core/probe/probe-harness.ts +120 -0
  170. package/src/core/probe/registry.ts +89 -0
  171. package/src/core/probe/runner.ts +136 -0
  172. package/src/core/probe/security-probe-class.ts +201 -0
  173. package/src/core/probe/security-probe.ts +1453 -0
  174. package/src/core/probe/shared.ts +505 -0
  175. package/src/core/probe/static-probe-class.ts +125 -0
  176. package/src/core/probe/types.ts +165 -0
  177. package/src/core/probe/verdict-aggregator.ts +33 -0
  178. package/src/core/probe/webhooks-probe.ts +284 -0
  179. package/src/core/reporter/console.ts +69 -4
  180. package/src/core/reporter/index.ts +2 -3
  181. package/src/core/reporter/json.ts +15 -2
  182. package/src/core/reporter/junit.ts +27 -12
  183. package/src/core/reporter/ndjson.ts +37 -0
  184. package/src/core/reporter/types.ts +3 -0
  185. package/src/core/runner/assertions.ts +62 -2
  186. package/src/core/runner/async-pool.ts +108 -0
  187. package/src/core/runner/auth-path.ts +8 -0
  188. package/src/core/runner/ci-context.ts +72 -0
  189. package/src/core/runner/executor.ts +391 -52
  190. package/src/core/runner/form-encode.ts +51 -0
  191. package/src/core/runner/http-client.ts +115 -7
  192. package/src/core/runner/learn-drift.ts +293 -0
  193. package/src/core/runner/preflight-vars.ts +149 -0
  194. package/src/core/runner/progress-tracker.ts +73 -0
  195. package/src/core/runner/rate-limiter.ts +203 -0
  196. package/src/core/runner/run-kind.ts +39 -0
  197. package/src/core/runner/schema-validator.ts +312 -0
  198. package/src/core/runner/send-request.ts +153 -20
  199. package/src/core/runner/types.ts +38 -0
  200. package/src/core/secrets/registry.ts +164 -0
  201. package/src/core/secrets/secrets-file.ts +115 -0
  202. package/src/core/selectors/operation-filter.ts +144 -0
  203. package/src/core/setup-api.ts +419 -17
  204. package/src/core/severity/category.ts +94 -0
  205. package/src/core/severity/index.ts +121 -0
  206. package/src/core/spec/layers.ts +154 -0
  207. package/src/core/util/format-eta.ts +21 -0
  208. package/src/core/utils.ts +5 -1
  209. package/src/core/workspace/config.ts +129 -0
  210. package/src/core/workspace/manifest.ts +283 -0
  211. package/src/core/workspace/output-rotation.ts +62 -0
  212. package/src/core/workspace/root.ts +94 -0
  213. package/src/core/workspace/triage-path.ts +87 -0
  214. package/src/db/lint-runs.ts +47 -0
  215. package/src/db/migrate.ts +126 -0
  216. package/src/db/migrations/0001_run_kind.sql +25 -0
  217. package/src/db/migrations/sql.d.ts +4 -0
  218. package/src/db/queries/collections.ts +133 -0
  219. package/src/db/queries/coverage.ts +9 -0
  220. package/src/db/queries/dashboard.ts +59 -0
  221. package/src/db/queries/results.ts +128 -0
  222. package/src/db/queries/runs.ts +235 -0
  223. package/src/db/queries/sessions.ts +42 -0
  224. package/src/db/queries/settings.ts +28 -0
  225. package/src/db/queries/types.ts +172 -0
  226. package/src/db/queries.ts +72 -802
  227. package/src/db/schema.ts +179 -48
  228. package/src/cli/commands/export.ts +0 -144
  229. package/src/cli/commands/guide.ts +0 -127
  230. package/src/cli/commands/init.ts +0 -57
  231. package/src/cli/commands/serve.ts +0 -81
  232. package/src/cli/commands/sync.ts +0 -269
  233. package/src/cli/commands/update.ts +0 -189
  234. package/src/cli/commands/validate.ts +0 -34
  235. package/src/core/exporter/postman.ts +0 -963
  236. package/src/core/generator/guide-builder.ts +0 -253
  237. package/src/core/meta/types.ts +0 -21
  238. package/src/core/parser/index.ts +0 -21
  239. package/src/core/runner/execute-run.ts +0 -132
  240. package/src/core/runner/index.ts +0 -12
  241. package/src/core/sync/spec-differ.ts +0 -38
  242. package/src/web/data/collection-state.ts +0 -362
  243. package/src/web/routes/api.ts +0 -314
  244. package/src/web/routes/dashboard.ts +0 -350
  245. package/src/web/routes/runs.ts +0 -64
  246. package/src/web/schemas.ts +0 -121
  247. package/src/web/server.ts +0 -134
  248. package/src/web/static/htmx.min.cjs +0 -1
  249. package/src/web/static/style.css +0 -1148
  250. package/src/web/views/endpoints-tab.ts +0 -174
  251. package/src/web/views/explorer-tab.ts +0 -402
  252. package/src/web/views/health-strip.ts +0 -92
  253. package/src/web/views/layout.ts +0 -48
  254. package/src/web/views/results.ts +0 -210
  255. package/src/web/views/runs-tab.ts +0 -126
  256. 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}}\`
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
- }
@@ -1,21 +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
- /** Spec URL or file path used for last generation */
16
- specUrl: string;
17
- /** SHA-256 hex of spec content at time of last generation */
18
- specHash: string;
19
- /** Per-file metadata, keyed by filename (e.g. "smoke-users.yaml") */
20
- files: Record<string, FileMeta>;
21
- }
@@ -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
- }
@@ -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
- }