Skip to content

Commit d4f69ee

Browse files
committed
ENH: Add health check model and responses
1 parent cce3da4 commit d4f69ee

File tree

7 files changed

+88
-16
lines changed

7 files changed

+88
-16
lines changed

src/fmu_settings_api/__main__.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from starlette.middleware.cors import CORSMiddleware
99

1010
from .config import settings
11+
from .models import HealthCheck
1112
from .v1.main import api_v1_router
1213

1314

@@ -24,10 +25,19 @@ def custom_generate_unique_id(route: APIRoute) -> str:
2425
app.include_router(api_v1_router)
2526

2627

27-
@app.get("/health", tags=["app"])
28-
async def health_check() -> dict[str, str]:
28+
@app.get(
29+
"/health",
30+
tags=["app"],
31+
response_model=HealthCheck,
32+
summary="A health check on the application",
33+
description=(
34+
"This route requires no form of authentication or authorization. "
35+
"It can be used to check if the application is running and responsive."
36+
),
37+
)
38+
async def health_check() -> HealthCheck:
2939
"""Simple health check endpoint."""
30-
return {"status": "ok"}
40+
return HealthCheck()
3141

3242

3343
def run_server(
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
"""Models used for messages and responses at API endpoints."""
22

3-
from .common import APIKey, Message, SessionResponse
3+
from .common import APIKey, HealthCheck, Message, SessionResponse
44
from .project import FMUDirPath, FMUProject
55

6-
__all__ = ["FMUDirPath", "FMUProject", "Message", "SessionResponse", "APIKey"]
6+
__all__ = [
7+
"APIKey",
8+
"FMUDirPath",
9+
"FMUProject",
10+
"HealthCheck",
11+
"Message",
12+
"SessionResponse",
13+
]

src/fmu_settings_api/models/common.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
"""Common response models from the API."""
22

3+
from typing import Literal
4+
35
from fmu.settings.models.user_config import UserConfig
46
from pydantic import BaseModel, SecretStr
57

68
from .project import FMUProject
79

810

11+
class HealthCheck(BaseModel):
12+
"""Returns "ok" if the route is functioning correctly."""
13+
14+
status: Literal["ok"] = "ok"
15+
16+
917
class SessionResponse(BaseModel):
1018
"""Information returned when a session is initially created."""
1119

src/fmu_settings_api/v1/main.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414
get_session,
1515
verify_auth_token,
1616
)
17-
from fmu_settings_api.models import FMUProject, SessionResponse
17+
from fmu_settings_api.models import FMUProject, HealthCheck, SessionResponse
1818
from fmu_settings_api.session import (
1919
add_fmu_project_to_session,
2020
create_fmu_session,
2121
)
22+
from fmu_settings_api.v1.responses import GetSessionResponses
2223

2324
from .routes import project, user
2425

@@ -28,10 +29,20 @@
2829
api_v1_router.include_router(user.router, dependencies=[Depends(get_session)])
2930

3031

31-
@api_v1_router.get("/health", dependencies=[Depends(get_session)])
32-
async def v1_health_check() -> dict[str, str]:
32+
@api_v1_router.get(
33+
"/health",
34+
response_model=HealthCheck,
35+
dependencies=[Depends(get_session)],
36+
summary="A health check on the /v1 routes.",
37+
description=(
38+
"This route requires a valid session to return 200 OK. it can used to "
39+
"check if the user has a valid session."
40+
),
41+
responses=GetSessionResponses,
42+
)
43+
async def v1_health_check() -> HealthCheck:
3344
"""Simple health check endpoint."""
34-
return {"status": "ok"}
45+
return HealthCheck()
3546

3647

3748
@api_v1_router.post(
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Contains mappings of responses that API end points return."""
2+
3+
from typing import Any, Final, TypeAlias
4+
5+
Responses: TypeAlias = dict[int | str, dict[str, Any]]
6+
7+
GetSessionResponses: Final[Responses] = {
8+
401: {
9+
"description": "No active or valid session was found",
10+
"content": {
11+
"application/json": {
12+
"example": {
13+
"examples": [
14+
{"detail": "No active session found"},
15+
{"detail": "Invalid or expired session"},
16+
{"detail": "No FMU project directory open"},
17+
],
18+
},
19+
},
20+
},
21+
},
22+
500: {
23+
"description": "Something unexpected has happened",
24+
"content": {
25+
"application/json": {
26+
"example": {"detail": "Session error: {string content of exception}"},
27+
},
28+
},
29+
},
30+
}

tests/test_main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from fastapi.testclient import TestClient
55

66
from fmu_settings_api.__main__ import app
7+
from fmu_settings_api.models import HealthCheck
78

89
client = TestClient(app)
910

@@ -15,5 +16,6 @@ def test_main_invocation() -> None:
1516
def test_health_check() -> None:
1617
"""Test the health check endpoint."""
1718
response = client.get("/health")
18-
assert response.status_code == status.HTTP_200_OK
19+
assert response.status_code == status.HTTP_200_OK, response.json()
1920
assert response.json() == {"status": "ok"}
21+
assert HealthCheck() == HealthCheck.model_validate(response.json())

tests/test_v1/test_health.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from fmu_settings_api.__main__ import app
77
from fmu_settings_api.config import settings
8+
from fmu_settings_api.models import HealthCheck
89

910
client = TestClient(app)
1011

@@ -14,30 +15,31 @@
1415
def test_health_check_no_session() -> None:
1516
"""Test the health check endpoint with missing token and no session."""
1617
response = client.get(ROUTE)
17-
assert response.status_code == status.HTTP_401_UNAUTHORIZED
18+
assert response.status_code == status.HTTP_401_UNAUTHORIZED, response.json()
1819
assert response.json() == {"detail": "No active session found"}
1920

2021

2122
def test_health_check_no_session_bad_token() -> None:
2223
"""Test the health check endpoint with an invalid token but no session."""
2324
token = "no" * 32
2425
response = client.get(ROUTE, headers={settings.TOKEN_HEADER_NAME: token})
25-
assert response.status_code == status.HTTP_401_UNAUTHORIZED
26+
assert response.status_code == status.HTTP_401_UNAUTHORIZED, response.json()
2627
assert response.json() == {"detail": "No active session found"}
2728

2829

2930
def test_health_check_no_session_valid_token(mock_token: str) -> None:
3031
"""Test the health check endpoint with a valid token but no session."""
3132
response = client.get(ROUTE, headers={settings.TOKEN_HEADER_NAME: mock_token})
32-
assert response.status_code == status.HTTP_401_UNAUTHORIZED
33+
assert response.status_code == status.HTTP_401_UNAUTHORIZED, response.json()
3334
assert response.json() == {"detail": "No active session found"}
3435

3536

3637
def test_health_check_no_session_valid_session(client_with_session: TestClient) -> None:
3738
"""Test the health check endpoint with a valid session."""
3839
response = client_with_session.get(ROUTE)
39-
assert response.status_code == status.HTTP_200_OK
40+
assert response.status_code == status.HTTP_200_OK, response.json()
4041
assert response.json() == {"status": "ok"}
42+
assert HealthCheck() == HealthCheck.model_validate(response.json())
4143

4244

4345
def test_health_check_no_session_valid_session_invalid_token(
@@ -48,8 +50,9 @@ def test_health_check_no_session_valid_session_invalid_token(
4850
response = client_with_session.get(
4951
ROUTE, headers={settings.TOKEN_HEADER_NAME: token}
5052
)
51-
assert response.status_code == status.HTTP_200_OK
53+
assert response.status_code == status.HTTP_200_OK, response.json()
5254
assert response.json() == {"status": "ok"}
55+
assert HealthCheck() == HealthCheck.model_validate(response.json())
5356

5457

5558
def test_health_check_no_session_valid_session_valid_token(
@@ -59,5 +62,6 @@ def test_health_check_no_session_valid_session_valid_token(
5962
response = client_with_session.get(
6063
ROUTE, headers={settings.TOKEN_HEADER_NAME: mock_token}
6164
)
62-
assert response.status_code == status.HTTP_200_OK
65+
assert response.status_code == status.HTTP_200_OK, response.json()
6366
assert response.json() == {"status": "ok"}
67+
assert HealthCheck() == HealthCheck.model_validate(response.json())

0 commit comments

Comments
 (0)