""" DAGI Router API Tests Тести для endpoints: - GET /internal/node/{node_id}/dagi-router/agents - GET /internal/node/{node_id}/metrics/current - POST /internal/node/{node_id}/dagi-audit/run - POST /internal/node/{node_id}/dagi-router/phantom/sync - POST /internal/node/{node_id}/dagi-router/stale/mark """ import pytest import httpx from typing import Any, Dict # Test configuration CITY_SERVICE_URL = "http://localhost:7001" NODE1_ID = "node-1-hetzner-gex44" NODE2_ID = "node-2-macbook-m4max" # ============================================================================ # Fixtures # ============================================================================ @pytest.fixture def client(): """HTTP client для тестування""" return httpx.Client(base_url=CITY_SERVICE_URL, timeout=30.0) @pytest.fixture def node_ids(): """Node IDs для тестування""" return [NODE1_ID, NODE2_ID] # ============================================================================ # DAGI Router Agents Tests # ============================================================================ class TestDAGIRouterAgents: """Тести для GET /internal/node/{node_id}/dagi-router/agents""" def test_get_agents_returns_valid_response(self, client): """Endpoint повертає валідну структуру""" response = client.get(f"/city/internal/node/{NODE1_ID}/dagi-router/agents") assert response.status_code == 200 data = response.json() # Перевірка структури assert "node_id" in data assert "summary" in data assert "agents" in data # Перевірка summary summary = data["summary"] assert "active" in summary assert "phantom" in summary assert "stale" in summary assert "router_total" in summary assert "system_total" in summary # Types assert isinstance(summary["active"], int) assert isinstance(summary["phantom"], int) assert isinstance(data["agents"], list) def test_get_agents_for_unknown_node(self, client): """Endpoint повертає пустий response для невідомої ноди""" response = client.get("/city/internal/node/unknown-node-id/dagi-router/agents") # Має повернути 200 з пустим списком, не 404 assert response.status_code == 200 data = response.json() assert data["agents"] == [] assert data["summary"]["active"] == 0 def test_agents_have_required_fields(self, client): """Агенти мають всі необхідні поля""" response = client.get(f"/city/internal/node/{NODE1_ID}/dagi-router/agents") assert response.status_code == 200 data = response.json() if data["agents"]: agent = data["agents"][0] # Required fields assert "id" in agent assert "name" in agent assert "status" in agent # Status must be valid assert agent["status"] in ["active", "phantom", "stale", "error"] # ============================================================================ # Node Metrics Tests # ============================================================================ class TestNodeMetrics: """Тести для GET /internal/node/{node_id}/metrics/current""" def test_get_metrics_returns_valid_response(self, client): """Endpoint повертає валідну структуру""" response = client.get(f"/city/internal/node/{NODE1_ID}/metrics/current") assert response.status_code == 200 data = response.json() # Required fields assert "node_id" in data assert data["node_id"] == NODE1_ID # Metric fields assert "cpu_cores" in data assert "cpu_usage" in data assert "gpu_model" in data assert "gpu_memory_total" in data assert "gpu_memory_used" in data assert "ram_total" in data assert "ram_used" in data assert "disk_total" in data assert "disk_used" in data assert "agent_count_router" in data assert "agent_count_system" in data def test_get_metrics_for_unknown_node(self, client): """Endpoint повертає minimal response для невідомої ноди""" response = client.get("/city/internal/node/unknown-node-id/metrics/current") # Має повернути 200 з мінімальним response assert response.status_code == 200 data = response.json() assert data["node_id"] == "unknown-node-id" def test_metrics_have_numeric_values(self, client): """Метрики мають числові значення""" response = client.get(f"/city/internal/node/{NODE1_ID}/metrics/current") assert response.status_code == 200 data = response.json() # All numeric fields should be numbers numeric_fields = [ "cpu_cores", "cpu_usage", "gpu_memory_total", "gpu_memory_used", "ram_total", "ram_used", "disk_total", "disk_used", "agent_count_router", "agent_count_system" ] for field in numeric_fields: assert isinstance(data[field], (int, float)), f"{field} should be numeric" # ============================================================================ # DAGI Audit Tests # ============================================================================ class TestDAGIAudit: """Тести для POST /internal/node/{node_id}/dagi-audit/run""" def test_run_audit_returns_valid_response(self, client): """POST audit повертає валідну структуру""" response = client.post(f"/city/internal/node/{NODE1_ID}/dagi-audit/run") assert response.status_code == 200 data = response.json() assert "status" in data assert data["status"] == "completed" assert "summary" in data assert "message" in data # Summary fields summary = data["summary"] assert "router_total" in summary assert "db_total" in summary assert "active_count" in summary assert "phantom_count" in summary assert "stale_count" in summary def test_get_audit_summary(self, client): """GET audit summary повертає дані""" response = client.get(f"/city/internal/node/{NODE1_ID}/dagi-audit") # Може бути 200 з даними або null assert response.status_code == 200 data = response.json() if data: assert "node_id" in data assert "timestamp" in data assert "active_count" in data # ============================================================================ # Phantom/Stale Sync Tests # ============================================================================ class TestPhantomStaleSync: """Тести для phantom/stale sync endpoints""" def test_phantom_sync_empty_list(self, client): """Sync з пустим списком не падає""" response = client.post( f"/city/internal/node/{NODE1_ID}/dagi-router/phantom/sync", json={"agent_ids": []} ) assert response.status_code == 200 data = response.json() assert data["status"] == "completed" assert data["created_count"] == 0 def test_stale_mark_empty_list(self, client): """Mark stale з пустим списком не падає""" response = client.post( f"/city/internal/node/{NODE1_ID}/dagi-router/stale/mark", json={"agent_ids": []} ) assert response.status_code == 200 data = response.json() assert data["status"] == "completed" assert data["marked_count"] == 0 # ============================================================================ # Integration Tests # ============================================================================ class TestIntegration: """Інтеграційні тести""" def test_full_audit_flow(self, client): """Повний цикл: audit → get agents → get metrics""" # 1. Run audit audit_response = client.post(f"/city/internal/node/{NODE1_ID}/dagi-audit/run") assert audit_response.status_code == 200 # 2. Get agents agents_response = client.get(f"/city/internal/node/{NODE1_ID}/dagi-router/agents") assert agents_response.status_code == 200 agents_data = agents_response.json() # 3. Get metrics metrics_response = client.get(f"/city/internal/node/{NODE1_ID}/metrics/current") assert metrics_response.status_code == 200 # 4. Verify consistency audit_data = audit_response.json() # Agent counts should match assert agents_data["summary"]["active"] + agents_data["summary"]["phantom"] + agents_data["summary"]["stale"] >= 0 def test_both_nodes_accessible(self, client, node_ids): """Обидві ноди доступні через API""" for node_id in node_ids: response = client.get(f"/city/internal/node/{node_id}/metrics/current") assert response.status_code == 200 data = response.json() assert data["node_id"] == node_id # ============================================================================ # Run tests # ============================================================================ if __name__ == "__main__": pytest.main([__file__, "-v", "--tb=short"])