Initial Commit

This commit is contained in:
belviskhoremk
2026-05-12 00:34:21 +00:00
commit d2dc43b16f
57 changed files with 6056 additions and 0 deletions

114
tests/test_slot_service.py Normal file
View File

@@ -0,0 +1,114 @@
"""Unit tests for slot generation and calendar logic — no HTTP involved."""
import asyncio
from datetime import date, time, timedelta
from unittest.mock import AsyncMock, MagicMock
import pytest
from app.services.slot_service import generate_slots, _fmt_time # type: ignore
from app.exceptions import AppError
def make_mock_db(schedule=None, blocked=None):
db = AsyncMock()
db.transaction = MagicMock(return_value=_TxCtx())
db.fetch = AsyncMock(side_effect=lambda q, *a: _route_fetch(q, schedule, blocked))
db.fetchval = AsyncMock(return_value=None)
db.execute = AsyncMock(return_value="INSERT 1")
return db
def _route_fetch(query, schedule, blocked):
if "weekly_schedule" in query:
return schedule or []
if "blocked_dates" in query:
return blocked or []
return []
class _TxCtx:
async def __aenter__(self): return self
async def __aexit__(self, *a): return False
MONDAY = 0 # weekday() value for Monday
SCHED_ROW = {
"day_of_week": MONDAY,
"start_time": time(9, 0),
"end_time": time(11, 0),
"slot_duration_minutes": 60,
}
async def test_generate_slots_creates_correct_count():
"""Mon 09:0011:00 with 60-min slots → 2 slots per Monday."""
db = make_mock_db(schedule=[SCHED_ROW])
# Find the next Monday
today = date.today()
days_ahead = (7 - today.weekday()) % 7 or 7
next_monday = today + timedelta(days=days_ahead)
db.fetchval = AsyncMock(return_value=None) # no existing slots
created = await generate_slots(db, next_monday, next_monday)
assert created == 2
async def test_generate_slots_skips_blocked_dates():
"""A blocked Monday produces 0 slots."""
today = date.today()
days_ahead = (7 - today.weekday()) % 7 or 7
next_monday = today + timedelta(days=days_ahead)
blocked = [{"date": next_monday}]
db = make_mock_db(schedule=[SCHED_ROW], blocked=blocked)
db.fetchval = AsyncMock(return_value=None)
created = await generate_slots(db, next_monday, next_monday)
assert created == 0
async def test_generate_slots_skips_existing_slots():
"""If a slot already exists, it is not duplicated."""
today = date.today()
days_ahead = (7 - today.weekday()) % 7 or 7
next_monday = today + timedelta(days=days_ahead)
db = make_mock_db(schedule=[SCHED_ROW])
db.fetchval = AsyncMock(return_value=1) # all slots already exist
created = await generate_slots(db, next_monday, next_monday)
assert created == 0
async def test_generate_slots_range_over_90_days_rejected():
with pytest.raises(AppError) as exc_info:
db = AsyncMock()
await generate_slots(db, date(2026, 1, 1), date(2026, 5, 1))
assert exc_info.value.code == "RANGE_TOO_LARGE"
async def test_generate_slots_no_schedule_raises():
db = make_mock_db(schedule=[])
with pytest.raises(AppError) as exc_info:
await generate_slots(db, date(2026, 6, 1), date(2026, 6, 7))
assert exc_info.value.code == "NO_SCHEDULE"
# ── Time formatting ───────────────────────────────────────────────────────────
def test_fmt_time_strips_seconds():
from datetime import time as t
assert _fmt_time(t(10, 0, 0)) == "10:00"
assert _fmt_time(t(9, 30)) == "09:30"
def test_fmt_time_string_input():
assert _fmt_time("14:30:00") == "14:30"
assert _fmt_time("08:00") == "08:00"
def test_fmt_time_none():
assert _fmt_time(None) == ""