diff --git a/ai-hub/integration_tests/API_COVERAGE.md b/ai-hub/integration_tests/API_COVERAGE.md index ffb70c9..8850f48 100644 --- a/ai-hub/integration_tests/API_COVERAGE.md +++ b/ai-hub/integration_tests/API_COVERAGE.md @@ -45,8 +45,12 @@ - [x] `POST /nodes/{node_id}/fs/rm` (Delete File/Dir) ### Users / Auth (`/users`) -Covered roughly by `integration_tests/test_login.py` +Covered by `integration_tests/test_login.py` and `test_user_profile.py` - [x] `POST /users/login/local` (Perform Day 1 Local Login) +- [x] `GET /users/me/profile` (Get Current User Profile) +- [x] `PUT /users/me/profile` (Update User Profile) +- [x] `GET /users/me` (Get Current User Status) +- [x] `GET /users/config` (Public Auth Configuration) - [ ] Check full coverage against `api_reference/users.md` for remaining methods ### Sessions (`/sessions`) diff --git a/ai-hub/integration_tests/test_user_profile.py b/ai-hub/integration_tests/test_user_profile.py new file mode 100644 index 0000000..b3d731f --- /dev/null +++ b/ai-hub/integration_tests/test_user_profile.py @@ -0,0 +1,110 @@ +import os +import httpx +import pytest + +BASE_URL = os.getenv("SYNC_TEST_BASE_URL", "http://127.0.0.1:8002/api/v1") +ADMIN_EMAIL = os.getenv("SUPER_ADMINS", "admin@jerxie.com").split(',')[0] +ADMIN_PASSWORD = os.getenv("CORTEX_ADMIN_PASSWORD", "admin") + +def get_auth_headers(): + """Helper to log in and get auth headers.""" + login_data = { + "email": ADMIN_EMAIL, + "password": ADMIN_PASSWORD + } + with httpx.Client(timeout=10.0) as client: + r = client.post(f"{BASE_URL}/users/login/local", json=login_data) + assert r.status_code == 200, f"Login failed: {r.text}" + json_data = r.json() + user_id = json_data.get("user_id") + assert user_id, "No user_id returned in login response" + return {"X-User-ID": user_id} + +def test_get_user_profile_success(): + """ + Tests that a logged-in user can retrieve their profile. + """ + headers = get_auth_headers() + with httpx.Client(timeout=10.0) as client: + r = client.get(f"{BASE_URL}/users/me/profile", headers=headers) + + assert r.status_code == 200, f"Expected 200 OK, got {r.status_code}: {r.text}" + + json_data = r.json() + assert "email" in json_data, "Response missing 'email'" + assert json_data["email"] == ADMIN_EMAIL, "Response email does not match admin email" + assert "role" in json_data, "Response missing 'role'" + assert json_data["role"] == "admin", "Expected role to be 'admin'" + +def test_update_user_profile_success(): + """ + Tests that a logged-in user can update their profile. + """ + headers = get_auth_headers() + update_data = { + "full_name": "Updated Test Admin", + "username": "updated_admin" + } + with httpx.Client(timeout=10.0) as client: + # Perform update + r = client.put(f"{BASE_URL}/users/me/profile", headers=headers, json=update_data) + assert r.status_code == 200, f"Expected 200 OK, got {r.status_code}: {r.text}" + + json_data = r.json() + assert json_data["full_name"] == "Updated Test Admin", "Full name was not updated" + assert json_data["username"] == "updated_admin", "Username was not updated" + + # Verify update by getting profile again + r = client.get(f"{BASE_URL}/users/me/profile", headers=headers) + assert r.status_code == 200 + json_data = r.json() + assert json_data["full_name"] == "Updated Test Admin" + assert json_data["username"] == "updated_admin" + +def test_get_user_profile_unauthorized(): + """ + Tests that accessing the profile without authentication fails. + """ + with httpx.Client(timeout=10.0) as client: + r = client.get(f"{BASE_URL}/users/me/profile") + + # Since get_current_user_id defaults to "anonymous" and looks for it in DB, + # it will likely return 404 if "anonymous" user doesn't exist, or 401 if it rejects it. + # Let's accept both for now or see what happens. + # Based on code, if not user: raise HTTPException(status_code=404, detail="User not found") + # So we expect 404 if "anonymous" is not in DB. + assert r.status_code in [401, 404], f"Expected 401 or 404, got {r.status_code}: {r.text}" + +def test_get_user_status_authenticated(): + """ + Tests GET /users/me with authentication. + """ + headers = get_auth_headers() + with httpx.Client(timeout=10.0) as client: + r = client.get(f"{BASE_URL}/users/me", headers=headers) + assert r.status_code == 200, f"Expected 200 OK, got {r.status_code}: {r.text}" + json_data = r.json() + assert json_data["is_logged_in"] is True + assert json_data["email"] == ADMIN_EMAIL + +def test_get_user_status_anonymous(): + """ + Tests GET /users/me without authentication. + """ + with httpx.Client(timeout=10.0) as client: + r = client.get(f"{BASE_URL}/users/me") + assert r.status_code == 200, f"Expected 200 OK, got {r.status_code}: {r.text}" + json_data = r.json() + assert json_data["is_logged_in"] is False + assert json_data["email"] == "anonymous" + +def test_get_auth_config(): + """ + Tests GET /users/config. + """ + with httpx.Client(timeout=10.0) as client: + r = client.get(f"{BASE_URL}/users/config") + assert r.status_code == 200, f"Expected 200 OK, got {r.status_code}: {r.text}" + json_data = r.json() + assert "oidc_configured" in json_data + assert "allow_password_login" in json_data