237 lines
9.4 KiB
Python
237 lines
9.4 KiB
Python
"""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
|