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.
- {pytest_httpchain-0.3.0 → pytest_httpchain-0.4.0}/PKG-INFO +15 -3
- {pytest_httpchain-0.3.0 → pytest_httpchain-0.4.0}/README.md +14 -2
- {pytest_httpchain-0.3.0 → pytest_httpchain-0.4.0}/pyproject.toml +1 -1
- pytest_httpchain-0.4.0/src/pytest_httpchain/cli.py +83 -0
- {pytest_httpchain-0.3.0 → pytest_httpchain-0.4.0}/src/pytest_httpchain/plugin.py +9 -6
- {pytest_httpchain-0.3.0 → pytest_httpchain-0.4.0}/src/pytest_httpchain/skill.md +25 -0
- pytest_httpchain-0.4.0/src/pytest_httpchain/validation.py +814 -0
- pytest_httpchain-0.3.0/src/pytest_httpchain/cli.py +0 -59
- pytest_httpchain-0.3.0/src/pytest_httpchain/validation.py +0 -272
- {pytest_httpchain-0.3.0 → pytest_httpchain-0.4.0}/LICENSE +0 -0
- {pytest_httpchain-0.3.0 → pytest_httpchain-0.4.0}/src/pytest_httpchain/__init__.py +0 -0
- {pytest_httpchain-0.3.0 → pytest_httpchain-0.4.0}/src/pytest_httpchain/carrier.py +0 -0
- {pytest_httpchain-0.3.0 → pytest_httpchain-0.4.0}/src/pytest_httpchain/constants.py +0 -0
- {pytest_httpchain-0.3.0 → pytest_httpchain-0.4.0}/src/pytest_httpchain/exceptions.py +0 -0
- {pytest_httpchain-0.3.0 → pytest_httpchain-0.4.0}/src/pytest_httpchain/har_writer.py +0 -0
- {pytest_httpchain-0.3.0 → pytest_httpchain-0.4.0}/src/pytest_httpchain/report_formatter.py +0 -0
- {pytest_httpchain-0.3.0 → pytest_httpchain-0.4.0}/src/pytest_httpchain/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-httpchain
|
|
3
|
-
Version: 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,
|
|
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.
|
|
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,
|
|
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.
|
|
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
|
|
|
@@ -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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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.
|