merge(chv-muse-round12): 合入 P4 浏览器完全对齐 (1c8fc3f)
冲突按并集解:build.sh 保留 P3 authd 检查/拷入/runcmd(含 start chv-authd)+ P4 三件套检查/拷入(P4 的重复 runcmd 行 删掉——同一文件写两次会盖掉 authd 启动行)+ P4 的 cdp-broker.service/tmpfiles(含 ask-queue)+ P3 叶子模块条件 拷入;buildinfo.py 两边内容项并集;chv-browser.py 取 P4 重构 版(broker 客户端);production-readiness.md P4 的 §15 顺延为 §16。P2 的 BrowserTest 改走假 CDP broker(断言不变),两路 干跑夹具补齐并集文件集。
This commit is contained in:
commit
c24cc0ba92
10 files changed
+2116
-278
No files matched your search
@@ -2,7 +2,7 @@
|
||||
|
||||
只测本轨交付物(chv-guest/chv-cell.py、chv-broker.py、chv-browser.py、
|
||||
build.sh 干跑、buildinfo.py 内容项)。全部用假 socket/假进程/假
|
||||
playwright,绝不连真实网络、不起真实 VM、不改生产行为。
|
||||
CDP broker,绝不连真实网络、不起真实 VM、不改生产行为。
|
||||
"""
|
||||
import importlib.util
|
||||
import json
|
||||
@@ -33,6 +33,7 @@ def _load_script(name):
|
||||
CELL = _load_script("chv-cell.py")
|
||||
BROKER = _load_script("chv-broker.py")
|
||||
BROWSER = _load_script("chv-browser.py")
|
||||
GUARD = _load_script("chv_browser_guard.py")
|
||||
BUILDINFO = _load_script("buildinfo.py")
|
||||
|
||||
|
||||
@@ -329,21 +330,21 @@ class BrokerTest(unittest.TestCase):
|
||||
|
||||
class BrowserTest(unittest.TestCase):
|
||||
def test_validate_url(self):
|
||||
self.assertTrue(BROWSER.validate_url("https://example.com")[0])
|
||||
self.assertTrue(BROWSER.validate_url("http://example.com/x")[0])
|
||||
self.assertTrue(GUARD.validate_url("https://example.com")[0])
|
||||
self.assertTrue(GUARD.validate_url("http://example.com/x")[0])
|
||||
self.assertTrue(
|
||||
BROWSER.validate_url("https://user@example.com/")[0])
|
||||
GUARD.validate_url("https://user@example.com/")[0])
|
||||
for bad in ("ftp://example.com", "file:///etc/passwd",
|
||||
"https://localhost:8080/", "http://127.0.0.1/",
|
||||
"https://192.168.1.1/", "https://10.0.0.5/",
|
||||
"https://172.17.0.2/", "", None):
|
||||
ok, _ = BROWSER.validate_url(bad)
|
||||
ok, _ = GUARD.validate_url(bad)
|
||||
self.assertFalse(ok, bad)
|
||||
|
||||
def test_truncate_a11y_small_tree_untouched(self):
|
||||
tree = {"role": "WebArea", "name": "Example",
|
||||
"children": [{"role": "heading", "name": "Hi"}]}
|
||||
out, truncated = BROWSER.truncate_a11y(tree)
|
||||
out, truncated = GUARD.truncate_a11y(tree)
|
||||
self.assertFalse(truncated)
|
||||
self.assertEqual(out, tree)
|
||||
|
||||
@@ -351,13 +352,13 @@ class BrowserTest(unittest.TestCase):
|
||||
tree = {"role": "WebArea",
|
||||
"children": [{"role": "link", "name": "l%d" % i}
|
||||
for i in range(300)]}
|
||||
out, truncated = BROWSER.truncate_a11y(tree)
|
||||
out, truncated = GUARD.truncate_a11y(tree)
|
||||
self.assertTrue(truncated)
|
||||
self.assertLessEqual(len(out["children"]), 200)
|
||||
|
||||
def test_truncate_a11y_char_cap(self):
|
||||
tree = {"role": "WebArea", "name": "x" * 70000, "children": []}
|
||||
out, truncated = BROWSER.truncate_a11y(tree)
|
||||
out, truncated = GUARD.truncate_a11y(tree)
|
||||
self.assertTrue(truncated)
|
||||
self.assertTrue(out.get("truncated_chars"))
|
||||
|
||||
@@ -371,28 +372,26 @@ class BrowserTest(unittest.TestCase):
|
||||
resp = BROWSER.handle_request(agent, {"op": "bogus"})
|
||||
self.assertFalse(resp["ok"])
|
||||
|
||||
def test_open_rejects_bad_url_without_loading_playwright(self):
|
||||
def exploding_factory():
|
||||
raise AssertionError("坏 URL 不应加载 playwright")
|
||||
def test_open_rejects_bad_url_without_calling_broker(self):
|
||||
def exploding_broker(req):
|
||||
raise AssertionError("坏 URL 不应调 broker")
|
||||
|
||||
agent = BROWSER.BrowserAgent(playwright_factory=exploding_factory)
|
||||
agent = BROWSER.BrowserAgent(broker_call=exploding_broker)
|
||||
resp = agent.open("https://127.0.0.1/secret")
|
||||
self.assertFalse(resp["ok"])
|
||||
|
||||
def test_snapshot_and_screenshot_need_open_first(self):
|
||||
agent = BROWSER.BrowserAgent(
|
||||
playwright_factory=lambda: (_ for _ in ()).throw(
|
||||
AssertionError("不应加载 playwright")))
|
||||
self.assertFalse(agent.snapshot()["ok"])
|
||||
self.assertFalse(agent.screenshot()["ok"])
|
||||
self.assertTrue(agent.close()["ok"])
|
||||
|
||||
def test_open_returns_a11y_snapshot_with_fake_playwright(self):
|
||||
page = FakePage()
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
agent = BROWSER.BrowserAgent(
|
||||
playwright_factory=lambda: FakePlaywright(page),
|
||||
shots_dir=tmp)
|
||||
broker_call=FakeCdpBroker(tmp))
|
||||
self.assertFalse(agent.snapshot()["ok"])
|
||||
self.assertFalse(agent.screenshot()["ok"])
|
||||
self.assertTrue(agent.close()["ok"])
|
||||
|
||||
def test_open_returns_a11y_snapshot_with_fake_cdp_broker(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
agent = BROWSER.BrowserAgent(
|
||||
broker_call=FakeCdpBroker(tmp))
|
||||
resp = agent.open("https://example.com")
|
||||
self.assertTrue(resp["ok"])
|
||||
self.assertEqual(resp["title"], "Example Domain")
|
||||
@@ -407,62 +406,43 @@ class BrowserTest(unittest.TestCase):
|
||||
self.assertTrue(agent.close()["ok"])
|
||||
|
||||
|
||||
class FakePage:
|
||||
def __init__(self):
|
||||
self.url = "https://example.com/"
|
||||
self._closed = False
|
||||
class FakeCdpBroker:
|
||||
"""假 CDP broker:收到 req dict 返回 resp dict;open 后才有页。
|
||||
|
||||
def goto(self, url, timeout=None):
|
||||
self.url = url.rstrip("/") + "/"
|
||||
round-12:P4 起 BrowserAgent 不再直连 Playwright,Chromium
|
||||
的所有权在单元外 CDP broker 手里;本测试用假 broker 走同一
|
||||
协议,断言与 P2 原版一致。"""
|
||||
|
||||
def wait_for_load_state(self, state, timeout=None):
|
||||
pass
|
||||
def __init__(self, shots_dir):
|
||||
self._shots_dir = shots_dir
|
||||
self._open = False
|
||||
self.calls = []
|
||||
|
||||
def title(self):
|
||||
return "Example Domain"
|
||||
|
||||
@property
|
||||
def accessibility(self):
|
||||
return self
|
||||
|
||||
def snapshot(self):
|
||||
return {"role": "WebArea", "name": "Example Domain",
|
||||
def __call__(self, req):
|
||||
self.calls.append(req.get("op"))
|
||||
op = req.get("op")
|
||||
tree = {"role": "WebArea", "name": "Example Domain",
|
||||
"children": [{"role": "heading", "name": "Example"}]}
|
||||
|
||||
def screenshot(self, path=None):
|
||||
with open(path, "wb") as fh:
|
||||
fh.write(b"\x89PNG")
|
||||
|
||||
def close(self):
|
||||
self._closed = True
|
||||
|
||||
|
||||
class FakeBrowser:
|
||||
def __init__(self, page):
|
||||
self._page = page
|
||||
|
||||
def new_page(self):
|
||||
return self._page
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
class FakeEntered:
|
||||
def __init__(self, page):
|
||||
self.chromium = mock.Mock()
|
||||
self.chromium.launch.return_value = FakeBrowser(page)
|
||||
|
||||
|
||||
class FakePlaywright:
|
||||
def __init__(self, page):
|
||||
self._page = page
|
||||
|
||||
def __enter__(self):
|
||||
return FakeEntered(self._page)
|
||||
|
||||
def __exit__(self, *exc):
|
||||
return False
|
||||
if op == "open":
|
||||
self._open = True
|
||||
return {"ok": True, "title": "Example Domain",
|
||||
"url": "https://example.com/", "a11y": tree}
|
||||
if op == "snapshot":
|
||||
if not self._open:
|
||||
return {"ok": False, "error": "尚未 open"}
|
||||
return {"ok": True, "title": "Example Domain",
|
||||
"url": "https://example.com/", "a11y": tree}
|
||||
if op == "screenshot":
|
||||
if not self._open:
|
||||
return {"ok": False, "error": "尚未 open"}
|
||||
path = os.path.join(self._shots_dir, "shot.png")
|
||||
with open(path, "wb") as fh:
|
||||
fh.write(b"\x89PNG")
|
||||
return {"ok": True, "path": path}
|
||||
if op == "close":
|
||||
self._open = False
|
||||
return {"ok": True}
|
||||
return {"ok": False, "error": "未知操作"}
|
||||
|
||||
|
||||
class CellCliTest(unittest.TestCase):
|
||||
@@ -570,7 +550,9 @@ class BuildDryRunTest(unittest.TestCase):
|
||||
"generate_lockfile.py", "chv-sync.sh",
|
||||
"chv-cell.py", "chv-broker.py", "chv-browser.py",
|
||||
"authd.py", "git-credential-chv-authd",
|
||||
"chv-authd.service"):
|
||||
"chv-authd.service",
|
||||
"chv-cdp-broker.py", "chv_browser_guard.py",
|
||||
"browser-blocklist.txt"):
|
||||
shutil.copy2(ROOT / "chv-guest" / name,
|
||||
os.path.join(guest, name))
|
||||
shutil.copy2(ROOT / "host.py", os.path.join(repo, "host.py"))
|
||||
|
||||
Reference in new issue
Block a user