AutoRpa/rpa_selection/tests/test_pipeline.py

186 lines
5.4 KiB
Python

from __future__ import annotations
from pipeline import MallOutcome, run_selection
from rules import Candidate, PurchaseLine, normalize_vendor
HANGZHOU = "杭州小药药自营旗舰店"
GUANGDONG = "广东壹号药业有限公司"
def _purchase(**overrides) -> PurchaseLine:
data = {
"approval_no": "国药准字H20100001",
"sale_price": 100.0,
"qty": 10.0,
"row_no": 2,
"generic_name": "阿莫西林",
"oms_id": 2026100201,
"bar_code": "06901234567890",
}
data.update(overrides)
return PurchaseLine(**data)
def _candidate(**overrides) -> Candidate:
vendor_raw = overrides.get("vendor_raw", GUANGDONG)
data = {
"mall_name": "1号药城",
"sku_id": "SKU1",
"name": "测试药品",
"spec": "0.25g",
"price": 90.0,
"expiry_raw": "13",
"expiry_months": 13.0,
"vendor_raw": vendor_raw,
"vendor_norm": normalize_vendor(vendor_raw),
"stock": 100.0,
"extra": {},
}
data.update(overrides)
if "vendor_raw" in overrides and "vendor_norm" not in overrides:
data["vendor_norm"] = normalize_vendor(overrides["vendor_raw"])
return Candidate(**data)
def test_missing_sale_price_is_business_fail():
result = run_selection(
_purchase(sale_price=None),
[MallOutcome("1号药城", candidates=[_candidate()])],
)
assert result.kind == "BUSINESS_FAIL"
assert result.reason == "NO_SALE_PRICE"
assert result.allocations == []
def test_missing_qty_is_business_fail():
result = run_selection(_purchase(qty=0), [MallOutcome("药帮忙")])
assert result.kind == "BUSINESS_FAIL"
assert result.reason == "NO_QTY"
def test_query_error_without_enough_stock_is_system_fail():
result = run_selection(
_purchase(),
[
MallOutcome("1号药城", failed_reason="QUERY_FAILED:timeout"),
MallOutcome("药帮忙", candidates=[]),
],
)
assert result.kind == "SYSTEM_FAIL"
assert result.allocations == []
assert result.mall_errors["1号药城"] == "QUERY_FAILED:timeout"
def test_empty_hit_is_no_hit():
result = run_selection(
_purchase(),
[
MallOutcome("1号药城", candidates=[]),
MallOutcome("药帮忙", candidates=[]),
],
)
assert result.kind == "BUSINESS_FAIL"
assert result.reason == "NO_HIT"
def test_all_rejected_uses_rule_reason():
result = run_selection(
_purchase(),
[MallOutcome("1号药城", candidates=[_candidate(vendor_raw="路边药店")])],
)
assert result.kind == "BUSINESS_FAIL"
assert result.reason == "NOT_SELF_WAREHOUSE"
assert any(item.reason == "NOT_SELF_WAREHOUSE" for item in result.rejected)
def test_split_across_malls_is_success():
result = run_selection(
_purchase(),
[
MallOutcome(
"药帮忙",
candidates=[
_candidate(
sku_id="HZ",
mall_name="药帮忙",
vendor_raw=HANGZHOU,
stock=3.0,
)
],
),
MallOutcome(
"1号药城",
candidates=[_candidate(sku_id="GD", stock=20.0)],
),
],
)
assert result.kind == "SUCCESS"
assert result.reason == "SELECTED"
qtys = [(item.candidate.sku_id, item.qty) for item in result.allocations]
assert qtys == [("HZ", 3), ("GD", 7)]
def test_one_mall_login_failed_other_fills_is_success():
result = run_selection(
_purchase(),
[
MallOutcome("药帮忙", failed_reason="LOGIN_FAILED:timeout"),
MallOutcome("1号药城", candidates=[_candidate(stock=20.0)]),
],
)
assert result.kind == "SUCCESS"
assert result.allocations[0].qty == 10
def test_one_mall_login_failed_other_short_is_system_fail():
result = run_selection(
_purchase(),
[
MallOutcome("药帮忙", failed_reason="LOGIN_FAILED:timeout"),
MallOutcome("1号药城", candidates=[_candidate(stock=4.0)]),
],
)
assert result.kind == "SYSTEM_FAIL"
assert result.allocations == []
assert result.mall_errors["药帮忙"].startswith("LOGIN_FAILED")
def test_short_stock_with_rejects_is_stock_insufficient():
result = run_selection(
_purchase(),
[
MallOutcome(
"药帮忙",
candidates=[
_candidate(
sku_id="HZ",
mall_name="药帮忙",
vendor_raw=HANGZHOU,
stock=4.0,
),
_candidate(
sku_id="ROAD",
mall_name="药帮忙",
vendor_raw="路边药店",
stock=100.0,
),
],
),
MallOutcome(
"1号药城",
candidates=[
_candidate(
sku_id="EXP",
expiry_raw="6",
expiry_months=6.0,
stock=100.0,
)
],
),
],
)
assert result.kind == "BUSINESS_FAIL"
assert result.reason == "STOCK_INSUFFICIENT"
assert result.allocations == []