| """Tests for the FastAPI backend.""" |
|
|
| from __future__ import annotations |
|
|
| import pytest |
| from fastapi.testclient import TestClient |
|
|
| from spinor_os.config import EventType, LoopStage |
| from spinor_os.server import app, get_os |
| from spinor_os.assignment import AssignmentProviderRouter, LocalConstrainedAllocationProvider |
| from spinor_os.engine import ExperimentationOS |
| from spinor_os.models import PredictedEffect, Strategy |
|
|
|
|
| @pytest.fixture |
| def client(): |
| """Yield a TestClient with a fresh OS instance.""" |
| with TestClient(app) as c: |
| yield c |
|
|
|
|
| def test_health(client): |
| response = client.get("/health") |
| assert response.status_code == 200 |
| data = response.json() |
| assert data["status"] == "healthy" |
| assert data["server"] == "ok" |
|
|
|
|
| def test_ladder(client): |
| response = client.get("/ladder") |
| assert response.status_code == 200 |
| data = response.json() |
| assert "maturity_ladder" in data |
| assert "canonical_loop" in data |
|
|
|
|
| def test_schema(client): |
| response = client.get("/schema") |
| assert response.status_code == 200 |
| data = response.json() |
| assert "experiment" in data |
| assert "rl_action" in data |
|
|
|
|
| def test_full_experiment_flow(client): |
| os = get_os() |
| |
| r = client.post("/employees", json={ |
| "employee_id": "api-001", |
| "role": "rep", |
| "territory": "northeast", |
| "skills": {"enterprise": 0.8}, |
| }) |
| assert r.status_code == 200 |
|
|
| |
| r = client.post("/hypotheses", json={ |
| "statement": "X increases Y", |
| "causal_claim": "X causes Y to increase.", |
| "predicted_effect": { |
| "metric": "y_rate", |
| "direction": "increase", |
| "magnitude": 0.1, |
| "unit": "pp", |
| "timing": "7d", |
| }, |
| "employee_owner": "api-001", |
| "falsification_criteria": ["Y does not increase"], |
| "customer_segment": "enterprise", |
| "territory": "northeast", |
| "modification": "mod-api", |
| "required_replications": 1, |
| }) |
| assert r.status_code == 200 |
| hypothesis_id = r.json()["hypothesis_id"] |
|
|
| |
| r = client.post("/experiments", json={"hypothesis_id": hypothesis_id}) |
| assert r.status_code == 200 |
| experiment_id = r.json()["experiment_id"] |
|
|
| |
| r = client.post(f"/experiments/{experiment_id}/missions", json={ |
| "employee_id": "api-001", |
| "candidates": [{ |
| "hypothesis_id": hypothesis_id, |
| "modification": "mod-api", |
| "customer_segment": "enterprise", |
| "territory": "northeast", |
| "timing": "2026-W31", |
| "resource_allocation": 1.0, |
| }], |
| }) |
| assert r.status_code == 200 |
| mission_id = r.json()["mission_id"] |
|
|
| |
| r = client.post(f"/experiments/{experiment_id}/events", json={ |
| "mission_id": mission_id, |
| "event_type": "outcome_observed", |
| "actor_id": "api-001", |
| "outcome_value": 0.2, |
| "metric": "y_rate", |
| "execution_quality": 0.85, |
| }) |
| assert r.status_code == 200 |
|
|
| |
| r = client.post(f"/experiments/{experiment_id}/attributes", json={ |
| "outcome_metric": "y_rate", |
| "outcome_value": 0.2, |
| "counterfactual_estimate": 0.05, |
| "method": "rct", |
| "confidence": 0.95, |
| "falsification_survived": True, |
| }) |
| assert r.status_code == 200 |
| claim = r.json() |
| assert claim["causal_effect"] == 0.15 |
|
|
| |
| r = client.post(f"/experiments/{experiment_id}/advance", json={"target": "systemize"}) |
| assert r.status_code == 200 |
| assert r.json()["current_stage"] == "systemize" |
|
|
| |
| r = client.post("/systemize", json={ |
| "experiment_id": experiment_id, |
| "name": "API Strategy", |
| "description": "Validated via API", |
| "automation_ready": True, |
| }) |
| assert r.status_code == 200 |
| strategy_id = r.json()["strategy_id"] |
|
|
| |
| os.golden_registry.rules.required_replications = 1 |
|
|
| r = client.post("/promote", json={ |
| "strategy_id": strategy_id, |
| "mechanism": "Validated via API test", |
| "automation_layer_id": "auto-api", |
| "adverse_retests": 1, |
| }) |
| assert r.status_code == 200 |
| assert r.json()["success"] is True |
|
|
|
|
| |
|
|
|
|
| def test_snapshot_capture(client): |
| r = client.post("/snapshots/capture") |
| assert r.status_code == 200 |
| data = r.json() |
| assert "snapshot_id" in data |
| assert "engine_state" in data |
|
|
|
|
| def test_snapshot_list(client): |
| client.post("/snapshots/capture") |
| r = client.get("/snapshots") |
| assert r.status_code == 200 |
| data = r.json() |
| assert isinstance(data, list) |
| assert len(data) >= 1 |
|
|
|
|
| def test_snapshot_latest(client): |
| client.post("/snapshots/capture") |
| r = client.get("/snapshots/latest") |
| assert r.status_code == 200 |
| assert "snapshot_id" in r.json() |
|
|
|
|
| def test_snapshot_get_by_id(client): |
| cap = client.post("/snapshots/capture").json() |
| sid = cap["snapshot_id"] |
| r = client.get(f"/snapshots/{sid}") |
| assert r.status_code == 200 |
| assert r.json()["snapshot_id"] == sid |
|
|
|
|
| def test_snapshot_ingest(client): |
| payload = { |
| "snapshot_id": "ingest-test-001", |
| "created_at": "2026-01-01T00:00:00Z", |
| "machine": "test-machine", |
| "sequence": 1, |
| "engine_state": {"employees": {}}, |
| "working_tree": {"branch": "main", "head_sha": "abc", "status_short": "", "diff_stat": "", "file_hashes": {}}, |
| "transcript": [], |
| } |
| r = client.post("/snapshots/ingest", json=payload) |
| assert r.status_code == 200 |
| assert r.json()["status"] == "ok" |
|
|
|
|
| |
|
|
|
|
| def test_oracle_provenance(client): |
| r = client.get("/oracle/provenance") |
| assert r.status_code == 200 |
| data = r.json() |
| assert "files" in data |
| assert "attestations" in data |
| assert data["file_count"] >= 1 |
|
|
|
|
| def test_oracle_seo(client): |
| r = client.get("/oracle/seo") |
| assert r.status_code == 200 |
| data = r.json() |
| assert "keywords" in data |
| assert "title_suggestion" in data |
| assert "sitemap_entry" in data |
|
|
|
|
| def test_oracle_full(client): |
| r = client.post("/oracle", json={"base_url": "https://test.local"}) |
| assert r.status_code == 200 |
| data = r.json() |
| assert "provenance" in data |
| assert "attribution_audit" in data |
| assert "seo" in data |
|
|
|
|
| def test_sitemap_xml(client): |
| r = client.get("/oracle/sitemap.xml") |
| assert r.status_code == 200 |
| assert "urlset" in r.text |
|
|
|
|
| def test_robots_txt(client): |
| r = client.get("/robots.txt") |
| assert r.status_code == 200 |
| assert "User-agent" in r.text |
|
|
|
|
| |
|
|
|
|
| def test_resume_no_snapshot(client): |
| |
| |
| client.post("/snapshots/capture") |
| r = client.post("/resume", json={"steps": 1}) |
| assert r.status_code == 200 |
| data = r.json() |
| assert "engine_health" in data |
| assert "transitions" in data |
|
|
|
|
| def test_resume_with_steps(client): |
| client.post("/snapshots/capture") |
| r = client.post("/resume", json={"steps": 3}) |
| assert r.status_code == 200 |
| assert "steps_attempted" in r.json() |
|
|
|
|
| |
|
|
|
|
| def test_ui_dashboard(client): |
| r = client.get("/ui") |
| assert r.status_code == 200 |
| assert "<html" in r.text.lower() |
| assert "SPINOR" in r.text |
|
|
|
|
| def test_assign_endpoint_uses_real_strategy_library(client, monkeypatch): |
| """/assign should route through the live OS and pick from the real strategy library.""" |
| from spinor_os import server |
|
|
| os = ExperimentationOS( |
| persistence=None, |
| assignment_router=AssignmentProviderRouter( |
| primary=LocalConstrainedAllocationProvider(), |
| local=LocalConstrainedAllocationProvider(), |
| ), |
| ) |
| os.register_employee( |
| employee_id="assign-api-001", |
| role="field_representative", |
| territory="northeast", |
| ) |
|
|
| strategy = Strategy( |
| name="Server /assign strategy", |
| description="Real strategy used by the server /assign endpoint test.", |
| hypothesis_id="HYP-ASSIGN-SERVER", |
| modification="server_assign_mod", |
| customer_segment="enterprise", |
| territory="northeast", |
| timing="2026-W31", |
| expected_effect=PredictedEffect( |
| metric="appointment_rate", |
| direction="increase", |
| magnitude=0.12, |
| unit="percentage_point", |
| timing="7d", |
| confidence=0.8, |
| ), |
| owner="assign-api-001", |
| maturity="validated_mechanism", |
| replication_count=3, |
| automation_ready=True, |
| ) |
| os.strategies[strategy.strategy_id] = strategy |
|
|
| monkeypatch.setattr(server, "get_os", lambda: os) |
|
|
| r = client.post("/assign", json={ |
| "organization_id": "org_server_test", |
| "employee_id": "assign-api-001", |
| "role": "field_representative", |
| "context": {"territory": "northeast"}, |
| }) |
| assert r.status_code == 200 |
| data = r.json() |
| assert data["count"] == 1 |
| assert data["decisions"][0]["assigned_strategy_id"] == strategy.strategy_id |
| library = data["provenance"][0]["sanitized_request"]["context"]["strategy_library"] |
| assert any(s["strategy_id"] == strategy.strategy_id for s in library) |
|
|
|
|
| def test_dashboard_alias(client): |
| r = client.get("/dashboard") |
| assert r.status_code == 200 |
| assert "<html" in r.text.lower() |
|
|