From 9a136323c5c53feeb06049d543eeb088261221df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 10 Nov 2025 03:16:35 +0000 Subject: [PATCH 1/4] Initial plan From 7c7a767993c4bf9ff68518664006383d6dbf14c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 10 Nov 2025 03:26:16 +0000 Subject: [PATCH 2/4] Convert tests to async style - Part 1: Client and fixtures Co-authored-by: Creeper19472 <38857196+Creeper19472@users.noreply.github.com> --- pytest.ini | 4 ++ tests/conftest.py | 37 ++++++------ tests/test_basic.py | 44 +++++++------- tests/test_client.py | 120 +++++++++++++++++++------------------- tests/test_debugging.py | 2 +- tests/test_directories.py | 44 +++++++------- tests/test_documents.py | 54 ++++++++--------- tests/test_groups.py | 50 ++++++++-------- tests/test_users.py | 50 ++++++++-------- 9 files changed, 204 insertions(+), 201 deletions(-) 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..0504ee0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,8 @@ import time import threading import sys -from typing import Generator +import asyncio +from typing import Generator, AsyncGenerator from tests.test_client import CFMSTestClient @@ -272,7 +273,7 @@ def admin_credentials(server_process) -> dict: @pytest.fixture -def client(server_process) -> Generator[CFMSTestClient, None, None]: +async def client(server_process) -> AsyncGenerator[CFMSTestClient, None]: """ Provide a connected test client for each test. """ @@ -282,29 +283,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: +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"] ) @@ -318,12 +319,12 @@ def authenticated_client(client: CFMSTestClient, admin_credentials: dict) -> CFM @pytest.fixture -def test_document(authenticated_client: CFMSTestClient) -> Generator[dict, None, None]: +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 +336,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 +352,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]: +async def test_user(authenticated_client: CFMSTestClient) -> AsyncGenerator[dict, None]: """ Create a test user and clean it up after the test. """ @@ -365,7 +366,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 +385,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]: +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 +414,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..19a82af 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -9,17 +9,15 @@ class TestServerBasics: """Test basic server functionality with improved assertions.""" - def test_server_connection(self, client: CFMSTestClient): + 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): + 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 +34,10 @@ 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): + 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 +55,10 @@ 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): + 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 +80,10 @@ 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): + 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 +97,10 @@ 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): + 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 +113,10 @@ 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): + 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 +129,13 @@ 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): + 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 +151,10 @@ 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): + 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 +163,10 @@ 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): + 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 +174,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..421bac8 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.client import connect, WebSocketClientProtocol def calculate_sha256(file_path: str) -> str: @@ -53,11 +53,11 @@ def __init__(self, host: str = "localhost", port: int = 5104, use_ssl: bool = Tr self.host = host self.port = port self.use_ssl = use_ssl - self.websocket: Optional[ClientConnection] = None + self.websocket: Optional[WebSocketClientProtocol] = None 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..3fe2bc5 100644 --- a/tests/test_debugging.py +++ b/tests/test_debugging.py @@ -9,6 +9,6 @@ class TestDebugging: """Debugging tests for development purposes.""" - def test_placeholder(self, authenticated_client: CFMSTestClient): + 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..af31318 100644 --- a/tests/test_directories.py +++ b/tests/test_directories.py @@ -9,17 +9,17 @@ class TestDirectoryOperations: """Test directory operations.""" - def test_list_directory_root(self, authenticated_client: CFMSTestClient): + 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): + 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 +31,62 @@ 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): + 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): + 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): + 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): + 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 +94,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 +102,9 @@ 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): + 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 +112,9 @@ def test_list_directory_without_auth(self, client: CFMSTestClient): assert response["code"] == 401 - def test_create_directory_without_auth(self, client: CFMSTestClient): + 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..5dec542 100644 --- a/tests/test_documents.py +++ b/tests/test_documents.py @@ -9,10 +9,10 @@ class TestDocumentOperations: """Test document CRUD operations with comprehensive validation.""" - def test_create_document(self, authenticated_client: CFMSTestClient): + 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 +29,14 @@ 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): + 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 +47,10 @@ 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): + 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 +61,10 @@ 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): + 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 +76,12 @@ 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): + 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 +95,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 +103,11 @@ 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): + 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 +116,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 +127,17 @@ 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): + 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 +146,33 @@ 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): + 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 +180,10 @@ 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): + 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 +196,10 @@ 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): + 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..b57a899 100644 --- a/tests/test_groups.py +++ b/tests/test_groups.py @@ -10,10 +10,10 @@ class TestGroupOperations: """Test group management operations with comprehensive validation.""" - def test_list_groups(self, authenticated_client: CFMSTestClient): + 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 +31,12 @@ 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): + 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 +50,14 @@ 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): + 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 +70,10 @@ 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): + 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 +87,10 @@ 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): + 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 +101,10 @@ 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): + 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 +113,10 @@ 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): + 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 +127,13 @@ 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): + 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 +141,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 +155,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 +166,10 @@ 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): + 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 +182,10 @@ 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): + 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 +198,10 @@ 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): + 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..19d681a 100644 --- a/tests/test_users.py +++ b/tests/test_users.py @@ -10,10 +10,10 @@ class TestUserOperations: """Test user management operations with comprehensive validation.""" - def test_list_users(self, authenticated_client: CFMSTestClient): + 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 +30,13 @@ 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): + 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 +51,14 @@ 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): + 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 +71,10 @@ 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): + 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 +85,13 @@ 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): + 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 +102,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 +113,17 @@ 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): + 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 +137,10 @@ 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): + 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 +152,10 @@ 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): + 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 +172,10 @@ 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): + 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 +188,10 @@ 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): + 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 +207,10 @@ 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): + 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 From 8ae2b45482f8c8d7db2747b0c7a16f5f0a1d9fbf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 10 Nov 2025 04:30:01 +0000 Subject: [PATCH 3/4] Add explicit pytest markers for async tests and fixtures - Add @pytest.mark.asyncio to all 51 async test functions - Change async fixtures to use @pytest_asyncio.fixture - Import pytest_asyncio in conftest.py Co-authored-by: Creeper19472 <38857196+Creeper19472@users.noreply.github.com> --- tests/conftest.py | 11 ++++++----- tests/test_basic.py | 10 ++++++++++ tests/test_debugging.py | 1 + tests/test_directories.py | 8 ++++++++ tests/test_documents.py | 10 ++++++++++ tests/test_groups.py | 11 +++++++++++ tests/test_users.py | 11 +++++++++++ 7 files changed, 57 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0504ee0..e0e1da7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,7 @@ import os import pytest +import pytest_asyncio import subprocess import time import threading @@ -272,7 +273,7 @@ def admin_credentials(server_process) -> dict: } -@pytest.fixture +@pytest_asyncio.fixture async def client(server_process) -> AsyncGenerator[CFMSTestClient, None]: """ Provide a connected test client for each test. @@ -299,7 +300,7 @@ async def client(server_process) -> AsyncGenerator[CFMSTestClient, None]: pass -@pytest.fixture +@pytest_asyncio.fixture async def authenticated_client(client: CFMSTestClient, admin_credentials: dict) -> CFMSTestClient: """ Provide an authenticated test client with admin credentials. @@ -318,7 +319,7 @@ async def authenticated_client(client: CFMSTestClient, admin_credentials: dict) return client -@pytest.fixture +@pytest_asyncio.fixture async def test_document(authenticated_client: CFMSTestClient) -> AsyncGenerator[dict, None]: """ Create a test document and clean it up after the test. @@ -357,7 +358,7 @@ async def test_document(authenticated_client: CFMSTestClient) -> AsyncGenerator[ pass # Ignore cleanup errors -@pytest.fixture +@pytest_asyncio.fixture async def test_user(authenticated_client: CFMSTestClient) -> AsyncGenerator[dict, None]: """ Create a test user and clean it up after the test. @@ -390,7 +391,7 @@ async def test_user(authenticated_client: CFMSTestClient) -> AsyncGenerator[dict pass -@pytest.fixture +@pytest_asyncio.fixture async def test_group(authenticated_client: CFMSTestClient) -> AsyncGenerator[dict, None]: """ Create a test group and clean it up after the test. diff --git a/tests/test_basic.py b/tests/test_basic.py index 19a82af..c5bd1a1 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -9,11 +9,13 @@ class TestServerBasics: """Test basic server functionality with improved assertions.""" + @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, 'id'), "WebSocket missing id attribute" + @pytest.mark.asyncio async def test_server_info(self, client: CFMSTestClient): """Test getting server information without authentication.""" try: @@ -34,6 +36,7 @@ async def test_server_info(self, client: CFMSTestClient): assert field in response["data"], \ f"Server info missing required field '{field}'" + @pytest.mark.asyncio async def test_unknown_action(self, client: CFMSTestClient): """Test that server properly rejects unknown action types.""" try: @@ -55,6 +58,7 @@ async def test_unknown_action(self, client: CFMSTestClient): class TestAuthentication: """Test authentication functionality with comprehensive scenarios.""" + @pytest.mark.asyncio async def test_login_success(self, client: CFMSTestClient, admin_credentials: dict): """Test successful login with valid admin credentials.""" try: @@ -80,6 +84,7 @@ async def test_login_success(self, client: CFMSTestClient, admin_credentials: di assert client.username == admin_credentials["username"], \ f"Client username mismatch: expected {admin_credentials['username']}, got {client.username}" + @pytest.mark.asyncio async def test_login_invalid_credentials(self, client: CFMSTestClient): """Test login fails with invalid credentials.""" try: @@ -97,6 +102,7 @@ async 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']}" + @pytest.mark.asyncio async def test_login_missing_username(self, client: CFMSTestClient): """Test login fails when username is missing.""" try: @@ -113,6 +119,7 @@ async def test_login_missing_username(self, client: CFMSTestClient): assert response["code"] == 400, \ f"Expected 400 for missing username, got {response.get('code')}" + @pytest.mark.asyncio async def test_login_missing_password(self, client: CFMSTestClient): """Test login fails when password is missing.""" try: @@ -129,6 +136,7 @@ async def test_login_missing_password(self, client: CFMSTestClient): assert response["code"] == 400, \ f"Expected 400 for missing password, got {response.get('code')}" + @pytest.mark.asyncio async def test_refresh_token(self, authenticated_client: CFMSTestClient): """Test token refresh functionality.""" old_token = authenticated_client.token @@ -151,6 +159,7 @@ async 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" + @pytest.mark.asyncio async def test_authentication_required(self, client: CFMSTestClient): """Test that protected endpoints require authentication.""" try: @@ -163,6 +172,7 @@ async def test_authentication_required(self, client: CFMSTestClient): assert response["code"] == 401, \ f"Expected 401 for unauthenticated request, got {response.get('code')}" + @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 diff --git a/tests/test_debugging.py b/tests/test_debugging.py index 3fe2bc5..6df0da3 100644 --- a/tests/test_debugging.py +++ b/tests/test_debugging.py @@ -9,6 +9,7 @@ class TestDebugging: """Debugging tests for development purposes.""" + @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 af31318..55bc582 100644 --- a/tests/test_directories.py +++ b/tests/test_directories.py @@ -9,6 +9,7 @@ class TestDirectoryOperations: """Test directory operations.""" + @pytest.mark.asyncio async def test_list_directory_root(self, authenticated_client: CFMSTestClient): """Test listing the root directory.""" response = await authenticated_client.list_directory() @@ -16,6 +17,7 @@ async def test_list_directory_root(self, authenticated_client: CFMSTestClient): assert response["code"] == 200 assert "data" in response + @pytest.mark.asyncio async def test_create_directory(self, authenticated_client: CFMSTestClient): """Test creating a new directory.""" dir_name = "Test Directory" @@ -35,6 +37,7 @@ async def test_create_directory(self, authenticated_client: CFMSTestClient): except Exception: pass + @pytest.mark.asyncio async def test_create_directory_with_empty_name(self, authenticated_client: CFMSTestClient): """Test creating a directory with an empty name.""" response = await authenticated_client.create_directory("") @@ -42,6 +45,7 @@ async def test_create_directory_with_empty_name(self, authenticated_client: CFMS # Should fail validation assert response["code"] == 400 + @pytest.mark.asyncio async def test_delete_directory(self, authenticated_client: CFMSTestClient): """Test deleting a directory.""" # First create a directory @@ -56,12 +60,14 @@ async def test_delete_directory(self, authenticated_client: CFMSTestClient): # Should get a response (success or failure is implementation-dependent) assert "code" in delete_response + @pytest.mark.asyncio async def test_delete_nonexistent_directory(self, authenticated_client: CFMSTestClient): """Test deleting a directory that doesn't exist.""" response = await authenticated_client.delete_directory("nonexistent_folder_id") assert response["code"] != 200 + @pytest.mark.asyncio async def test_list_directory_contents(self, authenticated_client: CFMSTestClient): """Test listing directory contents after creating items.""" # Create a test directory @@ -102,6 +108,7 @@ async def test_list_directory_contents(self, authenticated_client: CFMSTestClien class TestDirectoryWithoutAuth: """Test that directory operations require authentication.""" + @pytest.mark.asyncio async def test_list_directory_without_auth(self, client: CFMSTestClient): """Test that listing directories requires authentication.""" response = await client.send_request( @@ -112,6 +119,7 @@ async def test_list_directory_without_auth(self, client: CFMSTestClient): assert response["code"] == 401 + @pytest.mark.asyncio async def test_create_directory_without_auth(self, client: CFMSTestClient): """Test that creating a directory requires authentication.""" response = await client.send_request( diff --git a/tests/test_documents.py b/tests/test_documents.py index 5dec542..be688f8 100644 --- a/tests/test_documents.py +++ b/tests/test_documents.py @@ -9,6 +9,7 @@ class TestDocumentOperations: """Test document CRUD operations with comprehensive validation.""" + @pytest.mark.asyncio async def test_create_document(self, authenticated_client: CFMSTestClient): """Test creating a new document and verify response structure.""" try: @@ -33,6 +34,7 @@ async def test_create_document(self, authenticated_client: CFMSTestClient): except Exception: pass + @pytest.mark.asyncio async def test_get_document(self, authenticated_client: CFMSTestClient, test_document: dict): """Test retrieving a document by ID.""" try: @@ -47,6 +49,7 @@ async def test_get_document(self, authenticated_client: CFMSTestClient, test_doc assert "data" in response, "Response missing 'data'" + @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: @@ -61,6 +64,7 @@ async def test_get_nonexistent_document(self, authenticated_client: CFMSTestClie assert response["code"] in [400, 404], \ f"Expected 400 or 404 for nonexistent document, got {response.get('code')}" + @pytest.mark.asyncio async def test_get_document_info(self, authenticated_client: CFMSTestClient, test_document: dict): """Test getting document metadata.""" try: @@ -76,6 +80,7 @@ async def test_get_document_info(self, authenticated_client: CFMSTestClient, tes assert "data" in response, "Response missing 'data'" assert isinstance(response["data"], dict), "'data' should be a dictionary" + @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" @@ -103,6 +108,7 @@ async def test_rename_document(self, authenticated_client: CFMSTestClient, test_ assert info_response["data"]["title"] == new_title, \ f"Document title not updated: expected '{new_title}', got '{info_response['data'].get('title')}'" + @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 @@ -134,6 +140,7 @@ async def test_delete_document(self, authenticated_client: CFMSTestClient): assert get_response.get("code") != 200, \ "Document should not be retrievable after deletion" + @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: @@ -146,6 +153,7 @@ async def test_create_document_with_empty_title(self, authenticated_client: CFMS assert response["code"] == 400, \ f"Expected 400 for empty title, got {response.get('code')}" + @pytest.mark.asyncio async def test_create_multiple_documents(self, authenticated_client: CFMSTestClient): """Test creating multiple documents successfully.""" document_ids = [] @@ -180,6 +188,7 @@ async def test_create_multiple_documents(self, authenticated_client: CFMSTestCli class TestDocumentWithoutAuth: """Test that document operations properly require authentication.""" + @pytest.mark.asyncio async def test_create_document_without_auth(self, client: CFMSTestClient): """Test that creating a document requires authentication.""" try: @@ -196,6 +205,7 @@ async def test_create_document_without_auth(self, client: CFMSTestClient): assert response["code"] == 401, \ f"Expected 401 for unauthenticated request, got {response.get('code')}" + @pytest.mark.asyncio async def test_get_document_without_auth(self, client: CFMSTestClient): """Test that getting a document requires authentication.""" try: diff --git a/tests/test_groups.py b/tests/test_groups.py index b57a899..ff37193 100644 --- a/tests/test_groups.py +++ b/tests/test_groups.py @@ -10,6 +10,7 @@ class TestGroupOperations: """Test group management operations with comprehensive validation.""" + @pytest.mark.asyncio async def test_list_groups(self, authenticated_client: CFMSTestClient): """Test listing all groups with proper structure validation.""" try: @@ -31,6 +32,7 @@ async 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" + @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)}" @@ -54,6 +56,7 @@ async def test_create_group(self, authenticated_client: CFMSTestClient): except Exception: pass + @pytest.mark.asyncio async def test_get_group_info(self, authenticated_client: CFMSTestClient, test_group: dict): """Test retrieving group information.""" try: @@ -70,6 +73,7 @@ async def test_get_group_info(self, authenticated_client: CFMSTestClient, test_g assert response["data"]["name"] == test_group["group_name"], \ f"Group name mismatch: expected {test_group['group_name']}, got {response['data'].get('name')}" + @pytest.mark.asyncio async def test_get_sysop_group_info(self, authenticated_client: CFMSTestClient): """Test retrieving information for the default sysop group.""" try: @@ -87,6 +91,7 @@ async 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" + @pytest.mark.asyncio async def test_get_nonexistent_group_info(self, authenticated_client: CFMSTestClient): """Test retrieving info for non-existent group returns error.""" try: @@ -101,6 +106,7 @@ async def test_get_nonexistent_group_info(self, authenticated_client: CFMSTestCl assert response["code"] in [400, 404], \ f"Expected 400 or 404 for nonexistent group, got {response.get('code')}" + @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: @@ -113,6 +119,7 @@ async def test_create_group_with_empty_name(self, authenticated_client: CFMSTest assert response["code"] == 400, \ f"Expected 400 for empty group name, got {response.get('code')}" + @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: @@ -127,6 +134,7 @@ async def test_create_duplicate_group(self, authenticated_client: CFMSTestClient assert response["code"] in [400, 409], \ f"Expected 400 or 409 for duplicate group name, got {response.get('code')}" + @pytest.mark.asyncio async def test_delete_group(self, authenticated_client: CFMSTestClient): """Test deleting a group and verify removal.""" # Create a group to delete @@ -166,6 +174,7 @@ async def test_delete_group(self, authenticated_client: CFMSTestClient): class TestGroupWithoutAuth: """Test that group operations properly require authentication.""" + @pytest.mark.asyncio async def test_list_groups_without_auth(self, client: CFMSTestClient): """Test that listing groups requires authentication.""" try: @@ -182,6 +191,7 @@ async def test_list_groups_without_auth(self, client: CFMSTestClient): assert response["code"] == 401, \ f"Expected 401 for unauthenticated request, got {response.get('code')}" + @pytest.mark.asyncio async def test_create_group_without_auth(self, client: CFMSTestClient): """Test that creating a group requires authentication.""" try: @@ -198,6 +208,7 @@ async def test_create_group_without_auth(self, client: CFMSTestClient): assert response["code"] == 401, \ f"Expected 401 for unauthenticated request, got {response.get('code')}" + @pytest.mark.asyncio async def test_get_group_info_without_auth(self, client: CFMSTestClient): """Test that getting group info requires authentication.""" try: diff --git a/tests/test_users.py b/tests/test_users.py index 19d681a..b25331d 100644 --- a/tests/test_users.py +++ b/tests/test_users.py @@ -10,6 +10,7 @@ class TestUserOperations: """Test user management operations with comprehensive validation.""" + @pytest.mark.asyncio async def test_list_users(self, authenticated_client: CFMSTestClient): """Test listing all users with proper structure validation.""" try: @@ -30,6 +31,7 @@ async 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" + @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)}" @@ -55,6 +57,7 @@ async def test_create_user(self, authenticated_client: CFMSTestClient): except Exception: pass + @pytest.mark.asyncio async def test_get_user_info(self, authenticated_client: CFMSTestClient, test_user: dict): """Test retrieving user information.""" try: @@ -71,6 +74,7 @@ async def test_get_user_info(self, authenticated_client: CFMSTestClient, test_us assert response["data"]["username"] == test_user["username"], \ f"Username mismatch: expected {test_user['username']}, got {response['data'].get('username')}" + @pytest.mark.asyncio async def test_get_nonexistent_user_info(self, authenticated_client: CFMSTestClient): """Test retrieving info for non-existent user returns error.""" try: @@ -85,6 +89,7 @@ async def test_get_nonexistent_user_info(self, authenticated_client: CFMSTestCli assert response["code"] in [400, 404], \ f"Expected 400 or 404 for nonexistent user, got {response.get('code')}" + @pytest.mark.asyncio async def test_delete_user(self, authenticated_client: CFMSTestClient): """Test deleting a user and verify removal.""" # Create a user to delete @@ -120,6 +125,7 @@ async def test_delete_user(self, authenticated_client: CFMSTestClient): assert info_response.get("code") != 200, \ "User should not be retrievable after deletion" + @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: @@ -137,6 +143,7 @@ async def test_create_user_with_duplicate_username(self, authenticated_client: C assert response["code"] in [400, 409], \ f"Expected 400 or 409 for duplicate username, got {response.get('code')}" + @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: @@ -152,6 +159,7 @@ async def test_create_user_with_empty_username(self, authenticated_client: CFMST assert response["code"] == 400, \ f"Expected 400 for empty username, got {response.get('code')}" + @pytest.mark.asyncio async def test_get_admin_user_info(self, authenticated_client: CFMSTestClient): """Test retrieving admin user information.""" try: @@ -172,6 +180,7 @@ async def test_get_admin_user_info(self, authenticated_client: CFMSTestClient): class TestUserWithoutAuth: """Test that user operations properly require authentication.""" + @pytest.mark.asyncio async def test_list_users_without_auth(self, client: CFMSTestClient): """Test that listing users requires authentication.""" try: @@ -188,6 +197,7 @@ async def test_list_users_without_auth(self, client: CFMSTestClient): assert response["code"] == 401, \ f"Expected 401 for unauthenticated request, got {response.get('code')}" + @pytest.mark.asyncio async def test_create_user_without_auth(self, client: CFMSTestClient): """Test that creating a user requires authentication.""" try: @@ -207,6 +217,7 @@ async def test_create_user_without_auth(self, client: CFMSTestClient): assert response["code"] == 401, \ f"Expected 401 for unauthenticated request, got {response.get('code')}" + @pytest.mark.asyncio async def test_get_user_info_without_auth(self, client: CFMSTestClient): """Test that getting user info requires authentication.""" try: From ec435a0028130afce4b217bed5e79629a8d22606 Mon Sep 17 00:00:00 2001 From: Creeper19472 Date: Mon, 10 Nov 2025 12:35:33 +0800 Subject: [PATCH 4/4] replace websockets.client with websockets.asyncio.client --- tests/test_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 421bac8..b1f5c48 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -11,7 +11,7 @@ import ssl import asyncio from typing import Any, Dict, Optional -from websockets.client import connect, WebSocketClientProtocol +from websockets.asyncio.client import connect, ClientConnection def calculate_sha256(file_path: str) -> str: @@ -53,7 +53,7 @@ def __init__(self, host: str = "localhost", port: int = 5104, use_ssl: bool = Tr self.host = host self.port = port self.use_ssl = use_ssl - self.websocket: Optional[WebSocketClientProtocol] = None + self.websocket: Optional[ClientConnection] = None self.username: Optional[str] = None self.token: Optional[str] = None