test_metrics_api.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. """Integration tests for Prometheus Metrics API endpoint.
  2. Tests the /api/v1/metrics endpoint for Prometheus scraping.
  3. """
  4. import pytest
  5. from httpx import AsyncClient
  6. class TestMetricsAPI:
  7. """Integration tests for /api/v1/metrics endpoint."""
  8. # ========================================================================
  9. # Metrics endpoint access control
  10. # ========================================================================
  11. @pytest.mark.asyncio
  12. @pytest.mark.integration
  13. async def test_metrics_disabled_returns_404(self, async_client: AsyncClient):
  14. """Verify metrics endpoint returns 404 when disabled."""
  15. # Ensure prometheus is disabled
  16. await async_client.put("/api/v1/settings/", json={"prometheus_enabled": False})
  17. response = await async_client.get("/api/v1/metrics")
  18. assert response.status_code == 404
  19. assert "not enabled" in response.json()["detail"].lower()
  20. @pytest.mark.asyncio
  21. @pytest.mark.integration
  22. async def test_metrics_enabled_without_token(self, async_client: AsyncClient):
  23. """Verify metrics endpoint works when enabled without token."""
  24. # Enable prometheus without token
  25. await async_client.put("/api/v1/settings/", json={"prometheus_enabled": True, "prometheus_token": ""})
  26. response = await async_client.get("/api/v1/metrics")
  27. assert response.status_code == 200
  28. assert response.headers["content-type"].startswith("text/plain")
  29. @pytest.mark.asyncio
  30. @pytest.mark.integration
  31. async def test_metrics_with_token_requires_auth(self, async_client: AsyncClient):
  32. """Verify metrics endpoint requires auth when token is set."""
  33. # Enable prometheus with token
  34. await async_client.put("/api/v1/settings/", json={"prometheus_enabled": True, "prometheus_token": "secret123"})
  35. # Request without auth
  36. response = await async_client.get("/api/v1/metrics")
  37. assert response.status_code == 401
  38. # Request with wrong token
  39. response = await async_client.get("/api/v1/metrics", headers={"Authorization": "Bearer wrongtoken"})
  40. assert response.status_code == 401
  41. # Request with correct token
  42. response = await async_client.get("/api/v1/metrics", headers={"Authorization": "Bearer secret123"})
  43. assert response.status_code == 200
  44. # ========================================================================
  45. # Metrics content validation
  46. # ========================================================================
  47. @pytest.mark.asyncio
  48. @pytest.mark.integration
  49. async def test_metrics_format(self, async_client: AsyncClient):
  50. """Verify metrics are in Prometheus text format."""
  51. # Enable prometheus
  52. await async_client.put("/api/v1/settings/", json={"prometheus_enabled": True, "prometheus_token": ""})
  53. response = await async_client.get("/api/v1/metrics")
  54. assert response.status_code == 200
  55. content = response.text
  56. # Check for Prometheus format markers
  57. assert "# HELP" in content
  58. assert "# TYPE" in content
  59. @pytest.mark.asyncio
  60. @pytest.mark.integration
  61. async def test_metrics_contains_expected_metrics(self, async_client: AsyncClient):
  62. """Verify expected metrics are present."""
  63. # Enable prometheus
  64. await async_client.put("/api/v1/settings/", json={"prometheus_enabled": True, "prometheus_token": ""})
  65. response = await async_client.get("/api/v1/metrics")
  66. assert response.status_code == 200
  67. content = response.text
  68. # Check for key metrics
  69. assert "bambuddy_printers_connected" in content
  70. assert "bambuddy_printers_total" in content
  71. assert "bambuddy_prints_total" in content
  72. assert "bambuddy_filament_used_grams" in content
  73. assert "bambuddy_print_time_seconds" in content
  74. assert "bambuddy_queue_pending" in content
  75. @pytest.mark.asyncio
  76. @pytest.mark.integration
  77. async def test_metrics_printer_metrics_when_no_printers(self, async_client: AsyncClient):
  78. """Verify printer metrics work when no printers configured."""
  79. # Enable prometheus
  80. await async_client.put("/api/v1/settings/", json={"prometheus_enabled": True, "prometheus_token": ""})
  81. response = await async_client.get("/api/v1/metrics")
  82. assert response.status_code == 200
  83. content = response.text
  84. # Should still have system metrics
  85. assert "bambuddy_printers_total" in content
  86. assert "bambuddy_printers_connected" in content
  87. # ========================================================================
  88. # Settings persistence
  89. # ========================================================================
  90. @pytest.mark.asyncio
  91. @pytest.mark.integration
  92. async def test_prometheus_settings_persist(self, async_client: AsyncClient):
  93. """Verify prometheus settings are saved correctly."""
  94. # Update settings
  95. await async_client.put("/api/v1/settings/", json={"prometheus_enabled": True, "prometheus_token": "mytoken"})
  96. # Read back settings
  97. response = await async_client.get("/api/v1/settings/")
  98. settings = response.json()
  99. assert settings["prometheus_enabled"] is True
  100. assert settings["prometheus_token"] == "mytoken"
  101. # Disable and verify
  102. await async_client.put("/api/v1/settings/", json={"prometheus_enabled": False})
  103. response = await async_client.get("/api/v1/settings/")
  104. settings = response.json()
  105. assert settings["prometheus_enabled"] is False