""" 文件上传下载功能测试 - TDD Red阶段 测试文件上传、下载、验证和管理功能。 """ import pytest import allure import os import tempfile from typing import Any, Optional @allure.epic("核心框架") @allure.feature("文件上传下载功能 - TDD Red阶段") class TestFileHandler: """文件处理功能测试类 - TDD Red阶段(期望失败)""" @allure.title("测试文件上传 - TDD Red阶段") @allure.description("验证文件上传功能 - 期望失败(Red)") @allure.severity(allure.severity_level.CRITICAL) @pytest.mark.smoke def test_file_upload(self) -> None: """ TDD Red阶段: 测试文件上传 预期结果: - 能够上传文件 - 返回上传结果和文件信息 - 支持多种文件类型 """ from core.file_handler import FileUploader with allure.step("Step 1: 创建文件上传器"): uploader = FileUploader(upload_dir="/tmp/test_uploads") allure.attach("✅ 创建文件上传器", "步骤1", allure.attachment_type.TEXT) with allure.step("Step 2: 创建测试文件"): with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f: f.write("测试文件内容") test_file_path = f.name allure.attach(f"✅ 创建测试文件: {test_file_path}", "步骤2", allure.attachment_type.TEXT) with allure.step("Step 3: 上传文件"): with open(test_file_path, 'rb') as f: result = uploader.upload(f, filename="test.txt") allure.attach(f"✅ 上传结果: {result}", "步骤3", allure.attachment_type.TEXT) assert result.success is True, "文件上传应该成功" assert result.file_id is not None, "应该有文件ID" with allure.step("Step 4: 清理"): os.unlink(test_file_path) if result.file_path and os.path.exists(result.file_path): os.unlink(result.file_path) @allure.title("测试文件下载 - TDD Red阶段") @allure.description("验证文件下载功能 - 期望失败(Red)") @allure.severity(allure.severity_level.CRITICAL) @pytest.mark.smoke def test_file_download(self) -> None: """ TDD Red阶段: 测试文件下载 预期结果: - 能够下载已上传的文件 - 返回文件内容 - 支持断点续传 """ from core.file_handler import FileUploader, FileDownloader with allure.step("Step 1: 上传测试文件"): uploader = FileUploader(upload_dir="/tmp/test_uploads") with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f: f.write("下载测试内容") test_file_path = f.name with open(test_file_path, 'rb') as f: upload_result = uploader.upload(f, filename="download_test.txt") allure.attach("✅ 上传测试文件", "步骤1", allure.attachment_type.TEXT) with allure.step("Step 2: 下载文件"): # 使用同一个存储管理器 downloader = FileDownloader(storage_manager=uploader._storage) download_result = downloader.download(upload_result.file_id) allure.attach(f"✅ 下载结果: {download_result.success}", "步骤2", allure.attachment_type.TEXT) assert download_result.success is True, "文件下载应该成功" with allure.step("Step 3: 验证文件内容"): content = download_result.content.decode('utf-8') assert content == "下载测试内容", f"文件内容不匹配: {content}" allure.attach(f"✅ 文件内容验证通过", "步骤3", allure.attachment_type.TEXT) with allure.step("Step 4: 清理"): os.unlink(test_file_path) @allure.title("测试文件类型验证 - TDD Red阶段") @allure.description("验证文件类型检查功能 - 期望失败(Red)") @allure.severity(allure.severity_level.CRITICAL) @pytest.mark.smoke def test_file_type_validation(self) -> None: """ TDD Red阶段: 测试文件类型验证 预期结果: - 允许合法文件类型 - 拒绝非法文件类型 - 支持MIME类型检查 """ from core.file_handler import FileUploader, FileTypeValidator with allure.step("Step 1: 创建文件类型验证器"): validator = FileTypeValidator(allowed_extensions=['.txt', '.pdf', '.jpg']) allure.attach("✅ 创建文件类型验证器", "步骤1", allure.attachment_type.TEXT) with allure.step("Step 2: 测试合法文件类型"): is_valid = validator.validate("document.txt") allure.attach(f"✅ txt文件验证: {is_valid}", "步骤2", allure.attachment_type.TEXT) assert is_valid is True, "txt文件应该被允许" with allure.step("Step 3: 测试非法文件类型"): is_valid = validator.validate("script.exe") allure.attach(f"✅ exe文件验证: {is_valid}", "步骤3", allure.attachment_type.TEXT) assert is_valid is False, "exe文件应该被拒绝" @allure.title("测试文件大小限制 - TDD Red阶段") @allure.description("验证文件大小限制功能 - 期望失败(Red)") @allure.severity(allure.severity_level.NORMAL) @pytest.mark.regression def test_file_size_limit(self) -> None: """ TDD Red阶段: 测试文件大小限制 预期结果: - 允许小于限制的文件 - 拒绝超过限制的文件 """ from core.file_handler import FileUploader, FileSizeValidator with allure.step("Step 1: 创建文件大小验证器"): validator = FileSizeValidator(max_size=1024) # 1KB allure.attach("✅ 创建文件大小验证器(max_size=1KB)", "步骤1", allure.attachment_type.TEXT) with allure.step("Step 2: 测试小文件"): is_valid = validator.validate(512) # 512 bytes allure.attach(f"✅ 512字节文件: {is_valid}", "步骤2", allure.attachment_type.TEXT) assert is_valid is True, "小文件应该被允许" with allure.step("Step 3: 测试大文件"): is_valid = validator.validate(2048) # 2KB allure.attach(f"✅ 2KB文件: {is_valid}", "步骤3", allure.attachment_type.TEXT) assert is_valid is False, "大文件应该被拒绝" @allure.title("测试文件名安全验证 - TDD Red阶段") @allure.description("验证文件名安全检查功能 - 期望失败(Red)") @allure.severity(allure.severity_level.CRITICAL) @pytest.mark.smoke def test_filename_security(self) -> None: """ TDD Red阶段: 测试文件名安全验证 预期结果: - 检测路径遍历攻击 - 过滤危险字符 - 生成安全文件名 """ from core.file_handler import FilenameSanitizer with allure.step("Step 1: 创建文件名净化器"): sanitizer = FilenameSanitizer() allure.attach("✅ 创建文件名净化器", "步骤1", allure.attachment_type.TEXT) with allure.step("Step 2: 测试路径遍历攻击"): safe_name = sanitizer.sanitize("../../../etc/passwd") allure.attach(f"✅ 净化结果: {safe_name}", "步骤2", allure.attachment_type.TEXT) assert ".." not in safe_name, "路径遍历应该被阻止" with allure.step("Step 3: 测试危险字符"): safe_name = sanitizer.sanitize("file;rm -rf /|.txt") allure.attach(f"✅ 净化结果: {safe_name}", "步骤3", allure.attachment_type.TEXT) assert ";" not in safe_name and "|" not in safe_name, "危险字符应该被移除" @allure.title("测试文件存储管理 - TDD Red阶段") @allure.description("验证文件存储管理功能 - 期望失败(Red)") @allure.severity(allure.severity_level.NORMAL) @pytest.mark.regression def test_file_storage_management(self) -> None: """ TDD Red阶段: 测试文件存储管理 预期结果: - 支持多种存储后端 - 文件元数据管理 - 文件删除和清理 """ from core.file_handler import FileStorageManager with allure.step("Step 1: 创建存储管理器"): manager = FileStorageManager(storage_dir="/tmp/test_storage") allure.attach("✅ 创建存储管理器", "步骤1", allure.attachment_type.TEXT) with allure.step("Step 2: 保存文件"): file_id = manager.save("测试内容".encode('utf-8'), filename="test.txt") allure.attach(f"✅ 保存文件,ID: {file_id}", "步骤2", allure.attachment_type.TEXT) assert file_id is not None, "应该有文件ID" with allure.step("Step 3: 获取文件"): content = manager.get(file_id) allure.attach(f"✅ 获取文件内容", "步骤3", allure.attachment_type.TEXT) assert content == "测试内容".encode('utf-8'), "文件内容应该匹配" with allure.step("Step 4: 删除文件"): deleted = manager.delete(file_id) allure.attach(f"✅ 删除结果: {deleted}", "步骤4", allure.attachment_type.TEXT) assert deleted is True, "文件应该被删除" @allure.title("测试文件批量操作 - TDD Red阶段") @allure.description("验证文件批量操作功能 - 期望失败(Red)") @allure.severity(allure.severity_level.NORMAL) @pytest.mark.regression def test_file_batch_operations(self) -> None: """ TDD Red阶段: 测试文件批量操作 预期结果: - 支持批量上传 - 支持批量删除 - 支持批量下载 """ from core.file_handler import FileUploader with allure.step("Step 1: 创建文件上传器"): uploader = FileUploader(upload_dir="/tmp/test_batch") allure.attach("✅ 创建文件上传器", "步骤1", allure.attachment_type.TEXT) with allure.step("Step 2: 批量上传"): files = [] for i in range(3): with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f: f.write(f"文件{i}内容") files.append(f.name) results = uploader.upload_batch(files) allure.attach(f"✅ 批量上传: {len(results)}个文件", "步骤2", allure.attachment_type.TEXT) assert len(results) == 3, "应该上传3个文件" with allure.step("Step 3: 清理"): for f in files: if os.path.exists(f): os.unlink(f) @allure.title("测试文件元数据管理 - TDD Red阶段") @allure.description("验证文件元数据管理功能 - 期望失败(Red)") @allure.severity(allure.severity_level.NORMAL) @pytest.mark.regression def test_file_metadata(self) -> None: """ TDD Red阶段: 测试文件元数据管理 预期结果: - 记录文件元数据 - 支持元数据查询 - 支持元数据更新 """ from core.file_handler import FileStorageManager with allure.step("Step 1: 创建存储管理器"): manager = FileStorageManager(storage_dir="/tmp/test_metadata") allure.attach("✅ 创建存储管理器", "步骤1", allure.attachment_type.TEXT) with allure.step("Step 2: 保存文件带元数据"): file_id = manager.save( "测试内容".encode('utf-8'), filename="test.txt", metadata={"author": "test_user", "tags": ["test", "demo"]} ) allure.attach(f"✅ 保存文件,ID: {file_id}", "步骤2", allure.attachment_type.TEXT) with allure.step("Step 3: 获取元数据"): metadata = manager.get_metadata(file_id) allure.attach(f"✅ 元数据: {metadata}", "步骤3", allure.attachment_type.TEXT) assert metadata is not None, "应该有元数据" assert metadata.get("author") == "test_user", "作者信息应该匹配" with allure.step("Step 4: 清理"): manager.delete(file_id)