diff --git a/pytest.ini b/pytest.ini index 65c177f..916c434 100644 --- a/pytest.ini +++ b/pytest.ini @@ -14,6 +14,10 @@ addopts = --timeout=20 --timeout-method=thread +# Asyncio configuration +asyncio_mode = auto +asyncio_default_fixture_loop_scope = function + # Markers markers = slow: marks tests as slow (deselect with '-m "not slow"') diff --git a/tests/conftest.py b/tests/conftest.py index 8317e19..e0e1da7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,11 +4,13 @@ import os import pytest +import pytest_asyncio import subprocess import time import threading import sys -from typing import Generator +import asyncio +from typing import Generator, AsyncGenerator from tests.test_client import CFMSTestClient @@ -271,8 +273,8 @@ def admin_credentials(server_process) -> dict: } -@pytest.fixture -def client(server_process) -> Generator[CFMSTestClient, None, None]: +@pytest_asyncio.fixture +async def client(server_process) -> AsyncGenerator[CFMSTestClient, None]: """ Provide a connected test client for each test. """ @@ -282,29 +284,29 @@ def client(server_process) -> Generator[CFMSTestClient, None, None]: max_attempts = 5 for attempt in range(max_attempts): try: - test_client.connect() + await test_client.connect() break except (ConnectionRefusedError, TimeoutError, OSError) as e: if attempt == max_attempts - 1: pytest.fail(f"Failed to connect to server after {max_attempts} attempts: {e}") - time.sleep(1) + await asyncio.sleep(1) yield test_client # Cleanup try: - test_client.disconnect() + await test_client.disconnect() except: pass -@pytest.fixture -def authenticated_client(client: CFMSTestClient, admin_credentials: dict) -> CFMSTestClient: +@pytest_asyncio.fixture +async def authenticated_client(client: CFMSTestClient, admin_credentials: dict) -> CFMSTestClient: """ Provide an authenticated test client with admin credentials. """ try: - response = client.login( + response = await client.login( admin_credentials["username"], admin_credentials["password"] ) @@ -317,13 +319,13 @@ def authenticated_client(client: CFMSTestClient, admin_credentials: dict) -> CFM return client -@pytest.fixture -def test_document(authenticated_client: CFMSTestClient) -> Generator[dict, None, None]: +@pytest_asyncio.fixture +async def test_document(authenticated_client: CFMSTestClient) -> AsyncGenerator[dict, None]: """ Create a test document and clean it up after the test. """ try: - response = authenticated_client.create_document("Test Document") + response = await authenticated_client.create_document("Test Document") except Exception as e: pytest.fail(f"Failed to create test document: {e}") @@ -335,11 +337,11 @@ def test_document(authenticated_client: CFMSTestClient) -> Generator[dict, None, # Upload file to activate the document try: - authenticated_client.upload_file_to_server(task_id, "./pyproject.toml") + await authenticated_client.upload_file_to_server(task_id, "./pyproject.toml") except Exception as e: # Try to cleanup before failing try: - authenticated_client.delete_document(document_id) + await authenticated_client.delete_document(document_id) except: pass pytest.fail(f"Failed to upload file to document: {e}") @@ -351,13 +353,13 @@ def test_document(authenticated_client: CFMSTestClient) -> Generator[dict, None, # Cleanup try: - authenticated_client.delete_document(document_id) + await authenticated_client.delete_document(document_id) except Exception: pass # Ignore cleanup errors -@pytest.fixture -def test_user(authenticated_client: CFMSTestClient) -> Generator[dict, None, None]: +@pytest_asyncio.fixture +async def test_user(authenticated_client: CFMSTestClient) -> AsyncGenerator[dict, None]: """ Create a test user and clean it up after the test. """ @@ -365,7 +367,7 @@ def test_user(authenticated_client: CFMSTestClient) -> Generator[dict, None, Non password = "TestPassword123!" try: - response = authenticated_client.create_user( + response = await authenticated_client.create_user( username=username, password=password, nickname="Test User" @@ -384,20 +386,20 @@ def test_user(authenticated_client: CFMSTestClient) -> Generator[dict, None, Non # Cleanup try: - authenticated_client.delete_user(username) + await authenticated_client.delete_user(username) except Exception: pass -@pytest.fixture -def test_group(authenticated_client: CFMSTestClient) -> Generator[dict, None, None]: +@pytest_asyncio.fixture +async def test_group(authenticated_client: CFMSTestClient) -> AsyncGenerator[dict, None]: """ Create a test group and clean it up after the test. """ group_name = f"test_group_{int(time.time() * 1000)}" try: - response = authenticated_client.create_group( + response = await authenticated_client.create_group( group_name=group_name, permissions=[] ) @@ -413,6 +415,6 @@ def test_group(authenticated_client: CFMSTestClient) -> Generator[dict, None, No # Cleanup try: - authenticated_client.send_request("delete_group", {"group_name": group_name}) + await authenticated_client.send_request("delete_group", {"group_name": group_name}) except Exception: pass diff --git a/tests/test_basic.py b/tests/test_basic.py index ec65f30..c5bd1a1 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -9,17 +9,17 @@ class TestServerBasics: """Test basic server functionality with improved assertions.""" - def test_server_connection(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_server_connection(self, client: CFMSTestClient): """Test that we can establish and maintain a WebSocket connection.""" assert client.websocket is not None, "WebSocket connection was not established" - assert hasattr(client.websocket, 'protocol'), "WebSocket missing protocol attribute" - assert client.websocket.protocol.state.name == "OPEN", \ - f"WebSocket not in OPEN state: {client.websocket.protocol.state.name}" + assert hasattr(client.websocket, 'id'), "WebSocket missing id attribute" - def test_server_info(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_server_info(self, client: CFMSTestClient): """Test getting server information without authentication.""" try: - response = client.server_info() + response = await client.server_info() except Exception as e: pytest.fail(f"server_info() raised an exception: {e}") @@ -36,10 +36,11 @@ def test_server_info(self, client: CFMSTestClient): assert field in response["data"], \ f"Server info missing required field '{field}'" - def test_unknown_action(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_unknown_action(self, client: CFMSTestClient): """Test that server properly rejects unknown action types.""" try: - response = client.send_request("nonexistent_action_xyz_123", include_auth=False) + response = await client.send_request("nonexistent_action_xyz_123", include_auth=False) except Exception as e: pytest.fail(f"send_request() raised an exception: {e}") @@ -57,10 +58,11 @@ def test_unknown_action(self, client: CFMSTestClient): class TestAuthentication: """Test authentication functionality with comprehensive scenarios.""" - def test_login_success(self, client: CFMSTestClient, admin_credentials: dict): + @pytest.mark.asyncio + async def test_login_success(self, client: CFMSTestClient, admin_credentials: dict): """Test successful login with valid admin credentials.""" try: - response = client.login( + response = await client.login( admin_credentials["username"], admin_credentials["password"] ) @@ -82,10 +84,11 @@ def test_login_success(self, client: CFMSTestClient, admin_credentials: dict): assert client.username == admin_credentials["username"], \ f"Client username mismatch: expected {admin_credentials['username']}, got {client.username}" - def test_login_invalid_credentials(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_login_invalid_credentials(self, client: CFMSTestClient): """Test login fails with invalid credentials.""" try: - response = client.login("invalid_user_xyz", "invalid_password_xyz") + response = await client.login("invalid_user_xyz", "invalid_password_xyz") except Exception as e: pytest.fail(f"login() raised an exception: {e}") @@ -99,10 +102,11 @@ def test_login_invalid_credentials(self, client: CFMSTestClient): assert any(keyword in message for keyword in ["invalid", "credentials", "authentication"]), \ f"Error message doesn't indicate auth failure: {response['message']}" - def test_login_missing_username(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_login_missing_username(self, client: CFMSTestClient): """Test login fails when username is missing.""" try: - response = client.send_request( + response = await client.send_request( "login", {"password": "test_password"}, include_auth=False @@ -115,10 +119,11 @@ def test_login_missing_username(self, client: CFMSTestClient): assert response["code"] == 400, \ f"Expected 400 for missing username, got {response.get('code')}" - def test_login_missing_password(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_login_missing_password(self, client: CFMSTestClient): """Test login fails when password is missing.""" try: - response = client.send_request( + response = await client.send_request( "login", {"username": "test_user"}, include_auth=False @@ -131,13 +136,14 @@ def test_login_missing_password(self, client: CFMSTestClient): assert response["code"] == 400, \ f"Expected 400 for missing password, got {response.get('code')}" - def test_refresh_token(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_refresh_token(self, authenticated_client: CFMSTestClient): """Test token refresh functionality.""" old_token = authenticated_client.token assert old_token is not None, "Client should have a token before refresh" try: - response = authenticated_client.refresh_token() + response = await authenticated_client.refresh_token() except Exception as e: pytest.fail(f"refresh_token() raised an exception: {e}") @@ -153,10 +159,11 @@ def test_refresh_token(self, authenticated_client: CFMSTestClient): assert new_token is not None, "Token should still be set after refresh" assert new_token != old_token, "Token should change after refresh" - def test_authentication_required(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_authentication_required(self, client: CFMSTestClient): """Test that protected endpoints require authentication.""" try: - response = client.send_request("list_users", include_auth=False) + response = await client.send_request("list_users", include_auth=False) except Exception as e: pytest.fail(f"send_request() raised an exception: {e}") @@ -165,10 +172,11 @@ def test_authentication_required(self, client: CFMSTestClient): assert response["code"] == 401, \ f"Expected 401 for unauthenticated request, got {response.get('code')}" - def test_invalid_token(self, client: CFMSTestClient, admin_credentials: dict): + @pytest.mark.asyncio + async def test_invalid_token(self, client: CFMSTestClient, admin_credentials: dict): """Test request with an invalid authentication token.""" # Login first to set up proper session structure - login_response = client.login( + login_response = await client.login( admin_credentials["username"], admin_credentials["password"] ) @@ -176,7 +184,7 @@ def test_invalid_token(self, client: CFMSTestClient, admin_credentials: dict): # Now send request with invalid token try: - response = client.send_request( + response = await client.send_request( "list_users", username=admin_credentials["username"], token="invalid_token_xyz_12345" diff --git a/tests/test_client.py b/tests/test_client.py index f01f90f..b1f5c48 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -9,9 +9,9 @@ import mmap import os import ssl -import time +import asyncio from typing import Any, Dict, Optional -from websockets.sync.client import connect, ClientConnection +from websockets.asyncio.client import connect, ClientConnection def calculate_sha256(file_path: str) -> str: @@ -57,7 +57,7 @@ def __init__(self, host: str = "localhost", port: int = 5104, use_ssl: bool = Tr self.username: Optional[str] = None self.token: Optional[str] = None - def connect(self) -> None: + async def connect(self) -> None: """ Establish a WebSocket connection to the server with retry/backoff logic. """ @@ -81,30 +81,30 @@ def connect(self) -> None: for attempt in range(1, max_retries + 1): try: - # connect(...) returns a sync context manager / connection object - self.websocket = connect(uri, ssl=ssl_context) + # connect(...) returns an async connection object + self.websocket = await connect(uri, ssl=ssl_context) return except Exception as exc: last_exc = exc if attempt == max_retries: break - time.sleep(delay) + await asyncio.sleep(delay) delay *= backoff # If we reach here, all attempts failed raise RuntimeError(f"Failed to connect to {uri} after {max_retries} attempts") from last_exc - def disconnect(self) -> None: + async def disconnect(self) -> None: """ Close the WebSocket connection. """ if self.websocket is not None: - self.websocket.close() + await self.websocket.close() self.websocket = None self.username = None self.token = None - def send_request( + async def send_request( self, action: str, data: Optional[Dict[str, Any]] = None, @@ -137,11 +137,11 @@ def send_request( request["username"] = username if username is not None else self.username request["token"] = token if token is not None else self.token - self.websocket.send(json.dumps(request, ensure_ascii=False)) - response_text = self.websocket.recv() + await self.websocket.send(json.dumps(request, ensure_ascii=False)) + response_text = await self.websocket.recv() return json.loads(response_text) - def login(self, username: str, password: str) -> Dict[str, Any]: + async def login(self, username: str, password: str) -> Dict[str, Any]: """ Authenticate with the server. @@ -152,7 +152,7 @@ def login(self, username: str, password: str) -> Dict[str, Any]: Returns: The login response from the server """ - response = self.send_request( + response = await self.send_request( "login", {"username": username, "password": password}, include_auth=False @@ -164,30 +164,30 @@ def login(self, username: str, password: str) -> Dict[str, Any]: return response - def server_info(self) -> Dict[str, Any]: + async def server_info(self) -> Dict[str, Any]: """ Get server information. Returns: Server information including version and protocol version """ - return self.send_request("server_info", include_auth=False) + return await self.send_request("server_info", include_auth=False) - def refresh_token(self) -> Dict[str, Any]: + async def refresh_token(self) -> Dict[str, Any]: """ Refresh the authentication token. Returns: Response with new token """ - response = self.send_request("refresh_token") + response = await self.send_request("refresh_token") if response.get("code") == 200: self.token = response.get("data", {}).get("token") return response - def get_document(self, document_id: str) -> Dict[str, Any]: + async def get_document(self, document_id: str) -> Dict[str, Any]: """ Get a document by ID. @@ -197,9 +197,9 @@ def get_document(self, document_id: str) -> Dict[str, Any]: Returns: The document data """ - return self.send_request("get_document", {"document_id": document_id}) + return await self.send_request("get_document", {"document_id": document_id}) - def create_document(self, title: str, folder_id: Optional[str] = None) -> Dict[str, Any]: + async def create_document(self, title: str, folder_id: Optional[str] = None) -> Dict[str, Any]: """ Create a new document. @@ -213,9 +213,9 @@ def create_document(self, title: str, folder_id: Optional[str] = None) -> Dict[s data = {"title": title} if folder_id is not None: data["folder_id"] = folder_id - return self.send_request("create_document", data) + return await self.send_request("create_document", data) - def delete_document(self, document_id: str) -> Dict[str, Any]: + async def delete_document(self, document_id: str) -> Dict[str, Any]: """ Delete a document. @@ -225,9 +225,9 @@ def delete_document(self, document_id: str) -> Dict[str, Any]: Returns: Response indicating success or failure """ - return self.send_request("delete_document", {"document_id": document_id}) + return await self.send_request("delete_document", {"document_id": document_id}) - def rename_document(self, document_id: str, new_title: str) -> Dict[str, Any]: + async def rename_document(self, document_id: str, new_title: str) -> Dict[str, Any]: """ Rename a document. @@ -238,12 +238,12 @@ def rename_document(self, document_id: str, new_title: str) -> Dict[str, Any]: Returns: Response indicating success or failure """ - return self.send_request("rename_document", { + return await self.send_request("rename_document", { "document_id": document_id, "new_title": new_title }) - def get_document_info(self, document_id: str) -> Dict[str, Any]: + async def get_document_info(self, document_id: str) -> Dict[str, Any]: """ Get information about a document. @@ -253,9 +253,9 @@ def get_document_info(self, document_id: str) -> Dict[str, Any]: Returns: Document information """ - return self.send_request("get_document_info", {"document_id": document_id}) + return await self.send_request("get_document_info", {"document_id": document_id}) - def list_directory(self, folder_id: Optional[str] = None) -> Dict[str, Any]: + async def list_directory(self, folder_id: Optional[str] = None) -> Dict[str, Any]: """ List contents of a directory. @@ -268,9 +268,9 @@ def list_directory(self, folder_id: Optional[str] = None) -> Dict[str, Any]: data = {} data["folder_id"] = folder_id - return self.send_request("list_directory", data) + return await self.send_request("list_directory", data) - def create_directory(self, name: str, parent_id: Optional[str] = None) -> Dict[str, Any]: + async def create_directory(self, name: str, parent_id: Optional[str] = None) -> Dict[str, Any]: """ Create a new directory. @@ -284,9 +284,9 @@ def create_directory(self, name: str, parent_id: Optional[str] = None) -> Dict[s data = {"name": name} if parent_id is not None: data["parent_id"] = parent_id - return self.send_request("create_directory", data) + return await self.send_request("create_directory", data) - def delete_directory(self, folder_id: str) -> Dict[str, Any]: + async def delete_directory(self, folder_id: str) -> Dict[str, Any]: """ Delete a directory. @@ -296,9 +296,9 @@ def delete_directory(self, folder_id: str) -> Dict[str, Any]: Returns: Response indicating success or failure """ - return self.send_request("delete_directory", {"folder_id": folder_id}) + return await self.send_request("delete_directory", {"folder_id": folder_id}) - def create_user( + async def create_user( self, username: str, password: str, @@ -325,9 +325,9 @@ def create_user( data["nickname"] = nickname if groups is not None: data["groups"] = groups - return self.send_request("create_user", data) + return await self.send_request("create_user", data) - def delete_user(self, username: str) -> Dict[str, Any]: + async def delete_user(self, username: str) -> Dict[str, Any]: """ Delete a user. @@ -337,9 +337,9 @@ def delete_user(self, username: str) -> Dict[str, Any]: Returns: Response indicating success or failure """ - return self.send_request("delete_user", {"username": username}) + return await self.send_request("delete_user", {"username": username}) - def get_user_info(self, username: str) -> Dict[str, Any]: + async def get_user_info(self, username: str) -> Dict[str, Any]: """ Get information about a user. @@ -349,18 +349,18 @@ def get_user_info(self, username: str) -> Dict[str, Any]: Returns: User information """ - return self.send_request("get_user_info", {"username": username}) + return await self.send_request("get_user_info", {"username": username}) - def list_users(self) -> Dict[str, Any]: + async def list_users(self) -> Dict[str, Any]: """ List all users. Returns: List of users """ - return self.send_request("list_users", {}) + return await self.send_request("list_users", {}) - def create_group(self, group_name: str, permissions: Optional[list] = None) -> Dict[str, Any]: + async def create_group(self, group_name: str, permissions: Optional[list] = None) -> Dict[str, Any]: """ Create a new user group. @@ -374,18 +374,18 @@ def create_group(self, group_name: str, permissions: Optional[list] = None) -> D data: dict[str, Any] = {"group_name": group_name} if permissions is not None: data["permissions"] = permissions - return self.send_request("create_group", data) + return await self.send_request("create_group", data) - def list_groups(self) -> Dict[str, Any]: + async def list_groups(self) -> Dict[str, Any]: """ List all user groups. Returns: List of groups """ - return self.send_request("list_groups", {}) + return await self.send_request("list_groups", {}) - def get_group_info(self, group_name: str) -> Dict[str, Any]: + async def get_group_info(self, group_name: str) -> Dict[str, Any]: """ Get information about a group. @@ -395,9 +395,9 @@ def get_group_info(self, group_name: str) -> Dict[str, Any]: Returns: Group information """ - return self.send_request("get_group_info", {"group_name": group_name}) + return await self.send_request("get_group_info", {"group_name": group_name}) - def upload_file_to_server( + async def upload_file_to_server( self, task_id: str, file_path: str ): """ @@ -413,7 +413,7 @@ def upload_file_to_server( """ # Receive file metadata from the server - response = self.send_request( + response = await self.send_request( "upload_file", {"task_id": task_id}, include_auth=True @@ -434,8 +434,8 @@ def upload_file_to_server( } assert self.websocket - self.websocket.send(json.dumps(task_info, ensure_ascii=False)) - received_response = str(self.websocket.recv()) + await self.websocket.send(json.dumps(task_info, ensure_ascii=False)) + received_response = str(await self.websocket.recv()) if received_response.startswith("ready"): ready = True @@ -451,13 +451,13 @@ def upload_file_to_server( with open(file_path, "rb") as f: while True: chunk = f.read(chunk_size) - self.websocket.send(chunk) + await self.websocket.send(chunk) if not chunk or len(chunk) < chunk_size: break # need to wait for server confirmation - server_response = json.loads(self.websocket.recv()) + server_response = json.loads(await self.websocket.recv()) except Exception: raise @@ -619,11 +619,11 @@ def upload_file_to_server( # await aiofiles.os.remove(file_path) # raise - def __enter__(self): - """Context manager entry.""" - self.connect() + async def __aenter__(self): + """Async context manager entry.""" + await self.connect() return self - def __exit__(self, exc_type, exc_val, exc_tb): - """Context manager exit.""" - self.disconnect() + async def __aexit__(self, exc_type, exc_val, exc_tb): + """Async context manager exit.""" + await self.disconnect() diff --git a/tests/test_debugging.py b/tests/test_debugging.py index eb20661..6df0da3 100644 --- a/tests/test_debugging.py +++ b/tests/test_debugging.py @@ -9,6 +9,7 @@ class TestDebugging: """Debugging tests for development purposes.""" - def test_placeholder(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_placeholder(self, authenticated_client: CFMSTestClient): """Placeholder test to ensure test discovery works.""" assert True, "This is a placeholder test" diff --git a/tests/test_directories.py b/tests/test_directories.py index 709fd45..55bc582 100644 --- a/tests/test_directories.py +++ b/tests/test_directories.py @@ -9,17 +9,19 @@ class TestDirectoryOperations: """Test directory operations.""" - def test_list_directory_root(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_list_directory_root(self, authenticated_client: CFMSTestClient): """Test listing the root directory.""" - response = authenticated_client.list_directory() + response = await authenticated_client.list_directory() assert response["code"] == 200 assert "data" in response - def test_create_directory(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_create_directory(self, authenticated_client: CFMSTestClient): """Test creating a new directory.""" dir_name = "Test Directory" - response = authenticated_client.create_directory(dir_name) + response = await authenticated_client.create_directory(dir_name) # Directory creation might succeed or fail based on permissions # We just check the response is valid @@ -31,62 +33,66 @@ def test_create_directory(self, authenticated_client: CFMSTestClient): directory_id = response["data"].get("id") if directory_id: try: - authenticated_client.delete_directory(directory_id) + await authenticated_client.delete_directory(directory_id) except Exception: pass - def test_create_directory_with_empty_name(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_create_directory_with_empty_name(self, authenticated_client: CFMSTestClient): """Test creating a directory with an empty name.""" - response = authenticated_client.create_directory("") + response = await authenticated_client.create_directory("") # Should fail validation assert response["code"] == 400 - def test_delete_directory(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_delete_directory(self, authenticated_client: CFMSTestClient): """Test deleting a directory.""" # First create a directory - create_response = authenticated_client.create_directory("Directory to Delete") + create_response = await authenticated_client.create_directory("Directory to Delete") if create_response["code"] == 200: directory_id = create_response["data"]["id"] # Delete it - delete_response = authenticated_client.delete_directory(directory_id) + delete_response = await authenticated_client.delete_directory(directory_id) # Should get a response (success or failure is implementation-dependent) assert "code" in delete_response - def test_delete_nonexistent_directory(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_delete_nonexistent_directory(self, authenticated_client: CFMSTestClient): """Test deleting a directory that doesn't exist.""" - response = authenticated_client.delete_directory("nonexistent_folder_id") + response = await authenticated_client.delete_directory("nonexistent_folder_id") assert response["code"] != 200 - def test_list_directory_contents(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_list_directory_contents(self, authenticated_client: CFMSTestClient): """Test listing directory contents after creating items.""" # Create a test directory - dir_response = authenticated_client.create_directory("Test List Dir") + dir_response = await authenticated_client.create_directory("Test List Dir") if dir_response["code"] == 200: directory_id = dir_response["data"]["id"] try: # Create a document in the directory - doc_response = authenticated_client.create_document( + doc_response = await authenticated_client.create_document( "Test Doc in Dir", folder_id=directory_id ) if doc_response["code"] == 200: # List the directory - list_response = authenticated_client.list_directory(directory_id) + list_response = await authenticated_client.list_directory(directory_id) assert list_response["code"] == 200 assert "data" in list_response # Cleanup document try: - authenticated_client.delete_document( + await authenticated_client.delete_document( doc_response["data"]["document_id"] ) except Exception: @@ -94,7 +100,7 @@ def test_list_directory_contents(self, authenticated_client: CFMSTestClient): finally: # Cleanup directory try: - authenticated_client.delete_directory(directory_id) + await authenticated_client.delete_directory(directory_id) except Exception: pass @@ -102,9 +108,10 @@ def test_list_directory_contents(self, authenticated_client: CFMSTestClient): class TestDirectoryWithoutAuth: """Test that directory operations require authentication.""" - def test_list_directory_without_auth(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_list_directory_without_auth(self, client: CFMSTestClient): """Test that listing directories requires authentication.""" - response = client.send_request( + response = await client.send_request( "list_directory", {"folder_id": None}, include_auth=False @@ -112,9 +119,10 @@ def test_list_directory_without_auth(self, client: CFMSTestClient): assert response["code"] == 401 - def test_create_directory_without_auth(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_create_directory_without_auth(self, client: CFMSTestClient): """Test that creating a directory requires authentication.""" - response = client.send_request( + response = await client.send_request( "create_directory", {"name": "Test"}, include_auth=False diff --git a/tests/test_documents.py b/tests/test_documents.py index 3100702..be688f8 100644 --- a/tests/test_documents.py +++ b/tests/test_documents.py @@ -9,10 +9,11 @@ class TestDocumentOperations: """Test document CRUD operations with comprehensive validation.""" - def test_create_document(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_create_document(self, authenticated_client: CFMSTestClient): """Test creating a new document and verify response structure.""" try: - response = authenticated_client.create_document("Test Document") + response = await authenticated_client.create_document("Test Document") except Exception as e: pytest.fail(f"create_document() raised an exception: {e}") @@ -29,14 +30,15 @@ def test_create_document(self, authenticated_client: CFMSTestClient): # Cleanup document_id = response["data"]["document_id"] try: - authenticated_client.delete_document(document_id) + await authenticated_client.delete_document(document_id) except Exception: pass - def test_get_document(self, authenticated_client: CFMSTestClient, test_document: dict): + @pytest.mark.asyncio + async def test_get_document(self, authenticated_client: CFMSTestClient, test_document: dict): """Test retrieving a document by ID.""" try: - response = authenticated_client.get_document(test_document["document_id"]) + response = await authenticated_client.get_document(test_document["document_id"]) except Exception as e: pytest.fail(f"get_document() raised an exception: {e}") @@ -47,10 +49,11 @@ def test_get_document(self, authenticated_client: CFMSTestClient, test_document: assert "data" in response, "Response missing 'data'" - def test_get_nonexistent_document(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_get_nonexistent_document(self, authenticated_client: CFMSTestClient): """Test retrieving a document that doesn't exist returns appropriate error.""" try: - response = authenticated_client.get_document("nonexistent_doc_id_xyz_123") + response = await authenticated_client.get_document("nonexistent_doc_id_xyz_123") except Exception as e: pytest.fail(f"get_document() raised an exception: {e}") @@ -61,10 +64,11 @@ def test_get_nonexistent_document(self, authenticated_client: CFMSTestClient): assert response["code"] in [400, 404], \ f"Expected 400 or 404 for nonexistent document, got {response.get('code')}" - def test_get_document_info(self, authenticated_client: CFMSTestClient, test_document: dict): + @pytest.mark.asyncio + async def test_get_document_info(self, authenticated_client: CFMSTestClient, test_document: dict): """Test getting document metadata.""" try: - response = authenticated_client.get_document_info(test_document["document_id"]) + response = await authenticated_client.get_document_info(test_document["document_id"]) except Exception as e: pytest.fail(f"get_document_info() raised an exception: {e}") @@ -76,12 +80,13 @@ def test_get_document_info(self, authenticated_client: CFMSTestClient, test_docu assert "data" in response, "Response missing 'data'" assert isinstance(response["data"], dict), "'data' should be a dictionary" - def test_rename_document(self, authenticated_client: CFMSTestClient, test_document: dict): + @pytest.mark.asyncio + async def test_rename_document(self, authenticated_client: CFMSTestClient, test_document: dict): """Test renaming a document and verifying the change.""" new_title = "Renamed Test Document XYZ" try: - response = authenticated_client.rename_document( + response = await authenticated_client.rename_document( test_document["document_id"], new_title ) @@ -95,7 +100,7 @@ def test_rename_document(self, authenticated_client: CFMSTestClient, test_docume # Verify the rename try: - info_response = authenticated_client.get_document_info(test_document["document_id"]) + info_response = await authenticated_client.get_document_info(test_document["document_id"]) except Exception as e: pytest.fail(f"get_document_info() raised an exception: {e}") @@ -103,11 +108,12 @@ def test_rename_document(self, authenticated_client: CFMSTestClient, test_docume assert info_response["data"]["title"] == new_title, \ f"Document title not updated: expected '{new_title}', got '{info_response['data'].get('title')}'" - def test_delete_document(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_delete_document(self, authenticated_client: CFMSTestClient): """Test deleting a document and verify it's removed.""" # Create a document to delete try: - create_response = authenticated_client.create_document("Document to Delete") + create_response = await authenticated_client.create_document("Document to Delete") except Exception as e: pytest.fail(f"Failed to create document for deletion test: {e}") @@ -116,7 +122,7 @@ def test_delete_document(self, authenticated_client: CFMSTestClient): # Delete it try: - delete_response = authenticated_client.delete_document(document_id) + delete_response = await authenticated_client.delete_document(document_id) except Exception as e: pytest.fail(f"delete_document() raised an exception: {e}") @@ -127,17 +133,18 @@ def test_delete_document(self, authenticated_client: CFMSTestClient): # Verify it's gone try: - get_response = authenticated_client.get_document(document_id) + get_response = await authenticated_client.get_document(document_id) except Exception as e: pytest.fail(f"get_document() raised an exception during verification: {e}") assert get_response.get("code") != 200, \ "Document should not be retrievable after deletion" - def test_create_document_with_empty_title(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_create_document_with_empty_title(self, authenticated_client: CFMSTestClient): """Test that creating a document with empty title fails validation.""" try: - response = authenticated_client.create_document("") + response = await authenticated_client.create_document("") except Exception as e: pytest.fail(f"create_document() raised an exception: {e}") @@ -146,33 +153,34 @@ def test_create_document_with_empty_title(self, authenticated_client: CFMSTestCl assert response["code"] == 400, \ f"Expected 400 for empty title, got {response.get('code')}" - def test_create_multiple_documents(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_create_multiple_documents(self, authenticated_client: CFMSTestClient): """Test creating multiple documents successfully.""" document_ids = [] num_documents = 3 try: for i in range(num_documents): - response = authenticated_client.create_document(f"Test Document {i}") + response = await authenticated_client.create_document(f"Test Document {i}") assert response.get("code") == 200, \ f"Failed to create document {i}: {response}" # Upload file to activate the document task_id = response["data"]["task_data"]["task_id"] - authenticated_client.upload_file_to_server(task_id, "./pyproject.toml") + await authenticated_client.upload_file_to_server(task_id, "./pyproject.toml") document_ids.append(response["data"]["document_id"]) # Verify all documents exist for doc_id in document_ids: - response = authenticated_client.get_document_info(doc_id) + response = await authenticated_client.get_document_info(doc_id) assert response.get("code") == 200, \ f"Document {doc_id} not found after creation" finally: # Cleanup all documents for doc_id in document_ids: try: - authenticated_client.delete_document(doc_id) + await authenticated_client.delete_document(doc_id) except Exception: pass @@ -180,10 +188,11 @@ def test_create_multiple_documents(self, authenticated_client: CFMSTestClient): class TestDocumentWithoutAuth: """Test that document operations properly require authentication.""" - def test_create_document_without_auth(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_create_document_without_auth(self, client: CFMSTestClient): """Test that creating a document requires authentication.""" try: - response = client.send_request( + response = await client.send_request( "create_document", {"title": "Test Document"}, include_auth=False @@ -196,10 +205,11 @@ def test_create_document_without_auth(self, client: CFMSTestClient): assert response["code"] == 401, \ f"Expected 401 for unauthenticated request, got {response.get('code')}" - def test_get_document_without_auth(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_get_document_without_auth(self, client: CFMSTestClient): """Test that getting a document requires authentication.""" try: - response = client.send_request( + response = await client.send_request( "get_document", {"document_id": "test_doc_id"}, include_auth=False diff --git a/tests/test_groups.py b/tests/test_groups.py index 6b79e4c..ff37193 100644 --- a/tests/test_groups.py +++ b/tests/test_groups.py @@ -10,10 +10,11 @@ class TestGroupOperations: """Test group management operations with comprehensive validation.""" - def test_list_groups(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_list_groups(self, authenticated_client: CFMSTestClient): """Test listing all groups with proper structure validation.""" try: - response = authenticated_client.list_groups() + response = await authenticated_client.list_groups() except Exception as e: pytest.fail(f"list_groups() raised an exception: {e}") @@ -31,12 +32,13 @@ def test_list_groups(self, authenticated_client: CFMSTestClient): assert "sysop" in group_names, "Default 'sysop' group should exist" assert "user" in group_names, "Default 'user' group should exist" - def test_create_group(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_create_group(self, authenticated_client: CFMSTestClient): """Test creating a new group with unique name.""" group_name = f"test_group_{int(time.time() * 1000)}" try: - response = authenticated_client.create_group( + response = await authenticated_client.create_group( group_name=group_name, permissions=[] ) @@ -50,14 +52,15 @@ def test_create_group(self, authenticated_client: CFMSTestClient): # Cleanup try: - authenticated_client.send_request("delete_group", {"group_name": group_name}) + await authenticated_client.send_request("delete_group", {"group_name": group_name}) except Exception: pass - def test_get_group_info(self, authenticated_client: CFMSTestClient, test_group: dict): + @pytest.mark.asyncio + async def test_get_group_info(self, authenticated_client: CFMSTestClient, test_group: dict): """Test retrieving group information.""" try: - response = authenticated_client.get_group_info(test_group["group_name"]) + response = await authenticated_client.get_group_info(test_group["group_name"]) except Exception as e: pytest.fail(f"get_group_info() raised an exception: {e}") @@ -70,10 +73,11 @@ def test_get_group_info(self, authenticated_client: CFMSTestClient, test_group: assert response["data"]["name"] == test_group["group_name"], \ f"Group name mismatch: expected {test_group['group_name']}, got {response['data'].get('name')}" - def test_get_sysop_group_info(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_get_sysop_group_info(self, authenticated_client: CFMSTestClient): """Test retrieving information for the default sysop group.""" try: - response = authenticated_client.get_group_info("sysop") + response = await authenticated_client.get_group_info("sysop") except Exception as e: pytest.fail(f"get_group_info() raised an exception: {e}") @@ -87,10 +91,11 @@ def test_get_sysop_group_info(self, authenticated_client: CFMSTestClient): f"Expected group name 'sysop', got '{response['data'].get('name')}'" assert "permissions" in response["data"], "Group info should include permissions" - def test_get_nonexistent_group_info(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_get_nonexistent_group_info(self, authenticated_client: CFMSTestClient): """Test retrieving info for non-existent group returns error.""" try: - response = authenticated_client.get_group_info("nonexistent_group_xyz_12345") + response = await authenticated_client.get_group_info("nonexistent_group_xyz_12345") except Exception as e: pytest.fail(f"get_group_info() raised an exception: {e}") @@ -101,10 +106,11 @@ def test_get_nonexistent_group_info(self, authenticated_client: CFMSTestClient): assert response["code"] in [400, 404], \ f"Expected 400 or 404 for nonexistent group, got {response.get('code')}" - def test_create_group_with_empty_name(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_create_group_with_empty_name(self, authenticated_client: CFMSTestClient): """Test that creating a group with empty name fails validation.""" try: - response = authenticated_client.create_group("") + response = await authenticated_client.create_group("") except Exception as e: pytest.fail(f"create_group() raised an exception: {e}") @@ -113,10 +119,11 @@ def test_create_group_with_empty_name(self, authenticated_client: CFMSTestClient assert response["code"] == 400, \ f"Expected 400 for empty group name, got {response.get('code')}" - def test_create_duplicate_group(self, authenticated_client: CFMSTestClient, test_group: dict): + @pytest.mark.asyncio + async def test_create_duplicate_group(self, authenticated_client: CFMSTestClient, test_group: dict): """Test that creating a group with duplicate name fails.""" try: - response = authenticated_client.create_group(test_group["group_name"]) + response = await authenticated_client.create_group(test_group["group_name"]) except Exception as e: pytest.fail(f"create_group() raised an exception: {e}") @@ -127,13 +134,14 @@ def test_create_duplicate_group(self, authenticated_client: CFMSTestClient, test assert response["code"] in [400, 409], \ f"Expected 400 or 409 for duplicate group name, got {response.get('code')}" - def test_delete_group(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_delete_group(self, authenticated_client: CFMSTestClient): """Test deleting a group and verify removal.""" # Create a group to delete group_name = f"group_to_delete_{int(time.time() * 1000)}" try: - create_response = authenticated_client.create_group(group_name) + create_response = await authenticated_client.create_group(group_name) except Exception as e: pytest.fail(f"Failed to create group for deletion test: {e}") @@ -141,7 +149,7 @@ def test_delete_group(self, authenticated_client: CFMSTestClient): # Delete it try: - delete_response = authenticated_client.send_request( + delete_response = await authenticated_client.send_request( "delete_group", {"group_name": group_name} ) @@ -155,7 +163,7 @@ def test_delete_group(self, authenticated_client: CFMSTestClient): # Verify it's gone try: - info_response = authenticated_client.get_group_info(group_name) + info_response = await authenticated_client.get_group_info(group_name) except Exception as e: pytest.fail(f"get_group_info() raised an exception during verification: {e}") @@ -166,10 +174,11 @@ def test_delete_group(self, authenticated_client: CFMSTestClient): class TestGroupWithoutAuth: """Test that group operations properly require authentication.""" - def test_list_groups_without_auth(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_list_groups_without_auth(self, client: CFMSTestClient): """Test that listing groups requires authentication.""" try: - response = client.send_request( + response = await client.send_request( "list_groups", {}, include_auth=False @@ -182,10 +191,11 @@ def test_list_groups_without_auth(self, client: CFMSTestClient): assert response["code"] == 401, \ f"Expected 401 for unauthenticated request, got {response.get('code')}" - def test_create_group_without_auth(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_create_group_without_auth(self, client: CFMSTestClient): """Test that creating a group requires authentication.""" try: - response = client.send_request( + response = await client.send_request( "create_group", {"group_name": "testgroup"}, include_auth=False @@ -198,10 +208,11 @@ def test_create_group_without_auth(self, client: CFMSTestClient): assert response["code"] == 401, \ f"Expected 401 for unauthenticated request, got {response.get('code')}" - def test_get_group_info_without_auth(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_get_group_info_without_auth(self, client: CFMSTestClient): """Test that getting group info requires authentication.""" try: - response = client.send_request( + response = await client.send_request( "get_group_info", {"group_name": "sysop"}, include_auth=False diff --git a/tests/test_users.py b/tests/test_users.py index 5612c2f..b25331d 100644 --- a/tests/test_users.py +++ b/tests/test_users.py @@ -10,10 +10,11 @@ class TestUserOperations: """Test user management operations with comprehensive validation.""" - def test_list_users(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_list_users(self, authenticated_client: CFMSTestClient): """Test listing all users with proper structure validation.""" try: - response = authenticated_client.list_users() + response = await authenticated_client.list_users() except Exception as e: pytest.fail(f"list_users() raised an exception: {e}") @@ -30,13 +31,14 @@ def test_list_users(self, authenticated_client: CFMSTestClient): usernames = [user.get("username") for user in response["data"]["users"]] assert "admin" in usernames, "Admin user should be in users list" - def test_create_user(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_create_user(self, authenticated_client: CFMSTestClient): """Test creating a new user with unique username.""" username = f"test_user_{int(time.time() * 1000)}" password = "TestPassword123!" try: - response = authenticated_client.create_user( + response = await authenticated_client.create_user( username=username, password=password, nickname="Test User" @@ -51,14 +53,15 @@ def test_create_user(self, authenticated_client: CFMSTestClient): # Cleanup try: - authenticated_client.delete_user(username) + await authenticated_client.delete_user(username) except Exception: pass - def test_get_user_info(self, authenticated_client: CFMSTestClient, test_user: dict): + @pytest.mark.asyncio + async def test_get_user_info(self, authenticated_client: CFMSTestClient, test_user: dict): """Test retrieving user information.""" try: - response = authenticated_client.get_user_info(test_user["username"]) + response = await authenticated_client.get_user_info(test_user["username"]) except Exception as e: pytest.fail(f"get_user_info() raised an exception: {e}") @@ -71,10 +74,11 @@ def test_get_user_info(self, authenticated_client: CFMSTestClient, test_user: di assert response["data"]["username"] == test_user["username"], \ f"Username mismatch: expected {test_user['username']}, got {response['data'].get('username')}" - def test_get_nonexistent_user_info(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_get_nonexistent_user_info(self, authenticated_client: CFMSTestClient): """Test retrieving info for non-existent user returns error.""" try: - response = authenticated_client.get_user_info("nonexistent_user_xyz_12345") + response = await authenticated_client.get_user_info("nonexistent_user_xyz_12345") except Exception as e: pytest.fail(f"get_user_info() raised an exception: {e}") @@ -85,13 +89,14 @@ def test_get_nonexistent_user_info(self, authenticated_client: CFMSTestClient): assert response["code"] in [400, 404], \ f"Expected 400 or 404 for nonexistent user, got {response.get('code')}" - def test_delete_user(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_delete_user(self, authenticated_client: CFMSTestClient): """Test deleting a user and verify removal.""" # Create a user to delete username = f"user_to_delete_{int(time.time() * 1000)}" try: - create_response = authenticated_client.create_user( + create_response = await authenticated_client.create_user( username=username, password="TestPassword123!" ) @@ -102,7 +107,7 @@ def test_delete_user(self, authenticated_client: CFMSTestClient): # Delete it try: - delete_response = authenticated_client.delete_user(username) + delete_response = await authenticated_client.delete_user(username) except Exception as e: pytest.fail(f"delete_user() raised an exception: {e}") @@ -113,17 +118,18 @@ def test_delete_user(self, authenticated_client: CFMSTestClient): # Verify it's gone try: - info_response = authenticated_client.get_user_info(username) + info_response = await authenticated_client.get_user_info(username) except Exception as e: pytest.fail(f"get_user_info() raised an exception during verification: {e}") assert info_response.get("code") != 200, \ "User should not be retrievable after deletion" - def test_create_user_with_duplicate_username(self, authenticated_client: CFMSTestClient, test_user: dict): + @pytest.mark.asyncio + async def test_create_user_with_duplicate_username(self, authenticated_client: CFMSTestClient, test_user: dict): """Test that creating a user with duplicate username fails.""" try: - response = authenticated_client.create_user( + response = await authenticated_client.create_user( username=test_user["username"], password="AnotherPassword123!" ) @@ -137,10 +143,11 @@ def test_create_user_with_duplicate_username(self, authenticated_client: CFMSTes assert response["code"] in [400, 409], \ f"Expected 400 or 409 for duplicate username, got {response.get('code')}" - def test_create_user_with_empty_username(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_create_user_with_empty_username(self, authenticated_client: CFMSTestClient): """Test that creating a user with empty username fails validation.""" try: - response = authenticated_client.create_user( + response = await authenticated_client.create_user( username="", password="TestPassword123!" ) @@ -152,10 +159,11 @@ def test_create_user_with_empty_username(self, authenticated_client: CFMSTestCli assert response["code"] == 400, \ f"Expected 400 for empty username, got {response.get('code')}" - def test_get_admin_user_info(self, authenticated_client: CFMSTestClient): + @pytest.mark.asyncio + async def test_get_admin_user_info(self, authenticated_client: CFMSTestClient): """Test retrieving admin user information.""" try: - response = authenticated_client.get_user_info("admin") + response = await authenticated_client.get_user_info("admin") except Exception as e: pytest.fail(f"get_user_info() raised an exception: {e}") @@ -172,10 +180,11 @@ def test_get_admin_user_info(self, authenticated_client: CFMSTestClient): class TestUserWithoutAuth: """Test that user operations properly require authentication.""" - def test_list_users_without_auth(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_list_users_without_auth(self, client: CFMSTestClient): """Test that listing users requires authentication.""" try: - response = client.send_request( + response = await client.send_request( "list_users", {}, include_auth=False @@ -188,10 +197,11 @@ def test_list_users_without_auth(self, client: CFMSTestClient): assert response["code"] == 401, \ f"Expected 401 for unauthenticated request, got {response.get('code')}" - def test_create_user_without_auth(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_create_user_without_auth(self, client: CFMSTestClient): """Test that creating a user requires authentication.""" try: - response = client.send_request( + response = await client.send_request( "create_user", { "username": "testuser", @@ -207,10 +217,11 @@ def test_create_user_without_auth(self, client: CFMSTestClient): assert response["code"] == 401, \ f"Expected 401 for unauthenticated request, got {response.get('code')}" - def test_get_user_info_without_auth(self, client: CFMSTestClient): + @pytest.mark.asyncio + async def test_get_user_info_without_auth(self, client: CFMSTestClient): """Test that getting user info requires authentication.""" try: - response = client.send_request( + response = await client.send_request( "get_user_info", {"username": "admin"}, include_auth=False