pytest-httpchain 0.3.0__tar.gz → 0.4.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-httpchain
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: pytest plugin for HTTP testing using JSON files
5
5
  Keywords: testing,pytest,requests
6
6
  Author: Alexander Eresov
@@ -212,13 +212,25 @@ This writes `.claude/skills/pytest-httpchain/SKILL.md` with guidance for writing
212
212
 
213
213
  ### Scenario validation
214
214
 
215
- Validate scenario files for structure and common problems — undefined variables, duplicate stage names, fixture/variable conflicts, stages with no assertions:
215
+ Validate scenario files for structure and common problems — undefined variables, variables referenced before they are saved (data-flow ordering), duplicate stage names, fixture/variable conflicts, no-op `verify` steps, and contradictory body checks:
216
216
 
217
217
  ```bash
218
218
  uvx pytest-httpchain validate tests/test_login.http.json
219
219
  ```
220
220
 
221
- It exits non-zero when any file is invalid, so it doubles as a CI gate. The same checks also run automatically at **pytest collection time** — semantic errors fail collection and warnings are reported — so `pytest --collect-only` validates every scenario in your suite.
221
+ Each finding carries a stable diagnostic code (`HTTPCHAINxxx`) and a severity. It exits non-zero when any file is invalid, so it doubles as a CI gate. Use `--format json` for machine-readable output (editor/CI integration):
222
+
223
+ ```bash
224
+ uvx pytest-httpchain validate --format json tests/test_login.http.json
225
+ ```
226
+
227
+ The same checks also run automatically at **pytest collection time** — semantic errors fail collection and warnings are reported — so `pytest --collect-only` validates every scenario in your suite.
228
+
229
+ For deeper, opt-in checks, add `--deep`: it imports your `module:func` references to confirm they resolve, checks their call signatures (including the injected `response` for save/verify functions), and verifies referenced files and schemas exist. Because it imports your code it is never run at collection time; pair it with `--strict` to fail CI on any warning, and `--syspath` to add import roots:
230
+
231
+ ```bash
232
+ uvx pytest-httpchain validate --deep --strict tests/test_login.http.json
233
+ ```
222
234
 
223
235
  ### Editor schema
224
236
 
@@ -185,13 +185,25 @@ This writes `.claude/skills/pytest-httpchain/SKILL.md` with guidance for writing
185
185
 
186
186
  ### Scenario validation
187
187
 
188
- Validate scenario files for structure and common problems — undefined variables, duplicate stage names, fixture/variable conflicts, stages with no assertions:
188
+ Validate scenario files for structure and common problems — undefined variables, variables referenced before they are saved (data-flow ordering), duplicate stage names, fixture/variable conflicts, no-op `verify` steps, and contradictory body checks:
189
189
 
190
190
  ```bash
191
191
  uvx pytest-httpchain validate tests/test_login.http.json
192
192
  ```
193
193
 
194
- It exits non-zero when any file is invalid, so it doubles as a CI gate. The same checks also run automatically at **pytest collection time** — semantic errors fail collection and warnings are reported — so `pytest --collect-only` validates every scenario in your suite.
194
+ Each finding carries a stable diagnostic code (`HTTPCHAINxxx`) and a severity. It exits non-zero when any file is invalid, so it doubles as a CI gate. Use `--format json` for machine-readable output (editor/CI integration):
195
+
196
+ ```bash
197
+ uvx pytest-httpchain validate --format json tests/test_login.http.json
198
+ ```
199
+
200
+ The same checks also run automatically at **pytest collection time** — semantic errors fail collection and warnings are reported — so `pytest --collect-only` validates every scenario in your suite.
201
+
202
+ For deeper, opt-in checks, add `--deep`: it imports your `module:func` references to confirm they resolve, checks their call signatures (including the injected `response` for save/verify functions), and verifies referenced files and schemas exist. Because it imports your code it is never run at collection time; pair it with `--strict` to fail CI on any warning, and `--syspath` to add import roots:
203
+
204
+ ```bash
205
+ uvx pytest-httpchain validate --deep --strict tests/test_login.http.json
206
+ ```
195
207
 
196
208
  ### Editor schema
197
209
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pytest-httpchain"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "pytest plugin for HTTP testing using JSON files"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13,<4.0"
@@ -0,0 +1,83 @@
1
+ import enum
2
+ import json
3
+ from pathlib import Path
4
+ from typing import Annotated
5
+
6
+ import typer
7
+
8
+ app = typer.Typer()
9
+
10
+ SKILL_FILE = Path(__file__).parent / "skill.md"
11
+
12
+
13
+ class OutputFormat(enum.StrEnum):
14
+ text = "text"
15
+ json = "json"
16
+
17
+
18
+ @app.command()
19
+ def validate(
20
+ paths: Annotated[list[Path], typer.Argument(help="Scenario JSON file(s) to validate.")],
21
+ ref_parent_traversal_depth: Annotated[int, typer.Option(help="Maximum $ref parent directory traversal depth.")] = 3,
22
+ output_format: Annotated[OutputFormat, typer.Option("--format", help="Output format: human-readable text or machine-readable JSON.")] = OutputFormat.text,
23
+ deep: Annotated[bool, typer.Option("--deep", help="Run deep checks: resolve user-function imports/signatures and referenced files. Imports user modules.")] = False,
24
+ syspath: Annotated[list[Path] | None, typer.Option("--syspath", help="Extra directories to add to sys.path for --deep import resolution (repeatable).")] = None,
25
+ strict: Annotated[bool, typer.Option("--strict", help="Treat warnings as failures for the exit code.")] = False,
26
+ ) -> None:
27
+ """Validate pytest-httpchain scenario file(s).
28
+
29
+ Reports errors and warnings (each with a stable HTTPCHAINxxx diagnostic code)
30
+ per file and exits non-zero if any file is invalid (or, with --strict, has any
31
+ warnings).
32
+ """
33
+ from pytest_httpchain.validation import validate_scenario
34
+
35
+ results = [(path, validate_scenario(path, ref_parent_traversal_depth=ref_parent_traversal_depth, deep=deep, syspaths=list(syspath or []))) for path in paths]
36
+
37
+ def passed(result) -> bool:
38
+ return result.valid and not (strict and result.warnings)
39
+
40
+ all_passed = all(passed(result) for _, result in results)
41
+
42
+ if output_format is OutputFormat.json:
43
+ payload = {
44
+ "valid": all_passed,
45
+ "files": [{"path": str(path), "result": result.model_dump()} for path, result in results],
46
+ }
47
+ typer.echo(json.dumps(payload, indent=2, default=str))
48
+ else:
49
+ for path, result in results:
50
+ if not result.valid:
51
+ status = "INVALID"
52
+ elif result.warnings:
53
+ status = "FAILED (warnings)" if strict else "OK with warnings"
54
+ else:
55
+ status = "OK"
56
+ typer.echo(f"{path}: {status}")
57
+ for diagnostic in result.diagnostics:
58
+ typer.echo(f" {diagnostic.severity} [{diagnostic.code}]: {diagnostic.message}")
59
+
60
+ raise typer.Exit(0 if all_passed else 1)
61
+
62
+
63
+ @app.command()
64
+ def install(
65
+ global_: Annotated[bool, typer.Option("--global", "-g", help="Install to ~/.claude (personal scope) instead of project")] = False,
66
+ project_dir: Annotated[Path, typer.Option(help="Project directory (ignored with --global)")] = Path("."),
67
+ ) -> None:
68
+ """Install the Claude Code skill for authoring test scenarios."""
69
+ if global_:
70
+ _install_skill(Path.home() / ".claude" / "skills" / "pytest-httpchain")
71
+ else:
72
+ _install_skill(project_dir.resolve() / ".claude" / "skills" / "pytest-httpchain")
73
+
74
+
75
+ def _install_skill(skill_dir: Path) -> None:
76
+ skill_dir.mkdir(parents=True, exist_ok=True)
77
+ dest = skill_dir / "SKILL.md"
78
+ dest.write_text(SKILL_FILE.read_text())
79
+ typer.echo(f"Installed skill to {dest}")
80
+
81
+
82
+ if __name__ == "__main__":
83
+ app()
@@ -66,12 +66,15 @@ class JsonModule(python.Module):
66
66
  raise nodes.Collector.CollectError(full_error_msg) from None
67
67
 
68
68
  # semantic validation: cross-cutting checks the schema cannot express
69
- # (duplicate stage names, fixture/variable conflicts, undefined variables, ...)
70
- semantic_errors, semantic_warnings, _ = check_scenario(scenario, test_data)
71
- for warning in semantic_warnings:
72
- warnings.warn(ScenarioValidationWarning(f"{self.path}: {warning}"), stacklevel=2)
73
- if semantic_errors:
74
- detail = "\n".join(f" - {e}" for e in semantic_errors)
69
+ # (duplicate stage names, fixture/variable conflicts, undefined/forward-referenced
70
+ # variables, no-op verify, contradictory body checks, ...)
71
+ diagnostics, _ = check_scenario(scenario, test_data)
72
+ for diagnostic in diagnostics:
73
+ if diagnostic.severity == "warning":
74
+ warnings.warn(ScenarioValidationWarning(f"{self.path}: [{diagnostic.code}] {diagnostic.message}"), stacklevel=2)
75
+ error_diagnostics = [d for d in diagnostics if d.severity == "error"]
76
+ if error_diagnostics:
77
+ detail = "\n".join(f" - [{d.code}] {d.message}" for d in error_diagnostics)
75
78
  raise nodes.Collector.CollectError(f"Invalid test scenario in {self.path}:\n{detail}")
76
79
 
77
80
  # generate python test class
@@ -255,3 +255,28 @@ Or iterate over parameter sets in parallel:
255
255
  ]
256
256
  }
257
257
  ```
258
+
259
+ ## Validate your scenario
260
+
261
+ After writing a scenario, validate it (no server or network needed):
262
+
263
+ ```bash
264
+ pytest-httpchain validate test_<name>.http.json
265
+ ```
266
+
267
+ It checks structure plus semantics a JSON Schema cannot, each with a stable `HTTPCHAINxxx` code:
268
+
269
+ - `HTTPCHAIN003` — a `{{ var }}` that is never defined, saved, or provided as a fixture (likely a typo).
270
+ - `HTTPCHAIN004` — a variable used **before** the stage that saves it, or used in a stage's request when it is only saved in that same stage's response. Remember: a value `save`d in a stage's response is available to *later* response steps and *later* stages, never to the request that produced it.
271
+ - `HTTPCHAIN006` — a `verify` step that asserts nothing.
272
+ - `HTTPCHAIN007` / `HTTPCHAIN008` — body `contains`/`not_contains` (or `matches`/`not_matches`) that list the same value, which can never pass.
273
+
274
+ Add `--format json` for machine-readable output. The same checks run automatically during `pytest --collect-only`.
275
+
276
+ For a deeper check that imports your `module:func` references (confirming they resolve and their signatures match — including the injected `response` for save/verify functions) and verifies referenced files/schemas exist, add `--deep` (optionally `--syspath <dir>` for import roots, `--strict` to fail on warnings):
277
+
278
+ ```bash
279
+ pytest-httpchain validate --deep test_<name>.http.json
280
+ ```
281
+
282
+ Note: the HTTP response is **not** ambient in `{{ }}` templates — `save` what you need from a response first, then reference the saved variable.