Files
junhong_cmp_fiber/scripts/batch_exchange/test_batch_exchange.py
break c7f8b4c702
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m26s
批量换货脚本
2026-07-22 19:09:28 +09:00

173 lines
5.8 KiB
Python

"""批量换货脚本的输入校验测试。"""
from __future__ import annotations
import importlib.util
import sys
import tempfile
import unittest
from pathlib import Path
SCRIPT_PATH = Path(__file__).with_name("batch_exchange.py")
SPEC = importlib.util.spec_from_file_location("batch_exchange", SCRIPT_PATH)
assert SPEC is not None and SPEC.loader is not None
batch_exchange = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = batch_exchange
SPEC.loader.exec_module(batch_exchange)
class LoadExchangesTest(unittest.TestCase):
"""验证 CSV 映射校验不会把危险批次交给接口执行。"""
def write_csv(self, content: str) -> Path:
"""在临时目录中创建待解析的 CSV。"""
directory = tempfile.TemporaryDirectory()
self.addCleanup(directory.cleanup)
path = Path(directory.name) / "exchanges.csv"
path.write_text(content, encoding="utf-8")
return path
def test_loads_supported_header_and_rows(self) -> None:
"""支持标准表头并保留原始行号。"""
path = self.write_csv("old_identifier,new_identifier\nold-1,new-1\nold-2,new-2\n")
exchanges = batch_exchange.load_exchanges(path)
self.assertEqual(
exchanges,
[
batch_exchange.ExchangeInput(2, "old-1", "new-1"),
batch_exchange.ExchangeInput(3, "old-2", "new-2"),
],
)
def test_rejects_duplicate_old_identifier(self) -> None:
"""同一旧资产不能在一批中换出两次。"""
path = self.write_csv("old-1,new-1\nold-1,new-2\n")
with self.assertRaisesRegex(ValueError, "旧资产.*重复"):
batch_exchange.load_exchanges(path)
def test_rejects_duplicate_new_identifier(self) -> None:
"""同一新资产不能接收两次换货。"""
path = self.write_csv("old-1,new-1\nold-2,new-1\n")
with self.assertRaisesRegex(ValueError, "新资产.*重复"):
batch_exchange.load_exchanges(path)
def test_rejects_same_identifier_in_one_row(self) -> None:
"""禁止资产换给自身。"""
path = self.write_csv("old-1,old-1\n")
with self.assertRaisesRegex(ValueError, "新旧资产标识相同"):
batch_exchange.load_exchanges(path)
def test_rejects_cross_row_asset_reuse(self) -> None:
"""禁止资产在同批中同时作为旧资产和新资产。"""
path = self.write_csv("old-1,new-1\nnew-1,new-2\n")
with self.assertRaisesRegex(ValueError, "同时作为.*旧资产.*新资产"):
batch_exchange.load_exchanges(path)
def test_rejects_missing_column_value(self) -> None:
"""双列中的空值必须在请求前被拦截。"""
path = self.write_csv("old_identifier,new_identifier\nold-1,\n")
with self.assertRaisesRegex(ValueError, "均不能为空"):
batch_exchange.load_exchanges(path)
class BuildExchangePayloadTest(unittest.TestCase):
"""验证脚本不能关闭直接换货或数据迁移。"""
def test_forces_direct_flow_and_migration(self) -> None:
"""请求固定为 direct 且 migrate_data 为 true。"""
exchange = batch_exchange.ExchangeInput(2, "old-1", "new-1")
payload = batch_exchange.build_exchange_payload(
exchange,
"iot_card",
"批量直接换货",
None,
)
self.assertEqual(payload["flow_type"], "direct")
self.assertIs(payload["migrate_data"], True)
self.assertEqual(payload["old_asset_type"], "iot_card")
class ExchangeAssetTest(unittest.TestCase):
"""验证结果必须同时满足接口成功和迁移完成。"""
class FakeClient:
"""返回指定响应的换货客户端替身。"""
def __init__(self, result: object) -> None:
self.result = result
def create_exchange(self, *args: object) -> object:
"""返回测试预设的接口响应。"""
return self.result
def test_marks_completed_migration_as_success(self) -> None:
"""业务成功且迁移完成时结果为成功。"""
result = batch_exchange.HTTPResult(
status=200,
body={
"code": 0,
"msg": "success",
"data": {
"id": 123,
"exchange_no": "EX123",
"migration_completed": True,
"migration_balance": 100,
},
},
raw_body="",
)
outcome = batch_exchange.exchange_asset(
self.FakeClient(result),
"token",
batch_exchange.ExchangeInput(2, "old-1", "new-1"),
"iot_card",
"批量直接换货",
None,
)
self.assertTrue(outcome.success)
self.assertEqual(outcome.exchange_no, "EX123")
def test_rejects_success_response_without_completed_migration(self) -> None:
"""业务成功但未确认迁移完成时结果仍为失败。"""
result = batch_exchange.HTTPResult(
status=200,
body={
"code": 0,
"msg": "success",
"data": {
"id": 123,
"exchange_no": "EX123",
"migration_completed": False,
"migration_balance": 0,
},
},
raw_body="",
)
outcome = batch_exchange.exchange_asset(
self.FakeClient(result),
"token",
batch_exchange.ExchangeInput(2, "old-1", "new-1"),
"iot_card",
"批量直接换货",
None,
)
self.assertFalse(outcome.success)
self.assertIn("未确认数据迁移完成", outcome.message)
if __name__ == "__main__":
unittest.main()