All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m26s
173 lines
5.8 KiB
Python
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()
|