Skip to content
Merged

Dev #44

Show file tree
Hide file tree
Changes from all commits
Commits
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
51 changes: 42 additions & 9 deletions src/app/controller/rule_controller.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, Query, Request, Response, status
from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response, status

from app.errors.errors import no_jwt, not_found_error
from app.errors.errors import error_response, no_jwt, not_found_error, status_title
from app.middleware.jw_admin_auth import jw_admin_middleware
from app.models.rule import Rule
from app.service.admin_service import AdminService
Expand Down Expand Up @@ -114,7 +114,24 @@ def create(

admin_email = admin_service.get_current_session_admin(request)

return service.save_rule(new_rule, admin_email, token)
try:
response = service.save_rule(new_rule, admin_email, token)
return response
except HTTPException as e:
code = getattr(e, "status_code", 400)
return error_response(
title=status_title(code),
status=code,
detail=getattr(e, "detail", str(e)),
instance=str(request.url.path),
)
except Exception:
return error_response(
"Internal Server Error",
500,
"Internal Server Error",
instance=str(request.url.path),
)


@rule.get(
Expand Down Expand Up @@ -324,11 +341,27 @@ async def update(

admin_email = admin_service.get_current_session_admin(request)

updated = service.update_rule_by_id(id, updated_rule, admin_email, token)
if not updated:
return not_found_error(
"given id did not match any rule in db",
try:
updated = service.update_rule_by_id(id, updated_rule, admin_email, token)
if not updated:
return not_found_error(
"given id did not match any rule in db",
instance=str(request.url.path),
title="Rule not found",
)
return updated
except HTTPException as e:
code = getattr(e, "status_code", 400)
return error_response(
title=status_title(code),
status=code,
detail=getattr(e, "detail", str(e)),
instance=str(request.url.path),
)
except Exception:
return error_response(
"Internal Server Error",
500,
"Internal Server Error",
instance=str(request.url.path),
title="Rule not found",
)
return updated
12 changes: 12 additions & 0 deletions src/app/errors/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ def not_found_error(
return error_response(title, status.HTTP_404_NOT_FOUND, detail, instance, type_)


def status_title(status_code: int) -> str:
mapping = {
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Not Found",
422: "Validation Error",
500: "Internal Server Error",
}
return mapping.get(status_code, "Error")


async def validation_exception_handler(request: Request, exc: RequestValidationError):
return error_response(
type_="about:blank",
Expand Down
Empty file removed src/app/middleware/x.py
Empty file.
65 changes: 44 additions & 21 deletions src/app/service/rule_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from datetime import date, datetime

from bson import ObjectId
from fastapi import HTTPException

from app.db.db import db
from app.models.rule import Rule
Expand Down Expand Up @@ -35,6 +36,14 @@ def save_rule(self, newRule: Rule, admin_email: str, token: str):
rule_dict["effectiveDate"], datetime.min.time()
)

today = datetime.now().date()
new_date = rule_dict["effectiveDate"].date()
if new_date <= today:
logger.warning("The effective date must be after today.")
raise HTTPException(
status_code=400, detail="The effective date must be after today."
)

id = db.rule.insert_one(rule_dict).inserted_id
new_rule = rule_entity(db.rule.find_one({"_id": id}))

Expand All @@ -53,18 +62,20 @@ def save_rule(self, newRule: Rule, admin_email: str, token: str):

subject = f"Deleted Application Rule: {newRule.title}"
body = (
f"The following application rule has been deleted:\n\n"
f"Title: {newRule.title}\n"
f"Description: {newRule.description}\n"
f"Conditions:\n{conditions_formatted or 'None'}\n"
f"Effective Date: {newRule.effectiveDate}\n"
f"The following application rule has been deleted:<br><br>"
f"<b>Title:</b> {newRule.title}<br>"
f"<b>Description:</b> {newRule.description}<br>"
f"<b>Conditions:</b><br>{conditions_formatted.replace(chr(10), '<br>') if conditions_formatted else 'None'}<br>"
f"<b>Effective Date:</b> {newRule.effectiveDate}<br>"
)

self.notifications_service.email(subject, body, token)

logger.info(f"[SUCCESSFUL] POST: save_rule.\n\tRule saved with id = {id}")

return new_rule
except HTTPException:
raise
except Exception:
logger.error("[ERROR] POST: save_rule", exc_info=True)
return None
Expand Down Expand Up @@ -92,6 +103,14 @@ def update_rule_by_id(
rule_dict["effectiveDate"], datetime.min.time()
)

today = datetime.now().date()
new_date = rule_dict["effectiveDate"].date()
if new_date <= today:
logger.warning("The effective date must be after today.")
raise HTTPException(
status_code=400, detail="The effective date must be after today."
)

old_rule_raw = db.rule.find_one({"_id": ObjectId(id)})
if old_rule_raw is None:
return None
Expand All @@ -118,24 +137,26 @@ def update_rule_by_id(

subject = f"Updated Application Rule: {updated_rule.title}"
body = (
f"The application rule '{updated_rule.title}' has been updated.\n\n"
f"--- OLD RULE ---\n"
f"Title: {old_rule.get('title')}\n"
f"Description: {old_rule.get('description')}\n"
f"Conditions:\n{old_conditions_formatted or 'None'}\n"
f"Effective Date: {old_rule.get('effectiveDate')}\n\n"
f"--- NEW RULE ---\n"
f"Title: {updated_rule.title}\n"
f"Description: {updated_rule.description}\n"
f"Conditions:\n{new_conditions_formatted or 'None'}\n"
f"Effective Date: {updated_rule.effectiveDate}\n"
f"The application rule '<b>{updated_rule.title}</b>' has been updated.<br><br>"
f"<b>--- OLD RULE ---</b><br>"
f"<b>Title:</b> {old_rule.get('title')}<br>"
f"<b>Description:</b> {old_rule.get('description')}<br>"
f"<b>Conditions:</b><br>{old_conditions_formatted.replace(chr(10), '<br>') if old_conditions_formatted else 'None'}<br>"
f"<b>Effective Date:</b> {old_rule.get('effectiveDate')}<br><br>"
f"<b>--- NEW RULE ---</b><br>"
f"<b>Title:</b> {updated_rule.title}<br>"
f"<b>Description:</b> {updated_rule.description}<br>"
f"<b>Conditions:</b><br>{new_conditions_formatted.replace(chr(10), '<br>') if new_conditions_formatted else 'None'}<br>"
f"<b>Effective Date:</b> {updated_rule.effectiveDate}<br>"
)

self.notifications_service.email(subject, body, token)

logger.info(f"[SUCCESSFUL] UPDATE: update_rule_by_id id = {id}")
return rule_entity(db.rule.find_one({"_id": ObjectId(id)}))

except HTTPException:
raise
except Exception:
logger.error(f"[ERROR] UPDATE: update_rule_by_id id = {id}", exc_info=True)
return None
Expand Down Expand Up @@ -165,11 +186,11 @@ def delete_rule_by_id(self, id: str, admin_email: str, token: str):
f"Deleted Application Rule: {deleted.get('title', 'Unnamed Rule')}"
)
body = (
f"The following application rule has been deleted:\n\n"
f"Title: {deleted.get('title', 'Unnamed Rule')}\n"
f"Description: {deleted.get('description')}\n"
f"Conditions:\n{deleted_conditions_formatted or 'None'}\n"
f"Effective Date: {deleted.get('effectiveDate')}\n"
f"The following application rule has been deleted:<br><br>"
f"<b>Title:</b> {deleted.get('title', 'Unnamed Rule')}<br>"
f"<b>Description:</b> {deleted.get('description')}<br>"
f"<b>Conditions:</b><br>{deleted_conditions_formatted.replace(chr(10), '<br>') if deleted_conditions_formatted else 'None'}<br>"
f"<b>Effective Date:</b> {deleted.get('effectiveDate')}<br>"
)

self.notifications_service.email(subject, body, token)
Expand All @@ -179,6 +200,8 @@ def delete_rule_by_id(self, id: str, admin_email: str, token: str):
)

return deleted
except HTTPException:
raise
except Exception:
logger.error(
f"[ERROR] DELETE: delete_rule_by_id with id = {id}:", exc_info=True
Expand Down
22 changes: 14 additions & 8 deletions tests/unit/test_rule_service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime
from datetime import datetime, timedelta
from unittest.mock import MagicMock, patch

import pytest
Expand All @@ -8,6 +8,11 @@
from app.service.rule_service import RuleService


@pytest.fixture
def future_date():
return (datetime.now() + timedelta(days=1)).date()


@pytest.fixture(autouse=True)
def mock_log_event():
with patch("app.service.rule_service.log_event") as mock_log:
Expand All @@ -16,7 +21,7 @@ def mock_log_event():


@pytest.fixture
def mock_conn():
def mock_conn(future_date):
mock_db = MagicMock()
mock_rule_collection = MagicMock()
# Crea la lista de reglas de ejemplo
Expand All @@ -25,14 +30,14 @@ def mock_conn():
"_id": ObjectId(),
"title": "Rule 1",
"description": "Desc 1",
"effectiveDate": "2024-01-01",
"effectiveDate": datetime.combine(future_date, datetime.min.time()),
"conditions": [],
},
{
"_id": ObjectId(),
"title": "Rule 2",
"description": "Desc 2",
"effectiveDate": "2024-01-01",
"effectiveDate": datetime.combine(future_date, datetime.min.time()),
"conditions": [],
},
]
Expand All @@ -49,22 +54,23 @@ def mock_conn():


@pytest.fixture
def sample_rule():
def sample_rule(future_date):
return Rule(
title="Test Rule",
description="This is a test rule",
effectiveDate="2024-01-01",
effectiveDate=datetime.combine(future_date, datetime.min.time()),
conditions=[],
)


@pytest.fixture
def sample_rule_dict():
def sample_rule_dict(future_date):
date = (datetime.combine(future_date, datetime.min.time()),)
return {
"_id": ObjectId(),
"title": "Test Rule",
"description": "This is a test rule",
"effectiveDate": "2024-01-01",
"effectiveDate": date,
"conditions": [],
}

Expand Down