alphaH 05bc09f7f9 feat(乐收赢): 收银台在按下去之前拦住低于 1 元的金额 před 2 týdny
..
README.md 05bc09f7f9 feat(乐收赢): 收银台在按下去之前拦住低于 1 元的金额 před 2 týdny
amount-floor-harness.mjs 05bc09f7f9 feat(乐收赢): 收银台在按下去之前拦住低于 1 元的金额 před 2 týdny

README.md

收银台「最低 1 元」闸 —— 前端执行级验证台

这台子回答哪一句话

操作员在收银台输入 0.5,按下「收款」——那一枪会不会发出去?

「源码里写了 if (fen < MIN_FEN) return;」不是答案。这页有 8 个场景, 答案是数出来的:createCashierQr 被调了几次。

怎么跑

cd .claude/plan/2026-09-29-lsy-amount-floor-harness
node amount-floor-harness.mjs            # 默认拿本仓的 cashier/index.vue
node amount-floor-harness.mjs <另一份 SFC 路径>

不带参数、不联网、不碰库、不写任何东西。只认最后那行 SUMMARY —— 失败也 exit 0(与同目录各台一致),别拿 exit code 当判据。

它怎么做到「真」

环节 真的还是假的
SFC 真:@vue/compiler-sfc 解析 + compileScript 编译本仓那个 index.vue
vue 真:frontend/node_modules 里那份 vue(createRenderer 挂载)
被驱动的函数 真:start() 是 setup 返回的那袋绑定里的那一个,本台直接调它
金额换算 真:页面的 Math.round(Number(text) * 100) 原样跑
DOM 假:createRenderer 接一套哑节点,模板不渲染(inlineTemplate: false)
接口 假:@/api/module_payment/collection 换成记账桩(记下每次调用)
element-plus / qrcode 假:可调用、可无限取属性的 Proxy

「假」的那三样与要证的那句话无关:本台问的是有没有发那一枪,不是页面长什么样。 副作用一则:模板不渲染 ⇒ amountEl.value 是 undefined ⇒ 页面里的 amountEl.value?.focus() 是空操作。

八个场景

# 输入(元) 期望 它挡的是什么
A 0.5 不下单,提示「收款金额最低 1 元」 门槛外侧
B 0.99 不下单,同上 门槛内侧最后一格
C 1 下单 amount=100 门槛本身(放行侧正对照)
D 1.01 下单 amount=101 门槛之上
E 0 不下单,提示「请先填一个大于 0 的金额」 旧的 fen <= 0 分支没被新闸顶掉
F "" 不下单,同上 空串(Number("") 是 0,不是 NaN)
G abc 不下单,同上 非数字
H 0.999 下单 amount=100 四舍五入到分正好 1 元 ⇒ 放行(读数,不是缺陷)

A/B/E/F/G 是拦下侧,C/D/H 是放行侧 —— 两侧都必须有。 只有拦下侧的话,把 start() 改成第一行 return 也能全绿。

两向变异(读数在 logs/_mutate-fe-floor.txt)

变异 期望 实读
MIN_FEN 100 → 0(把闸拆掉) 必须红 SUMMARY FAIL 场景=8 不符=2,A 变成「下单 50」、B 变成「下单 99」
只改一行注释 必须仍绿 SUMMARY PASS 场景=8 不符=0

两条变异还原后复量 sha256,与变异前逐字节一致(e1e495e8ad5c…)。

踩过的两个坑

  1. 台子跑完不退出。 桩永远回 PAYING ⇒ start() 成功后 poll() 每 3 秒自己 重排一次定时器(schedulePoll),startTicking 另有一个 1 秒 interval。8 条读数 全对、SUMMARY 也打了,进程却一直挂着。修法是每个场景后 b.stopTimers?.(), 末尾再 process.exit(0) 兜底。 ★ 挂着那次是靠 Get-CimInstance Win32_Process 按命令行认出 amount-floor-harness.mjs 那个 PID 才只杀掉自己的(同机另有 codegraph / MCP / IDE 的十几个 node)。
  2. process.exit(0) 不设 exitCode。 与同目录各台一致:判据是 SUMMARY 那行, 不是退出码 —— 否则「台子崩了」与「场景不符」在 CI 里分不开。

与后端那道闸的关系

后端 LsyAmountFloor.MIN = 100(四个下单入口的 @Min)是真的闸: 开放 API 的调用方根本不经过这一页。这一页拦的是体验 —— 不拦的话操作员收到的是一句报文措辞(amount: amount 不能低于 1 元(100 分)), 而屏幕上正站着顾客。

两处的数值是同一件事写了两遍:frontend/.../cashier/index.vue 的 MIN_FEN 与 java/.../util/LsyAmountFloor.java 的 MIN。改一处必须改另一处, 两边各有测试/台子钉着(后端 LsyAmountFloorTest,前端本台)。