"""失败卡展示原因派生(repositories.comparison._derive_fail_display)单元测试。 用例取自线上真实 failed 记录(platform_results 形态),覆盖: - information 具体 → 直出 - information 笼统 + platform_results 有干净业务结局 → 救援出该原因(id 3030/2964 型) - information 笼统 + 仅系统失败(搜索失败等黑话)→ None(端侧品牌兜底,id 3027 型) - store_closed / no_delivery 被 pricebot 漏成 status=failed → 按 reason 关键字补判 - 打烊类脏店名 blob → 统一简短模板 - platform_results 为空 / 非对象 → None """ from __future__ import annotations from app.repositories import comparison as crud def test_specific_information_passthrough() -> None: # information 本身具体(未达起送/找不到菜等)→ 直出,不看 platform_results assert ( crud._derive_fail_display("淘宝闪购未达起送门槛,可加菜凑单后下单", {}) == "淘宝闪购未达起送门槛,可加菜凑单后下单" ) assert crud._derive_fail_display("未识别到商品", {}) == "未识别到商品" def test_generic_info_rescued_from_items_not_found() -> None: # id 3030 型:美团系统失败 + 京东 items_not_found,记录级 information 笼统 → 救出京东那条 pr = { "eleme": {"is_source": True, "status": "source", "price": 23.04}, "meituan_waimai": { "is_source": False, "status": "failed", "reason": "搜索店铺失败, 无法跳转到搜索页", }, "jd_waimai_standalone": { "is_source": False, "status": "items_not_found", "reason": "京东外卖此店内未找到这些菜品", }, } assert ( crud._derive_fail_display("比价过程出错,请稍后重试", pr) == "京东外卖此店内未找到这些菜品" ) def test_generic_info_rescued_from_store_not_found() -> None: # id 2964 型:美团系统失败 + 京东 store_not_found → 救出京东相似店铺文案 pr = { "taobao_flash": {"is_source": True, "status": "source", "price": 127.98}, "meituan": { "is_source": False, "status": "failed", "reason": "比价过程出错,请稍后重试", }, "jd_waimai": { "is_source": False, "status": "store_not_found", "reason": "未在京东找到「黔珍味·贵州牛肉蘸水健康菜 (望京店)」相似店铺", }, } assert ( crud._derive_fail_display("比价过程出错,请稍后重试", pr) == "未在京东找到「黔珍味·贵州牛肉蘸水健康菜 (望京店)」相似店铺" ) def test_generic_info_pure_system_failure_returns_none() -> None: # id 3027 型:唯一目标平台是自动化黑话失败 → 不给用户看 → None(端侧品牌兜底) pr = { "eleme": {"is_source": True, "status": "source", "price": 18.83}, "meituan_waimai": { "is_source": False, "status": "failed", "reason": "搜索店铺失败, 无法跳转到搜索页", }, } assert crud._derive_fail_display("比价过程出错,请稍后重试", pr) is None def test_store_closed_leaked_to_failed_is_rescued_and_cleaned() -> None: # 打烊被漏成 status=failed;reason 常带脏店名 blob → 统一简短模板 pr = { "jd_waimai": {"is_source": True, "status": "source", "price": 25}, "taobao_flash": { "is_source": False, "status": "failed", "reason": "淘宝闪购「沙胆彪炭炉牛杂煲(...),蜂鸟准时达,月售300+,起送¥20」本店已休息,无法比价", }, } assert crud._derive_fail_display("比价过程出错,请稍后重试", pr) == "门店休息中,无法比价" pr2 = { "taobao_flash": {"is_source": True, "status": "source", "price": 31.83}, "meituan": { "is_source": False, "status": "failed", "reason": "美团「奈雪的茶(北京王府井奥莱·香江」门店已打烊,无法比价", }, } assert crud._derive_fail_display("比价出错", pr2) == "门店已打烊,无法比价" def test_no_delivery_leaked_to_failed_is_rescued() -> None: # 单点不配送被漏成 status=failed;reason 本身干净 → 直接用 reason = "京东外卖该商家所选商品单点不配送,无法进入结算比价" pr = { "taobao_flash": {"is_source": True, "status": "source", "price": 20.1}, "jd_waimai_standalone": { "is_source": False, "status": "failed", "reason": reason, }, } assert crud._derive_fail_display("比价过程出错,请稍后重试", pr) == reason def test_empty_or_missing_platform_results_returns_none() -> None: # 「比价出错」+ 空 {} / None / 非对象:引擎早夭,无可展示原因 → None assert crud._derive_fail_display("比价出错", {}) is None assert crud._derive_fail_display("比价过程出错,请稍后重试", None) is None assert crud._derive_fail_display("比价出错", []) is None # 老 array 形态,防御 def test_clean_status_wins_over_priority_order() -> None: # 多个业务结局同现时按 _BIZ_STATUS_PRIORITY 选(below_minimum 优先于 store_not_found) pr = { "src": {"is_source": True, "status": "source", "price": 30}, "a": {"is_source": False, "status": "store_not_found", "reason": "未找到店铺A"}, "b": {"is_source": False, "status": "below_minimum", "reason": "B未达起送门槛"}, } assert crud._derive_fail_display("比价过程出错,请稍后重试", pr) == "B未达起送门槛"