238 lines
7.5 KiB
Python
238 lines
7.5 KiB
Python
# 接口 2:从表 2 取 push_status=0,组装文档报文。假连接录 SQL,不连真库,不发 HTTP。
|
||
|
||
from __future__ import annotations
|
||
|
||
from decimal import Decimal
|
||
|
||
from app.outbound.cart_settlement import (
|
||
MAX_BATCH,
|
||
batch_limit,
|
||
build_cart_settlement_envelope,
|
||
load_cart_settlement_envelope,
|
||
)
|
||
from app.outbound.oracle_repo import OracleCartSettlementRepo
|
||
from tests.fake_oracle import FakeDb
|
||
|
||
|
||
# 构造一条表 2 行,供组装层单测用。
|
||
def _row(**overrides):
|
||
row = {
|
||
"fid": 100001,
|
||
"operate_type": 1,
|
||
"oms_id": 20260928000001,
|
||
"platform_type": 48,
|
||
"supplier_name": "示例供应商",
|
||
"bar_code": "06901234567890",
|
||
"purchase_price": Decimal("128.5000"),
|
||
"purchase_qty": 10,
|
||
"purchase_total": Decimal("1285.0000"),
|
||
"status": 1,
|
||
"fail_reason": None,
|
||
"pay_order_no": None,
|
||
"pay_order_dtl_id": None,
|
||
"org_code": "901",
|
||
"push_status": 0,
|
||
"multi_platform_flag": 0,
|
||
}
|
||
row.update(overrides)
|
||
return row
|
||
|
||
|
||
# 把表 2 行放进假库。list_unpushed 走真 SQL,调用记在 db.calls。
|
||
def _open(rows: list[dict]) -> tuple[OracleCartSettlementRepo, FakeDb]:
|
||
db = FakeDb()
|
||
for row in rows:
|
||
db.carts[row["fid"]] = row
|
||
return OracleCartSettlementRepo(db.connection()), db
|
||
|
||
|
||
# 取出最近一次 execute 的 SQL 和绑定。
|
||
def _last_execute(db: FakeDb) -> tuple[str, dict]:
|
||
execute_calls = [call for call in db.calls if call[0] == "execute"]
|
||
assert execute_calls
|
||
_, sql, params = execute_calls[-1]
|
||
return sql, params or {}
|
||
|
||
|
||
# 未推行按文档信封字段组装,金额四位小数字符串。
|
||
def test_assembles_unpushed_rows_as_document_envelope():
|
||
repo, db = _open(
|
||
[
|
||
_row(),
|
||
_row(
|
||
fid=100002,
|
||
operate_type=2,
|
||
platform_type=66,
|
||
status=None,
|
||
pay_order_no="PAY20260928000001",
|
||
pay_order_dtl_id="1233",
|
||
),
|
||
]
|
||
)
|
||
envelope = load_cart_settlement_envelope(repo)
|
||
assert envelope == {
|
||
"data": [
|
||
{
|
||
"fid": 100001,
|
||
"operateType": 1,
|
||
"omsId": 20260928000001,
|
||
"platformType": 48,
|
||
"supplierName": "示例供应商",
|
||
"barCode": "06901234567890",
|
||
"purchasePrice": "128.5000",
|
||
"purchaseQty": 10,
|
||
"purchaseTotal": "1285.0000",
|
||
"status": 1,
|
||
"failReason": None,
|
||
"payOrderNo": None,
|
||
"payOrderDtlId": None,
|
||
"orgCode": "901",
|
||
},
|
||
{
|
||
"fid": 100002,
|
||
"operateType": 2,
|
||
"omsId": 20260928000001,
|
||
"platformType": 66,
|
||
"supplierName": "示例供应商",
|
||
"barCode": "06901234567890",
|
||
"purchasePrice": "128.5000",
|
||
"purchaseQty": 10,
|
||
"purchaseTotal": "1285.0000",
|
||
"status": None,
|
||
"failReason": None,
|
||
"payOrderNo": "PAY20260928000001",
|
||
"payOrderDtlId": "1233",
|
||
"orgCode": "901",
|
||
},
|
||
]
|
||
}
|
||
sql, params = _last_execute(db)
|
||
assert "rpa_cart_settlement_goods_data" in sql.lower()
|
||
assert "push_status = 0" in sql.lower()
|
||
assert params["limit"] == 500
|
||
|
||
|
||
# 失败行金额为 NULL 时该行置空,同批其它行照常组装。
|
||
def test_null_amounts_do_not_fail_the_batch():
|
||
repo, _db = _open(
|
||
[
|
||
_row(
|
||
fid=1,
|
||
status=2,
|
||
purchase_price=None,
|
||
purchase_total=None,
|
||
fail_reason="库存不足",
|
||
),
|
||
_row(fid=2, oms_id=20260928000002),
|
||
]
|
||
)
|
||
envelope = load_cart_settlement_envelope(repo)
|
||
failed, ok = envelope["data"]
|
||
assert failed["status"] == 2
|
||
assert failed["purchasePrice"] is None
|
||
assert failed["purchaseTotal"] is None
|
||
assert failed["failReason"] == "库存不足"
|
||
assert ok["purchasePrice"] == "128.5000"
|
||
assert ok["purchaseTotal"] == "1285.0000"
|
||
|
||
|
||
# push_status=1 的行不进报文。
|
||
def test_skips_already_pushed_rows():
|
||
repo, db = _open(
|
||
[
|
||
_row(fid=1, push_status=1),
|
||
_row(fid=2, oms_id=20260928000002, push_status=0),
|
||
]
|
||
)
|
||
envelope = load_cart_settlement_envelope(repo)
|
||
assert [item["fid"] for item in envelope["data"]] == [2]
|
||
sql, _params = _last_execute(db)
|
||
assert "push_status = 0" in sql.lower()
|
||
|
||
|
||
# 同一 omsId 的行必须落在同一批 data。
|
||
def test_keeps_same_oms_id_in_one_envelope():
|
||
rows = [
|
||
_row(fid=1, oms_id=1, platform_type=48, multi_platform_flag=1),
|
||
_row(fid=2, oms_id=1, platform_type=66, multi_platform_flag=1),
|
||
_row(fid=3, oms_id=2, platform_type=48),
|
||
]
|
||
envelope = build_cart_settlement_envelope(rows, limit=2)
|
||
fids = [item["fid"] for item in envelope["data"]]
|
||
assert fids == [1, 2]
|
||
|
||
|
||
# 批次已有行时,装不下的 omsId 整组留下。
|
||
def test_does_not_split_oms_id_group_across_batch_limit():
|
||
rows = [
|
||
_row(fid=1, oms_id=1, platform_type=48),
|
||
_row(fid=2, oms_id=2, platform_type=48, multi_platform_flag=1),
|
||
_row(fid=3, oms_id=2, platform_type=66, multi_platform_flag=1),
|
||
]
|
||
envelope = build_cart_settlement_envelope(rows, limit=2)
|
||
assert [item["fid"] for item in envelope["data"]] == [1]
|
||
|
||
|
||
# 默认上限 500;超过则按组截到 500。
|
||
def test_batch_cap_is_500():
|
||
assert MAX_BATCH == 500
|
||
rows = [_row(fid=i, oms_id=i) for i in range(1, 502)]
|
||
envelope = build_cart_settlement_envelope(rows)
|
||
assert len(envelope["data"]) == 500
|
||
assert envelope["data"][0]["fid"] == 1
|
||
assert envelope["data"][-1]["fid"] == 500
|
||
|
||
|
||
# 报文不含 push_status 等表内列。
|
||
def test_payload_omits_table_only_columns():
|
||
envelope = build_cart_settlement_envelope([_row()])
|
||
item = envelope["data"][0]
|
||
assert "pushStatus" not in item
|
||
assert "push_status" not in item
|
||
assert "multiPlatformFlag" not in item
|
||
assert "createTime" not in item
|
||
|
||
|
||
# 组装入口把 limit 传给仓库,让 SQL 侧截组。
|
||
def test_load_passes_limit_to_list_unpushed():
|
||
seen: dict = {}
|
||
|
||
# 只记 list_unpushed 收到的 limit。
|
||
class SpyRepo:
|
||
# 记下上限并返回空列表。
|
||
def list_unpushed(self, limit: int = 500) -> list[dict]:
|
||
seen["limit"] = limit
|
||
return []
|
||
|
||
load_cart_settlement_envelope(SpyRepo(), limit=3)
|
||
assert seen["limit"] == 3
|
||
|
||
|
||
# OMS_OUTBOUND_BATCH_LIMIT 覆盖默认 500。
|
||
def test_batch_limit_reads_env(monkeypatch):
|
||
monkeypatch.setenv("OMS_OUTBOUND_BATCH_LIMIT", "3")
|
||
assert batch_limit() == 3
|
||
|
||
|
||
# 非法或非正数上限回退 500。
|
||
def test_batch_limit_invalid_falls_back_to_500(monkeypatch):
|
||
monkeypatch.setenv("OMS_OUTBOUND_BATCH_LIMIT", "abc")
|
||
assert batch_limit() == 500
|
||
monkeypatch.setenv("OMS_OUTBOUND_BATCH_LIMIT", "0")
|
||
assert batch_limit() == 500
|
||
|
||
|
||
# 未显式传 limit 时按环境变量截组。
|
||
def test_load_uses_env_batch_limit(monkeypatch):
|
||
monkeypatch.setenv("OMS_OUTBOUND_BATCH_LIMIT", "1")
|
||
repo, db = _open(
|
||
[
|
||
_row(fid=1, oms_id=1),
|
||
_row(fid=2, oms_id=2),
|
||
]
|
||
)
|
||
envelope = load_cart_settlement_envelope(repo)
|
||
assert [item["fid"] for item in envelope["data"]] == [1]
|
||
_sql, params = _last_execute(db)
|
||
assert params["limit"] == 1
|