R12 真机:detect_checkout 在 chv-guest/、host.py、 executor.py 里零引用,结账页无任何拦截。按 P10 gap §4.1 接线:broker 加载 cloud/chv/checkout_detect.py(同目录 优先,仓库路径兜底;缺失时降级关闭检测、写警告,绝不 crash-loop);snapshot() 每次重算 verdict(aria 行拼伪节点, 与 _form_risk 同形),open() 导航前后两道门——落点是结账 页时返回 form_submit ask 并暂停,上一页是结账页时下一次 导航先审批;ask 批过后同 URL 不再重复 ask(批准即放行, 与 form_risk 同口径);verdict 进 snapshot()/status() 回包。 build.sh:checkout_detect.py 条件拷入 /usr/local/bin/(0644, P3/P6 同款只警告口径,M4 不硬要求 cloud/), extra_contents 与两处 lockfile 登记补齐。钱包三腿不在本轮 范围,未动。
175 lines
7.3 KiB
Python
175 lines
7.3 KiB
Python
"""对齐 Muse P10 结账检测测试:detect_checkout 纯函数。
|
||
|
||
只用标准库,不启动浏览器、不联网。覆盖:
|
||
- URL 信号(结账路径 / 购物车 / 普通页 / 坏 URL);
|
||
- 无障碍树信号(支付字段 / 密码字段 / 下单按钮 / 金额文本);
|
||
- 中英文特征;节点为空;页面金额只作展示信号。
|
||
"""
|
||
|
||
import importlib.util
|
||
import os
|
||
import sys
|
||
import tempfile
|
||
import unittest
|
||
from pathlib import Path
|
||
|
||
try:
|
||
from cloud.chv.checkout_detect import detect_checkout
|
||
except ImportError:
|
||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||
from cloud.chv.checkout_detect import detect_checkout
|
||
|
||
|
||
def _node(role: str, name: str) -> dict:
|
||
return {"role": role, "name": name}
|
||
|
||
|
||
class CheckoutUrlTest(unittest.TestCase):
|
||
def test_checkout_path_is_checkout_high(self) -> None:
|
||
verdict = detect_checkout("https://shop.example.com/checkout")
|
||
self.assertTrue(verdict.is_checkout)
|
||
self.assertEqual(verdict.confidence, "medium")
|
||
self.assertIn("checkout_url", verdict.reasons)
|
||
|
||
def test_payment_path_is_checkout(self) -> None:
|
||
verdict = detect_checkout(
|
||
"https://shop.example.com/order/payment?step=2")
|
||
self.assertTrue(verdict.is_checkout)
|
||
self.assertIn("checkout_url", verdict.reasons)
|
||
|
||
def test_cart_path_is_not_checkout(self) -> None:
|
||
verdict = detect_checkout("https://shop.example.com/cart")
|
||
self.assertFalse(verdict.is_checkout)
|
||
self.assertEqual(verdict.confidence, "low")
|
||
self.assertIn("cart_url", verdict.reasons)
|
||
|
||
def test_plain_page_is_not_checkout(self) -> None:
|
||
verdict = detect_checkout("https://shop.example.com/products/1")
|
||
self.assertFalse(verdict.is_checkout)
|
||
self.assertEqual(verdict.confidence, "low")
|
||
self.assertEqual(verdict.reasons, [])
|
||
|
||
def test_bad_url_does_not_crash(self) -> None:
|
||
verdict = detect_checkout("::::not a url::::")
|
||
self.assertFalse(verdict.is_checkout)
|
||
|
||
def test_no_nodes_url_only_decision(self) -> None:
|
||
verdict = detect_checkout(
|
||
"https://shop.example.com/checkout", None)
|
||
self.assertTrue(verdict.is_checkout)
|
||
|
||
|
||
class CheckoutNodesTest(unittest.TestCase):
|
||
def test_payment_fields_make_checkout_high(self) -> None:
|
||
nodes = [_node("textbox", "Card number"),
|
||
_node("textbox", "Expiry date"),
|
||
_node("textbox", "Security code (CVC)")]
|
||
verdict = detect_checkout("https://shop.example.com/buy",
|
||
nodes)
|
||
self.assertTrue(verdict.is_checkout)
|
||
self.assertEqual(verdict.confidence, "high")
|
||
self.assertEqual(verdict.form_risk, "payment")
|
||
self.assertIn("payment_fields", verdict.reasons)
|
||
|
||
def test_password_field_marks_form_risk(self) -> None:
|
||
nodes = [_node("textbox", "Password")]
|
||
verdict = detect_checkout("https://shop.example.com/login",
|
||
nodes)
|
||
self.assertFalse(verdict.is_checkout)
|
||
self.assertEqual(verdict.form_risk, "password")
|
||
|
||
def test_order_button_plus_amount_is_checkout(self) -> None:
|
||
nodes = [_node("button", "Place order"),
|
||
_node("text", "Total: $50.00")]
|
||
verdict = detect_checkout("https://shop.example.com/review",
|
||
nodes)
|
||
self.assertTrue(verdict.is_checkout)
|
||
self.assertEqual(verdict.confidence, "medium")
|
||
self.assertEqual(verdict.amount_text, "$50.00")
|
||
|
||
def test_order_button_alone_is_not_checkout(self) -> None:
|
||
nodes = [_node("button", "Place order")]
|
||
verdict = detect_checkout("https://shop.example.com/review",
|
||
nodes)
|
||
self.assertFalse(verdict.is_checkout)
|
||
|
||
def test_amount_only_is_not_checkout(self) -> None:
|
||
nodes = [_node("text", "Total: $50.00")]
|
||
verdict = detect_checkout("https://shop.example.com/review",
|
||
nodes)
|
||
self.assertFalse(verdict.is_checkout)
|
||
self.assertEqual(verdict.amount_text, "$50.00")
|
||
|
||
def test_chinese_payment_hints(self) -> None:
|
||
nodes = [_node("textbox", "卡号"),
|
||
_node("button", "立即支付")]
|
||
verdict = detect_checkout("https://shop.example.com/buy",
|
||
nodes)
|
||
self.assertTrue(verdict.is_checkout)
|
||
self.assertEqual(verdict.form_risk, "payment")
|
||
|
||
def test_checkout_url_plus_payment_fields_high(self) -> None:
|
||
nodes = [_node("textbox", "Card number"),
|
||
_node("button", "Pay now")]
|
||
verdict = detect_checkout("https://shop.example.com/checkout",
|
||
nodes)
|
||
self.assertTrue(verdict.is_checkout)
|
||
self.assertEqual(verdict.confidence, "high")
|
||
self.assertIn("checkout_url", verdict.reasons)
|
||
self.assertIn("payment_fields", verdict.reasons)
|
||
|
||
def test_yen_and_currency_code_amounts(self) -> None:
|
||
nodes = [_node("text", "合計:¥1,200"),
|
||
_node("text", "Total 25.00 USD")]
|
||
verdict = detect_checkout("https://shop.example.com/review",
|
||
nodes)
|
||
self.assertEqual(verdict.amount_text, "¥1,200")
|
||
|
||
def test_empty_nodes_plain_page(self) -> None:
|
||
verdict = detect_checkout("https://shop.example.com/", [])
|
||
self.assertFalse(verdict.is_checkout)
|
||
self.assertIsNone(verdict.form_risk)
|
||
self.assertIsNone(verdict.amount_text)
|
||
|
||
|
||
class BuildInfoContentsTest(unittest.TestCase):
|
||
"""round-12 修复轮(项 4,P10):checkout_detect 是条件拷入
|
||
件——存在时经 extra_contents 登记进 BUILDINFO contents
|
||
(不在 BASE_CONTENTS 里,缺失的构建绝不谎称已进镜像,
|
||
与 P3 叶子模块同款口径)。"""
|
||
|
||
def test_extra_contents_include_checkout_detect(self):
|
||
root = Path(__file__).resolve().parents[2]
|
||
path = root / "chv-guest" / "buildinfo.py"
|
||
spec = importlib.util.spec_from_file_location(
|
||
"chv_guest_p10_buildinfo", str(path))
|
||
module = importlib.util.module_from_spec(spec)
|
||
spec.loader.exec_module(module)
|
||
self.assertNotIn("checkout_detect",
|
||
module.BASE_CONTENTS)
|
||
with tempfile.TemporaryDirectory() as tmp:
|
||
out = os.path.join(tmp, "out")
|
||
os.makedirs(out)
|
||
for name, content in (("disk.raw", b"D"),
|
||
("vmlinux", b"K")):
|
||
with open(os.path.join(out, name), "wb") as fh:
|
||
fh.write(content)
|
||
for name, lines in (
|
||
("guest-versions-dpkg.txt", ["git 9.9.9-9ubuntu9"]),
|
||
("guest-versions-pip.txt", ["playwright==9.9.9"]),
|
||
("guest-versions-gt.txt", ["gt version 1.0.0"]),
|
||
("guest-versions-chromium.txt",
|
||
["chromium_headless_shell-1248"])):
|
||
with open(os.path.join(out, name), "w") as fh:
|
||
fh.write("\n".join(lines) + "\n")
|
||
info = module.build(
|
||
out, "chv-guest-24.04-0.164.0-alpha.9-abc1234", "24.04",
|
||
"0.164.0-alpha.9", "abc1234", "0" * 64, True, "1248",
|
||
["cloud/chv/checkout_detect.py"])
|
||
self.assertIn("cloud/chv/checkout_detect.py",
|
||
info["contents"])
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|