Skip to content

feat: added put endpoint to update api status by api_group #1167

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 22 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
eb62438
initial commit
Mukeli3 Feb 28, 2025
cd6df1d
Merge branch 'dev' into feat/extend-api-status
Mukeli3 Feb 28, 2025
ac8e8fe
Merge branch 'dev' into feat/extend-api-status
Mukeli3 Mar 1, 2025
9663f3b
Merge branch 'dev' into feat/extend-api-status
Mukeli3 Mar 1, 2025
4294075
Merge branch 'dev' into feat/extend-api-status
Mukeli3 Mar 1, 2025
153ff3a
Merge branch 'dev' into feat/extend-api-status
Mukeli3 Mar 1, 2025
937c5e4
feat: updated the test file to account for new endpoints
Mukeli3 Mar 1, 2025
77d3f48
fix: adjusted the match with main, give clearer error meessage(s)
Mukeli3 Mar 1, 2025
237c449
fix: corrected API spelling error
Mukeli3 Mar 1, 2025
e2bc4a9
fix: adjusted the functionality to account for invalid response
Mukeli3 Mar 1, 2025
05bc956
fix: adjusted to account for str as response
Mukeli3 Mar 1, 2025
9642389
fix: adjusted file to match functionality
Mukeli3 Mar 1, 2025
c53e357
fix: adjusted file toproperly account for invalid response time
Mukeli3 Mar 1, 2025
53a5606
fix: reverted file changes
Mukeli3 Mar 1, 2025
76bd326
fix: reverted file changes
Mukeli3 Mar 1, 2025
f4d07a4
Merge branch 'dev' into feat/extend-api-status
Mukeli3 Mar 1, 2025
8bdbc62
Merge branch 'dev' into feat/extend-api-status
Mukeli3 Mar 1, 2025
43ca2e8
Merge branch 'dev' into feat/extend-api-status
Mukeli3 Mar 2, 2025
9a18cea
fix: reverted changes on port
Mukeli3 Mar 2, 2025
f392822
fix: revert changes on main.py file
Mukeli3 Mar 2, 2025
04884ac
Merge branch 'dev' into feat/extend-api-status
Mukeli3 Mar 2, 2025
f038000
Merge branch 'dev' into feat/extend-api-status
Mukeli3 Mar 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion api/v1/routes/api_status.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Annotated
from api.db.database import get_db
from api.v1.schemas.api_status import APIStatusPost
from api.v1.schemas.api_status import APIStatusPost, APIStatusUpdate
from api.v1.services.api_status import APIStatusService
from api.utils.success_response import success_response
from fastapi import APIRouter, Depends, status
Expand Down Expand Up @@ -32,3 +32,15 @@ async def post_api_status(
data=new_status,
status_code=status.HTTP_201_CREATED
)
@api_status.put('/{api_group}', response_model=success_response, status_code=200)
async def update_api_status(
api_group: str,
schema: APIStatusUpdate,
db: Annotated[Session, Depends(get_db)]
):
updated_status = APIStatusService.update(db, api_group, schema)
return success_response(
message='API Status updated successfully',
data=updated_status,
status_code=status.HTTP_200_OK
)
5 changes: 2 additions & 3 deletions api/v1/schemas/api_status.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from decimal import Decimal
from pydantic import BaseModel, Field, PositiveInt, PositiveFloat, ConfigDict, StringConstraints

from typing import List, Optional
from datetime import datetime

Expand All @@ -22,7 +21,7 @@ class APIStatusPost(BaseModel):
api_group: str
status: str
response_time: Optional[Decimal] = None
details: str
details: str | None = None

class Config:
from_attributes = True
Expand All @@ -44,7 +43,7 @@ class APIStatusUpdate(BaseModel):
created_at (datetime): The date and time when the API status was created.
"""

api_group: str = Field(..., alias="apiGroup")
api_group: Optional[str] = Field(None, alias="apiGroup")
status: str
last_checked: Optional[datetime] = None
response_time: Optional[Decimal] = None
Expand Down
59 changes: 45 additions & 14 deletions api/v1/services/api_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from api.core.base.services import Service
from sqlalchemy.orm import Session
from api.v1.models.api_status import APIStatus
from api.v1.schemas.api_status import APIStatusPost
from api.v1.schemas.api_status import APIStatusPost, APIStatusUpdate
from fastapi import HTTPException


Expand Down Expand Up @@ -81,20 +81,51 @@ def upsert(db: Session, schema: APIStatusPost) -> APIStatus:
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="A database error occurred."
)
@staticmethod
def update(db: Session, api_group: str, schema: APIStatusUpdate) -> APIStatus:
"""
Update an existing API status record by api_group.

Parameters:
db (Session): The SQLAlchemy database session.
api_group (str): The api_group identifier to update.
schema (APIStatusUpdate): The data model with updated API status info.

Returns:
APIStatus: The updated API status record.

Raises:
HTTPException: 404 if the api_group doesn't exist, 500 for db errors.
"""
try:
existing_status = db.query(APIStatus).filter(APIStatus.api_group == api_group).first()
if not existing_status:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="API Status not found"
)
if schema.status is not None:
existing_status.status = schema.status
if schema.response_time is not None:
existing_status.response_time = Decimal(schema.response_time)
if schema.details is not None:
existing_status.details = schema.details
if schema.last_checked is not None:
existing_status.last_checked = schema.last_checked

db.commit()
db.refresh(existing_status)
return existing_status

except HTTException as e:
raise e
except Exception as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="A database error occured."
)

# @staticmethod
# def update(db: Session, schema: APIStatusPost) -> APIStatus:
# status = APIStatus(
# api_group=schema.api_group,
# status=schema.status,
# response_time=schema.response_time,
# details=schema.details,
# )
# db.add(status)
# db.commit()
# db.refresh(status)
# return status

@staticmethod
def delete_by_api_group(db: Session, api_group) -> APIStatus:
status = db.query(APIStatus).filter(APIStatus.api_group == api_group).first()
Expand Down
148 changes: 140 additions & 8 deletions tests/v1/status_page/test_post_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from uuid_extensions import uuid7
from fastapi import HTTPException

from api.db.database import get_db
from api.v1.models.contact_us import ContactUs
from api.v1.models.api_status import APIStatus
from api.v1.services.api_status import APIStatusService
from main import app



@pytest.fixture
def db_session_mock():
db_session = MagicMock(spec=Session)
return db_session


@pytest.fixture
def client(db_session_mock):
app.dependency_overrides[get_db] = lambda: db_session_mock
Expand All @@ -32,7 +34,19 @@ def mock_post_api_status():
api_group="Blog API",
status="Down",
response_time=None,
details="API not responding (HTTP 503)"
details="API not responding (HTTP 503)",
last_checked=datetime.now(timezone.utc)
)


def mock_updated_api_status():
return APIStatus(
id=str(uuid7()),
api_group="Blog API",
status="Up",
response_time=200.0,
details="API is running fine",
last_checked=datetime.now(timezone.utc)
)


Expand All @@ -44,8 +58,8 @@ def test_post_api_status(mock_create, db_session_mock, client):
db_session_mock.commit.return_value = None
db_session_mock.refresh.return_value = None

mock_create.return_value = mock_post_api_status()
# mock_check_existing.return_value = None
mock_status = mock_post_api_status()
mock_create.return_value = mock_status

response = client.post('/api/v1/api-status', json={
"api_group": "Blog API",
Expand All @@ -54,7 +68,13 @@ def test_post_api_status(mock_create, db_session_mock, client):
"details": "API not responding (HTTP 503)"
})

assert response.status_code == 201
assert response.status_code == 201, f"Expected 201, got {response.status_code}: {response.text}"
json_response = response.json()
assert json_response["status"] == "success"
assert json_response["status_code"] == 201
assert json_response["message"] == "API Status created successfully"
assert json_response["data"]["api_group"] == "Blog API"


@patch("api.v1.services.api_status.APIStatusService.fetch_all")
def test_get_api_status(mock_fetch, db_session_mock, client):
Expand All @@ -64,9 +84,121 @@ def test_get_api_status(mock_fetch, db_session_mock, client):
db_session_mock.commit.return_value = None
db_session_mock.refresh.return_value = None

mock_fetch.return_value = mock_post_api_status()
mock_status = mock_post_api_status()
mock_fetch.return_value = [mock_status]

response = client.get('/api/v1/api-status')

print(response.json())
assert response.status_code == 200
assert response.status_code == 200, f"Expected 200, got {response.status_code}: {response.text}"
json_response = response.json()
assert json_response["status"] == "success"
assert json_response["status_code"] == 200
assert json_response["message"] == "All API Status fetched successfully"
assert json_response["data"][0]["api_group"] == "Blog API"


@patch("api.v1.services.api_status.APIStatusService.update")
def test_put_api_status_success(mock_update, db_session_mock, client):
"""Tests the PUT /api/v1/api-status/{api_group} endpoint for successful update"""

db_session_mock.commit.return_value = None
db_session_mock.refresh.return_value = None

mock_status = mock_updated_api_status()
mock_update.return_value = mock_status

update_data = {
"apiGroup": "Blog API",
"status": "Up",
"response_time": 200,
"details": "API is running fine"
}
response = client.put('/api/v1/api-status/Blog API', json=update_data)

assert response.status_code == 200, f"Expected 200, got {response.status_code}: {response.text}"
json_response = response.json()
assert json_response["status"] == "success"
assert json_response["status_code"] == 200
assert json_response["message"] == "API Status updated successfully"
data = json_response["data"]
assert data["api_group"] == "Blog API"
assert data["status"] == "Up"
assert float(data["response_time"]) == 200.0
assert data["details"] == "API is running fine"


@patch("api.v1.services.api_status.APIStatusService.update")
def test_put_api_status_not_found(mock_update, db_session_mock, client):
"""Tests the PUT /api/v1/api-status/{api_group} endpoint for a non-existent api_group"""

mock_update.side_effect = HTTPException(
status_code=404, detail="API Status not found"
)

update_data = {
"apiGroup": "Nonexistent API",
"status": "Up"
}
response = client.put('/api/v1/api-status/Nonexistent API', json=update_data)

assert response.status_code == 404, f"Expected 404, got {response.status_code}: {response.text}"
json_response = response.json()
assert json_response["status"] is False
assert json_response["status_code"] == 404
assert json_response["message"] == "API Status not found"


@patch("api.v1.services.api_status.APIStatusService.update")
def test_put_api_status_partial_update(mock_update, db_session_mock, client):
"""Tests the PUT /api/v1/api-status/{api_group} endpoint for partial update"""

db_session_mock.commit.return_value = None
db_session_mock.refresh.return_value = None

partial_updated_status = APIStatus(
id=str(uuid7()),
api_group="Blog API",
status="Inactive",
response_time=None,
details="API not responding (HTTP 503)",
last_checked=datetime.now(timezone.utc)
)
mock_update.return_value = partial_updated_status

update_data = {
"status": "Inactive"
}
response = client.put('/api/v1/api-status/Blog API', json=update_data)

assert response.status_code == 200, f"Expected 200, got {response.status_code}: {response.text}"
json_response = response.json()
assert json_response["status"] == "success"
assert json_response["status_code"] == 200
assert json_response["message"] == "API Status updated successfully"
data = json_response["data"]
assert data["api_group"] == "Blog API"
assert data["status"] == "Inactive"
assert data["response_time"] is None
assert data["details"] == "API not responding (HTTP 503)"


@patch("api.v1.services.api_status.APIStatusService.update")
def test_put_api_status_invalid_response_time(mock_update, db_session_mock, client):
"""Tests the PUT /api/v1/api-status/{api_group} endpoint with invalid response_time"""

update_data = {
"apiGroup": "Blog API",
"status": "Up",
"response_time": "invalid"
}
response = client.put('/api/v1/api-status/Blog API', json=update_data)

assert response.status_code == 422, f"Expected 422, got {response.status_code}: {response.text}"
json_response = response.json()
assert json_response["status"] is False
assert json_response["status_code"] == 422
assert json_response["message"] == "Invalid input"
assert len(json_response["errors"]) == 1
assert json_response["errors"][0]["loc"] == ["body", "response_time"]
assert json_response["errors"][0]["msg"] == "Input should be a valid decimal"
assert json_response["errors"][0]["type"] == "decimal_parsing"