Refresh system/tool pins, install official Element Nightly, expand development tools, and finish desktop workflows with a shared charcoal/gold design. Retain host-specific updates and NixOS recovery generations.
128 lines
6.4 KiB
Python
128 lines
6.4 KiB
Python
"""Offline safety/behavior tests for desktop actions: no host desktop or power calls."""
|
|
import importlib.util
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
spec = importlib.util.spec_from_file_location("actions", sys.argv.pop(1))
|
|
actions = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(actions)
|
|
|
|
|
|
class ActionsTest(unittest.TestCase):
|
|
def test_picker_cancel_and_untrusted_output(self):
|
|
for code, text in [(1, "0"), (0, "$(touch /oops)"), (0, "-1"), (0, "9")]:
|
|
with patch.object(actions, "run", return_value=subprocess.CompletedProcess([], code, text)):
|
|
self.assertIsNone(actions.pick("Test", ["only item"]))
|
|
|
|
def test_picker_uses_index_and_sanitizes_labels(self):
|
|
with patch.object(actions, "run", return_value=subprocess.CompletedProcess([], 0, "0\n")) as run:
|
|
self.assertEqual(actions.pick("Test", ["title\nsecond\x00row"]), 0)
|
|
self.assertEqual(run.call_args.kwargs["input"], "title second row")
|
|
self.assertIn("--index", run.call_args.args)
|
|
|
|
def test_address_validation(self):
|
|
self.assertEqual(actions.address("0xabc123"), "address:0xabc123")
|
|
for value in ["", "0xABC; os.execute('bad')", "123", '"}']:
|
|
with self.assertRaises(ValueError):
|
|
actions.address(value)
|
|
|
|
def test_no_power_action_on_cancel(self):
|
|
with patch.object(actions, "pick", return_value=None), patch.object(actions, "run") as run:
|
|
actions.power()
|
|
run.assert_not_called()
|
|
with patch.object(actions, "pick", return_value=4), patch.object(actions, "confirm", return_value=False), patch.object(actions, "run") as run:
|
|
actions.power()
|
|
run.assert_not_called()
|
|
|
|
def test_screenshot_cancellation(self):
|
|
with patch.object(actions, "capture_geometry", return_value=None), patch.object(actions, "run") as run:
|
|
actions.screenshot()
|
|
run.assert_not_called()
|
|
|
|
def test_sensitive_and_locked_clipboard_not_stored(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
with patch.dict(os.environ, {"XDG_RUNTIME_DIR": directory, "CLIPBOARD_STATE": "sensitive"}), patch.object(actions, "run") as run:
|
|
actions.clipboard("store")
|
|
run.assert_not_called()
|
|
with patch.dict(os.environ, {"XDG_RUNTIME_DIR": directory, "CLIPBOARD_STATE": "data"}), patch.object(actions, "run") as run:
|
|
(actions.runtime() / "locked").touch()
|
|
actions.clipboard("store")
|
|
run.assert_not_called()
|
|
|
|
def test_clear_removes_database_not_just_entries(self):
|
|
with tempfile.TemporaryDirectory() as directory, patch.dict(os.environ, {"XDG_RUNTIME_DIR": directory}):
|
|
database = actions.runtime() / "clipboard.db"
|
|
database.write_bytes(b"old history pages")
|
|
actions.clipboard("clear")
|
|
self.assertFalse(database.exists())
|
|
|
|
def test_clipboard_roundtrip_preserves_bytes(self):
|
|
data = b" leading\ntrailing \x00\xff"
|
|
with tempfile.TemporaryDirectory() as directory, patch.dict(os.environ, {"XDG_RUNTIME_DIR": directory}):
|
|
(actions.runtime() / "clipboard.db").touch()
|
|
calls = []
|
|
def fake(*args, **kwargs):
|
|
calls.append((args, kwargs))
|
|
value = "4\tpreview\n" if args == ("cliphist", "list") else data
|
|
return subprocess.CompletedProcess(args, 0, value)
|
|
with patch.object(actions, "run", side_effect=fake), patch.object(actions, "pick", return_value=0):
|
|
actions.clipboard("pick")
|
|
self.assertEqual(calls[-1][0], ("wl-copy",))
|
|
self.assertEqual(calls[-1][1]["input"], data)
|
|
|
|
def test_web_search_is_url_encoded_not_shell(self):
|
|
result = subprocess.CompletedProcess([], 0, "hello; $(bad) & stuff")
|
|
with patch.object(actions, "run", return_value=result) as run:
|
|
actions.web_search()
|
|
self.assertEqual(run.call_args.args[:4], ("uwsm", "app", "--", "xdg-open"))
|
|
self.assertIn("hello%3B+%24%28bad%29+%26+stuff", run.call_args.args[4])
|
|
|
|
def test_lua_strings_do_not_use_json_control_escapes(self):
|
|
self.assertEqual(actions.lua('a"\n\x00'), '"\\097\\034\\010\\000"')
|
|
|
|
def test_lock_still_runs_when_clipboard_wipe_fails(self):
|
|
with tempfile.TemporaryDirectory() as directory, patch.dict(os.environ, {"XDG_RUNTIME_DIR": directory}):
|
|
with patch.object(actions, "clipboard", side_effect=subprocess.CalledProcessError(1, "cliphist")), patch.object(actions, "run", return_value=subprocess.CompletedProcess([], 0)) as run:
|
|
actions.lock_start()
|
|
run.assert_called_once_with("hyprlock", check=False)
|
|
self.assertFalse((actions.runtime() / "locked").exists())
|
|
|
|
def test_record_inhibitor_is_removed_on_failure(self):
|
|
calls = []
|
|
def fake(*args, **kwargs):
|
|
calls.append(args)
|
|
if args[0] == "wf-recorder":
|
|
raise subprocess.CalledProcessError(1, args)
|
|
return subprocess.CompletedProcess(args, 0)
|
|
with patch.object(actions.signal, "signal"), patch.object(actions, "run", side_effect=fake):
|
|
with self.assertRaises(subprocess.CalledProcessError):
|
|
actions.record_run(["-f", "test.mkv"])
|
|
self.assertEqual(calls[-1], ("swaync-client", "-Ir", "workstation-recording", "-sw"))
|
|
|
|
def test_only_osd_notifications_are_transient(self):
|
|
with patch.object(actions, "run") as run:
|
|
actions.notify("Saved", "Screenshot")
|
|
self.assertNotIn("int:transient:1", run.call_args.args)
|
|
actions.notify("Volume", "50%", 50)
|
|
self.assertIn("int:transient:1", run.call_args.args)
|
|
|
|
def test_display_timeout_restores_scale(self):
|
|
monitor = '[{"name":"eDP-1","width":1920,"height":1200,"scale":1.5}]'
|
|
calls = []
|
|
def fake(*args, **kwargs):
|
|
calls.append(args)
|
|
if args[0] == "fuzzel":
|
|
raise subprocess.TimeoutExpired(args, 15)
|
|
return subprocess.CompletedProcess(args, 0, "ok")
|
|
with patch.object(actions, "output", return_value=monitor), patch.object(actions, "pick", side_effect=[0, 0]), patch.object(actions, "run", side_effect=fake):
|
|
actions.displays()
|
|
self.assertIn("scale=1}", calls[0][-1])
|
|
self.assertIn("scale=1.5}", calls[-1][-1])
|
|
|
|
|
|
unittest.main()
|