0065d11e97
- bin/muse_choice_watcher.py + systemd/muse-choices-reconcile.*: automatic choice answering and timer reconciliation - bin/digest.py: fleet log and health summarization - docs/BOX-*-HTTPS.md: comprehensive HTTPS execution contracts and API documentation - docs/MUSE-CHOICES-POLICY.md & docs/SUPERVISION-SPEC.md: autonomous execution specs - tests/test_*.py: unit test suites for HTTPS API, choice watcher, fleet heal, and swarm pruning
569 lines
26 KiB
Python
569 lines
26 KiB
Python
"""Tests for bin/hatch_menu/* (mocked CDP, no live browser traffic).
|
|
|
|
One class per module: mouse, dialog, controls, toggles, and the tab
|
|
contracts with live-captured Settings fixtures.
|
|
"""
|
|
import json
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(REPO_ROOT / "bin"))
|
|
|
|
from hatch_menu import controls, dialog, mouse, toggles # noqa: E402
|
|
from hatch_menu.dialog import TAB_NAMES # noqa: E402
|
|
from hatch_menu.tabs import ( # noqa: E402
|
|
TAB_MODULES,
|
|
data_controls,
|
|
general,
|
|
permissions,
|
|
)
|
|
|
|
PIP_GENERAL = (
|
|
"General\nWallet\nWeekly limit resets on Oct 10\n100% used\n"
|
|
"Additional tokens\nNever expires\n10% used (901M tokens left)\n"
|
|
"Upgrade\nLanguage\nAppearance\nMode\nTheme color")
|
|
|
|
SIX_GENERAL = (
|
|
"100% used\nAdditional tokens\nNever expires\n"
|
|
"100% used (0 tokens left)\nUpgrade\nLanguage\nAppearance\n"
|
|
"Mode\nTheme color")
|
|
|
|
FRESH_GENERAL = (
|
|
"Usage\nFree plan\nWeekly limit resets on Oct 12\n15% used\n"
|
|
"Redeem invite code\n")
|
|
|
|
|
|
class FakeWS:
|
|
"""Websocket double; recv echoes the last sent id."""
|
|
|
|
def __init__(self):
|
|
self.sent = []
|
|
self.closed = False
|
|
|
|
def send(self, raw):
|
|
self.sent.append(json.loads(raw))
|
|
|
|
def recv(self):
|
|
return json.dumps({"id": self.sent[-1]["id"], "result": {}})
|
|
|
|
def close(self):
|
|
self.closed = True
|
|
|
|
|
|
class MouseTests(unittest.TestCase):
|
|
def test_real_click_sends_trusted_press_release(self):
|
|
ws = FakeWS()
|
|
mouse.real_click(ws, 36.0, 468.0)
|
|
self.assertEqual(len(ws.sent), 2)
|
|
press, release = ws.sent
|
|
self.assertEqual(press["method"], "Input.dispatchMouseEvent")
|
|
self.assertEqual(press["params"]["type"], "mousePressed")
|
|
self.assertEqual(release["params"]["type"], "mouseReleased")
|
|
self.assertEqual((press["params"]["x"], press["params"]["y"]),
|
|
(36.0, 468.0))
|
|
self.assertNotEqual(press["id"], release["id"])
|
|
|
|
def test_real_click_timeout_raises(self):
|
|
ws = mock.Mock()
|
|
ws.recv.side_effect = RuntimeError("boom")
|
|
with self.assertRaises(mouse.MouseError):
|
|
mouse.real_click(ws, 1, 2, timeout=0.01)
|
|
|
|
def test_escape_and_close_swallow_errors(self):
|
|
ws = mock.Mock()
|
|
ws.close.side_effect = RuntimeError("boom")
|
|
mouse.close(ws) # must not raise
|
|
with mock.patch.object(mouse, "cdp_evaluate",
|
|
side_effect=RuntimeError("boom")):
|
|
self.assertIsNone(mouse.escape(ws))
|
|
|
|
|
|
class DialogTests(unittest.TestCase):
|
|
def setUp(self):
|
|
p = mock.patch("time.sleep")
|
|
p.start()
|
|
self.addCleanup(p.stop)
|
|
|
|
def test_tab_names_complete(self):
|
|
self.assertEqual(len(TAB_NAMES), 10)
|
|
self.assertEqual(sorted(TAB_MODULES), sorted(TAB_NAMES))
|
|
|
|
def test_open_settings_success(self):
|
|
with mock.patch.object(dialog, "cdp_evaluate",
|
|
side_effect=[{"x": 1, "y": 2},
|
|
"CLICKED", True]), \
|
|
mock.patch.object(dialog, "real_click") as click:
|
|
self.assertTrue(dialog.open_settings(mock.Mock()))
|
|
click.assert_called_once_with(mock.ANY, 1, 2)
|
|
|
|
def test_open_settings_dock_missing(self):
|
|
with mock.patch.object(dialog, "cdp_evaluate",
|
|
side_effect=[None]), \
|
|
mock.patch.object(dialog, "real_click") as click:
|
|
self.assertFalse(dialog.open_settings(mock.Mock(), tries=1))
|
|
click.assert_not_called()
|
|
|
|
def test_open_settings_retries_then_succeeds(self):
|
|
seq = [{"x": 1, "y": 2}, "CLICKED", False,
|
|
{"x": 1, "y": 2}, "CLICKED", True]
|
|
with mock.patch.object(dialog, "cdp_evaluate",
|
|
side_effect=seq), \
|
|
mock.patch.object(dialog, "real_click"):
|
|
self.assertTrue(dialog.open_settings(mock.Mock(), tries=2))
|
|
|
|
def test_goto_tab_clicks(self):
|
|
with mock.patch.object(dialog, "cdp_evaluate",
|
|
return_value="CLICKED"):
|
|
self.assertTrue(dialog.goto_tab(mock.Mock(), "General"))
|
|
|
|
def test_goto_tab_no_match(self):
|
|
with mock.patch.object(dialog, "cdp_evaluate",
|
|
return_value="NO_TAB"):
|
|
self.assertFalse(dialog.goto_tab(mock.Mock(), "General"))
|
|
|
|
def test_dialog_text_none_on_error(self):
|
|
with mock.patch.object(dialog, "cdp_evaluate",
|
|
side_effect=RuntimeError("boom")):
|
|
self.assertIsNone(dialog.dialog_text(mock.Mock()))
|
|
|
|
def test_dialog_text_rejects_non_string(self):
|
|
with mock.patch.object(dialog, "cdp_evaluate", return_value=123):
|
|
self.assertIsNone(dialog.dialog_text(mock.Mock()))
|
|
|
|
def test_describe_rows_rejects_non_list(self):
|
|
with mock.patch.object(dialog, "goto_tab", return_value=True), \
|
|
mock.patch.object(dialog, "cdp_evaluate",
|
|
return_value="error"):
|
|
self.assertEqual(dialog.describe_rows(mock.Mock(),
|
|
"Permissions"), [])
|
|
|
|
def test_click_row_success_and_no_match(self):
|
|
with mock.patch.object(dialog, "cdp_evaluate",
|
|
side_effect=["CLICKED", "CLICKED",
|
|
"Websites\nrows"]):
|
|
self.assertTrue(dialog.click_row(mock.Mock(), "Websites",
|
|
"Permissions"))
|
|
with mock.patch.object(dialog, "cdp_evaluate",
|
|
side_effect=["CLICKED", "NO_MATCH"]):
|
|
self.assertFalse(dialog.click_row(mock.Mock(), "Websites",
|
|
"Permissions"))
|
|
|
|
def test_click_row_tab_failure(self):
|
|
with mock.patch.object(dialog, "cdp_evaluate",
|
|
return_value="NO_TAB") as cdp:
|
|
self.assertFalse(dialog.click_row(mock.Mock(), "Websites",
|
|
"Permissions"))
|
|
self.assertEqual(cdp.call_count, 1)
|
|
|
|
def test_row_js_excludes_tab_rail(self):
|
|
blob = json.dumps(dialog.TAB_NAMES)
|
|
self.assertIn(blob, dialog.JS_CLICK_ROW_TMPL)
|
|
self.assertIn(blob, dialog.JS_DESCRIBE_ROWS)
|
|
|
|
def test_describe_rows_maps(self):
|
|
rows = [{"name": "Websites", "help": "1 site", "extra": ""}]
|
|
with mock.patch.object(dialog, "goto_tab", return_value=True), \
|
|
mock.patch.object(dialog, "cdp_evaluate", return_value=rows):
|
|
self.assertEqual(dialog.describe_rows(mock.Mock(),
|
|
"Permissions"), rows)
|
|
|
|
|
|
class ControlsTests(unittest.TestCase):
|
|
def setUp(self):
|
|
p = mock.patch("time.sleep")
|
|
p.start()
|
|
self.addCleanup(p.stop)
|
|
|
|
def test_listers_empty_on_failure(self):
|
|
with mock.patch.object(controls, "cdp_evaluate",
|
|
side_effect=RuntimeError("boom")):
|
|
self.assertEqual(controls.list_radios(mock.Mock()), [])
|
|
self.assertEqual(controls.list_switches(mock.Mock()), [])
|
|
|
|
def test_set_radio_synthetic_success_skips_real_click(self):
|
|
rows = [{"heading": "Connector defaults", "value": "auto_allow",
|
|
"checked": True}]
|
|
with mock.patch.object(controls, "cdp_evaluate",
|
|
side_effect=["CLICKED", rows]), \
|
|
mock.patch.object(controls, "real_click") as click:
|
|
self.assertTrue(controls.set_radio_by_heading(
|
|
mock.Mock(), "Connector defaults", "auto_allow"))
|
|
click.assert_not_called()
|
|
|
|
def test_set_radio_falls_back_to_trusted_click(self):
|
|
off = [{"heading": "Connector defaults", "value": "auto_allow",
|
|
"checked": False}]
|
|
on = [{"heading": "Connector defaults", "value": "auto_allow",
|
|
"checked": True}]
|
|
with mock.patch.object(controls, "cdp_evaluate",
|
|
side_effect=["CLICKED"] + [off] * 10
|
|
+ [{"x": 5, "y": 6}, on]), \
|
|
mock.patch.object(controls, "real_click") as click:
|
|
self.assertTrue(controls.set_radio_by_heading(
|
|
mock.Mock(), "Connector defaults", "auto_allow"))
|
|
click.assert_called_once()
|
|
|
|
def test_set_radio_no_rect_fails(self):
|
|
off = [{"heading": "Connector defaults", "value": "auto_allow",
|
|
"checked": False}]
|
|
with mock.patch.object(controls, "cdp_evaluate",
|
|
side_effect=["CLICKED"] + [off] * 10
|
|
+ [None]), \
|
|
mock.patch.object(controls, "real_click"):
|
|
self.assertFalse(controls.set_radio_by_heading(
|
|
mock.Mock(), "Connector defaults", "auto_allow"))
|
|
|
|
def test_switch_state_unique_and_ambiguous(self):
|
|
one = [{"label": "Transparent proxy row", "aria": "",
|
|
"checked": False}]
|
|
two = one + [{"label": "Transparent proxy clone", "aria": "",
|
|
"checked": True}]
|
|
with mock.patch.object(controls, "cdp_evaluate",
|
|
return_value=one):
|
|
self.assertIs(controls.switch_state(mock.Mock(),
|
|
"Transparent proxy"), False)
|
|
with mock.patch.object(controls, "cdp_evaluate",
|
|
return_value=two):
|
|
self.assertIsNone(controls.switch_state(mock.Mock(),
|
|
"Transparent proxy"))
|
|
with mock.patch.object(controls, "cdp_evaluate", return_value=[]):
|
|
self.assertIsNone(controls.switch_state(mock.Mock(), "nope"))
|
|
|
|
def test_set_switch_noop_when_already_there(self):
|
|
one = [{"label": "Transparent proxy row", "aria": "",
|
|
"checked": True}]
|
|
with mock.patch.object(controls, "cdp_evaluate",
|
|
return_value=one) as cdp:
|
|
self.assertTrue(controls.set_switch(mock.Mock(),
|
|
"Transparent proxy", True))
|
|
self.assertEqual(cdp.call_count, 1) # list only, no click
|
|
|
|
def test_set_switch_flips(self):
|
|
off = [{"label": "Transparent proxy row", "aria": "",
|
|
"checked": False}]
|
|
on = [{"label": "Transparent proxy row", "aria": "",
|
|
"checked": True}]
|
|
with mock.patch.object(controls, "cdp_evaluate",
|
|
side_effect=[off, "CLICKED", on]):
|
|
self.assertTrue(controls.set_switch(mock.Mock(),
|
|
"Transparent proxy", True))
|
|
|
|
def test_set_switch_polls_then_lands(self):
|
|
off = [{"label": "Transparent proxy row", "aria": "",
|
|
"checked": False}]
|
|
on = [{"label": "Transparent proxy row", "aria": "",
|
|
"checked": True}]
|
|
with mock.patch.object(controls, "cdp_evaluate",
|
|
side_effect=[off, "CLICKED", off, off, on]):
|
|
self.assertTrue(controls.set_switch(mock.Mock(),
|
|
"Transparent proxy", True))
|
|
|
|
def test_listers_reject_wrong_types(self):
|
|
with mock.patch.object(controls, "cdp_evaluate",
|
|
return_value="error"):
|
|
self.assertEqual(controls.list_radios(mock.Mock()), [])
|
|
self.assertEqual(controls.list_switches(mock.Mock()), [])
|
|
|
|
|
|
class TogglesTests(unittest.TestCase):
|
|
def test_resolve_static_website_protocol(self):
|
|
spec = toggles.resolve_toggle("permissions.web_access")
|
|
self.assertEqual(spec["kind"], "radio-heading")
|
|
spec = toggles.resolve_toggle("permissions.websites:example.com")
|
|
self.assertEqual((spec["kind"], spec["host"]),
|
|
("website", "example.com"))
|
|
spec = toggles.resolve_toggle("permissions.protocols:mcp-sse")
|
|
self.assertEqual(spec["kind"], "protocol")
|
|
spec = toggles.resolve_toggle(
|
|
"permissions.protocols:Agent Skills endpoints")
|
|
self.assertEqual(spec["label"], "Agent Skills endpoints")
|
|
|
|
def test_resolve_protocol_network_and_substring(self):
|
|
spec = toggles.resolve_toggle("permissions.protocols:outbound-ssh")
|
|
self.assertEqual(spec["label"], "Outbound SSH")
|
|
spec = toggles.resolve_toggle("permissions.protocols:ssh")
|
|
self.assertEqual(spec["label"], "Outbound SSH")
|
|
with self.assertRaises(toggles.MenuError):
|
|
toggles.resolve_toggle("permissions.protocols:mail")
|
|
|
|
def test_resolve_unknowns_raise_before_cdp(self):
|
|
for bad in ("nope", "", "permissions.websites:",
|
|
"permissions.protocols:nope"):
|
|
with self.assertRaises(toggles.MenuError, msg=bad):
|
|
toggles.resolve_toggle(bad)
|
|
|
|
def test_normalize_onoff_table(self):
|
|
for yes in ("on", "ON", "true", "1", "yes", True):
|
|
self.assertTrue(toggles.normalize_onoff(yes), yes)
|
|
for no in ("off", "OFF", "false", "0", "no", False):
|
|
self.assertFalse(toggles.normalize_onoff(no), no)
|
|
for bad in ("maybe", "", None, 42):
|
|
self.assertIsNone(toggles.normalize_onoff(bad), bad)
|
|
|
|
def test_get_toggle_bad_node_and_toggle_raise(self):
|
|
with mock.patch.object(toggles, "get_cdp_ws") as g:
|
|
with self.assertRaises(toggles.MenuError):
|
|
toggles.get_toggle("nope", "permissions.web_access")
|
|
with self.assertRaises(toggles.MenuError):
|
|
toggles.get_toggle("pip", "nope")
|
|
g.assert_not_called()
|
|
|
|
def test_get_toggle_cdp_down_returns_error(self):
|
|
with mock.patch.object(toggles, "get_cdp_ws",
|
|
side_effect=ConnectionError("refused")):
|
|
res = toggles.get_toggle("pip", "permissions.web_access")
|
|
self.assertFalse(res["ok"])
|
|
self.assertIn("refused", res["error"])
|
|
|
|
def test_get_toggle_radio_heading(self):
|
|
rows = [{"heading": "Web access defaults", "value": "auto_allow",
|
|
"checked": False},
|
|
{"heading": "Web access defaults", "value": "always_ask",
|
|
"checked": True}]
|
|
with mock.patch.object(toggles, "get_cdp_ws",
|
|
return_value=(mock.Mock(), {})), \
|
|
mock.patch.object(dialog, "open_settings",
|
|
return_value=True), \
|
|
mock.patch.object(dialog, "goto_tab", return_value=True), \
|
|
mock.patch.object(controls, "list_radios", return_value=rows):
|
|
res = toggles.get_toggle("pip", "permissions.web_access")
|
|
self.assertEqual((res["ok"], res["value"]), (True, "always_ask"))
|
|
|
|
def test_set_toggle_bad_value_raises_before_cdp(self):
|
|
with mock.patch.object(toggles, "get_cdp_ws") as g:
|
|
with self.assertRaises(toggles.MenuError):
|
|
toggles.set_toggle("pip", "permissions.web_access",
|
|
"sometimes")
|
|
with self.assertRaises(toggles.MenuError):
|
|
toggles.set_toggle("pip", "permissions.websites:x.com",
|
|
"sometimes")
|
|
g.assert_not_called()
|
|
|
|
def test_set_toggle_success_with_readback(self):
|
|
with mock.patch.object(toggles, "get_cdp_ws",
|
|
return_value=(mock.Mock(), {})), \
|
|
mock.patch.object(dialog, "open_settings",
|
|
return_value=True), \
|
|
mock.patch.object(dialog, "goto_tab", return_value=True), \
|
|
mock.patch.object(controls, "set_radio_by_heading",
|
|
return_value=True), \
|
|
mock.patch.object(toggles, "get_toggle",
|
|
return_value={"ok": True,
|
|
"value": "always_ask"}):
|
|
res = toggles.set_toggle("pip", "permissions.web_access",
|
|
"always_ask")
|
|
self.assertEqual((res["ok"], res["value"]), (True, "always_ask"))
|
|
self.assertNotIn("readback_only", res)
|
|
|
|
def test_set_toggle_readback_only_success(self):
|
|
with mock.patch.object(toggles, "get_cdp_ws",
|
|
return_value=(mock.Mock(), {})), \
|
|
mock.patch.object(dialog, "open_settings",
|
|
return_value=True), \
|
|
mock.patch.object(dialog, "goto_tab", return_value=True), \
|
|
mock.patch.object(controls, "set_radio_by_heading",
|
|
return_value=False), \
|
|
mock.patch.object(toggles, "get_toggle",
|
|
return_value={"ok": True,
|
|
"value": "always_ask"}):
|
|
res = toggles.set_toggle("pip", "permissions.web_access",
|
|
"always_ask")
|
|
self.assertTrue(res["ok"])
|
|
self.assertTrue(res["readback_only"])
|
|
|
|
def test_set_toggle_readback_mismatch_fails(self):
|
|
with mock.patch.object(toggles, "get_cdp_ws",
|
|
return_value=(mock.Mock(), {})), \
|
|
mock.patch.object(dialog, "open_settings",
|
|
return_value=True), \
|
|
mock.patch.object(dialog, "goto_tab", return_value=True), \
|
|
mock.patch.object(controls, "set_radio_by_heading",
|
|
return_value=True), \
|
|
mock.patch.object(toggles, "get_toggle",
|
|
return_value={"ok": True,
|
|
"value": "auto_allow"}):
|
|
res = toggles.set_toggle("pip", "permissions.web_access",
|
|
"always_ask")
|
|
self.assertFalse(res["ok"])
|
|
self.assertIn("readback", res["error"])
|
|
|
|
def test_list_toggles_bad_tab_raises(self):
|
|
with self.assertRaises(toggles.MenuError):
|
|
toggles.list_toggles("pip", tab="Nope")
|
|
|
|
def test_list_toggles_aggregates(self):
|
|
with mock.patch.object(toggles, "get_cdp_ws",
|
|
return_value=(mock.Mock(), {})), \
|
|
mock.patch.object(dialog, "open_settings",
|
|
return_value=True), \
|
|
mock.patch("hatch_menu.tabs.permissions.all_toggles",
|
|
return_value={"permissions.web_access":
|
|
"always_ask"}), \
|
|
mock.patch("hatch_menu.tabs.data_controls.ai_improvement",
|
|
return_value=True), \
|
|
mock.patch("hatch_menu.tabs.general.theme",
|
|
return_value="blue"):
|
|
res = toggles.list_toggles("pip")
|
|
self.assertTrue(res["ok"])
|
|
self.assertEqual(res["toggles"]["permissions.web_access"],
|
|
"always_ask")
|
|
self.assertEqual(res["toggles"]["data_controls.ai_improvement"],
|
|
"on")
|
|
self.assertEqual(res["toggles"]["general.theme"], "blue")
|
|
|
|
def test_describe_tab(self):
|
|
with mock.patch.object(toggles, "get_cdp_ws",
|
|
return_value=(mock.Mock(), {})), \
|
|
mock.patch.object(dialog, "open_settings",
|
|
return_value=True), \
|
|
mock.patch.object(dialog, "goto_tab", return_value=True), \
|
|
mock.patch("hatch_menu.tabs.general.describe",
|
|
return_value={"theme": "blue"}):
|
|
res = toggles.describe_tab("pip", "General")
|
|
self.assertEqual(res["inventory"], {"theme": "blue"})
|
|
with self.assertRaises(toggles.MenuError):
|
|
toggles.describe_tab("pip", "Nope")
|
|
|
|
|
|
class TabContractTests(unittest.TestCase):
|
|
def test_general_parse_pip(self):
|
|
u = general.parse_usage_text(PIP_GENERAL)
|
|
self.assertEqual(u["weekly"], {"pct_used": 100,
|
|
"resets_on": "Oct 10"})
|
|
self.assertEqual(u["additional"]["tokens_left"], "901M")
|
|
self.assertEqual(u["additional"]["pct_used"], 10)
|
|
self.assertTrue(u["additional"]["never_expires"])
|
|
|
|
def test_general_parse_646_partial(self):
|
|
u = general.parse_usage_text(SIX_GENERAL)
|
|
self.assertEqual(u["weekly"]["pct_used"], 100)
|
|
self.assertIsNone(u["weekly"]["resets_on"])
|
|
self.assertEqual(u["additional"]["tokens_left"], "0")
|
|
|
|
def test_general_parse_empty_and_full_month(self):
|
|
self.assertIsNone(general.parse_usage_text(""))
|
|
self.assertIsNone(general.parse_usage_text(None))
|
|
u = general.parse_usage_text("Weekly limit resets on October 9")
|
|
self.assertEqual(u["weekly"]["resets_on"], "October 9")
|
|
|
|
def test_general_theme(self):
|
|
rows = [{"value": "default", "checked": False},
|
|
{"value": "blue", "checked": True}]
|
|
with mock.patch.object(dialog, "goto_tab", return_value=True), \
|
|
mock.patch.object(controls, "list_radios", return_value=rows):
|
|
self.assertEqual(general.theme(mock.Mock()), "blue")
|
|
with mock.patch.object(dialog, "goto_tab", return_value=False):
|
|
self.assertIsNone(general.theme(mock.Mock()))
|
|
|
|
def test_protocol_resolution(self):
|
|
self.assertEqual(permissions.resolve_protocol("mcp-sse"),
|
|
"Model Context Protocol servers (SSE)")
|
|
self.assertEqual(permissions.resolve_protocol(
|
|
"Agent Skills endpoints"), "Agent Skills endpoints")
|
|
self.assertEqual(permissions.resolve_protocol("outbound-ssh"),
|
|
"Outbound SSH")
|
|
self.assertEqual(permissions.resolve_protocol("Outbound SSH"),
|
|
"Outbound SSH")
|
|
self.assertEqual(permissions.resolve_protocol("ssh"),
|
|
"Outbound SSH")
|
|
self.assertEqual(permissions.resolve_protocol("tcp"),
|
|
"Other TCP connections")
|
|
self.assertIsNone(permissions.resolve_protocol("mail"))
|
|
self.assertIsNone(permissions.resolve_protocol("nope"))
|
|
self.assertIsNone(permissions.resolve_protocol(""))
|
|
self.assertIsNone(permissions.resolve_protocol(None))
|
|
|
|
def test_website_mode_rejects_bad_mode_without_cdp(self):
|
|
with mock.patch.object(permissions, "_eval") as ev:
|
|
self.assertFalse(permissions.set_website_mode(
|
|
mock.Mock(), "x.com", "Sometimes"))
|
|
ev.assert_not_called()
|
|
|
|
def test_set_website_noop_without_click(self):
|
|
rows = [{"host": "x.com", "mode": "Allow", "x": 1, "y": 2}]
|
|
with mock.patch.object(permissions, "_websites_raw",
|
|
return_value=rows), \
|
|
mock.patch.object(permissions, "_back_to_root",
|
|
return_value=True), \
|
|
mock.patch.object(permissions, "real_click") as click, \
|
|
mock.patch("time.sleep"):
|
|
self.assertTrue(permissions.set_website_mode(
|
|
mock.Mock(), "x.com", "Allow"))
|
|
click.assert_not_called()
|
|
|
|
def test_set_website_absent_host_fails(self):
|
|
with mock.patch.object(permissions, "_websites_raw",
|
|
return_value=[]), \
|
|
mock.patch.object(permissions, "_back_to_root",
|
|
return_value=True), \
|
|
mock.patch("time.sleep"):
|
|
self.assertFalse(permissions.set_website_mode(
|
|
mock.Mock(), "y.com", "Ask"))
|
|
|
|
def test_set_website_ask_verifies_by_absence(self):
|
|
rows = [{"host": "x.com", "mode": "Allow", "x": 1, "y": 2}]
|
|
items = [{"text": "Allow"}, {"text": "Ask"}, {"text": "Deny"}]
|
|
with mock.patch.object(permissions, "_websites_raw",
|
|
return_value=rows), \
|
|
mock.patch.object(permissions, "_back_to_root",
|
|
return_value=True), \
|
|
mock.patch.object(permissions, "real_click"), \
|
|
mock.patch.object(permissions, "_eval",
|
|
side_effect=[items, "CLICKED", []]), \
|
|
mock.patch("time.sleep"):
|
|
self.assertTrue(permissions.set_website_mode(
|
|
mock.Mock(), "x.com", "Ask"))
|
|
|
|
def test_stable_rows_waits_for_agreement(self):
|
|
rows = [{"host": "x.com"}]
|
|
with mock.patch.object(permissions, "_eval",
|
|
side_effect=[None, rows, rows]), \
|
|
mock.patch("time.sleep"):
|
|
self.assertEqual(
|
|
permissions._stable_rows(mock.Mock(), "js"), rows)
|
|
|
|
def test_stable_rows_gives_up(self):
|
|
with mock.patch.object(permissions, "_eval", return_value=None), \
|
|
mock.patch("time.sleep"):
|
|
self.assertIsNone(
|
|
permissions._stable_rows(mock.Mock(), "js"))
|
|
|
|
def test_back_to_root_prefers_back_affordance(self):
|
|
with mock.patch.object(dialog, "go_back", return_value=True), \
|
|
mock.patch.object(dialog, "dialog_text",
|
|
return_value="Manage permissions\nrows"):
|
|
self.assertTrue(permissions._back_to_root(mock.Mock()))
|
|
|
|
def test_theme_values_match_live(self):
|
|
self.assertIn("avatar", general.THEME_VALUES)
|
|
self.assertNotIn("match", general.THEME_VALUES)
|
|
|
|
def test_data_controls_pinned_label(self):
|
|
self.assertEqual(data_controls.SWITCH_LABEL,
|
|
"Help improve our AI models")
|
|
sws = [{"label": "Something else", "aria": "", "checked": False},
|
|
{"label": "Help improve our AI models", "aria": "",
|
|
"checked": True}]
|
|
with mock.patch.object(dialog, "goto_tab", return_value=True), \
|
|
mock.patch.object(controls, "list_switches",
|
|
return_value=sws):
|
|
self.assertTrue(data_controls.ai_improvement(mock.Mock()))
|
|
|
|
def test_data_controls_single_switch(self):
|
|
one = [{"label": "Help improve the model", "aria": "",
|
|
"checked": True}]
|
|
with mock.patch.object(dialog, "goto_tab", return_value=True), \
|
|
mock.patch.object(controls, "list_switches",
|
|
return_value=one):
|
|
self.assertTrue(data_controls.ai_improvement(mock.Mock()))
|
|
with mock.patch.object(dialog, "goto_tab", return_value=True), \
|
|
mock.patch.object(controls, "list_switches", return_value=[]):
|
|
self.assertIsNone(data_controls.ai_improvement(mock.Mock()))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|