297 lines
8.8 KiB
Python
297 lines
8.8 KiB
Python
from fastapi.testclient import TestClient
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models.attachment import AttachmentCategory
|
|
|
|
|
|
def _seed_categories(db: Session) -> list[int]:
|
|
cats = [
|
|
AttachmentCategory(name="TEST_CERT", sort_order=1, is_active=True),
|
|
AttachmentCategory(name="TEST_EXTRA", sort_order=2, is_active=True),
|
|
AttachmentCategory(name="TEST_OFF", sort_order=3, is_active=False),
|
|
]
|
|
for c in cats:
|
|
db.add(c)
|
|
db.commit()
|
|
for c in cats:
|
|
db.refresh(c)
|
|
return [c.id for c in cats]
|
|
|
|
|
|
def test_categories_lists_active_only(client: TestClient, db: Session):
|
|
_seed_categories(db)
|
|
resp = client.get("/CargoTrace/attachment/categories")
|
|
assert resp.status_code == 200
|
|
names = [c["name"] for c in resp.json()]
|
|
assert "TEST_CERT" in names
|
|
assert "TEST_EXTRA" in names
|
|
assert "TEST_OFF" not in names
|
|
# sorted by sort_order then name
|
|
assert names.index("TEST_CERT") < names.index("TEST_EXTRA")
|
|
|
|
|
|
def test_status_unknown_zongpai_is_undetermined(client: TestClient):
|
|
resp = client.get(
|
|
"/CargoTrace/attachment/status", params={"zongpai_no": "26BW0011"}
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["zongpai_no"] == "26BW0011"
|
|
assert data["determination"] == "undetermined"
|
|
assert data["items"] == []
|
|
assert data["all_complete"] is False
|
|
|
|
|
|
def test_status_invalid_zongpai(client: TestClient):
|
|
resp = client.get(
|
|
"/CargoTrace/attachment/status", params={"zongpai_no": "BAD"}
|
|
)
|
|
assert resp.status_code == 400
|
|
assert resp.json()["error_code"] == "INVALID_ZONGPAI"
|
|
|
|
|
|
def test_status_reflects_config_and_boxed(client: TestClient, db: Session):
|
|
cat_ids = _seed_categories(db)
|
|
cert_id = cat_ids[0]
|
|
# Plan: 检验证书 expected=80
|
|
client.put(
|
|
"/CargoTrace/attachment/config",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"determination": "has",
|
|
"items": [{"category_id": cert_id, "expected_qty": 80}],
|
|
},
|
|
)
|
|
# Box 30 of them
|
|
client.post(
|
|
"/CargoTrace/attachment/box",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"category_id": cert_id,
|
|
"box_no": 921,
|
|
"quantity": 30,
|
|
},
|
|
)
|
|
resp = client.get(
|
|
"/CargoTrace/attachment/status", params={"zongpai_no": "26BW0011"}
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["determination"] == "has"
|
|
assert data["all_complete"] is False
|
|
item = data["items"][0]
|
|
assert item["category_id"] == cert_id
|
|
assert item["expected_qty"] == 80
|
|
assert item["boxed_qty"] == 30
|
|
assert item["complete"] is False
|
|
|
|
|
|
def test_config_sets_none(client: TestClient):
|
|
resp = client.put(
|
|
"/CargoTrace/attachment/config",
|
|
json={"zongpai_no": "26BW0011", "determination": "none", "items": []},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert resp.json()["determination"] == "none"
|
|
assert resp.json()["items"] == []
|
|
|
|
|
|
def test_config_creates_and_updates_items(client: TestClient, db: Session):
|
|
cert_id, extra_id, _ = _seed_categories(db)
|
|
client.put(
|
|
"/CargoTrace/attachment/config",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"determination": "has",
|
|
"items": [
|
|
{"category_id": cert_id, "expected_qty": 80},
|
|
{"category_id": extra_id, "expected_qty": 5},
|
|
],
|
|
},
|
|
)
|
|
# Update: change extra expected, drop nothing
|
|
resp = client.put(
|
|
"/CargoTrace/attachment/config",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"determination": "has",
|
|
"items": [
|
|
{"category_id": cert_id, "expected_qty": 80},
|
|
{"category_id": extra_id, "expected_qty": 8},
|
|
],
|
|
},
|
|
)
|
|
assert resp.status_code == 200
|
|
extra = next(i for i in resp.json()["items"] if i["category_id"] == extra_id)
|
|
assert extra["expected_qty"] == 8
|
|
|
|
|
|
def test_config_rejects_removing_type_with_boxes(client: TestClient, db: Session):
|
|
cert_id, _, _ = _seed_categories(db)
|
|
client.put(
|
|
"/CargoTrace/attachment/config",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"determination": "has",
|
|
"items": [{"category_id": cert_id, "expected_qty": 80}],
|
|
},
|
|
)
|
|
client.post(
|
|
"/CargoTrace/attachment/box",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"category_id": cert_id,
|
|
"box_no": 922,
|
|
"quantity": 10,
|
|
},
|
|
)
|
|
# Try to drop the type that now has boxes
|
|
resp = client.put(
|
|
"/CargoTrace/attachment/config",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"determination": "has",
|
|
"items": [],
|
|
},
|
|
)
|
|
assert resp.status_code == 409
|
|
assert resp.json()["error_code"] == "CATEGORY_HAS_BOXES"
|
|
|
|
|
|
def test_config_allows_removing_type_with_zero_boxes(client: TestClient, db: Session):
|
|
cert_id, extra_id, _ = _seed_categories(db)
|
|
client.put(
|
|
"/CargoTrace/attachment/config",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"determination": "has",
|
|
"items": [
|
|
{"category_id": cert_id, "expected_qty": 80},
|
|
{"category_id": extra_id, "expected_qty": 5},
|
|
],
|
|
},
|
|
)
|
|
resp = client.put(
|
|
"/CargoTrace/attachment/config",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"determination": "has",
|
|
"items": [{"category_id": cert_id, "expected_qty": 80}],
|
|
},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert {i["category_id"] for i in resp.json()["items"]} == {cert_id}
|
|
|
|
|
|
def test_config_rejects_unknown_category(client: TestClient):
|
|
resp = client.put(
|
|
"/CargoTrace/attachment/config",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"determination": "has",
|
|
"items": [{"category_id": 9999999, "expected_qty": 1}],
|
|
},
|
|
)
|
|
assert resp.status_code == 404
|
|
assert resp.json()["error_code"] == "CATEGORY_NOT_FOUND"
|
|
|
|
|
|
def _config_one_type(client: TestClient, cert_id: int):
|
|
client.put(
|
|
"/CargoTrace/attachment/config",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"determination": "has",
|
|
"items": [{"category_id": cert_id, "expected_qty": 80}],
|
|
},
|
|
)
|
|
|
|
|
|
def test_attachment_location_create_and_duplicate(client: TestClient, db: Session):
|
|
cert_id = _seed_categories(db)[0]
|
|
_config_one_type(client, cert_id)
|
|
|
|
created = client.post(
|
|
"/CargoTrace/attachment/location",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"category_id": cert_id,
|
|
"location_code": "A01-01-01",
|
|
},
|
|
)
|
|
assert created.status_code == 200
|
|
assert created.json()["location_code"] == "A01-01-01"
|
|
assert created.json()["previous_location"] is None
|
|
|
|
dup = client.post(
|
|
"/CargoTrace/attachment/location",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"category_id": cert_id,
|
|
"location_code": "A02-02-02",
|
|
},
|
|
)
|
|
assert dup.status_code == 409
|
|
assert dup.json()["error_code"] == "DUPLICATE_LOCATION"
|
|
|
|
|
|
def test_attachment_location_normal_to_temp_then_transit(client: TestClient, db: Session):
|
|
cert_id = _seed_categories(db)[0]
|
|
_config_one_type(client, cert_id)
|
|
|
|
client.post(
|
|
"/CargoTrace/attachment/location",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"category_id": cert_id,
|
|
"location_code": "A01-01-01",
|
|
},
|
|
)
|
|
# normal -> temp storage (B prefix) allowed
|
|
to_temp = client.post(
|
|
"/CargoTrace/attachment/location",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"category_id": cert_id,
|
|
"location_code": "B01-01-01",
|
|
},
|
|
)
|
|
assert to_temp.status_code == 200
|
|
assert to_temp.json()["previous_location"] == "A01-01-01"
|
|
|
|
# -> transit (terminal)
|
|
to_transit = client.post(
|
|
"/CargoTrace/attachment/location",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"category_id": cert_id,
|
|
"location_code": "TRANS-001",
|
|
},
|
|
)
|
|
assert to_transit.status_code == 200
|
|
|
|
# already transit -> any op blocked
|
|
again = client.post(
|
|
"/CargoTrace/attachment/location",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"category_id": cert_id,
|
|
"location_code": "A03-03-03",
|
|
},
|
|
)
|
|
assert again.status_code == 409
|
|
assert again.json()["error_code"] == "ALREADY_OFF_SHELF"
|
|
|
|
|
|
def test_attachment_location_rejects_unknown_category(client: TestClient):
|
|
resp = client.post(
|
|
"/CargoTrace/attachment/location",
|
|
json={
|
|
"zongpai_no": "26BW0011",
|
|
"category_id": 9999999,
|
|
"location_code": "A01-01-01",
|
|
},
|
|
)
|
|
assert resp.status_code == 404
|
|
assert resp.json()["error_code"] == "CATEGORY_NOT_FOUND"
|