Skip to main content
Glama
test_files.py21.3 kB
"""Unit tests for files router.""" import json from datetime import UTC, datetime from io import BytesIO from unittest.mock import MagicMock, Mock, patch from uuid import UUID, uuid4 import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from yaraflux_mcp_server.auth import get_current_active_user, validate_admin from yaraflux_mcp_server.models import FileInfo, FileString, FileUploadResponse, User, UserRole from yaraflux_mcp_server.routers.files import router from yaraflux_mcp_server.storage import StorageError # Create test app app = FastAPI() app.include_router(router) @pytest.fixture def test_user(): """Test user fixture.""" return User(username="testuser", role=UserRole.USER, disabled=False, email="test@example.com") @pytest.fixture def test_admin(): """Test admin user fixture.""" return User(username="testadmin", role=UserRole.ADMIN, disabled=False, email="admin@example.com") @pytest.fixture def client_with_user(test_user): """TestClient with normal user dependency override.""" app.dependency_overrides[get_current_active_user] = lambda: test_user with TestClient(app) as client: yield client # Clear overrides after test app.dependency_overrides = {} @pytest.fixture def client_with_admin(test_admin): """TestClient with admin user dependency override.""" app.dependency_overrides[get_current_active_user] = lambda: test_admin app.dependency_overrides[validate_admin] = lambda: test_admin with TestClient(app) as client: yield client # Clear overrides after test app.dependency_overrides = {} @pytest.fixture def mock_file_info(): """Mock file info fixture.""" file_id = str(uuid4()) return { "file_id": file_id, "file_name": "test.txt", "file_size": 100, "file_hash": "abcdef1234567890", "mime_type": "text/plain", "uploaded_at": datetime.now(UTC).isoformat(), "metadata": {"uploader": "testuser"}, } class TestUploadFile: """Tests for upload_file endpoint.""" @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_upload_file_success(self, mock_get_storage, client_with_user, mock_file_info): """Test successful file upload.""" # Setup mock storage mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.save_file.return_value = mock_file_info # Create test file file_content = b"Test file content" file = {"file": ("test.txt", BytesIO(file_content), "text/plain")} # Optional metadata data = {"metadata": json.dumps({"test": "value"})} # Make request response = client_with_user.post("/files/upload", files=file, data=data) # Check response assert response.status_code == 200 result = response.json() assert result["file_info"]["file_name"] == "test.txt" assert result["file_info"]["file_size"] == 100 # Verify storage was called correctly mock_storage.save_file.assert_called_once() args = mock_storage.save_file.call_args[0] assert args[0] == "test.txt" # filename assert args[1] == file_content # content assert "uploader" in args[2] # metadata assert args[2]["uploader"] == "testuser" assert args[2]["test"] == "value" @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_upload_file_invalid_metadata(self, mock_get_storage, client_with_user, mock_file_info): """Test file upload with invalid JSON metadata.""" # Setup mock storage mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.save_file.return_value = mock_file_info # Create test file file_content = b"Test file content" file = {"file": ("test.txt", BytesIO(file_content), "text/plain")} # Invalid metadata - not JSON data = {"metadata": "not-json"} # Make request response = client_with_user.post("/files/upload", files=file, data=data) # Check response (should still succeed but with empty metadata) assert response.status_code == 200 # Verify storage was called with empty metadata except for uploader mock_storage.save_file.assert_called_once() args = mock_storage.save_file.call_args[0] assert args[2]["uploader"] == "testuser" assert "test" not in args[2] @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_upload_file_storage_error(self, mock_get_storage, client_with_user): """Test file upload with storage error.""" # Setup mock storage with error mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.save_file.side_effect = Exception("Storage error") # Create test file file_content = b"Test file content" file = {"file": ("test.txt", BytesIO(file_content), "text/plain")} # Make request response = client_with_user.post("/files/upload", files=file) # Check response assert response.status_code == 500 assert "Error uploading file" in response.json()["detail"] class TestFileInfo: """Tests for get_file_info endpoint.""" @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_get_file_info_success(self, mock_get_storage, client_with_user, mock_file_info): """Test getting file info successfully.""" # Setup mock storage mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.get_file_info.return_value = mock_file_info # Make request file_id = mock_file_info["file_id"] response = client_with_user.get(f"/files/info/{file_id}") # Check response assert response.status_code == 200 result = response.json() assert result["file_name"] == "test.txt" assert result["file_size"] == 100 assert result["file_id"] == file_id @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_get_file_info_not_found(self, mock_get_storage, client_with_user): """Test getting info for non-existent file.""" # Setup mock storage with not found error mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.get_file_info.side_effect = StorageError("File not found") # Make request with random UUID file_id = str(uuid4()) response = client_with_user.get(f"/files/info/{file_id}") # Check response assert response.status_code == 404 assert "File not found" in response.json()["detail"] @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_get_file_info_server_error(self, mock_get_storage, client_with_user): """Test getting file info with server error.""" # Setup mock storage with error mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.get_file_info.side_effect = Exception("Server error") # Make request file_id = str(uuid4()) response = client_with_user.get(f"/files/info/{file_id}") # Check response assert response.status_code == 500 assert "Error getting file info" in response.json()["detail"] class TestDownloadFile: """Tests for download_file endpoint.""" @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_download_file_binary(self, mock_get_storage, client_with_user, mock_file_info): """Test downloading file as binary.""" # Setup mock storage mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.get_file.return_value = b"Binary content" mock_storage.get_file_info.return_value = mock_file_info # Make request file_id = mock_file_info["file_id"] response = client_with_user.get(f"/files/download/{file_id}") # Check response assert response.status_code == 200 assert response.content == b"Binary content" assert "text/plain" in response.headers["Content-Type"] assert "attachment" in response.headers["Content-Disposition"] assert "test.txt" in response.headers["Content-Disposition"] @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_download_file_as_text(self, mock_get_storage, client_with_user, mock_file_info): """Test downloading text file as text.""" # Setup mock storage mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.get_file.return_value = b"Text content" mock_storage.get_file_info.return_value = mock_file_info # Make request file_id = mock_file_info["file_id"] response = client_with_user.get(f"/files/download/{file_id}?as_text=true") # Check response assert response.status_code == 200 assert response.text == "Text content" assert "text/plain" in response.headers["Content-Type"] @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_download_file_as_text_with_binary(self, mock_get_storage, client_with_user, mock_file_info): """Test downloading binary file as text falls back to binary.""" # Setup mock storage with binary content that can't be decoded mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.get_file.return_value = b"\xff\xfe\xfd" # Non-UTF8 bytes mock_storage.get_file_info.return_value = mock_file_info # Make request file_id = mock_file_info["file_id"] response = client_with_user.get(f"/files/download/{file_id}?as_text=true") # Check response - should fall back to binary assert response.status_code == 200 assert response.content == b"\xff\xfe\xfd" assert "text/plain" in response.headers["Content-Type"] @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_download_file_not_found(self, mock_get_storage, client_with_user): """Test downloading non-existent file.""" # Setup mock storage with not found error mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.get_file.side_effect = StorageError("File not found") # Make request with random UUID file_id = str(uuid4()) response = client_with_user.get(f"/files/download/{file_id}") # Check response assert response.status_code == 404 assert "File not found" in response.json()["detail"] class TestListFiles: """Tests for list_files endpoint.""" @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_list_files_success(self, mock_get_storage, client_with_user, mock_file_info): """Test listing files successfully.""" # Setup mock storage mock_storage = Mock() mock_get_storage.return_value = mock_storage # Create mock result with list of files mock_result = {"files": [mock_file_info, mock_file_info], "total": 2, "page": 1, "page_size": 100} mock_storage.list_files.return_value = mock_result # Make request response = client_with_user.get("/files/list") # Check response assert response.status_code == 200 result = response.json() assert len(result["files"]) == 2 assert result["total"] == 2 assert result["page"] == 1 assert result["page_size"] == 100 @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_list_files_with_params(self, mock_get_storage, client_with_user): """Test listing files with pagination and sorting parameters.""" # Setup mock storage mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.list_files.return_value = {"files": [], "total": 0, "page": 2, "page_size": 10} # Make request with custom params response = client_with_user.get("/files/list?page=2&page_size=10&sort_by=file_name&sort_desc=false") # Check response assert response.status_code == 200 # Verify storage was called with correct params mock_storage.list_files.assert_called_once_with(2, 10, "file_name", False) @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_list_files_error(self, mock_get_storage, client_with_user): """Test listing files with error.""" # Setup mock storage with error mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.list_files.side_effect = Exception("Database error") # Make request response = client_with_user.get("/files/list") # Check response assert response.status_code == 500 assert "Error listing files" in response.json()["detail"] class TestDeleteFile: """Tests for delete_file endpoint.""" @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_delete_file_success(self, mock_get_storage, client_with_admin, mock_file_info): """Test deleting file successfully as admin.""" # Setup mock storage mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.get_file_info.return_value = mock_file_info mock_storage.delete_file.return_value = True # Make request file_id = mock_file_info["file_id"] response = client_with_admin.delete(f"/files/{file_id}") # Check response assert response.status_code == 200 result = response.json() assert result["success"] is True assert "deleted successfully" in result["message"] assert result["file_id"] == file_id @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_delete_file_not_found(self, mock_get_storage, client_with_admin): """Test deleting non-existent file.""" # Setup mock storage with not found error mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.get_file_info.side_effect = StorageError("File not found") # Make request with random UUID file_id = str(uuid4()) response = client_with_admin.delete(f"/files/{file_id}") # Check response assert response.status_code == 404 assert "File not found" in response.json()["detail"] @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_delete_file_failure(self, mock_get_storage, client_with_admin, mock_file_info): """Test deletion failure.""" # Setup mock storage with successful info but failed deletion mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.get_file_info.return_value = mock_file_info mock_storage.delete_file.return_value = False # Make request file_id = mock_file_info["file_id"] response = client_with_admin.delete(f"/files/{file_id}") # Check response assert response.status_code == 200 # Still returns 200 but with success=False result = response.json() assert result["success"] is False assert "could not be deleted" in result["message"] def test_delete_file_non_admin(self, client_with_user): """Test deleting file as non-admin user.""" # Non-admin users should not be able to delete files file_id = str(uuid4()) # Make request with non-admin client response = client_with_user.delete(f"/files/{file_id}") # Check response - should be blocked by auth assert response.status_code == 403 class TestExtractStrings: """Tests for extract_strings endpoint.""" @pytest.mark.skip("FileString model not defined in tests") @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_extract_strings_success(self, mock_get_storage, client_with_user, mock_file_info): """Test extracting strings successfully.""" # Setup mock storage mock_storage = Mock() mock_get_storage.return_value = mock_storage # Mock strings result strings_result = { "file_id": mock_file_info["file_id"], "file_name": mock_file_info["file_name"], "strings": [ {"string": "test string", "offset": 0, "string_type": "ascii"}, {"string": "another string", "offset": 20, "string_type": "unicode"}, ], "total_strings": 2, "min_length": 4, "include_unicode": True, "include_ascii": True, } mock_storage.extract_strings.return_value = strings_result # Make request file_id = mock_file_info["file_id"] request_data = {"min_length": 4, "include_unicode": True, "include_ascii": True, "limit": 100} response = client_with_user.post(f"/files/strings/{file_id}", json=request_data) # Check response assert response.status_code == 200 result = response.json() assert result["file_id"] == file_id assert result["file_name"] == mock_file_info["file_name"] assert len(result["strings"]) == 2 # Verify storage was called with correct params mock_storage.extract_strings.assert_called_once_with(file_id, 4, True, True, 100) @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_extract_strings_not_found(self, mock_get_storage, client_with_user): """Test extracting strings from non-existent file.""" # Setup mock storage with not found error mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.extract_strings.side_effect = StorageError("File not found") # Make request with random UUID file_id = str(uuid4()) response = client_with_user.post(f"/files/strings/{file_id}", json={}) # Check response assert response.status_code == 404 assert "File not found" in response.json()["detail"] class TestGetHexView: """Tests for get_hex_view endpoint.""" @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_get_hex_view_success(self, mock_get_storage, client_with_user, mock_file_info): """Test getting hex view successfully.""" # Setup mock storage mock_storage = Mock() mock_get_storage.return_value = mock_storage # Mock hex view result hex_result = { "file_id": mock_file_info["file_id"], "file_name": mock_file_info["file_name"], "hex_content": "00000000: 4865 6c6c 6f20 576f 726c 6421 Hello World!", "offset": 0, "length": 12, "total_size": 12, "bytes_per_line": 16, "include_ascii": True, } mock_storage.get_hex_view.return_value = hex_result # Make request file_id = mock_file_info["file_id"] request_data = {"offset": 0, "length": 12, "bytes_per_line": 16} response = client_with_user.post(f"/files/hex/{file_id}", json=request_data) # Check response assert response.status_code == 200 result = response.json() assert result["file_id"] == file_id assert result["file_name"] == mock_file_info["file_name"] assert "Hello World!" in result["hex_content"] # Verify storage was called with correct params mock_storage.get_hex_view.assert_called_once_with(file_id, offset=0, length=12, bytes_per_line=16) @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_get_hex_view_not_found(self, mock_get_storage, client_with_user): """Test getting hex view for non-existent file.""" # Setup mock storage with not found error mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.get_hex_view.side_effect = StorageError("File not found") # Make request with random UUID file_id = str(uuid4()) response = client_with_user.post(f"/files/hex/{file_id}", json={}) # Check response assert response.status_code == 404 assert "File not found" in response.json()["detail"] @patch("yaraflux_mcp_server.routers.files.get_storage_client") def test_get_hex_view_error(self, mock_get_storage, client_with_user): """Test getting hex view with error.""" # Setup mock storage with error mock_storage = Mock() mock_get_storage.return_value = mock_storage mock_storage.get_hex_view.side_effect = Exception("Error processing file") # Make request file_id = str(uuid4()) response = client_with_user.post(f"/files/hex/{file_id}", json={}) # Check response assert response.status_code == 500 assert "Error getting hex view" in response.json()["detail"]

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ThreatFlux/YaraFlux'

If you have feedback or need assistance with the MCP directory API, please join our Discord server