Files
space-booking/backend/tests/test_attachments.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

273 lines
8.7 KiB
Python

"""Tests for attachments API."""
from io import BytesIO
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from app.models.attachment import Attachment
from app.models.booking import Booking
from app.models.user import User
@pytest.fixture
def test_attachment(db: Session, test_booking: Booking, test_user: User) -> Attachment:
"""Create test attachment."""
attachment = Attachment(
booking_id=test_booking.id,
filename="test.pdf",
stored_filename="uuid-test.pdf",
filepath="/tmp/uuid-test.pdf",
size=1024,
content_type="application/pdf",
uploaded_by=test_user.id,
)
db.add(attachment)
db.commit()
db.refresh(attachment)
return attachment
def test_upload_attachment(
client: TestClient, auth_headers: dict[str, str], test_booking: Booking
) -> None:
"""Test uploading file attachment."""
# Create a test PDF file
file_content = b"PDF file content here"
files = {"file": ("test.pdf", BytesIO(file_content), "application/pdf")}
response = client.post(
f"/api/bookings/{test_booking.id}/attachments", files=files, headers=auth_headers
)
assert response.status_code == 201
data = response.json()
assert data["booking_id"] == test_booking.id
assert data["filename"] == "test.pdf"
assert data["size"] == len(file_content)
assert data["content_type"] == "application/pdf"
def test_upload_attachment_invalid_type(
client: TestClient, auth_headers: dict[str, str], test_booking: Booking
) -> None:
"""Test uploading file with invalid type."""
file_content = b"Invalid file content"
files = {"file": ("test.exe", BytesIO(file_content), "application/exe")}
response = client.post(
f"/api/bookings/{test_booking.id}/attachments", files=files, headers=auth_headers
)
assert response.status_code == 400
assert "not allowed" in response.json()["detail"]
def test_upload_attachment_too_large(
client: TestClient, auth_headers: dict[str, str], test_booking: Booking
) -> None:
"""Test uploading file that exceeds size limit."""
# Create file larger than 10MB
file_content = b"x" * (11 * 1024 * 1024)
files = {"file": ("large.pdf", BytesIO(file_content), "application/pdf")}
response = client.post(
f"/api/bookings/{test_booking.id}/attachments", files=files, headers=auth_headers
)
assert response.status_code == 400
assert "too large" in response.json()["detail"]
def test_upload_exceeds_limit(
client: TestClient, auth_headers: dict[str, str], test_booking: Booking
) -> None:
"""Test uploading more than 5 files."""
# Upload 5 files
for i in range(5):
file_content = b"PDF file content"
files = {"file": (f"test{i}.pdf", BytesIO(file_content), "application/pdf")}
response = client.post(
f"/api/bookings/{test_booking.id}/attachments", files=files, headers=auth_headers
)
assert response.status_code == 201
# Try to upload 6th file
file_content = b"PDF file content"
files = {"file": ("test6.pdf", BytesIO(file_content), "application/pdf")}
response = client.post(
f"/api/bookings/{test_booking.id}/attachments", files=files, headers=auth_headers
)
assert response.status_code == 400
assert "Maximum 5 files" in response.json()["detail"]
def test_upload_to_others_booking(
client: TestClient, test_user: User, test_admin: User, db: Session
) -> None:
"""Test user cannot upload to another user's booking."""
from datetime import datetime
from app.core.security import create_access_token
from app.models.space import Space
# Create space
space = Space(name="Test Room", type="sala", capacity=10, is_active=True)
db.add(space)
db.commit()
# Create booking for admin
booking = Booking(
user_id=test_admin.id,
space_id=space.id,
title="Admin Meeting",
start_datetime=datetime(2024, 3, 15, 10, 0, 0),
end_datetime=datetime(2024, 3, 15, 12, 0, 0),
status="approved",
)
db.add(booking)
db.commit()
# Try to upload as regular user
user_token = create_access_token(subject=int(test_user.id))
headers = {"Authorization": f"Bearer {user_token}"}
file_content = b"PDF file content"
files = {"file": ("test.pdf", BytesIO(file_content), "application/pdf")}
response = client.post(f"/api/bookings/{booking.id}/attachments", files=files, headers=headers)
assert response.status_code == 403
def test_list_attachments(
client: TestClient, auth_headers: dict[str, str], test_booking: Booking, test_attachment: Attachment
) -> None:
"""Test listing attachments for a booking."""
response = client.get(f"/api/bookings/{test_booking.id}/attachments", headers=auth_headers)
assert response.status_code == 200
data = response.json()
assert len(data) == 1
assert data[0]["id"] == test_attachment.id
assert data[0]["filename"] == test_attachment.filename
def test_download_attachment(
client: TestClient, auth_headers: dict[str, str], test_attachment: Attachment
) -> None:
"""Test downloading attachment file."""
# Create actual file
Path(test_attachment.filepath).parent.mkdir(parents=True, exist_ok=True)
Path(test_attachment.filepath).write_bytes(b"Test file content")
response = client.get(f"/api/attachments/{test_attachment.id}/download", headers=auth_headers)
assert response.status_code == 200
assert response.content == b"Test file content"
# Cleanup
Path(test_attachment.filepath).unlink()
def test_download_attachment_not_found(client: TestClient, auth_headers: dict[str, str]) -> None:
"""Test downloading non-existent attachment."""
response = client.get("/api/attachments/999/download", headers=auth_headers)
assert response.status_code == 404
def test_delete_attachment(
client: TestClient, auth_headers: dict[str, str], test_attachment: Attachment, db: Session
) -> None:
"""Test deleting attachment."""
# Create actual file
Path(test_attachment.filepath).parent.mkdir(parents=True, exist_ok=True)
Path(test_attachment.filepath).write_bytes(b"Test file content")
response = client.delete(f"/api/attachments/{test_attachment.id}", headers=auth_headers)
assert response.status_code == 204
# Verify deleted from database
attachment = db.query(Attachment).filter(Attachment.id == test_attachment.id).first()
assert attachment is None
# Verify file deleted
assert not Path(test_attachment.filepath).exists()
def test_delete_attachment_not_owner(
client: TestClient, auth_headers: dict[str, str], test_user: User, db: Session
) -> None:
"""Test user cannot delete another user's attachment."""
from datetime import datetime
from app.core.security import get_password_hash
from app.models.space import Space
# Create another user
other_user = User(
email="other@example.com",
full_name="Other User",
hashed_password=get_password_hash("password"),
role="user",
is_active=True,
)
db.add(other_user)
db.commit()
# Create space
space = Space(name="Test Room", type="sala", capacity=10, is_active=True)
db.add(space)
db.commit()
# Create booking for other user
booking = Booking(
user_id=other_user.id,
space_id=space.id,
title="Other User Meeting",
start_datetime=datetime(2024, 3, 15, 10, 0, 0),
end_datetime=datetime(2024, 3, 15, 12, 0, 0),
status="approved",
)
db.add(booking)
db.commit()
# Create attachment uploaded by other user
attachment = Attachment(
booking_id=booking.id,
filename="other.pdf",
stored_filename="uuid-other.pdf",
filepath="/tmp/uuid-other.pdf",
size=1024,
content_type="application/pdf",
uploaded_by=other_user.id,
)
db.add(attachment)
db.commit()
# Try to delete as test_user
response = client.delete(f"/api/attachments/{attachment.id}", headers=auth_headers)
assert response.status_code == 403
def test_admin_can_delete_any_attachment(
client: TestClient, admin_headers: dict[str, str], test_attachment: Attachment
) -> None:
"""Test admin can delete any attachment."""
# Create actual file
Path(test_attachment.filepath).parent.mkdir(parents=True, exist_ok=True)
Path(test_attachment.filepath).write_bytes(b"Test file content")
response = client.delete(f"/api/attachments/{test_attachment.id}", headers=admin_headers)
assert response.status_code == 204
# Cleanup
if Path(test_attachment.filepath).exists():
Path(test_attachment.filepath).unlink()