Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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"')
Expand Down
48 changes: 25 additions & 23 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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.
"""
Expand All @@ -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"]
)
Expand All @@ -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}")

Expand All @@ -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}")
Expand All @@ -351,21 +353,21 @@ 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.
"""
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"
Expand All @@ -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=[]
)
Expand All @@ -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
54 changes: 31 additions & 23 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")

Expand All @@ -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}")

Expand All @@ -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"]
)
Expand All @@ -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}")

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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}")

Expand All @@ -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}")

Expand All @@ -165,18 +172,19 @@ 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"]
)
assert login_response["code"] == 200, f"Setup login failed: {login_response}"

# 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"
Expand Down
Loading