Files
crm-system/tests/test_users_me.py
T

196 lines
6.2 KiB
Python

"""User /me endpoint tests + RBAC verification."""
from __future__ import annotations
from httpx import AsyncClient
# === /users/me ===
async def test_get_me_returns_user_data(
client: AsyncClient, auth_headers: dict[str, str], registered_user: dict
) -> None:
"""GET /api/v1/users/me with a valid JWT returns 200 + email, name, role, org_id."""
resp = await client.get("/api/v1/users/me", headers=auth_headers)
assert resp.status_code == 200, resp.text
data = resp.json()
assert data["email"] == registered_user["email"]
assert data["name"] == registered_user["name"]
assert "role" in data
assert "org_id" in data
assert "id" in data
assert "password_hash" not in data
assert "avatar_url" in data
assert "email_notifications" in data
async def test_get_me_invalid_token_format(client: AsyncClient) -> None:
"""GET /api/v1/users/me with a malformed token returns 401."""
resp = await client.get(
"/api/v1/users/me",
headers={"Authorization": "Bearer this-is-not-a-jwt"},
)
assert resp.status_code == 401, resp.text
async def test_get_me_bogus_token(client: AsyncClient) -> None:
"""GET /api/v1/users/me with a token signed with the wrong key returns 401."""
import time
from jose import jwt
bogus = jwt.encode(
{
"sub": "1",
"org_id": 1,
"role": "admin",
"exp": int(time.time()) + 3600,
"iat": int(time.time()),
},
"completely-different-secret-key-32chars-abc",
algorithm="HS256",
)
resp = await client.get(
"/api/v1/users/me",
headers={"Authorization": f"Bearer {bogus}"},
)
assert resp.status_code == 401, resp.text
# === PATCH /users/{id} ===
async def test_update_profile_self(
client: AsyncClient, auth_headers: dict[str, str], registered_user: dict
) -> None:
"""PATCH /api/v1/users/{self_id} updates the current user's name."""
user_id = registered_user["user"]["id"]
resp = await client.patch(
f"/api/v1/users/{user_id}",
json={"name": "Updated Name", "email_notifications": False},
headers=auth_headers,
)
assert resp.status_code == 200, resp.text
data = resp.json()
assert data["name"] == "Updated Name"
assert data["email_notifications"] is False
# email and id should be unchanged
assert data["email"] == registered_user["email"]
assert data["id"] == user_id
async def test_update_profile_cannot_change_other(
client: AsyncClient, auth_headers: dict[str, str], registered_user: dict
) -> None:
"""Non-admin cannot update another user's profile (403)."""
# Bootstrap user IS admin (first registered user). To test the 403 case,
# create a sales_rep and try to update someone else.
# First, create a 2nd user (admin can do this)
create_resp = await client.post(
"/api/v1/users/",
json={
"email": "rep@test.com",
"password": "RepPass123!",
"name": "Test Rep",
"role": "sales_rep",
},
headers=auth_headers,
)
assert create_resp.status_code == 201, create_resp.text
other_id = create_resp.json()["id"]
# Now log in as the rep and try to update the admin
login_resp = await client.post(
"/api/v1/auth/login",
data={"username": "rep@test.com", "password": "RepPass123!"},
)
assert login_resp.status_code == 200, login_resp.text
rep_token = login_resp.json()["access_token"]
rep_headers = {"Authorization": f"Bearer {rep_token}"}
# Rep tries to update the admin → 403
admin_id = registered_user["user"]["id"]
resp = await client.patch(
f"/api/v1/users/{admin_id}",
json={"name": "Hacked"},
headers=rep_headers,
)
assert resp.status_code == 403, resp.text
# === GET /users/ (admin list) ===
async def test_list_users_as_admin(
client: AsyncClient, auth_headers: dict[str, str], registered_user: dict
) -> None:
"""GET /api/v1/users/ as admin returns 200 + paginated list."""
resp = await client.get("/api/v1/users/", headers=auth_headers)
assert resp.status_code == 200, resp.text
data = resp.json()
assert "items" in data
assert "total" in data
assert "page" in data
assert "page_size" in data
assert data["total"] >= 1
assert len(data["items"]) >= 1
# The bootstrap user is in the list
emails = [u["email"] for u in data["items"]]
assert registered_user["email"] in emails
async def test_list_users_as_sales_rep_forbidden(
client: AsyncClient, auth_headers: dict[str, str]
) -> None:
"""GET /api/v1/users/ as sales_rep → 403."""
# Create a sales_rep via the admin
create_resp = await client.post(
"/api/v1/users/",
json={
"email": "rep@test.com",
"password": "RepPass123!",
"name": "Test Rep",
"role": "sales_rep",
},
headers=auth_headers,
)
assert create_resp.status_code == 201, create_resp.text
# Log in as the rep
login_resp = await client.post(
"/api/v1/auth/login",
data={"username": "rep@test.com", "password": "RepPass123!"},
)
assert login_resp.status_code == 200, login_resp.text
rep_token = login_resp.json()["access_token"]
rep_headers = {"Authorization": f"Bearer {rep_token}"}
# Rep tries to list all users → 403
resp = await client.get("/api/v1/users/", headers=rep_headers)
assert resp.status_code == 403, resp.text
# === Refresh & Logout ===
async def test_refresh_token(
client: AsyncClient, auth_headers: dict[str, str]
) -> None:
"""POST /api/v1/auth/refresh issues a new token for the current user."""
resp = await client.post("/api/v1/auth/refresh", headers=auth_headers)
assert resp.status_code == 200, resp.text
data = resp.json()
assert "access_token" in data
assert data["token_type"] == "bearer"
assert data["expires_in"] > 0
async def test_logout(
client: AsyncClient, auth_headers: dict[str, str]
) -> None:
"""POST /api/v1/auth/logout returns 200 with confirmation message."""
resp = await client.post("/api/v1/auth/logout", headers=auth_headers)
assert resp.status_code == 200, resp.text
data = resp.json()
assert data["message"] == "logged out"