- 交互契约改对话式询问(禁点选工具、一次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 项全绿
114 lines
4.8 KiB
Python
114 lines
4.8 KiB
Python
"""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()
|