2025-10-19 21:35:02 -04:00
|
|
|
"""Integration tests for API endpoints."""
|
|
|
|
|
|
2026-05-16 09:53:12 -04:00
|
|
|
from importlib import metadata
|
|
|
|
|
|
2025-10-19 21:35:02 -04:00
|
|
|
import pytest
|
2025-10-23 13:45:54 -04:00
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
2026-05-16 09:53:12 -04:00
|
|
|
from backend.main import app, compatibility_status
|
2025-10-30 08:09:45 -04:00
|
|
|
|
2025-10-23 13:45:54 -04:00
|
|
|
client = TestClient(app)
|
2025-10-19 21:35:02 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.integration
|
|
|
|
|
class TestAPIIntegration:
|
|
|
|
|
"""Integration tests for API endpoints."""
|
|
|
|
|
|
|
|
|
|
def test_health_check(self) -> None:
|
|
|
|
|
"""Test API health check endpoint."""
|
2025-10-23 13:45:54 -04:00
|
|
|
response = client.get("/health")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
assert response.json() == {"status": "healthy"}
|
2025-10-19 21:35:02 -04:00
|
|
|
|
2025-10-23 13:45:54 -04:00
|
|
|
def test_root_endpoint(self) -> None:
|
|
|
|
|
"""Test root endpoint."""
|
|
|
|
|
response = client.get("/")
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
assert response.json() == {"message": "Plex Playlist Backend API"}
|
2026-05-15 19:24:59 -04:00
|
|
|
|
|
|
|
|
def test_startup_rejects_invalid_runtime_policy(
|
|
|
|
|
self, monkeypatch: pytest.MonkeyPatch
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Startup should fail fast when compatibility policy is invalid."""
|
|
|
|
|
monkeypatch.setenv("BACKEND_REQUIRED_PYTHON", "99.0")
|
|
|
|
|
|
|
|
|
|
with pytest.raises(RuntimeError), TestClient(app):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
monkeypatch.delenv("BACKEND_REQUIRED_PYTHON", raising=False)
|
|
|
|
|
|
|
|
|
|
def test_compatibility_endpoint_reports_policy_status(
|
|
|
|
|
self, monkeypatch: pytest.MonkeyPatch
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Compatibility endpoint should expose runtime and pinning policy status."""
|
|
|
|
|
monkeypatch.setenv("BACKEND_REQUIRED_PYTHON", "3.14")
|
|
|
|
|
|
|
|
|
|
with TestClient(app) as local_client:
|
|
|
|
|
response = local_client.get("/compatibility")
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
payload = response.json()
|
|
|
|
|
assert payload["ok"] is True
|
|
|
|
|
assert payload["required_python"] == "3.14"
|
|
|
|
|
assert "current_python" in payload
|
2026-05-16 09:53:12 -04:00
|
|
|
assert payload["python_policy_valid"] is True
|
2026-05-15 19:24:59 -04:00
|
|
|
assert "required_packages" in payload
|
|
|
|
|
assert payload["required_packages"]["fastapi"] == "0.120.2"
|
|
|
|
|
assert payload["required_packages"]["uvicorn"] == "0.38.0"
|
2026-05-16 09:53:12 -04:00
|
|
|
assert payload["package_errors"] == {}
|
2026-05-15 19:24:59 -04:00
|
|
|
|
|
|
|
|
monkeypatch.delenv("BACKEND_REQUIRED_PYTHON", raising=False)
|
2026-05-16 09:53:12 -04:00
|
|
|
|
|
|
|
|
def test_startup_rejects_malformed_required_python(
|
|
|
|
|
self, monkeypatch: pytest.MonkeyPatch
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Startup should fail fast when Python policy is not strict major.minor."""
|
|
|
|
|
monkeypatch.setenv("BACKEND_REQUIRED_PYTHON", "3.14.1")
|
|
|
|
|
|
|
|
|
|
with pytest.raises(RuntimeError), TestClient(app):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
monkeypatch.delenv("BACKEND_REQUIRED_PYTHON", raising=False)
|
|
|
|
|
|
|
|
|
|
def test_compatibility_status_handles_missing_package_metadata(
|
|
|
|
|
self, monkeypatch: pytest.MonkeyPatch
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Compatibility checks should handle missing package metadata gracefully."""
|
|
|
|
|
|
|
|
|
|
def raise_not_found(_: str) -> str:
|
|
|
|
|
raise metadata.PackageNotFoundError("fake")
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("backend.main._installed_version", raise_not_found)
|
|
|
|
|
status = compatibility_status()
|
|
|
|
|
package_checks = status["package_checks"]
|
|
|
|
|
package_errors = status["package_errors"]
|
|
|
|
|
|
|
|
|
|
assert status["ok"] is False
|
|
|
|
|
assert isinstance(package_checks, dict)
|
|
|
|
|
assert isinstance(package_errors, dict)
|
|
|
|
|
assert package_checks["fastapi"] is False
|
|
|
|
|
assert package_checks["uvicorn"] is False
|
|
|
|
|
assert "fastapi" in package_errors
|
|
|
|
|
assert "uvicorn" in package_errors
|