"""Integration tests for channels health_router endpoints.""" from __future__ import annotations import httpx import pytest from .conftest import BASE_URL, DEFAULT_CHANNEL_TYPE, NON_EXISTENT_ACCOUNT_ID pytestmark = [pytest.mark.asyncio, pytest.mark.integration] HEALTH_URL = f"{BASE_URL}/health" SINGLE_HEALTH_URL = ( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/{NON_EXISTENT_ACCOUNT_ID}/health" ) PROBE_URL = f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/{NON_EXISTENT_ACCOUNT_ID}/probe" # ============================================================================= # === GET /health (NO AUTH — wrapped in success/data) === # ============================================================================= async def test_get_health_does_not_require_auth(test_client: httpx.AsyncClient): # Act — no Authorization header response = await test_client.get(HEALTH_URL) # Assert — 200 with success/data wrapper; data holds status/version/... assert response.status_code == 200, response.text payload = response.json() assert payload["success"] is True data = payload["data"] assert "status" in data assert "version" in data assert "degraded_components" in data assert "channels" in data async def test_get_health_works_with_standard_user( test_client: httpx.AsyncClient, standard_user ): # Act — standard user headers also accepted (no auth required) response = await test_client.get(HEALTH_URL, headers=standard_user["headers"]) # Assert assert response.status_code == 200, response.text payload = response.json() assert payload["success"] is True data = payload["data"] assert "status" in data assert "version" in data assert "degraded_components" in data assert "channels" in data async def test_get_health_not_captured_by_channel_type_path( test_client: httpx.AsyncClient ): # Act — /health must hit the static health endpoint, not be captured as # channel_type="health" by the dynamic /{channel_type}/{account_id}/... path response = await test_client.get(HEALTH_URL) # Assert — success/data wrapper confirms correct route match assert response.status_code == 200, response.text payload = response.json() assert payload["success"] is True data = payload["data"] assert "status" in data assert "version" in data assert "channels" in data # ============================================================================= # === GET /health/detail (admin auth matrix) === # ============================================================================= async def test_get_health_detail_requires_auth(test_client: httpx.AsyncClient): # Act response = await test_client.get(f"{HEALTH_URL}/detail") # Assert assert response.status_code == 401, response.text async def test_get_health_detail_requires_admin( test_client: httpx.AsyncClient, standard_user ): # Act response = await test_client.get( f"{HEALTH_URL}/detail", headers=standard_user["headers"] ) # Assert assert response.status_code == 403, response.text async def test_admin_can_get_health_detail( test_client: httpx.AsyncClient, admin_headers ): # Act response = await test_client.get(f"{HEALTH_URL}/detail", headers=admin_headers) # Assert assert response.status_code == 200, response.text payload = response.json() assert payload["success"] is True assert "data" in payload # ============================================================================= # === POST /health/diagnostics/export (admin auth matrix) === # ============================================================================= async def test_export_diagnostics_requires_auth(test_client: httpx.AsyncClient): # Act response = await test_client.post( f"{HEALTH_URL}/diagnostics/export", json={} ) # Assert assert response.status_code == 401, response.text async def test_export_diagnostics_requires_admin( test_client: httpx.AsyncClient, standard_user ): # Act response = await test_client.post( f"{HEALTH_URL}/diagnostics/export", json={}, headers=standard_user["headers"], ) # Assert assert response.status_code == 403, response.text async def test_admin_can_export_diagnostics_with_empty_body( test_client: httpx.AsyncClient, admin_headers ): # Arrange — all fields optional, empty dict is valid body: dict = {} # Act response = await test_client.post( f"{HEALTH_URL}/diagnostics/export", json=body, headers=admin_headers ) # Assert assert response.status_code == 200, response.text payload = response.json() assert payload["success"] is True assert "data" in payload # ============================================================================= # === POST /health/diagnostics/export audit_log_count validation === # ============================================================================= async def test_export_diagnostics_rejects_audit_log_count_zero( test_client: httpx.AsyncClient, admin_headers ): # Act — audit_log_count=0 violates ge=1 response = await test_client.post( f"{HEALTH_URL}/diagnostics/export", json={"audit_log_count": 0}, headers=admin_headers, ) # Assert assert response.status_code == 422, response.text async def test_export_diagnostics_rejects_audit_log_count_over_max( test_client: httpx.AsyncClient, admin_headers ): # Act — audit_log_count=501 violates le=500 response = await test_client.post( f"{HEALTH_URL}/diagnostics/export", json={"audit_log_count": 501}, headers=admin_headers, ) # Assert assert response.status_code == 422, response.text # ============================================================================= # === Single-channel health (non-existent account -> 404 or 501) === # ============================================================================= async def test_get_single_health_nonexistent_account( test_client: httpx.AsyncClient, admin_headers ): # Act response = await test_client.get(SINGLE_HEALTH_URL, headers=admin_headers) # Assert — non-existent account: 404 (not found) or 501 (adapter not registered) assert response.status_code in (404, 501), response.text async def test_probe_channel_nonexistent_account( test_client: httpx.AsyncClient, admin_headers ): # Arrange — valid probe body with defaults body = {"probe_type": "connectivity", "timeout_ms": 5000} # Act response = await test_client.post( PROBE_URL, json=body, headers=admin_headers ) # Assert — non-existent account: 404 or 501 assert response.status_code in (404, 501), response.text # ============================================================================= # === POST /probe timeout_ms validation === # ============================================================================= async def test_probe_rejects_timeout_below_min( test_client: httpx.AsyncClient, admin_headers ): # Act — timeout_ms=999 violates ge=1000 response = await test_client.post( PROBE_URL, json={"timeout_ms": 999}, headers=admin_headers, ) # Assert assert response.status_code == 422, response.text async def test_probe_rejects_timeout_above_max( test_client: httpx.AsyncClient, admin_headers ): # Act — timeout_ms=30001 violates le=30000 response = await test_client.post( PROBE_URL, json={"timeout_ms": 30001}, headers=admin_headers, ) # Assert assert response.status_code == 422, response.text