mirror of
http://88.130.71.182:3000/BlitTech/badoHair_be.git
synced 2026-06-13 09:00:42 +00:00
115 lines
3.5 KiB
Python
115 lines
3.5 KiB
Python
"""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:00–11: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) == ""
|