FreedomBox/plinth/tests/test_actions_actions.py
Sunil Mohan Adapa 5a1f4b6647
actions: Allow actions to be called by other users
There is not much additional risk by doing this. This is needed in case of some
exceptional cases such as storage.validate_directory() which need to run as a
different user other than root.

Tests:

- Directory validation works in transmission and deluge.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
2022-10-08 18:50:49 -04:00

174 lines
5.7 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Test module for code that runs methods are privileged actions.
"""
import typing
from unittest.mock import call as mock_call
from unittest.mock import patch
import pytest
from plinth.actions import privileged
actions_name = 'actions'
@patch('importlib.import_module')
@patch('plinth.module_loader.get_module_import_path')
@patch('os.getuid')
def test_call_syntax_checks(getuid, get_module_import_path, import_module,
actions_module):
"""Test that calling a method results in proper syntax checks."""
call = actions_module._call
# Module name validation
getuid.return_value = 0
with pytest.raises(SyntaxError, match='Invalid module name'):
call('foo.bar', 'x-action', {})
# Module import test
get_module_import_path.return_value = 'plinth.modules.test_module'
import_module.side_effect = ModuleNotFoundError
with pytest.raises(SyntaxError, match='Specified module not found'):
call('test_module', 'x-action', {})
import_module.assert_has_calls(
[mock_call('plinth.modules.test_module.privileged')])
# Finding action in a module
module = type('', (), {})
import_module.side_effect = None
import_module.return_value = module
with pytest.raises(SyntaxError, match='Specified action not found'):
call('test_module', 'x-action', {})
# Checking if action is privileged
def unprivileged_func():
pass
setattr(module, 'func', unprivileged_func)
with pytest.raises(SyntaxError,
match='Specified action is not privileged action'):
call('test-module', 'func', {})
# Argument validation
@privileged
def func():
return 'foo'
setattr(module, 'func', func)
with pytest.raises(SyntaxError, match='Invalid arguments format'):
call('test-module', 'func', {})
# Successful call
return_value = call('test-module', 'func', {'args': [], 'kwargs': {}})
assert return_value == {'result': 'success', 'return': 'foo'}
# Exception call
@privileged
def exception_func():
raise RuntimeError('foo exception')
setattr(module, 'func', exception_func)
return_value = call('test-module', 'func', {'args': [], 'kwargs': {}})
assert return_value['result'] == 'exception'
assert return_value['exception']['module'] == 'builtins'
assert return_value['exception']['name'] == 'RuntimeError'
assert return_value['exception']['args'] == ('foo exception', )
assert isinstance(return_value['exception']['traceback'], list)
for line in return_value['exception']['traceback']:
assert isinstance(line, str)
def test_assert_valid_arguments(actions_module):
"""Test that checking valid arguments works."""
assert_valid = actions_module._assert_valid_arguments
values = [
None, [], 10, {}, {
'args': []
}, {
'kwargs': {}
}, {
'args': {},
'kwargs': {}
}, {
'args': [],
'kwargs': []
}
]
for value in values:
with pytest.raises(SyntaxError, match='Invalid arguments format'):
assert_valid(lambda: None, value)
def func(a: int, b: str, c: int = 3, d: str = 'foo'):
pass
with pytest.raises(SyntaxError, match='Too many arguments'):
assert_valid(func, {'args': [1, 2, 3], 'kwargs': {'c': 3, 'd': 4}})
with pytest.raises(SyntaxError, match='Too many arguments'):
assert_valid(func, {'args': [1, 2, 3, 4, 5], 'kwargs': {}})
with pytest.raises(SyntaxError, match='Too many arguments'):
assert_valid(func, {
'args': [],
'kwargs': {
'a': 1,
'b': '2',
'c': 3,
'd': '4',
'e': 5
}
})
with pytest.raises(SyntaxError, match='Argument not provided: b'):
assert_valid(func, {'args': [1], 'kwargs': {}})
with pytest.raises(SyntaxError, match='Unknown argument: e'):
assert_valid(func, {'args': [1, '2'], 'kwargs': {'e': 5}})
with pytest.raises(SyntaxError, match='Duplicate argument: c'):
assert_valid(func, {'args': [1, '2', 3], 'kwargs': {'c': 4}})
with pytest.raises(TypeError, match='Expected type str for arg #1'):
assert_valid(func, {'args': [1, 2], 'kwargs': {}})
with pytest.raises(TypeError, match='Expected type int for arg c'):
assert_valid(func, {'args': [1, '2'], 'kwargs': {'c': '3'}})
def test_assert_valid_type(actions_module):
"""Test that type validation works as expected."""
assert_valid = actions_module._assert_valid_type
assert_valid(None, None, typing.Any)
# Invalid values for int, str, float and Optional
values = [[1, bool], ['foo', int], [1, str], [1, float],
[1, typing.Optional[str]], [1.1, typing.Union[int, str]],
[1, list], [1, dict], [[1], list[str]],
[{
'a': 'b'
}, dict[int, str]], [{
1: 2
}, dict[int, str]]]
for value in values:
with pytest.raises(TypeError):
assert_valid('arg', *value)
# Valid values
assert_valid('arg', True, bool)
assert_valid('arg', 1, int)
assert_valid('arg', '1', str)
assert_valid('arg', 1.1, float)
assert_valid('arg', None, typing.Optional[int])
assert_valid('arg', 1, typing.Optional[int])
assert_valid('arg', 1, typing.Union[int, str])
assert_valid('arg', '1', typing.Union[int, str])
assert_valid('arg', [], list[int])
assert_valid('arg', ['foo'], list[str])
assert_valid('arg', {}, dict[int, str])
assert_valid('arg', {1: 'foo'}, dict[int, str])