Spaces:
Sleeping
Sleeping
sal
test: update identity tests for on-device commitment API (M2 fix) and add malformed-commitment case
68de54f | """Tests for FastAPI endpoints (api/main.py). | |
| Uses FastAPI TestClient β no server startup needed. | |
| """ | |
| import hashlib | |
| import pytest | |
| from fastapi.testclient import TestClient | |
| from api.main import app | |
| def c(): | |
| return TestClient(app) | |
| # ββ Root ββ | |
| class TestRoot: | |
| def test_200(self, c): assert c.get("/").status_code == 200 | |
| def test_project(self, c): assert c.get("/").json()["project"] == "ZeroSense" | |
| def test_status(self, c): assert c.get("/").json()["status"] == "live" | |
| def test_hackathon(self, c): assert "Stellar Hacks" in c.get("/").json()["hackathon"] | |
| # ββ Generate Proof ββ | |
| class TestGenerateProof: | |
| def _req(self, c, pixels=None, task_id=None): | |
| body = { | |
| "robot_id": "robot-001", | |
| "sensor_frames": [{"pixels": pixels or [0.8]*64, "frame_id": 0}], | |
| } | |
| if task_id: | |
| body["task_id"] = task_id | |
| return c.post("/generate-proof", json=body) | |
| def test_200(self, c): assert self._req(c).status_code == 200 | |
| def test_has_task_id(self, c): assert "task_id" in self._req(c).json() | |
| def test_has_proof_hex(self, c): assert "proof_hex" in self._req(c).json() | |
| def test_has_confidence(self, c): assert "confidence" in self._req(c).json() | |
| def test_has_action_label(self, c): assert "action_label" in self._req(c).json() | |
| def test_has_input_hash(self, c): assert "input_hash" in self._req(c).json() | |
| def test_confidence_range(self, c): | |
| assert 0 <= self._req(c).json()["confidence"] <= 100 | |
| def test_action_label_valid(self, c): | |
| assert self._req(c).json()["action_label"] in ("task_complete","obstacle_detected","incident") | |
| def test_explicit_task_id(self, c): | |
| r = self._req(c, task_id="mytask00000000000000000000000001") | |
| assert r.json()["task_id"] == "mytask00000000000000000000000001" | |
| def test_mock_proof_512_chars(self, c): | |
| assert len(self._req(c).json()["proof_hex"]) == 512 | |
| def test_multi_frame(self, c): | |
| r = c.post("/generate-proof", json={ | |
| "robot_id": "robot-001", | |
| "sensor_frames": [ | |
| {"pixels": [0.8]*64, "frame_id": 0}, | |
| {"pixels": [0.9]*64, "frame_id": 1}, | |
| ] | |
| }) | |
| assert r.status_code == 200 | |
| # ββ Verify Proof ββ | |
| class TestVerifyProof: | |
| def _verify(self, c, confidence=97, task_id="task_v_001"): | |
| return c.post("/verify-proof", json={ | |
| "robot_id": "robot-001", "task_id": task_id, | |
| "proof_hex": "0"*512, "model_hash": "mobilenet_v2_int8", | |
| "confidence": confidence, "action_type": 0, | |
| }) | |
| def test_200(self, c): assert self._verify(c).status_code == 200 | |
| def test_status_verified(self, c): assert self._verify(c).json()["status"] == "verified" | |
| def test_auto_pay_high_conf(self, c): assert self._verify(c, 98, "task_v_002").json()["auto_payment"] is True | |
| def test_no_auto_pay_low_conf(self, c): assert self._verify(c, 80, "task_v_003").json()["auto_payment"] is False | |
| def test_has_stellar_tx(self, c): assert self._verify(c, task_id="task_v_004").json()["stellar_tx"] is not None | |
| # ββ Claim Payment ββ | |
| class TestClaimPayment: | |
| def test_200(self, c): | |
| assert c.post("/claim-payment", json={"task_id":"task_c_001","confidence":96}).status_code == 200 | |
| def test_paid_status(self, c): | |
| assert c.post("/claim-payment", json={"task_id":"task_c_002","confidence":97}).json()["status"] == "paid" | |
| def test_has_stellar_tx(self, c): | |
| assert "stellar_tx" in c.post("/claim-payment", json={"task_id":"task_c_003","confidence":95}).json() | |
| # ββ Insurance Claim ββ | |
| class TestInsurance: | |
| def test_200(self, c): | |
| r = c.post("/file-insurance-claim", json={ | |
| "robot_id":"robot-001","incident_proof_hash":"abc"*21+"d","claim_amount":1_000_000 | |
| }) | |
| assert r.status_code == 200 | |
| def test_filed_status(self, c): | |
| r = c.post("/file-insurance-claim", json={ | |
| "robot_id":"robot-001","incident_proof_hash":"def"*21+"g","claim_amount":500_000 | |
| }) | |
| assert r.json()["status"] == "filed" | |
| # ββ Robot Identity ββ | |
| # SECURITY (M2 fix): the API now only accepts a pre-computed on-device SHA256 | |
| # commitment β never the raw sensor noise sample. `_commitment()` here stands | |
| # in for the hashing a robot's firmware would do locally before ever calling | |
| # the endpoint (see `reference_ondevice_commitment_example` in api/main.py). | |
| class TestIdentity: | |
| def _commitment(seed: str) -> str: | |
| return hashlib.sha256(seed.encode()).hexdigest() | |
| def test_200(self, c): | |
| r = c.post("/robot/register-identity", json={ | |
| "robot_id": "robot-001", | |
| "fingerprint_commitment": self._commitment("robot-001-noise"), | |
| }) | |
| assert r.status_code == 200 | |
| def test_has_identity_hash(self, c): | |
| r = c.post("/robot/register-identity", json={ | |
| "robot_id": "robot-002", | |
| "fingerprint_commitment": self._commitment("robot-002-noise"), | |
| }) | |
| assert len(r.json()["identity_hash"]) == 64 | |
| def test_different_robots_different_hashes(self, c): | |
| r1 = c.post("/robot/register-identity", json={ | |
| "robot_id": "rA", "fingerprint_commitment": self._commitment("noise-a"), | |
| }) | |
| r2 = c.post("/robot/register-identity", json={ | |
| "robot_id": "rB", "fingerprint_commitment": self._commitment("noise-b"), | |
| }) | |
| assert r1.json()["identity_hash"] != r2.json()["identity_hash"] | |
| def test_rejects_malformed_commitment(self, c): | |
| r = c.post("/robot/register-identity", json={ | |
| "robot_id": "rC", "fingerprint_commitment": "not-a-valid-hex-digest", | |
| }) | |
| assert r.status_code == 400 | |
| def test_rejects_wrong_length_commitment(self, c): | |
| r = c.post("/robot/register-identity", json={ | |
| "robot_id": "rD", "fingerprint_commitment": "ab" * 10, | |
| }) | |
| assert r.status_code == 400 | |
| # ββ Robot Status + Fleet ββ | |
| class TestStatus: | |
| def test_robot_status_200(self, c): assert c.get("/robot/robot-001/status").status_code == 200 | |
| def test_robot_has_fields(self, c): | |
| d = c.get("/robot/robot-001/status").json() | |
| assert "robot_id" in d and "zrep_balance" in d | |
| def test_fleet_200(self, c): assert c.get("/fleet/report").status_code == 200 | |
| def test_fleet_has_fields(self, c): | |
| d = c.get("/fleet/report").json() | |
| assert "total_tasks_verified" in d and "guardian_status" in d | |
| # ββ Guardian ββ | |
| class TestGuardian: | |
| def test_start_200(self, c): assert c.post("/guardian/start").status_code == 200 | |
| def test_start_status(self, c): | |
| assert c.post("/guardian/start").json()["status"] in ("started","already_running") | |
| def test_stop_200(self, c): assert c.post("/guardian/stop").status_code == 200 | |
| def test_stop_status(self, c): assert c.post("/guardian/stop").json()["status"] == "stopped" | |
| def test_agents_count(self, c): | |
| c.post("/guardian/stop") | |
| r = c.post("/guardian/start") | |
| if "agents" in r.json(): | |
| assert len(r.json()["agents"]) == 7 | |
| c.post("/guardian/stop") | |