| """ |
| Tests for Stage 70 β bundle CLI. |
| |
| python -m delivery.bundle create DIR [--out PATH] |
| python -m delivery.bundle verify ZIP |
| |
| Recipient who got a bundle.zip in an email can verify in one shell |
| command. Exit-code contract: |
| 0 β create succeeded OR verify passed (manifest intact) |
| 1 β verify failed (sha256 mismatch on at least one file) |
| 2 β bad args / file not found / not a zip |
| """ |
| import json |
| import zipfile |
|
|
| from delivery.bundle import _cli, bundle_pilot_output |
|
|
|
|
| def _populate(tmp_path): |
| src = tmp_path / "pilot_out" |
| src.mkdir() |
| (src / "report.html").write_text("<h1>R</h1>", encoding="utf-8") |
| (src / "queue.html").write_text("<h1>Q</h1>", encoding="utf-8") |
| return src |
|
|
|
|
| def _run(capsys, *args): |
| capsys.readouterr() |
| rc = _cli(list(args)) |
| out, err = capsys.readouterr() |
| body = json.loads(out) if out.strip() else None |
| err_body = json.loads(err) if err.strip() else None |
| return rc, body, err_body |
|
|
|
|
| |
|
|
| def test_create_emits_bundle_path_and_writes_zip(tmp_path, capsys): |
| src = _populate(tmp_path) |
| rc, body, _ = _run(capsys, "create", str(src)) |
| assert rc == 0 |
| assert "bundle_path" in body |
| zip_path = tmp_path / "pilot_out.zip" |
| assert zip_path.exists() |
|
|
|
|
| def test_create_with_explicit_out(tmp_path, capsys): |
| src = _populate(tmp_path) |
| out = tmp_path / "custom.zip" |
| rc, body, _ = _run(capsys, "create", str(src), "--out", str(out)) |
| assert rc == 0 |
| assert body["bundle_path"] == str(out.resolve()) |
| assert out.exists() |
|
|
|
|
| def test_create_missing_dir_exits_2(tmp_path, capsys): |
| rc, _, err = _run(capsys, "create", str(tmp_path / "nope")) |
| assert rc == 2 |
| assert "does not exist" in err["error"] |
|
|
|
|
| |
|
|
| def test_verify_intact_bundle_exits_0(tmp_path, capsys): |
| src = _populate(tmp_path) |
| zip_path = bundle_pilot_output(src) |
| rc, body, _ = _run(capsys, "verify", str(zip_path)) |
| assert rc == 0 |
| assert body["ok"] is True |
| assert body["n_mismatches"] == 0 |
|
|
|
|
| def test_verify_tampered_bundle_exits_1(tmp_path, capsys): |
| """The recipient's smoking gun β if anyone modified a file in the |
| bundle, verify returns exit 1 + ok=false + the mismatching paths |
| in the JSON. Cron-friendly: pipe to ``jq .ok`` and gate on it.""" |
| src = _populate(tmp_path) |
| src_zip = bundle_pilot_output(src) |
| tampered = tmp_path / "tampered.zip" |
| with zipfile.ZipFile(src_zip, "r") as zin, \ |
| zipfile.ZipFile(tampered, "w", zipfile.ZIP_DEFLATED) as zout: |
| for name in zin.namelist(): |
| data = zin.read(name) |
| if name == "report.html": |
| data = b"<h1>EVIL</h1>" |
| zout.writestr(name, data) |
| rc, body, _ = _run(capsys, "verify", str(tampered)) |
| assert rc == 1 |
| assert body["ok"] is False |
| assert body["n_mismatches"] == 1 |
| assert body["mismatches"][0]["path"] == "report.html" |
|
|
|
|
| def test_verify_missing_file_exits_2(tmp_path, capsys): |
| rc, _, err = _run(capsys, "verify", str(tmp_path / "no_such.zip")) |
| assert rc == 2 |
| assert "error" in err |
|
|
|
|
| def test_verify_non_zip_exits_2(tmp_path, capsys): |
| """A path that exists but isn't a zip β exit 2 with a clear |
| 'not a valid zip' message. Operator pointing at the wrong file |
| sees the right error, not a Python traceback.""" |
| bad = tmp_path / "not_a_zip.txt" |
| bad.write_text("plain text not a zip", encoding="utf-8") |
| rc, _, err = _run(capsys, "verify", str(bad)) |
| assert rc == 2 |
| assert "not a valid zip" in err["error"] |
|
|