Files
nixconfig/desktop-actions-test.py
OpenAI Coding Assistant 527d989a01 feat: complete and visually audit the Hyprland workstation
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.
2026-09-05 23:22:27 -05:00

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()