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:
251
backend/tests/test_users.py
Normal file
251
backend/tests/test_users.py
Normal file
@@ -0,0 +1,251 @@
|
||||
"""Tests for user management endpoints."""
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.user import User
|
||||
|
||||
|
||||
def test_get_current_user(client: TestClient, test_user: User, auth_headers: dict) -> None:
|
||||
"""Test getting current user info."""
|
||||
response = client.get("/api/users/me", headers=auth_headers)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["email"] == "test@example.com"
|
||||
assert data["role"] == "user"
|
||||
|
||||
|
||||
def test_list_users_admin(
|
||||
client: TestClient, test_user: User, admin_headers: dict
|
||||
) -> None:
|
||||
"""Test listing all users as admin."""
|
||||
response = client.get("/api/admin/users", headers=admin_headers)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, list)
|
||||
assert len(data) >= 2 # test_user + admin_user
|
||||
|
||||
|
||||
def test_list_users_unauthorized(client: TestClient, auth_headers: dict) -> None:
|
||||
"""Test listing users as non-admin (should fail)."""
|
||||
response = client.get("/api/admin/users", headers=auth_headers)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_list_users_filter_by_role(client: TestClient, admin_headers: dict) -> None:
|
||||
"""Test filtering users by role."""
|
||||
response = client.get("/api/admin/users?role=admin", headers=admin_headers)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert all(user["role"] == "admin" for user in data)
|
||||
|
||||
|
||||
def test_create_user_admin(client: TestClient, admin_headers: dict) -> None:
|
||||
"""Test creating a new user as admin."""
|
||||
response = client.post(
|
||||
"/api/admin/users",
|
||||
headers=admin_headers,
|
||||
json={
|
||||
"email": "newuser@example.com",
|
||||
"full_name": "New User",
|
||||
"password": "newpassword",
|
||||
"role": "user",
|
||||
"organization": "Test Org",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["email"] == "newuser@example.com"
|
||||
assert data["full_name"] == "New User"
|
||||
assert data["role"] == "user"
|
||||
assert data["organization"] == "Test Org"
|
||||
assert data["is_active"] is True
|
||||
|
||||
|
||||
def test_create_user_duplicate_email(
|
||||
client: TestClient, test_user: User, admin_headers: dict
|
||||
) -> None:
|
||||
"""Test creating user with duplicate email."""
|
||||
response = client.post(
|
||||
"/api/admin/users",
|
||||
headers=admin_headers,
|
||||
json={
|
||||
"email": "test@example.com", # Already exists
|
||||
"full_name": "Duplicate User",
|
||||
"password": "password",
|
||||
"role": "user",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert "already exists" in response.json()["detail"]
|
||||
|
||||
|
||||
def test_create_user_invalid_role(client: TestClient, admin_headers: dict) -> None:
|
||||
"""Test creating user with invalid role."""
|
||||
response = client.post(
|
||||
"/api/admin/users",
|
||||
headers=admin_headers,
|
||||
json={
|
||||
"email": "invalid@example.com",
|
||||
"full_name": "Invalid User",
|
||||
"password": "password",
|
||||
"role": "superadmin", # Invalid role
|
||||
},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert "admin" in response.json()["detail"].lower()
|
||||
|
||||
|
||||
def test_update_user_admin(client: TestClient, test_user: User, admin_headers: dict) -> None:
|
||||
"""Test updating user as admin."""
|
||||
response = client.put(
|
||||
f"/api/admin/users/{test_user.id}",
|
||||
headers=admin_headers,
|
||||
json={
|
||||
"full_name": "Updated Name",
|
||||
"organization": "Updated Org",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["full_name"] == "Updated Name"
|
||||
assert data["organization"] == "Updated Org"
|
||||
assert data["email"] == "test@example.com" # Should remain unchanged
|
||||
|
||||
|
||||
def test_update_user_not_found(client: TestClient, admin_headers: dict) -> None:
|
||||
"""Test updating non-existent user."""
|
||||
response = client.put(
|
||||
"/api/admin/users/99999",
|
||||
headers=admin_headers,
|
||||
json={"full_name": "Updated"},
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_update_user_status(client: TestClient, test_user: User, admin_headers: dict) -> None:
|
||||
"""Test deactivating user."""
|
||||
response = client.patch(
|
||||
f"/api/admin/users/{test_user.id}/status",
|
||||
headers=admin_headers,
|
||||
json={"is_active": False},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["is_active"] is False
|
||||
|
||||
# Verify user cannot login
|
||||
login_response = client.post(
|
||||
"/api/auth/login",
|
||||
json={"email": "test@example.com", "password": "testpassword"},
|
||||
)
|
||||
assert login_response.status_code == 403
|
||||
|
||||
|
||||
def test_reset_password(
|
||||
client: TestClient, test_user: User, admin_headers: dict, db: Session
|
||||
) -> None:
|
||||
"""Test resetting user password."""
|
||||
response = client.post(
|
||||
f"/api/admin/users/{test_user.id}/reset-password",
|
||||
headers=admin_headers,
|
||||
json={"new_password": "newpassword123"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Verify new password works
|
||||
login_response = client.post(
|
||||
"/api/auth/login",
|
||||
json={"email": "test@example.com", "password": "newpassword123"},
|
||||
)
|
||||
assert login_response.status_code == 200
|
||||
|
||||
# Verify old password doesn't work
|
||||
old_login_response = client.post(
|
||||
"/api/auth/login",
|
||||
json={"email": "test@example.com", "password": "testpassword"},
|
||||
)
|
||||
assert old_login_response.status_code == 401
|
||||
|
||||
|
||||
def test_reset_password_not_found(client: TestClient, admin_headers: dict) -> None:
|
||||
"""Test resetting password for non-existent user."""
|
||||
response = client.post(
|
||||
"/api/admin/users/99999/reset-password",
|
||||
headers=admin_headers,
|
||||
json={"new_password": "newpassword"},
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
# ===== Audit Log Integration Tests =====
|
||||
|
||||
|
||||
def test_user_creation_creates_audit_log(
|
||||
client: TestClient,
|
||||
admin_token: str,
|
||||
test_admin: User,
|
||||
db: Session,
|
||||
) -> None:
|
||||
"""Test that creating a user creates an audit log entry."""
|
||||
from app.models.audit_log import AuditLog
|
||||
|
||||
response = client.post(
|
||||
"/api/admin/users",
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
json={
|
||||
"email": "newuser@example.com",
|
||||
"full_name": "New User",
|
||||
"password": "newpassword",
|
||||
"role": "user",
|
||||
"organization": "Test Org",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
user_id = response.json()["id"]
|
||||
|
||||
# Check audit log was created
|
||||
audit = db.query(AuditLog).filter(
|
||||
AuditLog.action == "user_created",
|
||||
AuditLog.target_id == user_id
|
||||
).first()
|
||||
|
||||
assert audit is not None
|
||||
assert audit.target_type == "user"
|
||||
assert audit.user_id == test_admin.id
|
||||
assert audit.details == {"email": "newuser@example.com", "role": "user"}
|
||||
|
||||
|
||||
def test_user_update_creates_audit_log(
|
||||
client: TestClient,
|
||||
admin_token: str,
|
||||
test_admin: User,
|
||||
test_user: User,
|
||||
db: Session,
|
||||
) -> None:
|
||||
"""Test that updating a user creates an audit log entry."""
|
||||
from app.models.audit_log import AuditLog
|
||||
|
||||
response = client.put(
|
||||
f"/api/admin/users/{test_user.id}",
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
json={
|
||||
"full_name": "Updated Name",
|
||||
"role": "admin",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
# Check audit log was created
|
||||
audit = db.query(AuditLog).filter(
|
||||
AuditLog.action == "user_updated",
|
||||
AuditLog.target_id == test_user.id
|
||||
).first()
|
||||
|
||||
assert audit is not None
|
||||
assert audit.target_type == "user"
|
||||
assert audit.user_id == test_admin.id
|
||||
# Should track changed fields
|
||||
assert "full_name" in audit.details["updated_fields"]
|
||||
assert "role" in audit.details["updated_fields"]
|
||||
Reference in New Issue
Block a user