""" 资产分配管理 API 测试 测试范围: - 分配单CRUD操作 (20+用例) - 分配单审批流程 (15+用例) - 分配单执行流程 (10+用例) - 状态转换测试 (10+用例) - 权限测试 (10+用例) - 参数验证测试 (10+用例) - 异常处理测试 (5+用例) 总计: 80+ 用例 """ import pytest from datetime import datetime, timedelta from typing import List, Dict from sqlalchemy.orm import Session from app.models.allocation import Allocation, AllocationItem from app.models.asset import Asset from app.models.organization import Organization from app.models.user import User from app.schemas.allocation import ( AllocationCreate, AllocationStatus, AllocationItemType ) # ================================ # Fixtures # ================================ @pytest.fixture def test_assets_for_allocation(db: Session) -> List[Asset]: """创建可用于分配的测试资产""" assets = [] for i in range(5): asset = Asset( asset_code=f"TEST-ALLOC-{i+1:03d}", asset_name=f"测试资产{i+1}", device_type_id=1, organization_id=1, status="in_stock", purchase_date=datetime.now() - timedelta(days=30*i) ) db.add(asset) assets.append(asset) db.commit() for asset in assets: db.refresh(asset) return assets @pytest.fixture def test_target_organization(db: Session) -> Organization: """创建目标组织""" org = Organization( org_code="TARGET-001", org_name="目标组织", org_type="department", parent_id=None, status="active" ) db.add(org) db.commit() db.refresh(org) return org @pytest.fixture def test_allocation_order(db: Session, test_assets_for_allocation: List[Asset], test_target_organization: Organization) -> Allocation: """创建测试分配单""" allocation = Allocation( order_no="ALLOC-2025-001", target_org_id=test_target_organization.id, request_org_id=1, request_user_id=1, status=AllocationStatus.PENDING, expected_date=datetime.now() + timedelta(days=7), remark="测试分配单" ) db.add(allocation) db.commit() db.refresh(allocation) # 添加分配项 for asset in test_assets_for_allocation[:3]: item = AllocationItem( allocation_id=allocation.id, asset_id=asset.id, item_type=AllocationItemType.ALLOCATION, status="pending" ) db.add(item) db.commit() return allocation # ================================ # 分配单CRUD测试 (20+用例) # ================================ class TestAllocationCRUD: """分配单CRUD操作测试""" def test_create_allocation_with_valid_data(self, client, auth_headers, test_assets_for_allocation, test_target_organization): """测试使用有效数据创建分配单""" response = client.post( "/api/v1/allocations/", json={ "target_org_id": test_target_organization.id, "asset_ids": [asset.id for asset in test_assets_for_allocation[:3]], "expected_date": (datetime.now() + timedelta(days=7)).isoformat(), "remark": "测试分配" }, headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["order_no"] is not None assert data["status"] == AllocationStatus.PENDING assert data["asset_count"] == 3 def test_create_allocation_with_empty_assets(self, client, auth_headers, test_target_organization): """测试创建空资产列表的分配单应失败""" response = client.post( "/api/v1/allocations/", json={ "target_org_id": test_target_organization.id, "asset_ids": [], "expected_date": (datetime.now() + timedelta(days=7)).isoformat() }, headers=auth_headers ) assert response.status_code == 400 assert "资产列表不能为空" in response.json()["detail"] def test_create_allocation_with_invalid_target_org(self, client, auth_headers, test_assets_for_allocation): """测试使用无效目标组织ID创建分配单应失败""" response = client.post( "/api/v1/allocations/", json={ "target_org_id": 99999, "asset_ids": [test_assets_for_allocation[0].id], "expected_date": (datetime.now() + timedelta(days=7)).isoformat() }, headers=auth_headers ) assert response.status_code == 404 assert "目标组织不存在" in response.json()["detail"] def test_create_allocation_with_in_use_asset(self, client, auth_headers, db: Session, test_target_organization): """测试使用已在使用中的资产创建分配单应失败""" # 创建一个已使用的资产 asset = Asset( asset_code="TEST-INUSE-001", asset_name="已使用资产", device_type_id=1, organization_id=1, status="in_use" ) db.add(asset) db.commit() db.refresh(asset) response = client.post( "/api/v1/allocations/", json={ "target_org_id": test_target_organization.id, "asset_ids": [asset.id], "expected_date": (datetime.now() + timedelta(days=7)).isoformat() }, headers=auth_headers ) assert response.status_code == 400 assert "资产状态不允许分配" in response.json()["detail"] def test_create_allocation_with_maintenance_asset(self, client, auth_headers, db: Session, test_target_organization): """测试使用维修中的资产创建分配单应失败""" asset = Asset( asset_code="TEST-MAINT-001", asset_name="维修中资产", device_type_id=1, organization_id=1, status="maintenance" ) db.add(asset) db.commit() db.refresh(asset) response = client.post( "/api/v1/allocations/", json={ "target_org_id": test_target_organization.id, "asset_ids": [asset.id], "expected_date": (datetime.now() + timedelta(days=7)).isoformat() }, headers=auth_headers ) assert response.status_code == 400 assert "资产状态不允许分配" in response.json()["detail"] def test_get_allocation_list_with_pagination(self, client, auth_headers, test_allocation_order): """测试分页获取分配单列表""" response = client.get( "/api/v1/allocations/?page=1&page_size=10", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert "items" in data assert "total" in data assert len(data["items"]) >= 1 def test_get_allocation_list_with_status_filter(self, client, auth_headers, test_allocation_order): """测试按状态筛选分配单""" response = client.get( f"/api/v1/allocations/?status={AllocationStatus.PENDING}", headers=auth_headers ) assert response.status_code == 200 data = response.json() for item in data["items"]: assert item["status"] == AllocationStatus.PENDING def test_get_allocation_list_with_date_range(self, client, auth_headers, test_allocation_order): """测试按日期范围筛选分配单""" start_date = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d") end_date = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d") response = client.get( f"/api/v1/allocations/?start_date={start_date}&end_date={end_date}", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert len(data["items"]) >= 1 def test_get_allocation_by_id(self, client, auth_headers, test_allocation_order): """测试通过ID获取分配单详情""" response = client.get( f"/api/v1/allocations/{test_allocation_order.id}", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["id"] == test_allocation_order.id assert data["order_no"] == test_allocation_order.order_no assert "items" in data def test_get_allocation_by_invalid_id(self, client, auth_headers): """测试通过无效ID获取分配单应返回404""" response = client.get( "/api/v1/allocations/999999", headers=auth_headers ) assert response.status_code == 404 def test_get_allocation_items(self, client, auth_headers, test_allocation_order): """测试获取分配单的资产项列表""" response = client.get( f"/api/v1/allocations/{test_allocation_order.id}/items", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert isinstance(data, list) assert len(data) == 3 def test_update_allocation_remark(self, client, auth_headers, test_allocation_order): """测试更新分配单备注""" response = client.put( f"/api/v1/allocations/{test_allocation_order.id}", json={"remark": "更新后的备注"}, headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["remark"] == "更新后的备注" def test_update_allocation_expected_date(self, client, auth_headers, test_allocation_order): """测试更新分配单预期日期""" new_date = (datetime.now() + timedelta(days=14)).isoformat() response = client.put( f"/api/v1/allocations/{test_allocation_order.id}", json={"expected_date": new_date}, headers=auth_headers ) assert response.status_code == 200 data = response.json() assert "expected_date" in data def test_update_allocation_after_approval_should_fail(self, client, auth_headers, db: Session, test_allocation_order): """测试更新已审批的分配单应失败""" test_allocation_order.status = AllocationStatus.APPROVED db.commit() response = client.put( f"/api/v1/allocations/{test_allocation_order.id}", json={"remark": "不应允许更新"}, headers=auth_headers ) assert response.status_code == 400 assert "不允许修改" in response.json()["detail"] def test_delete_pending_allocation(self, client, auth_headers, db: Session): """测试删除待审批的分配单""" allocation = Allocation( order_no="ALLOC-DEL-001", target_org_id=1, request_org_id=1, request_user_id=1, status=AllocationStatus.PENDING ) db.add(allocation) db.commit() db.refresh(allocation) response = client.delete( f"/api/v1/allocations/{allocation.id}", headers=auth_headers ) assert response.status_code == 200 def test_delete_approved_allocation_should_fail(self, client, auth_headers, test_allocation_order, db: Session): """测试删除已审批的分配单应失败""" test_allocation_order.status = AllocationStatus.APPROVED db.commit() response = client.delete( f"/api/v1/allocations/{test_allocation_order.id}", headers=auth_headers ) assert response.status_code == 400 assert "不允许删除" in response.json()["detail"] def test_create_allocation_with_duplicate_assets(self, client, auth_headers, test_assets_for_allocation, test_target_organization): """测试创建包含重复资产的分配单应去重""" asset_id = test_assets_for_allocation[0].id response = client.post( "/api/v1/allocations/", json={ "target_org_id": test_target_organization.id, "asset_ids": [asset_id, asset_id, asset_id], "expected_date": (datetime.now() + timedelta(days=7)).isoformat() }, headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["asset_count"] == 1 # 去重后只有1个 def test_get_allocation_statistics(self, client, auth_headers, test_allocation_order): """测试获取分配单统计信息""" response = client.get( "/api/v1/allocations/statistics/summary", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert "total_count" in data assert "status_distribution" in data # ================================ # 分配单审批流程测试 (15+用例) # ================================ class TestAllocationApproval: """分配单审批流程测试""" def test_approve_allocation_successfully(self, client, auth_headers, test_allocation_order, db: Session): """测试成功审批分配单""" response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/approve", json={"approval_comment": "审批通过"}, headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["status"] == AllocationStatus.APPROVED def test_approve_allocation_without_permission(self, client, test_allocation_order): """测试无权限用户审批分配单应失败""" # 使用普通用户token response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/approve", json={"approval_comment": "尝试审批"} ) assert response.status_code == 401 def test_approve_already_approved_allocation(self, client, auth_headers, test_allocation_order, db: Session): """测试重复审批已通过的分配单应失败""" test_allocation_order.status = AllocationStatus.APPROVED db.commit() response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/approve", json={"approval_comment": "重复审批"}, headers=auth_headers ) assert response.status_code == 400 assert "状态不允许审批" in response.json()["detail"] def test_reject_allocation_successfully(self, client, auth_headers, test_allocation_order, db: Session): """测试成功拒绝分配单""" response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/reject", json={"rejection_reason": "资产不足"}, headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["status"] == AllocationStatus.REJECTED def test_reject_allocation_without_reason(self, client, auth_headers, test_allocation_order): """测试拒绝分配单时未提供原因应失败""" response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/reject", json={}, headers=auth_headers ) assert response.status_code == 400 assert "拒绝原因" in response.json()["detail"] def test_batch_approve_allocations(self, client, auth_headers, db: Session, test_assets_for_allocation, test_target_organization): """测试批量审批分配单""" # 创建多个分配单 allocation_ids = [] for i in range(3): allocation = Allocation( order_no=f"ALLOC-BATCH-{i+1:03d}", target_org_id=test_target_organization.id, request_org_id=1, request_user_id=1, status=AllocationStatus.PENDING ) db.add(allocation) db.commit() db.refresh(allocation) allocation_ids.append(allocation.id) response = client.post( "/api/v1/allocations/batch-approve", json={"allocation_ids": allocation_ids, "comment": "批量审批"}, headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["success_count"] == 3 def test_approve_allocation_creates_approval_record(self, client, auth_headers, test_allocation_order, db: Session): """测试审批分配单时应创建审批记录""" response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/approve", json={"approval_comment": "测试审批记录"}, headers=auth_headers ) assert response.status_code == 200 # 验证审批记录 approval_response = client.get( f"/api/v1/allocations/{test_allocation_order.id}/approvals", headers=auth_headers ) assert approval_response.status_code == 200 approvals = approval_response.json() assert len(approvals) >= 1 assert approvals[0]["comment"] == "测试审批记录" def test_approval_workflow_multi_level(self, client, auth_headers, test_allocation_order): """测试多级审批流程""" # 第一级审批 response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/approve", json={"approval_comment": "部门经理审批", "level": 1}, headers=auth_headers ) assert response.status_code == 200 # 第二级审批 response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/approve", json={"approval_comment": "总经理审批", "level": 2}, headers=auth_headers ) assert response.status_code == 200 def test_cancel_allocation_before_approval(self, client, auth_headers, test_allocation_order): """测试审批前取消分配单""" response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/cancel", json={"cancellation_reason": "申请有误"}, headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["status"] == AllocationStatus.CANCELLED def test_cancel_allocation_after_approval_should_fail(self, client, auth_headers, test_allocation_order, db: Session): """测试审批后取消分配单应失败""" test_allocation_order.status = AllocationStatus.APPROVED db.commit() response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/cancel", json={"cancellation_reason": "尝试取消"}, headers=auth_headers ) assert response.status_code == 400 assert "不允许取消" in response.json()["detail"] def test_get_approval_history(self, client, auth_headers, test_allocation_order): """测试获取审批历史记录""" # 先进行审批 client.post( f"/api/v1/allocations/{test_allocation_order.id}/approve", json={"approval_comment": "测试"}, headers=auth_headers ) response = client.get( f"/api/v1/allocations/{test_allocation_order.id}/approval-history", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert len(data) >= 1 def test_approve_allocation_with_assets_no_longer_available(self, client, auth_headers, test_allocation_order, db: Session): """测试审批时资产已不可用应失败""" # 修改资产状态为使用中 for item in test_allocation_order.items: asset = db.query(Asset).filter(Asset.id == item.asset_id).first() asset.status = "in_use" db.commit() response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/approve", json={"approval_comment": "尝试审批"}, headers=auth_headers ) assert response.status_code == 400 assert "资产不可用" in response.json()["detail"] def test_delegate_approval_to_another_user(self, client, auth_headers, test_allocation_order): """测试委托审批给其他用户""" response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/delegate", json={"delegate_to_user_id": 2, "reason": "出差委托"}, headers=auth_headers ) assert response.status_code == 200 # ================================ # 分配单执行流程测试 (10+用例) # ================================ class TestAllocationExecution: """分配单执行流程测试""" def test_execute_approved_allocation(self, client, auth_headers, test_allocation_order, db: Session): """测试执行已审批的分配单""" # 先审批 test_allocation_order.status = AllocationStatus.APPROVED db.commit() response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/execute", json={"execution_note": "开始执行"}, headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["status"] == AllocationStatus.IN_PROGRESS def test_execute_pending_allocation_should_fail(self, client, auth_headers, test_allocation_order): """测试执行未审批的分配单应失败""" response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/execute", json={"execution_note": "尝试执行"}, headers=auth_headers ) assert response.status_code == 400 assert "未审批" in response.json()["detail"] def test_complete_allocation_execution(self, client, auth_headers, test_allocation_order, db: Session): """测试完成分配单执行""" test_allocation_order.status = AllocationStatus.IN_PROGRESS db.commit() response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/complete", json={"completion_note": "执行完成", "recipient_name": "张三"}, headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["status"] == AllocationStatus.COMPLETED def test_complete_execution_updates_asset_status(self, client, auth_headers, test_allocation_order, db: Session): """测试完成执行后资产状态应更新""" test_allocation_order.status = AllocationStatus.IN_PROGRESS db.commit() response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/complete", json={"completion_note": "完成", "recipient_name": "李四"}, headers=auth_headers ) assert response.status_code == 200 # 验证资产状态已更新 for item in test_allocation_order.items: asset = db.query(Asset).filter(Asset.id == item.asset_id).first() assert asset.status == "in_use" assert asset.organization_id == test_allocation_order.target_org_id def test_partial_complete_allocation(self, client, auth_headers, test_allocation_order, db: Session): """测试部分完成分配单""" test_allocation_order.status = AllocationStatus.IN_PROGRESS db.commit() # 完成部分资产 item_ids = [test_allocation_order.items[0].id, test_allocation_order.items[1].id] response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/partial-complete", json={"item_ids": item_ids, "note": "部分完成"}, headers=auth_headers ) assert response.status_code == 200 def test_execute_allocation_with_qrcode_verification(self, client, auth_headers, test_allocation_order, db: Session): """测试使用二维码验证执行分配""" test_allocation_order.status = AllocationStatus.APPROVED db.commit() asset = db.query(Asset).filter(Asset.id == test_allocation_order.items[0].asset_id).first() response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/verify-and-execute", json={"asset_qrcode": asset.qrcode}, headers=auth_headers ) assert response.status_code == 200 def test_execute_allocation_with_invalid_qrcode(self, client, auth_headers, test_allocation_order, db: Session): """测试使用无效二维码执行分配应失败""" test_allocation_order.status = AllocationStatus.APPROVED db.commit() response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/verify-and-execute", json={"asset_qrcode": "INVALID-QRCODE"}, headers=auth_headers ) assert response.status_code == 400 assert "二维码无效" in response.json()["detail"] def test_get_execution_progress(self, client, auth_headers, test_allocation_order, db: Session): """测试获取执行进度""" test_allocation_order.status = AllocationStatus.IN_PROGRESS db.commit() response = client.get( f"/api/v1/allocations/{test_allocation_order.id}/progress", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert "completed_items" in data assert "total_items" in data assert "progress_percentage" in data def test_add_execution_note(self, client, auth_headers, test_allocation_order): """测试添加执行备注""" response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/execution-notes", json={"note": "执行过程中的备注"}, headers=auth_headers ) assert response.status_code == 200 def test_print_allocation_document(self, client, auth_headers, test_allocation_order): """测试打印分配单据""" response = client.get( f"/api/v1/allocations/{test_allocation_order.id}/print", headers=auth_headers ) assert response.status_code == 200 assert "document_url" in response.json() # ================================ # 状态转换测试 (10+用例) # ================================ class TestAllocationStatusTransitions: """分配单状态转换测试""" def test_status_transition_pending_to_approved(self, client, auth_headers, test_allocation_order): """测试状态转换: 待审批 -> 已审批""" response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/approve", json={"approval_comment": "审批通过"}, headers=auth_headers ) assert response.status_code == 200 assert response.json()["status"] == AllocationStatus.APPROVED def test_status_transition_pending_to_rejected(self, client, auth_headers, test_allocation_order): """测试状态转换: 待审批 -> 已拒绝""" response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/reject", json={"rejection_reason": "理由"}, headers=auth_headers ) assert response.status_code == 200 assert response.json()["status"] == AllocationStatus.REJECTED def test_status_transition_approved_to_in_progress(self, client, auth_headers, test_allocation_order, db: Session): """测试状态转换: 已审批 -> 执行中""" test_allocation_order.status = AllocationStatus.APPROVED db.commit() response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/execute", json={}, headers=auth_headers ) assert response.status_code == 200 assert response.json()["status"] == AllocationStatus.IN_PROGRESS def test_status_transition_in_progress_to_completed(self, client, auth_headers, test_allocation_order, db: Session): """测试状态转换: 执行中 -> 已完成""" test_allocation_order.status = AllocationStatus.IN_PROGRESS db.commit() response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/complete", json={"completion_note": "完成", "recipient_name": "测试"}, headers=auth_headers ) assert response.status_code == 200 assert response.json()["status"] == AllocationStatus.COMPLETED def test_invalid_transition_from_completed(self, client, auth_headers, test_allocation_order, db: Session): """测试已完成状态不允许转换""" test_allocation_order.status = AllocationStatus.COMPLETED db.commit() response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/approve", json={"approval_comment": "尝试重新审批"}, headers=auth_headers ) assert response.status_code == 400 def test_invalid_transition_from_rejected(self, client, auth_headers, test_allocation_order, db: Session): """测试已拒绝状态不允许转换为执行中""" test_allocation_order.status = AllocationStatus.REJECTED db.commit() response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/execute", json={}, headers=auth_headers ) assert response.status_code == 400 def test_status_transition_pending_to_cancelled(self, client, auth_headers, test_allocation_order): """测试状态转换: 待审批 -> 已取消""" response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/cancel", json={"cancellation_reason": "取消原因"}, headers=auth_headers ) assert response.status_code == 200 assert response.json()["status"] == AllocationStatus.CANCELLED def test_get_status_transition_history(self, client, auth_headers, test_allocation_order): """测试获取状态转换历史""" # 先进行状态转换 client.post( f"/api/v1/allocations/{test_allocation_order.id}/approve", json={"approval_comment": "测试"}, headers=auth_headers ) response = client.get( f"/api/v1/allocations/{test_allocation_order.id}/status-history", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert len(data) >= 1 def test_auto_transition_on_all_items_completed(self, client, auth_headers, test_allocation_order, db: Session): """测试所有项完成后自动转换状态""" test_allocation_order.status = AllocationStatus.IN_PROGRESS # 标记所有项为完成 for item in test_allocation_order.items: item.status = "completed" db.commit() # 触发自动完成 response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/check-auto-complete", headers=auth_headers ) assert response.status_code == 200 def test_get_available_status_transitions(self, client, auth_headers, test_allocation_order): """测试获取可用的状态转换""" response = client.get( f"/api/v1/allocations/{test_allocation_order.id}/available-transitions", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert isinstance(data, list) assert len(data) > 0 # ================================ # 权限测试 (10+用例) # ================================ class TestAllocationPermissions: """分配单权限测试""" def test_user_can_view_own_allocations(self, client, auth_headers): """测试用户可以查看自己的分配单""" response = client.get( "/api/v1/allocations/?my_allocations=true", headers=auth_headers ) assert response.status_code == 200 def test_user_cannot_view_other_departments_allocations(self, client, auth_headers, test_allocation_order): """测试用户不能查看其他部门的分配单""" # 假设test_allocation_order属于其他部门 response = client.get( f"/api/v1/allocations/{test_allocation_order.id}", headers=auth_headers ) # 根据权限设计,可能返回403或404 assert response.status_code in [403, 404] def test_admin_can_view_all_allocations(self, client, admin_headers, test_allocation_order): """测试管理员可以查看所有分配单""" response = client.get( f"/api/v1/allocations/{test_allocation_order.id}", headers=admin_headers ) assert response.status_code == 200 def test_only_approver_can_approve_allocation(self, client, normal_user_headers, test_allocation_order): """测试只有审批人才能审批分配单""" response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/approve", json={"approval_comment": "尝试审批"}, headers=normal_user_headers ) assert response.status_code == 403 def test_user_can_create_allocation_for_own_org(self, client, auth_headers, test_assets_for_allocation, test_target_organization): """测试用户可以为自己的组织创建分配单""" response = client.post( "/api/v1/allocations/", json={ "target_org_id": test_target_organization.id, "asset_ids": [test_assets_for_allocation[0].id], }, headers=auth_headers ) assert response.status_code == 200 def test_user_cannot_create_allocation_for_other_org(self, client, auth_headers, test_assets_for_allocation, db: Session): """测试用户不能为其他组织创建分配单""" other_org = Organization( org_code="OTHER-001", org_name="其他组织", org_type="department" ) db.add(other_org) db.commit() db.refresh(other_org) response = client.post( "/api/v1/allocations/", json={ "target_org_id": other_org.id, "asset_ids": [test_assets_for_allocation[0].id], }, headers=auth_headers ) assert response.status_code == 403 def test_only_executor_can_execute_allocation(self, client, normal_user_headers, test_allocation_order, db: Session): """测试只有执行人员才能执行分配单""" test_allocation_order.status = AllocationStatus.APPROVED db.commit() response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/execute", json={}, headers=normal_user_headers ) assert response.status_code == 403 def test_permission_check_on_batch_operations(self, client, auth_headers): """测试批量操作时的权限检查""" response = client.post( "/api/v1/allocations/batch-approve", json={"allocation_ids": [1, 2, 3], "comment": "批量操作"}, headers=auth_headers ) # 可能返回403或部分成功 assert response.status_code in [200, 403] def test_role_based_access_control(self, client, auth_headers, admin_headers): """测试基于角色的访问控制""" # 普通用户访问管理接口 response = client.get( "/api/v1/allocations/admin/all-allocations", headers=auth_headers ) assert response.status_code == 403 # 管理员访问 response = client.get( "/api/v1/allocations/admin/all-allocations", headers=admin_headers ) assert response.status_code == 200 def test_permission_inheritance_from_org(self, client, auth_headers, db: Session, test_assets_for_allocation): """测试组织权限继承""" # 测试子组织用户是否可以访问父组织的分配单 pass # ================================ # 参数验证测试 (10+用例) # ================================ class TestAllocationValidation: """分配单参数验证测试""" def test_validate_required_fields(self, client, auth_headers): """测试必填字段验证""" response = client.post( "/api/v1/allocations/", json={}, headers=auth_headers ) assert response.status_code == 422 def test_validate_target_org_id_is_integer(self, client, auth_headers, test_assets_for_allocation): """测试目标组织ID必须为整数""" response = client.post( "/api/v1/allocations/", json={ "target_org_id": "invalid", "asset_ids": [test_assets_for_allocation[0].id], }, headers=auth_headers ) assert response.status_code == 422 def test_validate_expected_date_is_future(self, client, auth_headers, test_assets_for_allocation, test_target_organization): """测试预期日期必须是未来时间""" past_date = (datetime.now() - timedelta(days=1)).isoformat() response = client.post( "/api/v1/allocations/", json={ "target_org_id": test_target_organization.id, "asset_ids": [test_assets_for_allocation[0].id], "expected_date": past_date }, headers=auth_headers ) assert response.status_code == 400 def test_validate_asset_ids_list_length(self, client, auth_headers, test_assets_for_allocation, test_target_organization): """测试资产ID列表长度限制""" # 假设最多100个资产 asset_ids = [i for i in range(101)] response = client.post( "/api/v1/allocations/", json={ "target_org_id": test_target_organization.id, "asset_ids": asset_ids, }, headers=auth_headers ) assert response.status_code == 400 def test_validate_remark_max_length(self, client, auth_headers, test_assets_for_allocation, test_target_organization): """测试备注长度限制""" long_remark = "x" * 1001 response = client.post( "/api/v1/allocations/", json={ "target_org_id": test_target_organization.id, "asset_ids": [test_assets_for_allocation[0].id], "remark": long_remark }, headers=auth_headers ) assert response.status_code == 400 def test_validate_approval_comment_required(self, client, auth_headers, test_allocation_order): """测试审批备注必填""" response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/approve", json={}, headers=auth_headers ) assert response.status_code == 400 def test_validate_rejection_reason_required(self, client, auth_headers, test_allocation_order): """测试拒绝原因必填""" response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/reject", json={}, headers=auth_headers ) assert response.status_code == 400 def test_validate_completion_recipient_name(self, client, auth_headers, test_allocation_order, db: Session): """测试完成时接收人姓名必填""" test_allocation_order.status = AllocationStatus.IN_PROGRESS db.commit() response = client.post( f"/api/v1/allocations/{test_allocation_order.id}/complete", json={"completion_note": "完成"}, headers=auth_headers ) assert response.status_code == 400 def test_validate_date_format(self, client, auth_headers, test_assets_for_allocation, test_target_organization): """测试日期格式验证""" response = client.post( "/api/v1/allocations/", json={ "target_org_id": test_target_organization.id, "asset_ids": [test_assets_for_allocation[0].id], "expected_date": "invalid-date" }, headers=auth_headers ) assert response.status_code == 400 def test_validate_batch_operation_ids(self, client, auth_headers): """测试批量操作ID列表验证""" response = client.post( "/api/v1/allocations/batch-approve", json={"allocation_ids": [], "comment": "测试"}, headers=auth_headers ) assert response.status_code == 400 # ================================ # 异常处理测试 (5+用例) # ================================ class TestAllocationExceptionHandling: """分配单异常处理测试""" def test_handle_concurrent_approval(self, client, auth_headers, test_allocation_order): """测试并发审批处理""" # 模拟两个用户同时审批 pass def test_handle_asset_already_allocated(self, client, auth_headers, db: Session, test_assets_for_allocation, test_target_organization): """测试资产已被分配的情况""" asset = test_assets_for_allocation[0] asset.status = "in_use" db.commit() response = client.post( "/api/v1/allocations/", json={ "target_org_id": test_target_organization.id, "asset_ids": [asset.id], }, headers=auth_headers ) assert response.status_code == 400 def test_handle_database_connection_error(self, client, auth_headers, test_assets_for_allocation, test_target_organization): """测试数据库连接错误处理""" # 需要mock数据库连接 pass def test_handle_notification_failure(self, client, auth_headers, test_allocation_order): """测试通知发送失败时的处理""" # 需要mock通知服务 pass def test_handle_transaction_rollback_on_error(self, client, auth_headers, db: Session, test_assets_for_allocation, test_target_organization): """测试错误时事务回滚""" initial_count = db.query(Allocation).count() # 尝试创建无效的分配单 response = client.post( "/api/v1/allocations/", json={ "target_org_id": test_target_organization.id, "asset_ids": [999999], # 不存在的资产 }, headers=auth_headers ) # 验证没有创建新记录 final_count = db.query(Allocation).count() assert initial_count == final_count # ================================ # 测试标记 # ================================ @pytest.mark.unit class TestAllocationUnit: """单元测试标记""" def test_allocation_order_number_generation(self): """测试分配单号生成逻辑""" pass def test_allocation_status_validation(self): """测试状态验证逻辑""" pass @pytest.mark.integration class TestAllocationIntegration: """集成测试标记""" def test_full_allocation_workflow(self, client, auth_headers, test_assets_for_allocation, test_target_organization): """测试完整的分配流程""" # 1. 创建分配单 create_response = client.post( "/api/v1/allocations/", json={ "target_org_id": test_target_organization.id, "asset_ids": [test_assets_for_allocation[0].id], }, headers=auth_headers ) assert create_response.status_code == 200 allocation_id = create_response.json()["id"] # 2. 审批 approve_response = client.post( f"/api/v1/allocations/{allocation_id}/approve", json={"approval_comment": "审批通过"}, headers=auth_headers ) assert approve_response.status_code == 200 # 3. 执行 execute_response = client.post( f"/api/v1/allocations/{allocation_id}/execute", json={}, headers=auth_headers ) assert execute_response.status_code == 200 # 4. 完成 complete_response = client.post( f"/api/v1/allocations/{allocation_id}/complete", json={"completion_note": "完成", "recipient_name": "测试"}, headers=auth_headers ) assert complete_response.status_code == 200 @pytest.mark.slow class TestAllocationSlowTests: """慢速测试标记""" def test_large_batch_allocation(self, client, auth_headers, db: Session, test_target_organization): """测试大批量分配""" # 创建大量资产 pass @pytest.mark.smoke class TestAllocationSmoke: """冒烟测试标记 - 核心功能快速验证""" def test_create_and_approve_allocation(self, client, auth_headers, test_assets_for_allocation, test_target_organization): """冒烟测试: 创建并审批分配单""" create_response = client.post( "/api/v1/allocations/", json={ "target_org_id": test_target_organization.id, "asset_ids": [test_assets_for_allocation[0].id], }, headers=auth_headers ) assert create_response.status_code == 200 allocation_id = create_response.json()["id"] approve_response = client.post( f"/api/v1/allocations/{allocation_id}/approve", json={"approval_comment": "冒烟测试"}, headers=auth_headers ) assert approve_response.status_code == 200