dingdanquanliucheng/backend/tests/test_logistics.py
2026-06-19 23:03:14 +08:00

316 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""TASK-001 ~ TASK-014: 司机任务管理与履约测试。
覆盖功能点:
- 管理层创建司机任务
- 司机任务列表(数据隔离)
- 任务详情脱敏
- 司机接单、揽货、送达
- 状态流转验证(合法/非法)
- 司机越权操作
"""
from __future__ import annotations
import pytest
@pytest.mark.logistics
@pytest.mark.p0
class TestTaskCreation:
"""TASK-001: 管理层创建司机任务。"""
def test_create_task_success(self, client, manager_headers, make_order):
"""TASK-001: 管理层创建任务成功。
实际接口: POST /api/logistics/tasks
权限: require_roles("manager", "admin") + require_permissions("logistics:task:create")
"""
order = make_order(order_status="approved")
resp = client.post("/api/logistics/tasks", headers=manager_headers, json={
"order_id": order.id,
"driver_id": 5,
"pickup_address": "泰兴工厂",
"delivery_address": "上海仓库",
"pickup_content": "测试货物",
"quantity": 10,
"factory_id": order.factory_id,
})
assert resp.status_code == 200
data = resp.json()
assert data["code"] == 0
def test_create_task_invalid_order(self, client, manager_headers, make_order):
"""非 approved 状态订单不能创建任务。"""
order = make_order(order_status="draft")
resp = client.post("/api/logistics/tasks", headers=manager_headers, json={
"order_id": order.id,
"driver_id": 5,
"pickup_address": "泰兴工厂",
"delivery_address": "上海仓库",
"pickup_content": "测试货物",
"quantity": 10,
})
assert resp.status_code in (400, 409)
@pytest.mark.logistics
@pytest.mark.p0
class TestTaskList:
"""TASK-002: 司机任务列表测试。"""
def test_driver_only_see_own_tasks(self, client, driver_headers, make_task):
"""TASK-002: 司机只能看到自己的任务。"""
task = make_task(driver_id=5)
resp = client.get("/api/driver/tasks", headers=driver_headers)
assert resp.status_code == 200
data = resp.json()
# 所有返回的任务都应该是当前司机的
for t in data["data"]["list"]:
assert t["driver_id"] == 5
def test_driver_cannot_see_other_tasks(self, client, driver_headers, driver2_headers, make_task):
"""TASK-010: 司机不能看到其他司机的任务。"""
# 创建分配给 driver02 的任务
task = make_task(driver_id=6)
# driver01 查看任务列表
resp = client.get("/api/driver/tasks", headers=driver_headers)
data = resp.json()
task_ids = [t["task_id"] for t in data["data"]["list"]]
assert task.id not in task_ids
@pytest.mark.logistics
@pytest.mark.p0
class TestTaskDetail:
"""TASK-003: 任务详情脱敏测试。"""
def test_driver_task_detail_no_financial(self, client, driver_headers, make_task):
"""TASK-003: 司机任务详情不显示财务字段。"""
task = make_task(driver_id=5)
resp = client.get(f"/api/driver/tasks/{task.id}", headers=driver_headers)
assert resp.status_code == 200
data = resp.json()["data"]
# 司机不应看到客户敏感信息和财务字段
assert "customer_mobile" not in data or data.get("customer_mobile") is None
assert "profit_total" not in data or data.get("profit_total") is None
def test_driver_cannot_view_other_task_detail(self, client, driver_headers, driver2_headers, make_task):
"""TASK-010: 司机不能查看其他司机的任务详情。"""
task = make_task(driver_id=6)
resp = client.get(f"/api/driver/tasks/{task.id}", headers=driver_headers)
assert resp.status_code in (403, 404)
@pytest.mark.logistics
@pytest.mark.p0
class TestTaskAccept:
"""TASK-004 ~ TASK-005: 司机接单测试。"""
def test_accept_task_success(self, client, driver_headers, make_task):
"""TASK-004: 司机接单成功。
实际接口: POST /api/driver/tasks/{task_id}/accept
请求体: DriverTaskOperateRequest (photo_files, video_files, remark)
"""
task = make_task(status="pending", driver_id=5)
resp = client.post(f"/api/driver/tasks/{task.id}/accept", headers=driver_headers, json={
"photo_files": [],
"video_files": [],
})
assert resp.status_code == 200
data = resp.json()
assert data["code"] == 0
def test_double_accept(self, client, driver_headers, make_task):
"""TASK-005: 重复接单应返回错误。"""
task = make_task(status="accepted", driver_id=5)
resp = client.post(f"/api/driver/tasks/{task.id}/accept", headers=driver_headers, json={
"photo_files": [],
"video_files": [],
})
assert resp.status_code in (400, 409)
def test_accept_other_driver_task(self, client, driver_headers, driver2_headers, make_task):
"""TASK-010: 司机不能接其他司机的任务。"""
task = make_task(status="pending", driver_id=6)
resp = client.post(f"/api/driver/tasks/{task.id}/accept", headers=driver_headers, json={
"photo_files": [],
"video_files": [],
})
assert resp.status_code in (403, 404)
@pytest.mark.logistics
@pytest.mark.p0
class TestTaskPickup:
"""TASK-006 ~ TASK-007: 司机揽货测试。"""
def test_pickup_success(self, client, driver_headers, make_task):
"""TASK-006: 司机揽货成功(含照片)。
实际接口: POST /api/driver/tasks/{task_id}/pickup
请求体: DriverTaskOperateRequest
注意: 揽货前需要先提交运单号waybill
"""
task = make_task(status="accepted", driver_id=5)
# 先提交运单号
client.post(f"/api/driver/tasks/{task.id}/waybills", headers=driver_headers, json={
"waybills": [{
"tracking_number": "SF1234567890",
"express_company": "shunfeng",
"express_name": "顺丰速运",
}],
})
# 再揽货
resp = client.post(f"/api/driver/tasks/{task.id}/pickup", headers=driver_headers, json={
"photo_files": [{"file_url": "https://oss.example.com/pickup.jpg", "file_name": "pickup.jpg"}],
"video_files": [],
})
assert resp.status_code == 200
data = resp.json()
assert data["code"] == 0
def test_pickup_without_photo(self, client, driver_headers, make_task):
"""TASK-007: 揽货无照片应返回错误。"""
task = make_task(status="accepted", driver_id=5)
resp = client.post(f"/api/driver/tasks/{task.id}/pickup", headers=driver_headers, json={
"photo_files": [],
"video_files": [],
})
# 无照片揽货可能被拒绝或允许(取决于业务规则)
assert resp.status_code in (200, 400, 422)
def test_pickup_from_wrong_status(self, client, driver_headers, make_task):
"""TASK-009: pending 状态不能直接揽货。"""
task = make_task(status="pending", driver_id=5)
resp = client.post(f"/api/driver/tasks/{task.id}/pickup", headers=driver_headers, json={
"photo_files": [{"file_url": "https://oss.example.com/pickup.jpg", "file_name": "pickup.jpg"}],
"video_files": [],
})
assert resp.status_code in (400, 409)
@pytest.mark.logistics
@pytest.mark.p0
class TestTaskDeliver:
"""TASK-008: 司机送达测试。"""
def test_deliver_success(self, client, driver_headers, make_task):
"""TASK-008: 司机确认送达成功。
实际接口: POST /api/driver/tasks/{task_id}/deliver
请求体: DriverTaskOperateRequest
注意: 送达前需要先揽货,揽货前需要先提交运单号
"""
task = make_task(status="accepted", driver_id=5)
# 先提交运单号
client.post(f"/api/driver/tasks/{task.id}/waybills", headers=driver_headers, json={
"waybills": [{
"tracking_number": "SF1234567890",
"express_company": "shunfeng",
"express_name": "顺丰速运",
}],
})
# 揽货
client.post(f"/api/driver/tasks/{task.id}/pickup", headers=driver_headers, json={
"photo_files": [{"file_url": "https://oss.example.com/pickup.jpg", "file_name": "pickup.jpg"}],
"video_files": [],
})
# 送达
resp = client.post(f"/api/driver/tasks/{task.id}/deliver", headers=driver_headers, json={
"photo_files": [],
"video_files": [],
})
assert resp.status_code == 200
data = resp.json()
assert data["code"] == 0
def test_deliver_from_wrong_status(self, client, driver_headers, make_task):
"""TASK-009: pending 状态不能直接送达。"""
task = make_task(status="pending", driver_id=5)
resp = client.post(f"/api/driver/tasks/{task.id}/deliver", headers=driver_headers, json={
"photo_files": [],
"video_files": [],
})
assert resp.status_code in (400, 409)
@pytest.mark.logistics
@pytest.mark.p0
class TestTaskStatusFlow:
"""TASK-012: 任务状态流转完整性测试。"""
def test_full_task_flow(self, client, driver_headers, make_task):
"""完整状态流转: pending → accepted → picked_up → delivered
注意: 揽货前需要先提交运单号
"""
# 1. 创建任务
task = make_task(status="pending", driver_id=5)
assert task.status == "pending"
# 2. 接单
accept_resp = client.post(f"/api/driver/tasks/{task.id}/accept", headers=driver_headers, json={
"photo_files": [],
"video_files": [],
})
assert accept_resp.status_code == 200
# 3. 提交运单号
client.post(f"/api/driver/tasks/{task.id}/waybills", headers=driver_headers, json={
"waybills": [{
"tracking_number": "SF1234567890",
"express_company": "shunfeng",
"express_name": "顺丰速运",
}],
})
# 4. 揽货
pickup_resp = client.post(f"/api/driver/tasks/{task.id}/pickup", headers=driver_headers, json={
"photo_files": [{"file_url": "https://oss.example.com/pickup.jpg", "file_name": "pickup.jpg"}],
"video_files": [],
})
assert pickup_resp.status_code == 200
# 5. 送达
deliver_resp = client.post(f"/api/driver/tasks/{task.id}/deliver", headers=driver_headers, json={
"photo_files": [],
"video_files": [],
})
assert deliver_resp.status_code == 200
@pytest.mark.logistics
@pytest.mark.p0
class TestTaskOrderSync:
"""TASK-012: 任务与订单状态联动测试。"""
def test_task_creation_updates_order(self, client, manager_headers, make_order):
"""创建任务后订单状态应更新。"""
order = make_order(order_status="approved")
# 创建任务
task_resp = client.post("/api/logistics/tasks", headers=manager_headers, json={
"order_id": order.id,
"driver_id": 5,
"pickup_address": "泰兴工厂",
"delivery_address": "上海仓库",
"pickup_content": "测试货物",
"quantity": 10,
"factory_id": order.factory_id,
})
assert task_resp.status_code == 200
# 检查订单状态是否更新
order_resp = client.get(f"/api/orders/{order.id}", headers=manager_headers)
order_status = order_resp.json()["data"]["order_status"]
# 订单状态应该更新为履约相关状态
assert order_status in ("pending_driver", "pending_driver_accept", "accepted", "in_progress", "pending_factory")