testing
This commit is contained in:
236
backend/tests/test_users.py
Normal file
236
backend/tests/test_users.py
Normal file
@@ -0,0 +1,236 @@
|
||||
"""Tests for user management endpoints (/api/users/*)."""
|
||||
import pytest
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Shared fixtures
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@pytest.fixture
|
||||
def registered_user(client):
|
||||
"""Register a test user and return the API response data."""
|
||||
c, _ = client
|
||||
response = c.post("/api/users/register", json={
|
||||
"email": "test@example.com",
|
||||
"displayName": "Test User",
|
||||
"photoURL": "https://example.com/photo.jpg",
|
||||
})
|
||||
assert response.status_code == 200
|
||||
return response.json()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# POST /api/users/register
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestRegisterUser:
|
||||
def test_register_new_user_returns_200(self, client):
|
||||
c, _ = client
|
||||
response = c.post("/api/users/register", json={"email": "new@example.com", "displayName": "New User"})
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_register_returns_user_fields(self, client):
|
||||
c, _ = client
|
||||
response = c.post("/api/users/register", json={"email": "new@example.com", "displayName": "New User"})
|
||||
data = response.json()
|
||||
assert data["email"] == "new@example.com"
|
||||
assert data["displayName"] == "New User"
|
||||
assert "id" in data
|
||||
assert "createdAt" in data
|
||||
assert "updatedAt" in data
|
||||
|
||||
def test_register_returns_registered_message(self, client):
|
||||
c, _ = client
|
||||
response = c.post("/api/users/register", json={"email": "brand_new@example.com"})
|
||||
assert response.json()["message"] == "User registered successfully"
|
||||
|
||||
def test_register_existing_user_is_idempotent(self, client):
|
||||
c, _ = client
|
||||
payload = {"email": "existing@example.com"}
|
||||
c.post("/api/users/register", json=payload)
|
||||
response = c.post("/api/users/register", json=payload)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["message"] == "User already exists"
|
||||
|
||||
def test_register_idempotent_returns_same_id(self, client):
|
||||
c, _ = client
|
||||
payload = {"email": "same@example.com"}
|
||||
r1 = c.post("/api/users/register", json=payload).json()
|
||||
r2 = c.post("/api/users/register", json=payload).json()
|
||||
assert r1["id"] == r2["id"]
|
||||
|
||||
def test_register_uses_email_prefix_as_default_display_name(self, client):
|
||||
c, _ = client
|
||||
response = c.post("/api/users/register", json={"email": "johndoe@example.com"})
|
||||
assert response.json()["displayName"] == "johndoe"
|
||||
|
||||
def test_register_default_theme_is_light(self, client):
|
||||
c, _ = client
|
||||
response = c.post("/api/users/register", json={"email": "x@example.com"})
|
||||
assert response.json()["theme"] == "light"
|
||||
|
||||
def test_register_missing_email_returns_422(self, client):
|
||||
c, _ = client
|
||||
response = c.post("/api/users/register", json={"displayName": "No Email"})
|
||||
assert response.status_code == 422
|
||||
|
||||
def test_register_without_optional_fields(self, client):
|
||||
c, _ = client
|
||||
response = c.post("/api/users/register", json={"email": "minimal@example.com"})
|
||||
assert response.status_code == 200
|
||||
assert response.json()["photoURL"] is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /api/users/by-email/{email}
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGetUserByEmail:
|
||||
def test_returns_existing_user(self, client, registered_user):
|
||||
c, _ = client
|
||||
email = registered_user["email"]
|
||||
response = c.get(f"/api/users/by-email/{email}")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["email"] == email
|
||||
|
||||
def test_returns_all_user_fields(self, client, registered_user):
|
||||
c, _ = client
|
||||
response = c.get(f"/api/users/by-email/{registered_user['email']}")
|
||||
data = response.json()
|
||||
for field in ("id", "email", "displayName", "theme", "createdAt", "updatedAt"):
|
||||
assert field in data
|
||||
|
||||
def test_nonexistent_email_returns_404(self, client):
|
||||
c, _ = client
|
||||
response = c.get("/api/users/by-email/ghost@example.com")
|
||||
assert response.status_code == 404
|
||||
assert "User not found" in response.json()["detail"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /api/users/{user_id}
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGetUserById:
|
||||
def test_returns_existing_user(self, client, registered_user):
|
||||
c, _ = client
|
||||
user_id = registered_user["id"]
|
||||
response = c.get(f"/api/users/{user_id}")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["id"] == user_id
|
||||
|
||||
def test_invalid_object_id_format_returns_400(self, client):
|
||||
c, _ = client
|
||||
response = c.get("/api/users/not-a-valid-objectid")
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_nonexistent_valid_id_returns_404(self, client):
|
||||
c, _ = client
|
||||
response = c.get("/api/users/507f1f77bcf86cd799439011")
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PUT /api/users/{user_id}
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestUpdateUser:
|
||||
def test_update_display_name(self, client, registered_user):
|
||||
c, _ = client
|
||||
user_id = registered_user["id"]
|
||||
response = c.put(f"/api/users/{user_id}", json={"displayName": "Updated Name"})
|
||||
assert response.status_code == 200
|
||||
assert response.json()["displayName"] == "Updated Name"
|
||||
|
||||
def test_update_theme_to_dark(self, client, registered_user):
|
||||
c, _ = client
|
||||
user_id = registered_user["id"]
|
||||
response = c.put(f"/api/users/{user_id}", json={"theme": "dark"})
|
||||
assert response.status_code == 200
|
||||
assert response.json()["theme"] == "dark"
|
||||
|
||||
def test_update_photo_url(self, client, registered_user):
|
||||
c, _ = client
|
||||
user_id = registered_user["id"]
|
||||
new_url = "https://new-photo.example.com/pic.jpg"
|
||||
response = c.put(f"/api/users/{user_id}", json={"photoURL": new_url})
|
||||
assert response.status_code == 200
|
||||
assert response.json()["photoURL"] == new_url
|
||||
|
||||
def test_update_persists_to_database(self, client, registered_user):
|
||||
c, _ = client
|
||||
user_id = registered_user["id"]
|
||||
c.put(f"/api/users/{user_id}", json={"displayName": "Persisted Name"})
|
||||
response = c.get(f"/api/users/{user_id}")
|
||||
assert response.json()["displayName"] == "Persisted Name"
|
||||
|
||||
def test_partial_update_does_not_clear_other_fields(self, client, registered_user):
|
||||
c, _ = client
|
||||
user_id = registered_user["id"]
|
||||
# Update only theme
|
||||
c.put(f"/api/users/{user_id}", json={"theme": "dark"})
|
||||
response = c.get(f"/api/users/{user_id}")
|
||||
data = response.json()
|
||||
assert data["theme"] == "dark"
|
||||
assert data["displayName"] == "Test User" # original value preserved
|
||||
|
||||
def test_update_nonexistent_user_returns_404(self, client):
|
||||
c, _ = client
|
||||
response = c.put("/api/users/507f1f77bcf86cd799439011", json={"displayName": "X"})
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_update_invalid_id_format_returns_400(self, client):
|
||||
c, _ = client
|
||||
response = c.put("/api/users/bad-id", json={"displayName": "X"})
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DELETE /api/users/{user_id}
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestDeleteUser:
|
||||
def test_delete_user_returns_200(self, client, registered_user):
|
||||
c, _ = client
|
||||
response = c.delete(f"/api/users/{registered_user['id']}")
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_delete_user_returns_deletion_counts(self, client, registered_user):
|
||||
c, _ = client
|
||||
response = c.delete(f"/api/users/{registered_user['id']}")
|
||||
data = response.json()
|
||||
assert data["user_deleted"] == 1
|
||||
assert "entries_deleted" in data
|
||||
|
||||
def test_delete_user_makes_them_unretrievable(self, client, registered_user):
|
||||
c, _ = client
|
||||
user_id = registered_user["id"]
|
||||
c.delete(f"/api/users/{user_id}")
|
||||
response = c.get(f"/api/users/{user_id}")
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_delete_user_also_deletes_their_entries(self, client, registered_user):
|
||||
c, _ = client
|
||||
user_id = registered_user["id"]
|
||||
# Create 2 entries for this user
|
||||
for _ in range(2):
|
||||
c.post(f"/api/entries/{user_id}", json={
|
||||
"encryption": {
|
||||
"encrypted": True,
|
||||
"ciphertext": "dGVzdA==",
|
||||
"nonce": "bm9uY2U=",
|
||||
"algorithm": "XSalsa20-Poly1305",
|
||||
}
|
||||
})
|
||||
response = c.delete(f"/api/users/{user_id}")
|
||||
assert response.json()["entries_deleted"] == 2
|
||||
|
||||
def test_delete_nonexistent_user_returns_404(self, client):
|
||||
c, _ = client
|
||||
response = c.delete("/api/users/507f1f77bcf86cd799439011")
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_delete_invalid_id_format_returns_400(self, client):
|
||||
c, _ = client
|
||||
response = c.delete("/api/users/bad-id")
|
||||
assert response.status_code == 400
|
||||
Reference in New Issue
Block a user