Spaces:
Running
Running
| """Integration tests for the /api/operators routes.""" | |
| from __future__ import annotations | |
| from unittest.mock import patch | |
| import pytest | |
| from fastapi.testclient import TestClient | |
| from polyglot_alpha.api.main import create_app | |
| def client(isolated_db) -> TestClient: | |
| """Create a TestClient with an isolated SQLite DB.""" | |
| app = create_app() | |
| return TestClient(app) | |
| def _stub_chain_register(*_args, **_kwargs): | |
| return {"stake_tx": "0xabc123stake", "register_tx": "0xdef456register"} | |
| def test_register_operator_creates_row_and_returns_seed_repuration( | |
| client: TestClient, | |
| ) -> None: | |
| with patch( | |
| "polyglot_alpha.api.routes.operators._try_register_on_chain", | |
| side_effect=_stub_chain_register, | |
| ): | |
| resp = client.post( | |
| "/api/operators/register", | |
| json={ | |
| "operator_address": "0xtestop001", | |
| "display_name": "Test Operator", | |
| "signature": "0xdeadbeef", | |
| }, | |
| ) | |
| assert resp.status_code == 200, resp.text | |
| body = resp.json() | |
| assert body["status"] == "registered" | |
| assert body["stake_tx"] == "0xabc123stake" | |
| assert body["reputation_tx"] == "0xdef456register" | |
| assert body["initial_reputation"] == 0.7 | |
| assert body["auction_stream_url"] == "/sse/auctions" | |
| assert body["operator_address"] == "0xtestop001" | |
| def test_register_operator_idempotent_for_new_address(client: TestClient) -> None: | |
| """Re-registering an address with 0 bids/wins is allowed (only commits one row).""" | |
| with patch( | |
| "polyglot_alpha.api.routes.operators._try_register_on_chain", | |
| side_effect=_stub_chain_register, | |
| ): | |
| client.post( | |
| "/api/operators/register", | |
| json={ | |
| "operator_address": "0xtestop002", | |
| "display_name": "Test 2", | |
| "signature": "0xab", | |
| }, | |
| ) | |
| # Second call with no bids -> not yet 409. | |
| resp = client.post( | |
| "/api/operators/register", | |
| json={ | |
| "operator_address": "0xtestop002", | |
| "display_name": "Test 2", | |
| "signature": "0xab", | |
| }, | |
| ) | |
| assert resp.status_code == 200 | |
| def test_list_operators_returns_seeders_and_externals(client: TestClient) -> None: | |
| with patch( | |
| "polyglot_alpha.api.routes.operators._try_register_on_chain", | |
| side_effect=_stub_chain_register, | |
| ): | |
| client.post( | |
| "/api/operators/register", | |
| json={ | |
| "operator_address": "0xtestop003", | |
| "display_name": "External", | |
| "signature": "0xab", | |
| }, | |
| ) | |
| resp = client.get("/api/operators") | |
| assert resp.status_code == 200 | |
| operators = resp.json() | |
| assert isinstance(operators, list) | |
| # The external operator we just registered must appear with kind=external. | |
| matches = [o for o in operators if o["address"] == "0xtestop003"] | |
| assert len(matches) == 1 | |
| assert matches[0]["kind"] == "external" | |
| def test_get_operator_returns_arc_explorer_url(client: TestClient) -> None: | |
| with patch( | |
| "polyglot_alpha.api.routes.operators._try_register_on_chain", | |
| side_effect=_stub_chain_register, | |
| ): | |
| client.post( | |
| "/api/operators/register", | |
| json={ | |
| "operator_address": "0xtestop004", | |
| "display_name": "Lookup Target", | |
| "signature": "0xab", | |
| }, | |
| ) | |
| resp = client.get("/api/operators/0xtestop004") | |
| assert resp.status_code == 200 | |
| body = resp.json() | |
| assert "arc_explorer_url" in body | |
| assert "testnet.arc.network" in body["arc_explorer_url"] | |
| assert body["kind"] == "external" | |
| assert body["reputation"] == 0.7 | |
| def test_get_unknown_operator_returns_404(client: TestClient) -> None: | |
| resp = client.get("/api/operators/0xdoesnotexist1234567890abcdef12345678") | |
| assert resp.status_code == 404 | |
| def test_register_operator_mock_mode_returns_sim_tx_and_no_chain_call( | |
| client: TestClient, | |
| ) -> None: | |
| """Mock mode must skip the chain helper and return ``0xsim_`` tx hashes.""" | |
| with patch( | |
| "polyglot_alpha.api.routes.operators._try_register_on_chain" | |
| ) as chain_mock: | |
| resp = client.post( | |
| "/api/operators/register", | |
| json={ | |
| "operator_address": "0xtestop900", | |
| "display_name": "Mock Operator", | |
| "model_label": "claude-opus-4-7", | |
| "languages": ["zh", "ja"], | |
| "stake_amount_usdc": 100.0, | |
| "mode": "mock", | |
| }, | |
| ) | |
| assert resp.status_code == 200, resp.text | |
| body = resp.json() | |
| assert body["is_simulated"] is True | |
| assert body["success"] is True | |
| assert body["stake_tx"].startswith("0xsim_") | |
| assert body["reputation_tx"].startswith("0xsim_") | |
| assert body["registration_id"].startswith("reg_") | |
| chain_mock.assert_not_called() | |
| def test_register_operator_rejects_unknown_language(client: TestClient) -> None: | |
| resp = client.post( | |
| "/api/operators/register", | |
| json={ | |
| "operator_address": "0xtestop901", | |
| "display_name": "Bad Langs", | |
| "languages": ["zh", "klingon"], | |
| "mode": "mock", | |
| }, | |
| ) | |
| assert resp.status_code == 422 | |
| assert "unsupported_language_codes" in resp.text | |
| def test_pending_fees_returns_cumulative_for_operator( | |
| client: TestClient, isolated_db | |
| ) -> None: | |
| """``/operators/{addr}/pending-fees`` mirrors AgentReputation.cumulative_fees.""" | |
| from datetime import datetime, timezone | |
| from polyglot_alpha.persistence.db import session_scope | |
| from polyglot_alpha.persistence.models import AgentReputation | |
| addr = "0xtestop910" | |
| with session_scope() as s: | |
| s.add( | |
| AgentReputation( | |
| agent_address=addr, | |
| avg_quality=0.8, | |
| cumulative_fees=42.5, | |
| last_updated=datetime.now(timezone.utc), | |
| ) | |
| ) | |
| resp = client.get(f"/api/operators/{addr}/pending-fees") | |
| assert resp.status_code == 200, resp.text | |
| body = resp.json() | |
| assert body["operator_address"] == addr | |
| assert body["pending_usdc"] == 42.5 | |
| assert body["event_count"] == 0 | |
| def test_claim_fees_mock_mode_zeros_balance_and_returns_sim_tx( | |
| client: TestClient, isolated_db | |
| ) -> None: | |
| from datetime import datetime, timezone | |
| from polyglot_alpha.persistence.db import session_scope | |
| from polyglot_alpha.persistence.models import AgentReputation | |
| addr = "0xtestop911" | |
| with session_scope() as s: | |
| s.add( | |
| AgentReputation( | |
| agent_address=addr, | |
| avg_quality=0.8, | |
| cumulative_fees=17.25, | |
| last_updated=datetime.now(timezone.utc), | |
| ) | |
| ) | |
| resp = client.post( | |
| f"/api/operators/{addr}/claim-fees", | |
| json={"mode": "mock"}, | |
| ) | |
| assert resp.status_code == 200, resp.text | |
| body = resp.json() | |
| assert body["success"] is True | |
| assert body["is_simulated"] is True | |
| assert body["amount_claimed_usdc"] == 17.25 | |
| assert body["tx_hash"].startswith("0xsim_") | |
| # Subsequent claim should be a no-op. | |
| follow = client.post( | |
| f"/api/operators/{addr}/claim-fees", | |
| json={"mode": "mock"}, | |
| ) | |
| assert follow.status_code == 200 | |
| assert follow.json()["success"] is False | |
| assert follow.json()["amount_claimed_usdc"] == 0.0 | |
| def test_claim_fees_returns_404_for_unknown_operator(client: TestClient) -> None: | |
| resp = client.post( | |
| "/api/operators/0xnotanoperator9999999999999999999999999/claim-fees", | |
| json={"mode": "mock"}, | |
| ) | |
| assert resp.status_code == 404 | |
| def test_relay_bid_endpoint_is_phase_2_stub(client: TestClient) -> None: | |
| resp = client.post( | |
| "/api/auctions/evt-42/bid", | |
| json={ | |
| "operator_address": "0xtestop005", | |
| "bid_amount_usdc": 1.25, | |
| "candidate_hash": "0x" + "ab" * 32, | |
| "signature": "0xsig", | |
| }, | |
| ) | |
| assert resp.status_code == 200 | |
| body = resp.json() | |
| assert body["status"] == "phase_2_stub" | |
| assert body["tx_hash"] is None | |