Commit 9326844a authored by wenwen.tang's avatar wenwen.tang 😕

test: 补行粒度核对与楼层总表的单测,回归脚本改数据驱动并加沙箱闸门

- 新增 tools/test_round6.py(39 项):覆盖行粒度核对全链路 ——
  进度与部分确认、旧 units 展开成行、只核单一类目、确认后取消勾选会撤销确认、
  未知行过滤、账期级部分确认放行、批次摘要字段
- 新增 tools/test_masters.py:楼层总表 公共/套内/空调总 的系数与金额口径
- test_rules.py:免租期断言改为与 `rentfree.windows()` 对拍的数据驱动写法,
  不再写死「3 段 / 只有 302室」(台账新增 801/802 后会过期)
- ui_verify.py/2/3/4/5/6.py:**未设 RENTOS_BASE 时直接拒绝运行**。
  此前 ui_verify2 直连 8899 真实服务,往 `data/batches/` 写了 17 个测试批次、
  还改脏了台账 2 行。跑这批脚本必须经 `tools/ui_verify_runner.py` 沙箱
- ui_verify2/5/7.py:批次台账改为按账期汇总默认折叠后,脚本改为只点
  `.collapsed` 分组(hash 导航不刷新页面,折叠状态会延续)
- 新增 tools/ui_verify11.py(85 项):核对按行 + 列显示 + 部分确认全链路
parent 3aa94d82
This diff is collapsed.
This diff is collapsed.
......@@ -41,8 +41,10 @@ class TestPlans(unittest.TestCase):
"skip_rentfree": 0})
self.assertEqual(p["advance_days"], plans.MAX_ADVANCE)
self.assertEqual(p["send_time"], "08:05")
self.assertTrue(p["enabled"])
self.assertFalse(p["skip_rentfree"])
# 电费已改「全部手动发送」:配置里写 enabled=true 也必须被强制关掉(其余设置保留)
self.assertFalse(p["enabled"])
self.assertTrue(plans.normalize("rent_parking", {"enabled": 1})["enabled"])
bad = plans.normalize("elec", {"advance_days": "abc", "send_time": "25:00", "enabled": False})
self.assertEqual(bad["advance_days"], plans.BY_KEY["elec"]["advance_days"])
......@@ -90,22 +92,37 @@ class TestRentfree(unittest.TestCase):
self.assertEqual(rentfree.norm("706b"), "706B")
def test_windows_from_real_table(self):
"""真实台账里的三段免租期:315室 / 302室 / 303室(各 0.5 个月)。"""
ws = {w["unit"]: w for w in rentfree.windows()}
self.assertIn("302室", ws)
self.assertEqual(ws["302室"]["begin"], "2026-08-16")
self.assertEqual(ws["302室"]["end"], "2026-08-31")
for w in ws.values():
"""真实台账里的免租窗口:首N月(315室 / 302室 / 303室)+ 每年8-9月(逸腾 801/802)。
只断言结构与口径不变,**不写死日期集合** —— 台账新增/删除免租行都不该让本用例变红。
"""
ws = rentfree.windows()
by_unit = {}
for w in ws:
by_unit.setdefault(w["unit"], []).append(w)
for u in ("302室", "303室", "315室"):
self.assertIn(u, by_unit)
# 302室首N月窗口:8-16 起(含起始不含结束)
first = [w for w in by_unit["302室"] if w["type"] == "首N月免租"][0]
self.assertEqual(first["begin"], "2026-08-16")
self.assertEqual(first["end"], "2026-08-31")
for w in ws:
self.assertGreater(w["months"], 0)
self.assertLess(w["begin"], w["end"])
def test_active_and_hit(self):
self.assertEqual([a["unit"] for a in rentfree.active("2026-08-20")], ["302室"])
self.assertEqual([a["unit"] for a in rentfree.active("2026-09-10")], ["303室"])
self.assertEqual(rentfree.active("2026-10-09"), []) # 今天都不在免租期
# 边界:结束日当天已恢复正常(含起始不含结束)
self.assertEqual(rentfree.active("2026-08-31"), [])
self.assertEqual([a["unit"] for a in rentfree.active("2026-08-16")], ["302室"])
"""active() 与 windows() 同一口径(含起始不含结束)——用 windows 对拍,数据驱动。"""
for d in ("2026-01-01", "2026-01-16", "2026-08-16", "2026-08-20", "2026-08-31",
"2026-09-10", "2026-10-01", "2026-10-09", "2027-08-01"):
day = dt.date.fromisoformat(d)
want = sorted({w["unit"] for w in rentfree.windows()
if w["_begin"] <= day < w["_end"]})
self.assertEqual(sorted(a["unit"] for a in rentfree.active(d)), want,
msg=f"active({d}) 与 windows() 口径不一致")
# 302室 0.5 月窗口的边界:含起始不含结束
self.assertIn("302室", [a["unit"] for a in rentfree.active("2026-08-16")])
self.assertNotIn("302室", [a["unit"] for a in rentfree.active("2026-08-31")])
self.assertNotIn("302室", [a["unit"] for a in rentfree.active("2026-10-09")])
hits = rentfree.hit(["302室", "306室"], "2026-08-20")
self.assertEqual([h["matched"] for h in hits], ["302室"])
......@@ -216,24 +233,33 @@ class TestScheduler(unittest.TestCase):
return scheduler.status()
def test_trigger_date_is_due_minus_advance(self):
pc = plans.all_of(CFG_ON)["elec"]
"""房租车位费:next_at = 「下一账单日」− 提前天数。
(电费 2026-10-13 起改为 100% 手动发送,不再排期,见 TestPlans /
test_round6.TestSchedulerSkipsElec)
"""
from rentos import tables
rows = [{"_id": "x", "A": "精锐", "B": "3楼", "C": "302室", "F": "逢林",
"I": "2026-01-01", "J": "2028-12-31", "K": 24, "L": 0.5, "N": 3,
"O": 1008, "P": 6570, "AC": "2026-12-01", "AJ": "2026-12-01"}]
pc = {"advance_days": 5, "enabled": True, "send_time": "09:00"}
def info(now, fired=""):
with mock.patch.object(store, "list_batches",
return_value=[{"batch_id": BATCH["batch_id"]}]), \
mock.patch.object(store, "load_batch", return_value=dict(BATCH)):
return scheduler.plan_info("elec", pc, True, fired, now)
# 缴费期限 08-24、提前 3 天 → 触发日 08-21 09:00
wait = info(dt.datetime(2026, 8, 20, 10, 0))
self.assertEqual(wait["next_at"], "2026-08-21 09:00")
with mock.patch.object(tables, "load_table",
return_value={"rows": rows, "columns": []}), \
mock.patch.object(scheduler, "_auto_pending", return_value=None):
return scheduler.plan_info("rent_parking", pc, True, fired, now)
# 下一账单日 12-01、提前 5 天 → 推送日 11-26 09:00
wait = info(dt.datetime(2026, 11, 20, 10, 0))
self.assertEqual(wait["next_at"], "2026-11-26 09:00")
self.assertEqual(wait["state"], "wait")
self.assertIn("还有 1 天", wait["note"])
self.assertIn("还有 6 天", wait["note"])
due = info(dt.datetime(2026, 8, 21, 9, 5))
due = info(dt.datetime(2026, 11, 26, 9, 5))
self.assertEqual(due["state"], "due")
fired = info(dt.datetime(2026, 8, 21, 9, 5), "2026-08-21")
fired = info(dt.datetime(2026, 11, 26, 9, 5), "2026-11-26")
self.assertEqual(fired["state"], "fired")
def test_rent_parking_schedules_by_next_bill(self):
......@@ -275,7 +301,12 @@ class TestScheduler(unittest.TestCase):
def test_master_switch_off(self):
cfg = {"notify": dict(CFG_ON["notify"], enabled=False)}
st = self._status(cfg, {"fired": {}})
self.assertTrue(all(p["state"] == "off" for p in st["plans"]))
by = {p["key"]: p for p in st["plans"]}
self.assertEqual(by["rent_parking"]["state"], "off")
# 电费是「手动发送」:总开关开不开都不排期
self.assertEqual(by["elec"]["state"], "manual")
self.assertEqual(st["enabled_count"], 0)
self.assertEqual(st["manual_count"], 1)
def test_due_plans_respects_flags_and_date(self):
def due(now, fired=None, cfg=CFG_ON):
......@@ -286,11 +317,11 @@ class TestScheduler(unittest.TestCase):
mock.patch.object(scheduler, "load_state", return_value={"fired": fired or {}}):
return scheduler.due_plans(now)
self.assertEqual(due(dt.datetime(2026, 8, 20, 9, 30)), []) # 未到触发日
self.assertEqual(due(dt.datetime(2026, 8, 21, 8, 59)), []) # 到日未到时点
self.assertEqual(due(dt.datetime(2026, 8, 21, 9, 0)), ["elec"]) # 到日到时点
# 电费已改全手动:即使到了「缴费期限 − 提前天数」那天也不该被自动触发
self.assertEqual(due(dt.datetime(2026, 8, 20, 9, 30)), [])
self.assertEqual(due(dt.datetime(2026, 8, 21, 9, 0)), [])
self.assertEqual(due(dt.datetime(2026, 8, 22, 10, 0),
fired={"elec": "2026-08-22"}), []) # 当天已触发
fired={"elec": "2026-08-22"}), [])
off = {"notify": dict(CFG_ON["notify"], enabled=False)}
self.assertEqual(due(dt.datetime(2026, 8, 25, 10, 0), cfg=off), [])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment