Files
space-booking/backend/tests/test_reports.py
Claude Agent df4031d99c feat: Space Booking System - MVP complet
Sistem web pentru rezervarea de birouri și săli de ședință
cu flux de aprobare administrativă.

Stack: FastAPI + Vue.js 3 + SQLite + TypeScript

Features implementate:
- Autentificare JWT + Self-registration cu email verification
- CRUD Spații, Utilizatori, Settings (Admin)
- Calendar interactiv (FullCalendar) cu drag-and-drop
- Creare rezervări cu validare (durată, program, overlap, max/zi)
- Rezervări recurente (săptămânal)
- Admin: aprobare/respingere/anulare cereri
- Admin: creare directă rezervări (bypass approval)
- Admin: editare orice rezervare
- User: editare/anulare rezervări proprii
- Notificări in-app (bell icon + dropdown)
- Notificări email (async SMTP cu BackgroundTasks)
- Jurnal acțiuni administrative (audit log)
- Rapoarte avansate (utilizare, top users, approval rate)
- Șabloane rezervări (booking templates)
- Atașamente fișiere (upload/download)
- Conflict warnings (verificare disponibilitate real-time)
- Integrare Google Calendar (OAuth2)
- Suport timezone (UTC storage + user preference)
- 225+ teste backend

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 17:51:29 +00:00

297 lines
8.8 KiB
Python

"""Test report endpoints."""
from datetime import datetime
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from app.models.booking import Booking
from app.models.space import Space
from app.models.user import User
@pytest.fixture
def test_spaces(db: Session) -> list[Space]:
"""Create multiple test spaces."""
spaces = [
Space(
name="Conference Room A",
type="sala",
capacity=10,
description="Test room A",
is_active=True,
),
Space(
name="Office B",
type="birou",
capacity=2,
description="Test office B",
is_active=True,
),
]
for space in spaces:
db.add(space)
db.commit()
for space in spaces:
db.refresh(space)
return spaces
@pytest.fixture
def test_users(db: Session, test_user: User) -> list[User]:
"""Create multiple test users."""
from app.core.security import get_password_hash
user2 = User(
email="user2@example.com",
full_name="User Two",
hashed_password=get_password_hash("password"),
role="user",
is_active=True,
)
db.add(user2)
db.commit()
db.refresh(user2)
return [test_user, user2]
@pytest.fixture
def test_bookings(
db: Session, test_users: list[User], test_spaces: list[Space]
) -> list[Booking]:
"""Create multiple test bookings with various statuses."""
bookings = [
# User 1, Space 1, approved
Booking(
user_id=test_users[0].id,
space_id=test_spaces[0].id,
title="Meeting 1",
description="Test",
start_datetime=datetime(2024, 3, 15, 10, 0),
end_datetime=datetime(2024, 3, 15, 12, 0), # 2 hours
status="approved",
),
# User 1, Space 1, pending
Booking(
user_id=test_users[0].id,
space_id=test_spaces[0].id,
title="Meeting 2",
description="Test",
start_datetime=datetime(2024, 3, 16, 10, 0),
end_datetime=datetime(2024, 3, 16, 11, 0), # 1 hour
status="pending",
),
# User 1, Space 2, rejected
Booking(
user_id=test_users[0].id,
space_id=test_spaces[1].id,
title="Meeting 3",
description="Test",
start_datetime=datetime(2024, 3, 17, 10, 0),
end_datetime=datetime(2024, 3, 17, 13, 0), # 3 hours
status="rejected",
rejection_reason="Conflict",
),
# User 2, Space 1, approved
Booking(
user_id=test_users[1].id,
space_id=test_spaces[0].id,
title="Meeting 4",
description="Test",
start_datetime=datetime(2024, 3, 18, 10, 0),
end_datetime=datetime(2024, 3, 18, 14, 0), # 4 hours
status="approved",
),
# User 2, Space 1, canceled
Booking(
user_id=test_users[1].id,
space_id=test_spaces[0].id,
title="Meeting 5",
description="Test",
start_datetime=datetime(2024, 3, 19, 10, 0),
end_datetime=datetime(2024, 3, 19, 11, 30), # 1.5 hours
status="canceled",
cancellation_reason="Not needed",
),
]
for booking in bookings:
db.add(booking)
db.commit()
for booking in bookings:
db.refresh(booking)
return bookings
def test_usage_report(
client: TestClient,
admin_headers: dict[str, str],
test_bookings: list[Booking],
) -> None:
"""Test usage report generation."""
response = client.get("/api/admin/reports/usage", headers=admin_headers)
assert response.status_code == 200
data = response.json()
assert "items" in data
assert "total_bookings" in data
assert "date_range" in data
# Should have 2 spaces
assert len(data["items"]) == 2
# Check Conference Room A
conf_room = next((i for i in data["items"] if i["space_name"] == "Conference Room A"), None)
assert conf_room is not None
assert conf_room["total_bookings"] == 4 # 4 bookings in this space
assert conf_room["approved_bookings"] == 2
assert conf_room["pending_bookings"] == 1
assert conf_room["rejected_bookings"] == 0
assert conf_room["canceled_bookings"] == 1
assert conf_room["total_hours"] == 8.5 # 2 + 1 + 4 + 1.5
# Check Office B
office = next((i for i in data["items"] if i["space_name"] == "Office B"), None)
assert office is not None
assert office["total_bookings"] == 1
assert office["rejected_bookings"] == 1
assert office["total_hours"] == 3.0
# Total bookings across all spaces
assert data["total_bookings"] == 5
def test_usage_report_with_date_filter(
client: TestClient,
admin_headers: dict[str, str],
test_bookings: list[Booking],
) -> None:
"""Test usage report with date range filter."""
# Filter for March 15-16 only
response = client.get(
"/api/admin/reports/usage",
headers=admin_headers,
params={"start_date": "2024-03-15", "end_date": "2024-03-16"},
)
assert response.status_code == 200
data = response.json()
assert len(data["items"]) == 1 # Only Conference Room A
assert data["total_bookings"] == 2 # Meeting 1 and 2
def test_usage_report_with_space_filter(
client: TestClient,
admin_headers: dict[str, str],
test_bookings: list[Booking],
test_spaces: list[Space],
) -> None:
"""Test usage report with space filter."""
response = client.get(
"/api/admin/reports/usage",
headers=admin_headers,
params={"space_id": test_spaces[0].id},
)
assert response.status_code == 200
data = response.json()
assert len(data["items"]) == 1
assert data["items"][0]["space_name"] == "Conference Room A"
def test_top_users_report(
client: TestClient,
admin_headers: dict[str, str],
test_bookings: list[Booking],
test_users: list[User],
) -> None:
"""Test top users report."""
response = client.get("/api/admin/reports/top-users", headers=admin_headers)
assert response.status_code == 200
data = response.json()
assert "items" in data
assert "date_range" in data
# Should have 2 users
assert len(data["items"]) == 2
# Top user should be test_user with 3 bookings
assert data["items"][0]["user_email"] == test_users[0].email
assert data["items"][0]["total_bookings"] == 3
assert data["items"][0]["approved_bookings"] == 1
assert data["items"][0]["total_hours"] == 6.0 # 2 + 1 + 3
# Second user with 2 bookings
assert data["items"][1]["user_email"] == test_users[1].email
assert data["items"][1]["total_bookings"] == 2
assert data["items"][1]["approved_bookings"] == 1
assert data["items"][1]["total_hours"] == 5.5 # 4 + 1.5
def test_top_users_report_with_limit(
client: TestClient,
admin_headers: dict[str, str],
test_bookings: list[Booking],
) -> None:
"""Test top users report with limit."""
response = client.get(
"/api/admin/reports/top-users",
headers=admin_headers,
params={"limit": 1},
)
assert response.status_code == 200
data = response.json()
assert len(data["items"]) == 1 # Only top user
def test_approval_rate_report(
client: TestClient,
admin_headers: dict[str, str],
test_bookings: list[Booking],
) -> None:
"""Test approval rate report."""
response = client.get("/api/admin/reports/approval-rate", headers=admin_headers)
assert response.status_code == 200
data = response.json()
assert data["total_requests"] == 5
assert data["approved"] == 2
assert data["rejected"] == 1
assert data["pending"] == 1
assert data["canceled"] == 1
# Approval rate = 2 / (2 + 1) = 66.67%
assert data["approval_rate"] == 66.67
# Rejection rate = 1 / (2 + 1) = 33.33%
assert data["rejection_rate"] == 33.33
def test_reports_require_admin(
client: TestClient, auth_headers: dict[str, str]
) -> None:
"""Test that regular users cannot access reports."""
endpoints = [
"/api/admin/reports/usage",
"/api/admin/reports/top-users",
"/api/admin/reports/approval-rate",
]
for endpoint in endpoints:
response = client.get(endpoint, headers=auth_headers)
assert response.status_code == 403
assert response.json()["detail"] == "Not enough permissions"
def test_reports_require_auth(client: TestClient) -> None:
"""Test that reports require authentication."""
endpoints = [
"/api/admin/reports/usage",
"/api/admin/reports/top-users",
"/api/admin/reports/approval-rate",
]
for endpoint in endpoints:
response = client.get(endpoint)
assert response.status_code == 403