from fastapi.testclient import TestClient # --- GET /box/info 测试 --- def test_box_info_success(client: TestClient): """查询真实存在的总排号 → 200""" resp = client.get( "/CargoTrace/box/info", params={"zongpai_no": "26BW0011"} ) assert resp.status_code == 200 data = resp.json() assert data["zongpai_no"] == "26BW0011" assert data["paichan_no"] == "W00009" assert "work_order_no" in data assert data["work_order_no"] is None or isinstance(data["work_order_no"], str) assert data["quantity"] == 80 assert "current_zongpai_boxes" in data assert "existing_boxes" in data for box in data["existing_boxes"]: for item in box["items"]: assert "box_item_id" in item assert "max_box_no" in data assert data["suggested_box_no"] == data["max_box_no"] + 1 def test_box_info_invalid_zongpai(client: TestClient): """无效总排号格式 → 400""" resp = client.get( "/CargoTrace/box/info", params={"zongpai_no": "INVALID"} ) assert resp.status_code == 400 assert resp.json()["error_code"] == "INVALID_ZONGPAI" def test_box_info_not_found(client: TestClient): """格式合法但 ERP 中不存在 → 404""" resp = client.get( "/CargoTrace/box/info", params={"zongpai_no": "26B99999"} ) assert resp.status_code == 404 assert resp.json()["error_code"] == "ZONGPAI_NOT_FOUND" # --- POST /box 测试 --- def test_box_save_invalid_zongpai(client: TestClient): """无效总排号 → 400""" resp = client.post( "/CargoTrace/box", json={"zongpai_no": "INVALID", "box_no": 1, "quantity": 10}, ) assert resp.status_code == 400 assert resp.json()["error_code"] == "INVALID_ZONGPAI" def test_box_save_invalid_box_no(client: TestClient): """箱号 <= 0 → 400""" resp = client.post( "/CargoTrace/box", json={"zongpai_no": "26BW0011", "box_no": 0, "quantity": 10}, ) assert resp.status_code == 400 def test_box_save_invalid_quantity(client: TestClient): """数量 <= 0 → 400""" resp = client.post( "/CargoTrace/box", json={"zongpai_no": "26BW0011", "box_no": 1, "quantity": 0}, ) assert resp.status_code == 400 def test_box_save_zongpai_not_found(client: TestClient): """ERP 中不存在的总排号 → 404""" resp = client.post( "/CargoTrace/box", json={"zongpai_no": "26B99999", "box_no": 1, "quantity": 10}, ) assert resp.status_code == 404 assert resp.json()["error_code"] == "ZONGPAI_NOT_FOUND" def test_box_save_multi_code_appends_different_zongpais(client: TestClient): """多码凑箱:同一排产号同一箱号可追加不同总排号。""" box_no = 901 first = client.post( "/CargoTrace/box", json={ "zongpai_no": "26BW0011", "box_no": box_no, "quantity": 10, }, ) assert first.status_code == 200 assert first.json()["box_item_id"] is not None second = client.post( "/CargoTrace/box", json={ "zongpai_no": "26BW0012", "box_no": box_no, "quantity": 8, }, ) assert second.status_code == 200 assert second.json()["box_no"] == box_no assert second.json()["zongpai_no"] == "26BW0012" info = client.get("/CargoTrace/box/info", params={"zongpai_no": "26BW0011"}) assert info.status_code == 200 target_box = next( box for box in info.json()["existing_boxes"] if box["box_no"] == box_no ) assert {item["zongpai_no"] for item in target_box["items"]} == { "26BW0011", "26BW0012", } for item in target_box["items"]: assert "work_order_no" in item assert isinstance(item["total_quantity"], int) def test_box_save_multi_code_duplicate_same_zongpai(client: TestClient): """多码凑箱:同箱同总排重复录入返回 DUPLICATE_BOX_NO。""" box_no = 902 first = client.post( "/CargoTrace/box", json={ "zongpai_no": "26BW0011", "box_no": box_no, "quantity": 10, }, ) assert first.status_code == 200 duplicate = client.post( "/CargoTrace/box", json={ "zongpai_no": "26BW0011", "box_no": box_no, "quantity": 5, }, ) assert duplicate.status_code == 409 assert duplicate.json()["error_code"] == "DUPLICATE_BOX_NO" def test_box_save_split_same_zongpai_up_to_total_quantity(client: TestClient): """单码装箱:同一总排号可分箱提交,累计等于 ERP 数量时成功。""" first = client.post( "/CargoTrace/box", json={"zongpai_no": "26BW0011", "box_no": 905, "quantity": 30}, ) assert first.status_code == 200 second = client.post( "/CargoTrace/box", json={"zongpai_no": "26BW0011", "box_no": 906, "quantity": 50}, ) assert second.status_code == 200 info = client.get("/CargoTrace/box/info", params={"zongpai_no": "26BW0011"}) assert info.status_code == 200 current_boxes = [ item for item in info.json()["current_zongpai_boxes"] if item["box_no"] in (905, 906) ] assert sum(item["quantity"] for item in current_boxes) == 80 def test_box_save_rejects_quantity_over_remaining(client: TestClient): """保存后累计数量超过 ERP 总数量时返回 INVALID_QUANTITY。""" created = client.post( "/CargoTrace/box", json={"zongpai_no": "26BW0011", "box_no": 907, "quantity": 70}, ) assert created.status_code == 200 over = client.post( "/CargoTrace/box", json={"zongpai_no": "26BW0011", "box_no": 908, "quantity": 11}, ) assert over.status_code == 400 assert over.json()["error_code"] == "INVALID_QUANTITY" def test_box_update_same_box_no_does_not_conflict(client: TestClient): """PATCH 原箱号未变时,应排除当前 box_item_id,避免误判重复。""" box_no = 903 created = client.post( "/CargoTrace/box", json={ "zongpai_no": "26BW0011", "box_no": box_no, "quantity": 10, }, ) assert created.status_code == 200 box_item_id = created.json()["box_item_id"] updated = client.patch( f"/CargoTrace/box/{box_item_id}", json={"box_no": box_no, "quantity": 7}, ) assert updated.status_code == 200 assert updated.json()["quantity"] == 7 def test_box_update_rejects_quantity_over_remaining(client: TestClient): """PATCH 调整数量导致累计超过 ERP 总数量时返回 INVALID_QUANTITY。""" first = client.post( "/CargoTrace/box", json={"zongpai_no": "26BW0011", "box_no": 909, "quantity": 70}, ) assert first.status_code == 200 second = client.post( "/CargoTrace/box", json={"zongpai_no": "26BW0011", "box_no": 910, "quantity": 5}, ) assert second.status_code == 200 box_item_id = second.json()["box_item_id"] over = client.patch( f"/CargoTrace/box/{box_item_id}", json={"box_no": 910, "quantity": 11}, ) assert over.status_code == 400 assert over.json()["error_code"] == "INVALID_QUANTITY" def test_box_delete_removes_empty_box(client: TestClient): """DELETE 删除最后一条明细后,同步清理空箱号。""" box_no = 904 created = client.post( "/CargoTrace/box", json={ "zongpai_no": "26BW0011", "box_no": box_no, "quantity": 10, }, ) assert created.status_code == 200 box_item_id = created.json()["box_item_id"] deleted = client.delete(f"/CargoTrace/box/{box_item_id}") assert deleted.status_code == 200 assert deleted.json() == {"box_item_id": box_item_id, "deleted": True} recreated = client.post( "/CargoTrace/box", json={ "zongpai_no": "26BW0012", "box_no": box_no, "quantity": 8, }, ) assert recreated.status_code == 200