"""End-to-end CLI tests: generate -> run -> verify, including failure paths. The CLI contract (see conformance/README.md): fpcf generate --out DIR --seed HEX64 write a vector suite, exit 0 fpcf run --vectors DIR run the suite, exit 0 iff all pass fpcf verify FILE verify one operation file, exit 0 iff it is fully valid """ from __future__ import annotations import json from helpers import ( DEFAULT_SEED, load_json, run_cli, tamper_body, valid_single_ops, write_json, ) def _generate(tmp_path): out = tmp_path / "vectors" rc = run_cli("generate", "--out", str(out), "--seed", DEFAULT_SEED) assert rc == 0 return out def test_generate_writes_a_suite(tmp_path): out = _generate(tmp_path) assert (out / "manifest.json").is_file() manifest = load_json(out / "manifest.json") assert manifest["format"] == "fpcf-vectors/1" assert manifest["vectors"] def test_generate_then_run_succeeds(tmp_path): out = _generate(tmp_path) assert run_cli("run", "--vectors", str(out)) == 0 def test_run_fails_on_tampered_suite(tmp_path): out = _generate(tmp_path) manifest = load_json(out / "manifest.json") entry, op = valid_single_ops(out, manifest)[0] write_json(out / entry["file"], tamper_body(op)) assert run_cli("run", "--vectors", str(out)) != 0 def test_verify_accepts_a_valid_operation(tmp_path): out = _generate(tmp_path) manifest = load_json(out / "manifest.json") entry, _ = valid_single_ops(out, manifest)[0] assert run_cli("verify", str(out / entry["file"])) == 0 def test_verify_rejects_a_tampered_operation(tmp_path): out = _generate(tmp_path) manifest = load_json(out / "manifest.json") _, op = valid_single_ops(out, manifest)[0] bad_path = tmp_path / "bad-op.json" write_json(bad_path, tamper_body(op)) assert run_cli("verify", str(bad_path)) != 0 def test_verify_rejects_malformed_json(tmp_path): bad_path = tmp_path / "garbage.json" bad_path.write_bytes(b"{not json") assert run_cli("verify", str(bad_path)) != 0 def test_run_fails_on_missing_directory(tmp_path): assert run_cli("run", "--vectors", str(tmp_path / "does-not-exist")) != 0 def test_cli_generation_matches_library_generation(tmp_path): """The CLI must be a thin wrapper: same seed -> same manifest.""" from fpcf import vectors as fpcf_vectors cli_out = _generate(tmp_path) lib_out = tmp_path / "lib-vectors" fpcf_vectors.generate_vectors(lib_out, seed=DEFAULT_SEED) cli_manifest = json.loads((cli_out / "manifest.json").read_text("utf-8")) lib_manifest = json.loads((lib_out / "manifest.json").read_text("utf-8")) assert cli_manifest == lib_manifest