v3.6.0 同步发版
- 交互契约改对话式询问(禁点选工具、一次1~2问、先铺垫再问),规则库 3.6 - 盘面图默认生成+chart_shot 截图;品牌区、大运/流年小卡信息补全、交流群二维码卡 - day_boundary 子正换日;memory_save --update-chart 换盘留痕;share_export 事件简介+城市级地域 - share_push 失败诊断+代理引导;_runtime_guard;修 STAR_ORDER 三奇贵人 KeyError - README 规则计数 541→542;manifest 重建(1275 文件);测试 142 项全绿
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
"""chart_shot.py 与 cast_chart --shot:HTML → PNG 截图的分级回退与优雅失败。"""
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
SKILL_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(SKILL_ROOT / "scripts"))
|
||||
|
||||
import chart_shot # noqa: E402
|
||||
|
||||
CHROME_OK = Path(chart_shot.CHROME).is_file()
|
||||
try:
|
||||
import playwright # noqa: F401
|
||||
PLAYWRIGHT_OK = True
|
||||
except ImportError:
|
||||
PLAYWRIGHT_OK = False
|
||||
SHOT_OK = CHROME_OK or PLAYWRIGHT_OK
|
||||
|
||||
HTML = "<!DOCTYPE html><html><body><h1>命盘</h1></body></html>"
|
||||
|
||||
|
||||
class ShootUnitTest(unittest.TestCase):
|
||||
def test_missing_html_exit_2(self):
|
||||
r = subprocess.run(
|
||||
[sys.executable, str(SKILL_ROOT / "scripts" / "chart_shot.py"),
|
||||
"/nonexistent/x.html"], capture_output=True, text=True, timeout=30)
|
||||
self.assertEqual(r.returncode, 2)
|
||||
self.assertIn("ERROR", r.stderr)
|
||||
|
||||
def test_no_tools_fails_gracefully(self):
|
||||
"""playwright 与 Chrome 都不可用时:shoot 返回 None,CLI 退出码 2 并给提示。"""
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
html = Path(td) / "p.html"
|
||||
html.write_text(HTML, encoding="utf-8")
|
||||
with mock.patch.object(chart_shot, "_shot_playwright",
|
||||
return_value=False), \
|
||||
mock.patch.object(chart_shot, "_shot_chrome",
|
||||
return_value=False):
|
||||
self.assertIsNone(chart_shot.shoot(html))
|
||||
self.assertFalse(html.with_suffix(".png").exists())
|
||||
with mock.patch.object(sys, "argv",
|
||||
["chart_shot.py", str(html)]):
|
||||
with mock.patch("sys.stderr") as err:
|
||||
self.assertEqual(chart_shot.main(), 2)
|
||||
err.write.assert_called()
|
||||
|
||||
def test_content_height_bounds(self):
|
||||
"""高度估算不越界:空页夹到下限,超长页夹到上限。"""
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
empty = Path(td) / "e.html"
|
||||
empty.write_text(HTML, encoding="utf-8")
|
||||
self.assertEqual(chart_shot._content_height(empty), 1300)
|
||||
big = Path(td) / "b.html"
|
||||
big.write_text(
|
||||
'<div class="strip">' + '<div class="cell"></div>' * 5000
|
||||
+ "</div>", encoding="utf-8")
|
||||
self.assertEqual(chart_shot._content_height(big), 4200)
|
||||
|
||||
|
||||
@unittest.skipUnless(SHOT_OK, "环境无 playwright 与 Chrome,跳过真实截图")
|
||||
class ShootRealTest(unittest.TestCase):
|
||||
def test_real_png(self):
|
||||
"""真实截图:PNG 非空、宽 2200(1100 窗口 × 2 倍分辨率)。"""
|
||||
import struct
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
html = Path(td) / "p.html"
|
||||
html.write_text(HTML, encoding="utf-8")
|
||||
png = chart_shot.shoot(html)
|
||||
self.assertIsNotNone(png)
|
||||
data = png.read_bytes()
|
||||
self.assertGreater(len(data), 1000)
|
||||
self.assertEqual(data[:8], b"\x89PNG\r\n\x1a\n")
|
||||
w, _h = struct.unpack(">II", data[16:24])
|
||||
self.assertEqual(w, 2200)
|
||||
|
||||
def test_cli_success(self):
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
html = Path(td) / "p.html"
|
||||
html.write_text(HTML, encoding="utf-8")
|
||||
out = Path(td) / "o.png"
|
||||
r = subprocess.run(
|
||||
[sys.executable, str(SKILL_ROOT / "scripts" / "chart_shot.py"),
|
||||
str(html), "-o", str(out)],
|
||||
capture_output=True, text=True, timeout=120)
|
||||
self.assertEqual(r.returncode, 0, r.stderr)
|
||||
self.assertIn(str(out), r.stdout)
|
||||
self.assertGreater(out.stat().st_size, 0)
|
||||
|
||||
|
||||
@unittest.skipUnless(SHOT_OK, "环境无 playwright 与 Chrome,跳过 --shot 端到端")
|
||||
class CastShotE2ETest(unittest.TestCase):
|
||||
def test_shot_flag_outputs_paths(self):
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
html = Path(td) / "p.html"
|
||||
r = subprocess.run(
|
||||
[sys.executable, str(SKILL_ROOT / "scripts" / "cast_chart.py"),
|
||||
"--name", "张三", "--gender", "男", "--birth", "1990-05-14 08:30",
|
||||
"--place", "四川省成都市", "--html", str(html), "--shot"],
|
||||
capture_output=True, text=True, timeout=120,
|
||||
cwd=str(SKILL_ROOT / "scripts"))
|
||||
self.assertEqual(r.returncode, 0, r.stderr)
|
||||
self.assertTrue(r.stdout.startswith("# 命盘:张三"))
|
||||
self.assertIn(f"HTML 盘面:{html.resolve()}", r.stdout)
|
||||
png = html.with_suffix(".png")
|
||||
self.assertIn(f"盘面截图:{png.resolve()}", r.stdout)
|
||||
self.assertGreater(png.stat().st_size, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user