from unittest.mock import AsyncMock import pytest from fastapi.testclient import TestClient from src.backend import main from src.backend.constants import Auth0Users from src.backend.main import app from src.backend.models import User from src.backend.routers.app import ROUTE from src.backend.users.manage import verify_auth0 ROUTE = f"/api{ROUTE}" MOCK_SUB: str = "mock_sub" MOCK_USER_ID: str = "123" @pytest.fixture(autouse=True) def dependency_override(): app.dependency_overrides[verify_auth0] = lambda: {"mock_auth0": "credentials"} @pytest.fixture def dependency_override_mock_sub(): app.dependency_overrides[verify_auth0] = lambda: {"sub": MOCK_SUB} @pytest.fixture def client(): with TestClient(app) as c: yield c @pytest.fixture def mock_user(): return User( id=0, name="John Doe", nickname="mock_nickname", email="mock_email", locale="mock_locale", timezone="mock_timezone", ).model_dump() @pytest.fixture def mock_state_db(mocker): mock = mocker.patch.object(main, "Client") mock.return_value = AsyncMock() yield mock @pytest.fixture def mock_state_db_users(mock_state_db): mock = mock_state_db.return_value.Users = AsyncMock() yield mock @pytest.fixture def mock_state_db_permissions(mock_state_db): mock = mock_state_db.return_value.Permissions = AsyncMock() yield mock class TestGetUserData: def test_get_user_data(self, client, dependency_override_mock_sub): response = client.get(f"{ROUTE}/user") data = response.json()["data"] assert response.status_code == 200 assert data is None, "Not expecting that a user exists for the mock subject." def test_get_user_data_no_subject(self, client): response = client.get(f"{ROUTE}/user") assert response.status_code == 400 @pytest.mark.usefixtures("mock_state_db_users") class TestPatchUserData: @staticmethod def patch_user_response(client, json_data, by_id=False): url = f"{ROUTE}/user" if by_id: url += f"/{MOCK_USER_ID}" # Mock user ID return client.patch(url, json=json_data) def test_patch_user_data_no_sub(self, client, mock_user): response = self.patch_user_response(client, mock_user) assert response.status_code == 400 def test_patch_user_data_no_fields(self, client, dependency_override_mock_sub): response = self.patch_user_response(client, {}) assert response.status_code == 400 @pytest.mark.parametrize("nickname", ["", " "]) def test_patch_user_data_nickname_empty( self, client, dependency_override_mock_sub, mock_user, nickname ): mock_user[Auth0Users.NICKNAME] = nickname response = self.patch_user_response(client, mock_user) assert response.status_code == 400 def test_patch_user_data_nickname_missing( self, client, dependency_override_mock_sub, mock_user ): mock_user.pop(Auth0Users.NICKNAME) response = self.patch_user_response(client, mock_user) assert response.status_code == 400 @pytest.mark.parametrize("full_name", ["", " ", "invalid!chars"]) def test_patch_user_data_full_name_empty_or_invalid_chars( self, client, dependency_override_mock_sub, mock_user, full_name ): mock_user[Auth0Users.NAME] = full_name response = self.patch_user_response(client, mock_user) assert response.status_code == 400 def test_patch_user_data_full_name_too_short( self, client, dependency_override_mock_sub, mock_user ): mock_user[Auth0Users.NAME] = "ab" response = self.patch_user_response(client, mock_user) assert response.status_code == 400 @pytest.mark.parametrize( "by_id,expected_method_to_be_called", [(False, "update"), (True, "update_by_id")], ) def test_patch_user_data_ok( self, client, dependency_override_mock_sub, mock_user, mock_state_db_users, by_id, expected_method_to_be_called, ): method = getattr(mock_state_db_users, expected_method_to_be_called) response = self.patch_user_response(client, mock_user, by_id=by_id) method.assert_called_once_with(MOCK_USER_ID if by_id else MOCK_SUB, mock_user) assert response.status_code == 200 class TestUsersList: def test_users_list(self, client): response = client.get(f"{ROUTE}/users") data = response.json()["data"] assert response.status_code == 200 assert isinstance(data["users"], list) assert isinstance(data["total_count"], int) @pytest.mark.usefixtures("mock_state_db_permissions") class TestUpsertUserPermissions: def test_upsert_user_permissions( self, client, dependency_override_mock_sub, mock_state_db_permissions ): sample_permissions = {"MODULES.TEST": "READ", "MODULES.ANOTHER": "WRITE"} response = client.post( f"{ROUTE}/permissions/{MOCK_USER_ID}", # Mock user ID json=sample_permissions, ) assert response.status_code == 201 mock_state_db_permissions.upsert.assert_called_once_with( MOCK_USER_ID, sample_permissions ) class TestGetPermissionsMeta: def test_get_permissions_meta(self, client): response = client.get("/api/app/permissions/meta") data = response.json()["data"] assert response.status_code == 200 assert "READ" in data["levels"].values() assert isinstance(data["scopes"], dict) assert all(isinstance(scope, dict) for scope in data["scopes"].values())