# SPDX-License-Identifier: AGPL-3.0-or-later """ Test module for actions utilities that modify configuration. Verify that privileged actions perform as expected. See actions.py for a full description of the expectations. """ import json import os import subprocess from unittest.mock import Mock, call, patch import pytest from plinth import cfg from plinth.actions import privileged @pytest.fixture(name='popen') def fixture_popen(): """A fixture to patch subprocess.Popen called by privileged action.""" with patch('subprocess.Popen') as popen: def call_popen(command, **kwargs): write_fd = int(command[8]) if not isinstance(popen.called_with_write_fd, list): popen.called_with_write_fd = [] popen.called_with_write_fd.append(write_fd) os.write(write_fd, bytes(popen.return_value, encoding='utf-8')) proc = Mock() proc.communicate.return_value = ('', '') proc.returncode = 0 return proc popen.side_effect = call_popen yield popen def test_privileged_properties(): """Test that privileged decorator sets proper properties on the method.""" def func(): return wrapped_func = privileged(func) assert wrapped_func._privileged assert wrapped_func.__wrapped__ == func def test_privileged_argument_vararg_check(): """Test that privileged decorator checks for simple arguments.""" def func_with_varargs(*_args): return def func_with_kwargs(**_kwargs): return def func_with_kwonlyargs(*_args, _kwarg): return def func_with_kwonlydefaults(*_args, _kwargs='foo'): return for func in (func_with_varargs, func_with_kwargs, func_with_kwonlyargs, func_with_kwonlydefaults): with pytest.raises(SyntaxError): privileged(func) def test_privileged_argument_annotation_check(): """Test that privileged decorator checks for annotations to arguments.""" def func1(_a): return def func2(_a: int, _b): return def func_valid(_a: int, _b: dict[int, str]): return for func in (func1, func2): with pytest.raises(SyntaxError): privileged(func) privileged(func_valid) @patch('plinth.actions._get_privileged_action_module_name') def test_privileged_method_call(get_module_name, popen): """Test that privileged method calls the superuser action properly.""" def func_with_args(_a: int, _b: str, _c: int = 1, _d: str = 'dval', _e: str = 'eval'): return get_module_name.return_value = 'tests' popen.return_value = json.dumps({'result': 'success', 'return': 'bar'}) wrapped_func = privileged(func_with_args) return_value = wrapped_func(1, 'bval', None, _d='dnewval') assert return_value == 'bar' input_ = {'args': [1, 'bval', None], 'kwargs': {'_d': 'dnewval'}} input_ = json.dumps(input_) write_fd = popen.called_with_write_fd[0] close_from_fd = str(write_fd + 1) popen.assert_has_calls([ call([ 'sudo', '--non-interactive', '--close-from', close_from_fd, cfg.actions_dir + '/actions', 'tests', 'func_with_args', '--write-fd', str(write_fd) ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False, pass_fds=[write_fd]) ]) @patch('plinth.actions._get_privileged_action_module_name') def test_privileged_method_exceptions(get_module_name, popen): """Test that exceptions on privileged methods are return properly.""" def func_with_exception(): raise TypeError('type error') get_module_name.return_value = 'tests' popen.return_value = json.dumps({ 'result': 'exception', 'exception': { 'module': 'builtins', 'name': 'TypeError', 'args': ['type error'], 'traceback': [''] } }) wrapped_func = privileged(func_with_exception) with pytest.raises(TypeError, match='type error'): wrapped_func()