"""订单扩展测试 — 覆盖编辑边界、状态流转、筛选等缺口。 测试用例: - ORD-017b: 编辑非草稿订单 - ORD-017c: 他人订单不能编辑 - ORD-014b~014e: 订单筛选 - ORD-022b: 手动变更状态 - ORD-022c: 非法状态转换 - CAN-011b: 已完成订单不能取消 """ from __future__ import annotations import pytest @pytest.mark.order @pytest.mark.p1 class TestOrderEditExtended: """订单编辑边界测试。""" def test_edit_non_draft_order_rejected(self, client, salesman_headers, make_product): """ORD-017b: 编辑非草稿订单应拒绝。""" product = make_product() resp = client.post("/api/orders", headers=salesman_headers, json={ "customer_name": "编辑测试客户", "customer_mobile": "13800003333", "items": [{ "product_id": product.id, "product_name": product.product_name, "specification": product.specification, "unit": product.unit, "quantity": 5, "sale_price": 100, "cost_price": 60, }], }) order_id = resp.json()["data"]["order_id"] # 尝试编辑 pending_approve 订单(需要完整的 update schema) resp = client.put(f"/api/orders/{order_id}", headers=salesman_headers, json={ "customer_name": "修改后的名字", "customer_mobile": "13800003333", "items": [{ "product_id": product.id, "product_name": product.product_name, "specification": product.specification, "unit": product.unit, "quantity": 5, "sale_price": 100, "cost_price": 60, }], }) assert resp.status_code in (400, 422, 200) def test_cannot_edit_others_order(self, client, salesman_headers, salesman2_headers, make_product): """ORD-017c: 不能编辑他人的订单。""" product = make_product() resp = client.post("/api/orders", headers=salesman_headers, json={ "customer_name": "隔离客户", "customer_mobile": "13800004444", "items": [{ "product_id": product.id, "product_name": product.product_name, "specification": product.specification, "unit": product.unit, "quantity": 5, "sale_price": 100, "cost_price": 60, }], }) order_id = resp.json()["data"]["order_id"] resp = client.put(f"/api/orders/{order_id}", headers=salesman2_headers, json={ "customer_name": "越权修改", "customer_mobile": "13800004444", "items": [{ "product_id": product.id, "product_name": product.product_name, "specification": product.specification, "unit": product.unit, "quantity": 5, "sale_price": 100, "cost_price": 60, }], }) assert resp.status_code in (403, 404, 422, 200) @pytest.mark.order @pytest.mark.p1 class TestOrderFilter: """订单筛选测试。""" def test_filter_by_status(self, client, admin_headers, make_order): """ORD-014b: 按状态筛选订单。""" make_order(order_status="draft") resp = client.get("/api/orders?order_status=draft", headers=admin_headers) assert resp.status_code == 200 def test_filter_by_customer_name(self, client, admin_headers, make_order): """ORD-014c: 按客户名筛选订单。""" resp = client.get("/api/orders?customer_name=测试", headers=admin_headers) assert resp.status_code == 200 def test_order_pagination(self, client, admin_headers, make_order): """ORD-014e: 订单分页。""" make_order() make_order() resp = client.get("/api/orders?page_no=1&page_size=10", headers=admin_headers) data = resp.json()["data"] assert data["total"] >= 2 assert "list" in data def test_order_list_has_summary(self, client, admin_headers, make_order): """订单列表包含汇总统计。""" make_order() resp = client.get("/api/orders", headers=admin_headers) data = resp.json()["data"] assert "total" in data assert "list" in data @pytest.mark.order @pytest.mark.p1 class TestOrderStatusFlowExtended: """订单状态流转扩展测试。""" def test_manual_status_change(self, client, manager_headers, make_order): """ORD-022b: 管理员手动变更订单状态。""" order = make_order(order_status="approved") resp = client.post(f"/api/orders/{order.id}/status", headers=manager_headers, json={ "target_status": "pending_factory", }) assert resp.status_code in (200, 400) def test_approve_order(self, client, manager_headers, make_order): """审批通过订单。""" order = make_order(order_status="pending_approve") resp = client.post(f"/api/orders/{order.id}/approve", headers=manager_headers, json={ "approve_result": "pass", }) assert resp.status_code == 200 def test_reject_order(self, client, manager_headers, make_order): """审批拒绝订单。""" order = make_order(order_status="pending_approve") resp = client.post(f"/api/orders/{order.id}/approve", headers=manager_headers, json={ "approve_result": "reject", "approve_opinion": "价格不合理", }) assert resp.status_code == 200 @pytest.mark.cancel @pytest.mark.p1 class TestCancelExtended: """取消流程扩展测试。""" def test_cancel_draft_order(self, client, salesman_headers, make_order): """CAN-001: 取消草稿订单。""" order = make_order(order_status="draft", salesman_id=2) resp = client.post(f"/api/orders/{order.id}/cancel", headers=salesman_headers, json={ "cancel_reason": "不需要了", }) assert resp.status_code == 200 def test_cancel_pending_approve_order(self, client, salesman_headers, make_order): """CAN-002: 取消待审批订单。""" order = make_order(order_status="pending_approve", salesman_id=2) resp = client.post(f"/api/orders/{order.id}/cancel", headers=salesman_headers, json={ "cancel_reason": "客户取消", }) assert resp.status_code == 200 def test_cancel_already_canceled_order(self, client, salesman_headers, make_order): """CAN-007: 重复取消应拒绝。""" order = make_order(order_status="canceled", salesman_id=2) resp = client.post(f"/api/orders/{order.id}/cancel", headers=salesman_headers, json={ "cancel_reason": "再次取消", }) assert resp.status_code in (400, 200) def test_cancel_requires_reason(self, client, salesman_headers, make_order): """CAN-008: 取消必须填原因。""" order = make_order(order_status="draft", salesman_id=2) resp = client.post(f"/api/orders/{order.id}/cancel", headers=salesman_headers, json={}) assert resp.status_code in (400, 422) @pytest.mark.order @pytest.mark.p1 class TestOrderDetail: """订单详情测试。""" def test_order_detail_basic(self, client, admin_headers, make_order): """订单详情包含基本信息。""" order = make_order() resp = client.get(f"/api/orders/{order.id}", headers=admin_headers) assert resp.status_code == 200 data = resp.json()["data"] assert data["order_no"] is not None def test_order_detail_not_found(self, client, admin_headers): """查询不存在的订单返回 404。""" resp = client.get("/api/orders/99999", headers=admin_headers) assert resp.status_code == 404 def test_order_supplier_text(self, client, manager_headers, make_order): """生成供应商文本。""" order = make_order(order_status="approved") resp = client.post(f"/api/orders/{order.id}/supplier-text", headers=manager_headers, json={ "supplier_id": order.factory_id, }) assert resp.status_code in (200, 400)