Pyramid web app for inspecting arborist Merkle trees over sharded SQLite: document roots, inclusion proofs, an interactive 3D Merkle lattice (six.js / WebGL with graceful fallback), audit-chain linkage, and a live SSE event stream. Generated artifacts (env/, vendored static/six/ bundles, demo databases) are gitignored and rebuilt via the Makefile (venv, six, init-db, serve).
237 lines
7.6 KiB
Python
237 lines
7.6 KiB
Python
"""End-to-end smoke tests against a real arborist demo shard."""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
# ---------- meta + home ------------------------------------------------
|
|
|
|
|
|
def test_version_endpoint(app):
|
|
res = app.get("/version")
|
|
assert res.status_code == 200
|
|
assert res.json["service"] == "arborist-viz"
|
|
assert "git_commit" in res.json
|
|
|
|
|
|
def test_home_renders(app):
|
|
res = app.get("/")
|
|
assert res.status_code == 200
|
|
assert b"arborist VIZ" in res.body
|
|
assert b"Merkle Command Center" in res.body
|
|
assert b"hash-drop" in res.body # hash-drop bar always present
|
|
|
|
|
|
def test_robots(app):
|
|
res = app.get("/robots.txt")
|
|
assert res.status_code == 200
|
|
|
|
|
|
# ---------- dashboards ------------------------------------------------
|
|
|
|
|
|
def test_dashboards_list(app):
|
|
res = app.get("/api/dashboards")
|
|
assert res.status_code == 200
|
|
ids = {d["id"] for d in res.json["dashboards"]}
|
|
assert {"default", "graveyard", "proof"} <= ids
|
|
|
|
|
|
def test_canned_dashboards_have_source_roots(app):
|
|
for dashboard_id in ("default", "graveyard", "proof"):
|
|
res = app.get(f"/api/dashboards/{dashboard_id}")
|
|
assert res.status_code == 200
|
|
cfg = res.json
|
|
assert cfg["id"] == dashboard_id
|
|
for w in cfg["widgets"]:
|
|
assert "source_roots" in w
|
|
assert "query" in w
|
|
|
|
|
|
def test_proof_dashboard_includes_lattice_widget(app):
|
|
res = app.get("/api/dashboards/proof")
|
|
types = {w["type"] for w in res.json["widgets"]}
|
|
assert "merkle-lattice-3d" in types
|
|
assert "merkle-proof" in types
|
|
|
|
|
|
def test_create_and_delete_dashboard(app):
|
|
body = {"id": "test-fresh", "title": "Test", "widgets": []}
|
|
res = app.post_json("/api/dashboards", body)
|
|
assert res.status_code == 200
|
|
assert res.json["created"]["id"] == "test-fresh"
|
|
|
|
res2 = app.get("/api/dashboards/test-fresh")
|
|
assert res2.status_code == 200
|
|
|
|
res3 = app.delete("/api/dashboards/test-fresh")
|
|
assert res3.status_code == 200
|
|
|
|
|
|
# ---------- roots / leaves / lattice ----------------------------------
|
|
|
|
|
|
def test_root_endpoint(app, demo_facts):
|
|
res = app.get(f"/api/root/{demo_facts['root']}")
|
|
assert res.status_code == 200
|
|
assert res.json["root_hash"] == demo_facts["root"]
|
|
assert res.json["leaf_count"] >= 1
|
|
assert res.json["source_roots"] == [demo_facts["root"]]
|
|
|
|
|
|
def test_root_not_found(app):
|
|
res = app.get("/api/root/" + "0" * 64, expect_errors=True)
|
|
assert res.status_code == 404
|
|
|
|
|
|
def test_leaves_reveal_in_dev(app, demo_facts):
|
|
"""development.ini sets privacy.reveal_private_leaves=true so the
|
|
operator can inspect their own corpus. production.ini keeps the
|
|
default-deny posture per §14 — see test_production_ini_is_deny."""
|
|
res = app.get(f"/api/root/{demo_facts['root']}/leaves")
|
|
assert res.status_code == 200
|
|
assert res.json["private_revealed"] is True
|
|
# At least one leaf must carry content under reveal mode.
|
|
assert any(leaf["content"] for leaf in res.json["leaves"])
|
|
|
|
|
|
def test_production_ini_is_deny():
|
|
"""production.ini must remain fail-closed for §14."""
|
|
import re
|
|
with open("production.ini") as f:
|
|
text = f.read()
|
|
m = re.search(r"privacy\.reveal_private_leaves\s*=\s*(\S+)", text)
|
|
assert m, "production.ini must set privacy.reveal_private_leaves"
|
|
assert m.group(1).lower() == "false", "production.ini must keep private leaves deny by default"
|
|
|
|
|
|
def test_lattice_returns_layers(app, demo_facts):
|
|
res = app.get(f"/api/lattice/{demo_facts['root']}")
|
|
assert res.status_code == 200
|
|
assert res.json["layer_count"] >= 1
|
|
assert res.json["leaf_count"] >= 1
|
|
# Root layer always has exactly one node.
|
|
assert len(res.json["layers"][-1]) == 1
|
|
assert res.json["layers"][-1][0] == demo_facts["root"]
|
|
|
|
|
|
# ---------- proof ------------------------------------------------------
|
|
|
|
|
|
def test_proof_passes_against_arborist_merkle(app, demo_facts):
|
|
res = app.get(f"/api/proof/{demo_facts['root']}/{demo_facts['leaf_index']}")
|
|
assert res.status_code == 200
|
|
assert res.json["passed"] is True
|
|
assert res.json["computed_root"] == demo_facts["root"]
|
|
assert res.json["leaf_hash"] == demo_facts["leaf_hash"]
|
|
|
|
|
|
def test_proof_leaf_out_of_range(app, demo_facts):
|
|
res = app.get(f"/api/proof/{demo_facts['root']}/9999", expect_errors=True)
|
|
assert res.status_code == 400
|
|
|
|
|
|
def test_proof_rejects_non_integer(app, demo_facts):
|
|
res = app.get(f"/api/proof/{demo_facts['root']}/notanint", expect_errors=True)
|
|
assert res.status_code == 400
|
|
|
|
|
|
# ---------- audit ------------------------------------------------------
|
|
|
|
|
|
def test_audit_event(app, demo_facts):
|
|
res = app.get(f"/api/audit/{demo_facts['event_hash']}")
|
|
assert res.status_code == 200
|
|
assert res.json["event_hash"] == demo_facts["event_hash"]
|
|
assert res.json["subject_root"] == demo_facts["root"]
|
|
|
|
|
|
def test_audit_chain(app, demo_facts):
|
|
res = app.get(f"/api/audit/chain/{demo_facts['event_hash']}")
|
|
assert res.status_code == 200
|
|
assert res.json["head_event_hash"] == demo_facts["event_hash"]
|
|
|
|
|
|
def test_audit_recent(app):
|
|
res = app.get("/api/audit/recent?limit=10")
|
|
assert res.status_code == 200
|
|
assert res.json["count"] >= 1
|
|
|
|
|
|
# ---------- hash resolver ---------------------------------------------
|
|
|
|
|
|
def test_resolve_document_root(app, demo_facts):
|
|
res = app.get(f"/api/resolve/{demo_facts['root']}")
|
|
assert res.status_code == 200
|
|
assert res.json["kind"] == "document_root"
|
|
assert res.json["url"] == f"/proof/{demo_facts['root']}"
|
|
|
|
|
|
def test_resolve_leaf_hash(app, demo_facts):
|
|
res = app.get(f"/api/resolve/{demo_facts['leaf_hash']}")
|
|
assert res.status_code == 200
|
|
assert res.json["kind"] == "leaf_hash"
|
|
assert res.json["document_root"] == demo_facts["root"]
|
|
assert res.json["leaf_index"] == demo_facts["leaf_index"]
|
|
|
|
|
|
def test_resolve_audit_event(app, demo_facts):
|
|
res = app.get(f"/api/resolve/{demo_facts['event_hash']}")
|
|
assert res.status_code == 200
|
|
assert res.json["kind"] == "audit_event"
|
|
|
|
|
|
def test_resolve_unknown(app):
|
|
res = app.get("/api/resolve/" + "0" * 64)
|
|
assert res.json["kind"] == "unknown"
|
|
|
|
|
|
def test_resolve_not_hex(app, demo_facts):
|
|
res = app.get("/api/resolve/not_hex_at_all_g!", expect_errors=True)
|
|
assert res.status_code == 400
|
|
|
|
|
|
# ---------- proof page render -----------------------------------------
|
|
|
|
|
|
def test_proof_page_renders(app, demo_facts):
|
|
res = app.get(f"/proof/{demo_facts['root']}")
|
|
assert res.status_code == 200
|
|
assert demo_facts["root"].encode() in res.body
|
|
assert b"lattice-canvas" in res.body
|
|
assert b"mountProofPage" in res.body
|
|
|
|
|
|
def test_audit_page_renders(app, demo_facts):
|
|
res = app.get(f"/audit/{demo_facts['event_hash']}")
|
|
assert res.status_code == 200
|
|
assert demo_facts["event_hash"].encode() in res.body
|
|
|
|
|
|
# ---------- #000062 gate ----------------------------------------------
|
|
|
|
|
|
def test_trace_gate_returns_501(app):
|
|
res = app.get("/api/trace/t1", expect_errors=True)
|
|
assert res.status_code == 501
|
|
|
|
|
|
def test_circuit_gate_returns_501(app):
|
|
res = app.get("/api/circuit/c1", expect_errors=True)
|
|
assert res.status_code == 501
|
|
|
|
|
|
# ---------- diff -------------------------------------------------------
|
|
|
|
|
|
def test_root_diff(app, demo_facts):
|
|
# demo shard has two docs; diff them.
|
|
res_roots = app.get("/api/roots")
|
|
assert res_roots.status_code == 200
|
|
roots = [r["document_root"] for r in res_roots.json["roots"]]
|
|
assert len(roots) >= 2
|
|
res = app.get(f"/api/diff/{roots[0]}/{roots[1]}")
|
|
assert res.status_code == 200
|
|
assert res.json["root_a"] == roots[0]
|
|
assert res.json["root_b"] == roots[1]
|
|
assert res.json["source_roots"] == [roots[0], roots[1]]
|