"""Test cases for DocVault API""" import unittest import json import os import tempfile import shutil import io # Add server to path import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) from server.app import create_app import server.config as config import server.utils.validators as validators import server.storage.manager as storage_manager from server.config import FOLDER_MARKER class DocVaultTestCase(unittest.TestCase): """Base test case for DocVault""" def setUp(self): """Set up test client and temporary data directory""" self.app = create_app() self.client = self.app.test_client() self.user_id = "test_user" self.temp_data_dir = tempfile.mkdtemp() self.user_data_path = os.path.join(self.temp_data_dir, self.user_id) os.makedirs(self.user_data_path, exist_ok=True) # Override storage directory for tests config.DATA_DIR = self.temp_data_dir validators.DATA_DIR = self.temp_data_dir storage_manager.DATA_DIR = self.temp_data_dir def tearDown(self): """Clean up temporary data""" if os.path.exists(self.temp_data_dir): shutil.rmtree(self.temp_data_dir) def get_headers(self, user_id=None): """Get request headers with user ID""" return {'X-User-ID': user_id or self.user_id} class HealthCheckTest(DocVaultTestCase): """Test health check endpoint""" def test_health_check(self): """Test health check endpoint""" response = self.client.get('/api/health') self.assertEqual(response.status_code, 200) data = json.loads(response.data) self.assertEqual(data['status'], 'healthy') self.assertEqual(data['service'], 'DocVault') class FolderOperationsTest(DocVaultTestCase): """Test folder operations""" def test_create_folder_success(self): """Test successful folder creation""" response = self.client.post( '/api/create-folder', json={'folder_path': 'Documents'}, headers=self.get_headers() ) self.assertEqual(response.status_code, 201) data = json.loads(response.data) self.assertTrue(data['success']) self.assertEqual(data['folder']['name'], 'Documents') def test_create_nested_folder_success(self): """Test nested folder creation""" response = self.client.post( '/api/create-folder', json={'folder_path': 'Documents/Projects/MyProject'}, headers=self.get_headers() ) self.assertEqual(response.status_code, 201) data = json.loads(response.data) self.assertTrue(data['success']) def test_create_folder_duplicate(self): """Test creating duplicate folder""" self.client.post( '/api/create-folder', json={'folder_path': 'Documents'}, headers=self.get_headers() ) response = self.client.post( '/api/create-folder', json={'folder_path': 'Documents'}, headers=self.get_headers() ) self.assertEqual(response.status_code, 400) data = json.loads(response.data) self.assertFalse(data['success']) self.assertEqual(data.get('code'), 'FOLDER_EXISTS') def test_create_folder_invalid_name(self): """Test creating folder with invalid characters""" response = self.client.post( '/api/create-folder', json={'folder_path': 'Invalid/../Folder'}, headers=self.get_headers() ) self.assertEqual(response.status_code, 400) data = json.loads(response.data) self.assertFalse(data['success']) def test_delete_folder_success(self): """Test successful folder deletion""" # Create folder first self.client.post( '/api/create-folder', json={'folder_path': 'ToDelete'}, headers=self.get_headers() ) # Delete it response = self.client.post( '/api/delete-folder', json={'folder_path': 'ToDelete'}, headers=self.get_headers() ) self.assertEqual(response.status_code, 200) data = json.loads(response.data) self.assertTrue(data['success']) def test_delete_nonexistent_folder(self): """Test deleting non-existent folder""" response = self.client.post( '/api/delete-folder', json={'folder_path': 'NonExistent'}, headers=self.get_headers() ) self.assertEqual(response.status_code, 400) data = json.loads(response.data) self.assertFalse(data['success']) self.assertEqual(data.get('code'), 'FOLDER_NOT_FOUND') def test_delete_non_empty_folder_without_force(self): """Test deleting non-empty folder without force""" # Create folder with file self.client.post( '/api/create-folder', json={'folder_path': 'WithFiles'}, headers=self.get_headers() ) # Try to delete without force response = self.client.post( '/api/delete-folder', json={'folder_path': 'WithFiles', 'force': False}, headers=self.get_headers() ) # Should fail if folder has marker file data = json.loads(response.data) if not data['success']: self.assertEqual(data.get('code'), 'FOLDER_NOT_EMPTY') class FileOperationsTest(DocVaultTestCase): """Test file operations""" def test_upload_file_success(self): """Test successful file upload""" # Create folder first self.client.post( '/api/create-folder', json={'folder_path': 'Documents'}, headers=self.get_headers() ) # Upload file data = {'folder_path': 'Documents', 'file': (io.BytesIO(b'Hello World'), 'test.txt')} response = self.client.post( '/api/upload-file', data=data, headers=self.get_headers() ) self.assertEqual(response.status_code, 201) response_data = json.loads(response.data) self.assertTrue(response_data['success']) self.assertEqual(response_data['file']['name'], 'test.txt') def test_upload_file_to_nonexistent_folder(self): """Test uploading file to non-existent folder (should create)""" data = {'folder_path': 'NewFolder', 'file': (io.BytesIO(b'Hello World'), 'test.txt')} response = self.client.post( '/api/upload-file', data=data, headers=self.get_headers() ) self.assertEqual(response.status_code, 201) response_data = json.loads(response.data) self.assertTrue(response_data['success']) def test_upload_file_no_file_provided(self): """Test upload with no file""" response = self.client.post( '/api/upload-file', data={'folder_path': 'Documents'}, headers=self.get_headers() ) self.assertEqual(response.status_code, 400) data = json.loads(response.data) self.assertFalse(data['success']) def test_upload_file_restricted_extension(self): """Test uploading file with restricted extension""" data = {'folder_path': 'Documents', 'file': (io.BytesIO(b'malware'), 'virus.exe')} response = self.client.post( '/api/upload-file', data=data, headers=self.get_headers() ) self.assertEqual(response.status_code, 400) response_data = json.loads(response.data) self.assertFalse(response_data['success']) def test_delete_file_success(self): """Test successful file deletion""" self.client.post( '/api/create-folder', json={'folder_path': 'Documents'}, headers=self.get_headers() ) self.client.post( '/api/upload-file', data={'folder_path': 'Documents', 'file': (io.BytesIO(b'Hello World'), 'test.txt')}, headers=self.get_headers() ) response = self.client.post( '/api/delete-file', json={'file_path': 'Documents/test.txt'}, headers=self.get_headers() ) self.assertEqual(response.status_code, 200) response_data = json.loads(response.data) self.assertTrue(response_data['success']) class ListOperationsTest(DocVaultTestCase): """Test listing operations""" def test_list_empty_root(self): """Test listing empty root""" response = self.client.get( '/api/list', headers=self.get_headers() ) self.assertEqual(response.status_code, 200) data = json.loads(response.data) self.assertTrue(data['success']) self.assertEqual(data['total_folders'], 0) self.assertEqual(data['total_files'], 0) def test_list_with_folders(self): """Test listing with folders""" # Create folders self.client.post( '/api/create-folder', json={'folder_path': 'Docs'}, headers=self.get_headers() ) self.client.post( '/api/create-folder', json={'folder_path': 'Images'}, headers=self.get_headers() ) # List response = self.client.get( '/api/list', headers=self.get_headers() ) self.assertEqual(response.status_code, 200) data = json.loads(response.data) self.assertTrue(data['success']) self.assertGreater(data['total_folders'], 0) def test_list_with_files(self): """Test listing with files""" # Create folder and upload file self.client.post( '/api/create-folder', json={'folder_path': 'Documents'}, headers=self.get_headers() ) self.client.post( '/api/upload-file', data={'folder_path': 'Documents', 'file': (io.BytesIO(b'# Read Me'), 'readme.md')}, headers=self.get_headers() ) # List subfolder response = self.client.get( '/api/list?folder_path=Documents', headers=self.get_headers() ) self.assertEqual(response.status_code, 200) data = json.loads(response.data) self.assertTrue(data['success']) self.assertGreater(data['total_files'], 0) class RenameOperationsTest(DocVaultTestCase): """Test rename operations""" def test_rename_folder_success(self): """Test successful folder rename""" # Create folder self.client.post( '/api/create-folder', json={'folder_path': 'OldName'}, headers=self.get_headers() ) # Rename it response = self.client.post( '/api/rename', json={'item_path': 'OldName', 'new_name': 'NewName'}, headers=self.get_headers() ) self.assertEqual(response.status_code, 200) data = json.loads(response.data) self.assertTrue(data['success']) self.assertEqual(data['item']['name'], 'NewName') def test_rename_file_success(self): """Test successful file rename""" # Create folder and upload file self.client.post( '/api/create-folder', json={'folder_path': 'Documents'}, headers=self.get_headers() ) self.client.post( '/api/upload-file', data={'folder_path': 'Documents', 'file': (io.BytesIO(b'content'), 'oldname.txt')}, headers=self.get_headers() ) # Rename response = self.client.post( '/api/rename', json={'item_path': 'Documents/oldname.txt', 'new_name': 'newname.txt'}, headers=self.get_headers() ) self.assertEqual(response.status_code, 200) data = json.loads(response.data) self.assertTrue(data['success']) class StorageStatsTest(DocVaultTestCase): """Test storage statistics""" def test_storage_stats(self): """Test storage statistics endpoint""" response = self.client.get( '/api/storage-stats', headers=self.get_headers() ) self.assertEqual(response.status_code, 200) data = json.loads(response.data) self.assertTrue(data['success']) self.assertIn('total_size', data) self.assertIn('total_files', data) self.assertIn('total_folders', data) class SecurityTest(DocVaultTestCase): """Test security features""" def test_path_traversal_prevention(self): """Test path traversal attack prevention""" response = self.client.post( '/api/create-folder', json={'folder_path': '../../etc/passwd'}, headers=self.get_headers() ) self.assertEqual(response.status_code, 400) data = json.loads(response.data) self.assertFalse(data['success']) def test_invalid_folder_name(self): """Test invalid folder name""" response = self.client.post( '/api/create-folder', json={'folder_path': 'Folder With Spaces'}, headers=self.get_headers() ) # Should fail or sanitize (depends on implementation) data = json.loads(response.data) # Either it fails or creates with sanitized name self.assertIsNotNone(data) def run_tests(): """Run all tests""" loader = unittest.TestLoader() suite = unittest.TestSuite() # Add test classes suite.addTests(loader.loadTestsFromTestCase(HealthCheckTest)) suite.addTests(loader.loadTestsFromTestCase(FolderOperationsTest)) suite.addTests(loader.loadTestsFromTestCase(FileOperationsTest)) suite.addTests(loader.loadTestsFromTestCase(ListOperationsTest)) suite.addTests(loader.loadTestsFromTestCase(RenameOperationsTest)) suite.addTests(loader.loadTestsFromTestCase(StorageStatsTest)) suite.addTests(loader.loadTestsFromTestCase(SecurityTest)) runner = unittest.TextTestRunner(verbosity=2) result = runner.run(suite) return result.wasSuccessful() if __name__ == '__main__': success = run_tests() sys.exit(0 if success else 1)