feat: enhance error handling and configuration validation
Some checks failed
CI / lint-and-test (push) Failing after 27s
Some checks failed
CI / lint-and-test (push) Failing after 27s
- Added a global exception handler to log unhandled exceptions and return a generic 500 JSON response without exposing details to the client. - Updated the configuration to validate the `DATABASE_URL` format, ensuring it starts with `sqlite://` or `postgresql://`, and log warnings for invalid formats. - Introduced safe parsing for numeric environment variables (`HTTP_PORT`, `INIT_DATA_MAX_AGE_SECONDS`) with defaults on invalid values, including logging warnings for out-of-range values. - Enhanced the duty schedule parser to enforce limits on the number of schedule rows and the length of full names and duty strings, raising appropriate errors when exceeded. - Updated internationalization messages to include generic error responses for import failures and parsing issues, improving user experience. - Added unit tests to verify the new error handling and configuration validation behaviors.
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
"""Tests for duty-schedule JSON parser."""
|
||||
|
||||
import json
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
|
||||
from duty_teller.importers.duty_schedule import (
|
||||
DUTY_MARKERS,
|
||||
MAX_FULL_NAME_LENGTH,
|
||||
MAX_SCHEDULE_ROWS,
|
||||
UNAVAILABLE_MARKER,
|
||||
VACATION_MARKER,
|
||||
DutyScheduleParseError,
|
||||
@@ -118,3 +121,38 @@ def test_unavailable_and_vacation_markers():
|
||||
assert entry.unavailable_dates == [date(2026, 2, 1)]
|
||||
assert entry.vacation_dates == [date(2026, 2, 2)]
|
||||
assert entry.duty_dates == [date(2026, 2, 3)]
|
||||
|
||||
|
||||
def test_parse_start_date_year_out_of_range():
|
||||
"""start_date year must be current ± 1; otherwise DutyScheduleParseError."""
|
||||
# Use a year far in the past/future so it fails regardless of test run date.
|
||||
raw_future = b'{"meta": {"start_date": "2030-01-01"}, "schedule": [{"name": "A", "duty": ""}]}'
|
||||
with pytest.raises(DutyScheduleParseError, match="year|2030"):
|
||||
parse_duty_schedule(raw_future)
|
||||
raw_past = b'{"meta": {"start_date": "2019-01-01"}, "schedule": [{"name": "A", "duty": ""}]}'
|
||||
with pytest.raises(DutyScheduleParseError, match="year|2019"):
|
||||
parse_duty_schedule(raw_past)
|
||||
|
||||
|
||||
def test_parse_too_many_schedule_rows():
|
||||
"""More than MAX_SCHEDULE_ROWS rows raises DutyScheduleParseError."""
|
||||
rows = [{"name": f"User {i}", "duty": ""} for i in range(MAX_SCHEDULE_ROWS + 1)]
|
||||
today = date.today()
|
||||
start = today.replace(month=1, day=1)
|
||||
payload = {"meta": {"start_date": start.isoformat()}, "schedule": rows}
|
||||
raw = json.dumps(payload).encode("utf-8")
|
||||
with pytest.raises(DutyScheduleParseError, match="too many|max"):
|
||||
parse_duty_schedule(raw)
|
||||
|
||||
|
||||
def test_parse_full_name_too_long():
|
||||
"""full_name longer than MAX_FULL_NAME_LENGTH raises DutyScheduleParseError."""
|
||||
long_name = "A" * (MAX_FULL_NAME_LENGTH + 1)
|
||||
today = date.today()
|
||||
start = today.replace(month=1, day=1)
|
||||
raw = (
|
||||
f'{{"meta": {{"start_date": "{start.isoformat()}"}}, '
|
||||
f'"schedule": [{{"name": "{long_name}", "duty": ""}}]}}'
|
||||
).encode("utf-8")
|
||||
with pytest.raises(DutyScheduleParseError, match="exceed|character"):
|
||||
parse_duty_schedule(raw)
|
||||
|
||||
Reference in New Issue
Block a user