"""dispatch 모듈 박스 묶기/SKU 합산 순수 로직 테스트. DB/엑셀 없이 store.py 의 규칙만 검증한다. python -m app.modules.dispatch.tests.test_grouping 또는 pytest 로 실행 가능. """ from __future__ import annotations from app.modules.dispatch import store def test_package_id_priority(): """묶음키 우선순위: package_id > tracking_id > order_id.""" rows = [ {"order_id": "O1", "package_id": "P1", "tracking_id": "T1", "seller_sku": "MT-0320", "quantity": "1"}, {"order_id": "O1", "package_id": "P1", "tracking_id": "T1", "seller_sku": "MT-0320", "quantity": "1"}, {"order_id": "O1", "package_id": "P2", "tracking_id": "T2", "seller_sku": "MY-0001", "quantity": "1"}, ] parcels = store.group_parcels(rows) assert len(parcels) == 2, parcels # P1, P2 → 박스 2개 p1 = next(p for p in parcels if p["package_id"] == "P1") # 같은 박스 같은 SKU 수량 합산 → 2 assert p1["items"][0]["quantity"] == 2, p1 def test_tracking_then_order_fallback(): """package_id 없으면 tracking_id, 그것도 없으면 order_id 로 묶는다.""" rows = [ {"order_id": "O9", "package_id": "", "tracking_id": "TRK", "seller_sku": "MT-0550", "quantity": ""}, {"order_id": "O9", "package_id": "", "tracking_id": "TRK", "seller_sku": "MX-0001", "quantity": "3"}, {"order_id": "O8", "package_id": "", "tracking_id": "", "seller_sku": "MT-0750", "quantity": "2"}, ] parcels = store.group_parcels(rows) assert len(parcels) == 2, parcels trk = next(p for p in parcels if p["tracking_id"] == "TRK") assert len(trk["items"]) == 2 # 다른 SKU 2종 # quantity 빈칸 → 1 로 보정 qty_by_sku = {it["seller_sku"]: it["quantity"] for it in trk["items"]} assert qty_by_sku["MT-0550"] == 1 assert qty_by_sku["MX-0001"] == 3 def test_quantity_and_tracking_preserved_as_string(): """수량 비면 1, 송장번호 숫자로 들어와도 문자열 보존.""" assert store.normalize_quantity("") == 1 assert store.normalize_quantity(None) == 1 assert store.normalize_quantity("0") == 1 assert store.normalize_quantity("5") == 5 # 큰 송장번호가 float 로 읽혀도 과학표기/.0 없이 보존 assert store.clean_text(12345678901.0) == "12345678901" assert store.clean_text(float("nan")) == "" def test_sku_summary(): rows = [ {"order_id": "A", "package_id": "PA", "tracking_id": "", "seller_sku": "MT-0320_3", "quantity": "1"}, {"order_id": "B", "package_id": "PB", "tracking_id": "", "seller_sku": "MT-0320_3", "quantity": "1"}, {"order_id": "C", "package_id": "PC", "tracking_id": "", "seller_sku": "MY-0001", "quantity": "5"}, ] parcels = store.group_parcels(rows) summary = store.sku_summary(parcels) totals = {r["seller_sku"]: r["total_qty"] for r in summary} assert totals == {"MT-0320_3": 2, "MY-0001": 5}, totals def test_missing_required_columns(): """필수 컬럼 누락 감지: seller_sku 없거나 묶음키 전무.""" mapping = store.resolve_columns(["Order ID", "Product Name", "Quantity"]) missing = store.missing_required(mapping) assert any("Seller SKU" in m for m in missing), missing # 묶음키(order id) 는 있으므로 그 누락 메시지는 없어야 함 assert not any("최소 1개" in m for m in missing), missing def test_column_alias_strip_and_case(): """헤더 앞뒤 공백/대소문자 무시하고 매핑.""" mapping = store.resolve_columns([" Order ID ", "TRACKING ID", "Seller SKU", "Quantity"]) assert "order_id" in mapping assert "tracking_id" in mapping assert "seller_sku" in mapping def _run_all(): fns = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)] for fn in fns: fn() print("PASS", fn.__name__) print(f"\n{len(fns)} tests passed.") if __name__ == "__main__": _run_all()