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>
This commit is contained in:
Claude Agent
2026-02-09 17:51:29 +00:00
commit df4031d99c
113 changed files with 24491 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
"""Tests for notifications API endpoints."""
from datetime import datetime
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from app.models.notification import Notification
from app.models.user import User
@pytest.fixture
def test_notification(db: Session, test_user: User) -> Notification:
"""Create test notification."""
notification = Notification(
user_id=test_user.id,
type="booking_created",
title="Test Notification",
message="This is a test notification",
is_read=False,
created_at=datetime.utcnow(),
)
db.add(notification)
db.commit()
db.refresh(notification)
return notification
@pytest.fixture
def test_read_notification(db: Session, test_user: User) -> Notification:
"""Create read test notification."""
notification = Notification(
user_id=test_user.id,
type="booking_approved",
title="Read Notification",
message="This notification has been read",
is_read=True,
created_at=datetime.utcnow(),
)
db.add(notification)
db.commit()
db.refresh(notification)
return notification
@pytest.fixture
def other_user_notification(db: Session, test_admin: User) -> Notification:
"""Create notification for another user."""
notification = Notification(
user_id=test_admin.id,
type="booking_created",
title="Admin Notification",
message="This belongs to admin",
is_read=False,
created_at=datetime.utcnow(),
)
db.add(notification)
db.commit()
db.refresh(notification)
return notification
def test_get_notifications_for_user(
client: TestClient,
auth_headers: dict[str, str],
test_notification: Notification,
test_read_notification: Notification,
other_user_notification: Notification,
) -> None:
"""Test getting all notifications for current user."""
response = client.get("/api/notifications", headers=auth_headers)
assert response.status_code == 200
data = response.json()
# Should only return notifications for current user
assert len(data) == 2
# Should be ordered by created_at DESC (most recent first)
notification_ids = [n["id"] for n in data]
assert test_notification.id in notification_ids
assert test_read_notification.id in notification_ids
assert other_user_notification.id not in notification_ids
def test_filter_unread_notifications(
client: TestClient,
auth_headers: dict[str, str],
test_notification: Notification,
test_read_notification: Notification,
) -> None:
"""Test filtering notifications by is_read status."""
# Get only unread notifications
response = client.get(
"/api/notifications",
headers=auth_headers,
params={"is_read": False},
)
assert response.status_code == 200
data = response.json()
assert len(data) == 1
assert data[0]["id"] == test_notification.id
assert data[0]["is_read"] is False
# Get only read notifications
response = client.get(
"/api/notifications",
headers=auth_headers,
params={"is_read": True},
)
assert response.status_code == 200
data = response.json()
assert len(data) == 1
assert data[0]["id"] == test_read_notification.id
assert data[0]["is_read"] is True
def test_mark_notification_as_read(
client: TestClient,
auth_headers: dict[str, str],
test_notification: Notification,
) -> None:
"""Test marking a notification as read."""
response = client.put(
f"/api/notifications/{test_notification.id}/read",
headers=auth_headers,
)
assert response.status_code == 200
data = response.json()
assert data["id"] == test_notification.id
assert data["is_read"] is True
assert data["title"] == "Test Notification"
assert data["message"] == "This is a test notification"
def test_cannot_mark_others_notification(
client: TestClient,
auth_headers: dict[str, str],
other_user_notification: Notification,
) -> None:
"""Test that users cannot mark other users' notifications as read."""
response = client.put(
f"/api/notifications/{other_user_notification.id}/read",
headers=auth_headers,
)
assert response.status_code == 403
data = response.json()
assert "own notifications" in data["detail"].lower()
def test_mark_nonexistent_notification(
client: TestClient,
auth_headers: dict[str, str],
) -> None:
"""Test marking a non-existent notification as read."""
response = client.put(
"/api/notifications/99999/read",
headers=auth_headers,
)
assert response.status_code == 404
data = response.json()
assert "not found" in data["detail"].lower()
def test_get_notifications_without_auth(client: TestClient) -> None:
"""Test that authentication is required for notifications endpoints."""
response = client.get("/api/notifications")
assert response.status_code == 403
response = client.put("/api/notifications/1/read")
assert response.status_code == 403