feat: initial commit — HermesMessages SaaS platform
Backend (FastAPI + Python 3.12): - Multi-tenant auth with JWT: login, register, refresh, Meta OAuth - Business & BusinessConfig management - WhatsApp webhook with HMAC signature verification - Bot engine powered by Claude AI - Calendar availability with Redis caching - Reservations CRUD with status management - Dashboard analytics (stats, agenda, peak hours) - Billing & plan management - Admin panel with platform-wide stats - Async bcrypt via asyncio.to_thread - IntegrityError handling for concurrent registration race conditions Frontend (React 18 + Vite + Tailwind CSS): - Multi-step guided registration form with helper text on every field - Login page with show/hide password toggle - Protected routes with AuthContext - Dashboard with stats cards, bar chart, and daily agenda - Reservations list with search, filters, and inline status actions - Calendar with weekly view, slot availability, and date blocking - Config page: business info, schedules, bot personality - Billing page with plan comparison and usage bar Design system: - Bricolage Grotesque + DM Sans typography - Emerald primary palette with semantic color tokens - scale(0.97) button press feedback, ease-out animations - Skeleton loaders, stagger animations, prefers-reduced-motion support - Accessible: aria-labels, visible focus rings, 4.5:1 contrast Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
118
backend/tests/test_reservations.py
Normal file
118
backend/tests/test_reservations.py
Normal file
@ -0,0 +1,118 @@
|
||||
from datetime import date, time
|
||||
|
||||
import pytest
|
||||
|
||||
from app.modules.reservations.schemas import ReservationCreate, ReservationUpdate, StatusUpdate
|
||||
from app.modules.reservations.service import (
|
||||
create_reservation,
|
||||
delete_reservation,
|
||||
get_reservation,
|
||||
list_reservations,
|
||||
update_status,
|
||||
)
|
||||
from tests.conftest import make_business, make_user
|
||||
|
||||
|
||||
async def _create_test_reservation(db, redis_mock, business_id):
|
||||
return await create_reservation(
|
||||
db=db,
|
||||
redis=redis_mock,
|
||||
business_id=business_id,
|
||||
data=ReservationCreate(
|
||||
client_name="Ana García",
|
||||
client_phone="5491112345678",
|
||||
date=date(2026, 5, 5),
|
||||
time_start=time(10, 0),
|
||||
party_size=2,
|
||||
source="manual",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def test_create_reservation(db, redis_mock):
|
||||
business = await make_business(db, name="Res Test")
|
||||
r = await _create_test_reservation(db, redis_mock, business.id)
|
||||
|
||||
assert r.id is not None
|
||||
assert r.client_name == "Ana García"
|
||||
assert r.time_end == time(11, 0) # 10:00 + 60min slot
|
||||
assert r.status == "pending"
|
||||
assert r.source == "manual"
|
||||
redis_mock.delete.assert_called_once() # caché invalidada
|
||||
|
||||
|
||||
async def test_list_reservations_filter_by_date(db, redis_mock):
|
||||
business = await make_business(db, name="List Test")
|
||||
await _create_test_reservation(db, redis_mock, business.id)
|
||||
|
||||
results = await list_reservations(db, business.id, filter_date=date(2026, 5, 5))
|
||||
assert len(results) >= 1
|
||||
assert all(r.date == date(2026, 5, 5) for r in results)
|
||||
|
||||
|
||||
async def test_list_reservations_filter_by_status(db, redis_mock):
|
||||
business = await make_business(db, name="Status Test")
|
||||
await _create_test_reservation(db, redis_mock, business.id)
|
||||
|
||||
results = await list_reservations(db, business.id, filter_status="pending")
|
||||
assert all(r.status == "pending" for r in results)
|
||||
|
||||
|
||||
async def test_get_reservation_not_found(db):
|
||||
business = await make_business(db, name="404 Test")
|
||||
from fastapi import HTTPException
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
await get_reservation(db, business.id, 999999)
|
||||
assert exc.value.status_code == 404
|
||||
|
||||
|
||||
async def test_get_reservation_wrong_business(db, redis_mock):
|
||||
b1 = await make_business(db, name="Business 1")
|
||||
b2 = await make_business(db, name="Business 2")
|
||||
r = await _create_test_reservation(db, redis_mock, b1.id)
|
||||
|
||||
from fastapi import HTTPException
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
await get_reservation(db, b2.id, r.id)
|
||||
assert exc.value.status_code == 404
|
||||
|
||||
|
||||
async def test_update_status_to_confirmed(db, redis_mock):
|
||||
business = await make_business(db, name="Confirm Test")
|
||||
r = await _create_test_reservation(db, redis_mock, business.id)
|
||||
|
||||
updated = await update_status(db, redis_mock, business.id, r.id, "confirmed")
|
||||
assert updated.status == "confirmed"
|
||||
|
||||
|
||||
async def test_update_status_cancelled_invalidates_cache(db, redis_mock):
|
||||
business = await make_business(db, name="Cancel Cache Test")
|
||||
r = await _create_test_reservation(db, redis_mock, business.id)
|
||||
redis_mock.delete.reset_mock()
|
||||
|
||||
await update_status(db, redis_mock, business.id, r.id, "cancelled")
|
||||
redis_mock.delete.assert_called_once() # caché invalidada al cancelar
|
||||
|
||||
|
||||
async def test_update_status_invalid_raises(db, redis_mock):
|
||||
business = await make_business(db, name="Invalid Status Test")
|
||||
r = await _create_test_reservation(db, redis_mock, business.id)
|
||||
|
||||
from fastapi import HTTPException
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
await update_status(db, redis_mock, business.id, r.id, "inexistente")
|
||||
assert exc.value.status_code == 422
|
||||
|
||||
|
||||
async def test_delete_reservation(db, redis_mock):
|
||||
business = await make_business(db, name="Delete Test")
|
||||
r = await _create_test_reservation(db, redis_mock, business.id)
|
||||
r_id = r.id
|
||||
redis_mock.delete.reset_mock()
|
||||
|
||||
await delete_reservation(db, redis_mock, business.id, r_id)
|
||||
redis_mock.delete.assert_called_once()
|
||||
|
||||
from fastapi import HTTPException
|
||||
with pytest.raises(HTTPException):
|
||||
await get_reservation(db, business.id, r_id)
|
||||
Reference in New Issue
Block a user