Testing utils.filesystem.chown

- changing existing file owner with proper permissions
- changing existing folder owner recursively with proper permissions

- non existant or no permission prevent changing owner of file / folder

- new test for reading yaml from file, raising a parser error

Updated read_yaml to take care of ParserError from yaml lib
This commit is contained in:
Raphael Marvie 2018-10-05 11:35:48 +02:00 committed by ljf
parent b8d10b71c3
commit f9c041f02b
2 changed files with 152 additions and 8 deletions

View file

@ -1,13 +1,13 @@
# encoding: utf-8 # encoding: utf-8
import os import os
import yaml
import errno import errno
import shutil import shutil
import json import json
import grp import yaml
from pwd import getpwnam import grp
import pwd
from moulinette import m18n from moulinette import m18n
from moulinette.globals import CACHE_DIR from moulinette.globals import CACHE_DIR
@ -91,6 +91,11 @@ def read_yaml(file_path):
m18n.g('corrupted_yaml', m18n.g('corrupted_yaml',
ressource=file_path, error=str(e))) ressource=file_path, error=str(e)))
except yaml.parser.ParserError as e:
raise MoulinetteError(errno.EINVAL,
m18n.g('corrupted_yaml',
ressource=file_path, error=str(e)))
return loaded_yaml return loaded_yaml
@ -217,7 +222,7 @@ def chown(path, uid=None, gid=None, recursive=False):
# Retrieve uid/gid # Retrieve uid/gid
if isinstance(uid, str): if isinstance(uid, str):
try: try:
uid = getpwnam(uid).pw_uid uid = pwd.getpwnam(uid).pw_uid
except KeyError: except KeyError:
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.g('unknown_user', user=uid)) m18n.g('unknown_user', user=uid))

View file

@ -111,7 +111,7 @@ def test_read_yaml_return_file_content_as_yaml(open, isfile):
@mock.patch('os.path.isfile') @mock.patch('os.path.isfile')
@mock.patch('builtins.open') @mock.patch('builtins.open')
def test_read_yaml_raise_error_on_bad_content(open, isfile): def test_read_yaml_raise_error_on_very_bad_content(open, isfile):
isfile.return_value = True isfile.return_value = True
file_content = 'foo, bar-\n t:' file_content = 'foo, bar-\n t:'
open.return_value = fake_open_for_read(file_content) open.return_value = fake_open_for_read(file_content)
@ -120,6 +120,17 @@ def test_read_yaml_raise_error_on_bad_content(open, isfile):
content = filesystem.read_yaml('bad_file.yaml') content = filesystem.read_yaml('bad_file.yaml')
@mock.patch('os.path.isfile')
@mock.patch('builtins.open')
def test_read_yaml_raise_error_on_bad_content(open, isfile):
isfile.return_value = True
file_content = 'foo: bar-\n- to'
open.return_value = fake_open_for_read(file_content)
with pytest.raises(MoulinetteError):
content = filesystem.read_yaml('bad_file.yaml')
######################################################################## ########################################################################
# Test writing a file # Test writing a file
######################################################################## ########################################################################
@ -129,7 +140,7 @@ def test_read_yaml_raise_error_on_bad_content(open, isfile):
@mock.patch('os.path.isdir') @mock.patch('os.path.isdir')
@mock.patch('builtins.open') @mock.patch('builtins.open')
def test_write_to_file_update_file_content(open, isdir): def test_write_to_file_update_file_content_with_string(open, isdir):
# WARNING order is dependant on actual implementation # WARNING order is dependant on actual implementation
isdir.side_effect = [False, True] isdir.side_effect = [False, True]
open.return_value, fake_file = fake_open_for_write() open.return_value, fake_file = fake_open_for_write()
@ -140,6 +151,20 @@ def test_write_to_file_update_file_content(open, isdir):
fake_file.write.assert_called_with(content) fake_file.write.assert_called_with(content)
@mock.patch('os.path.isdir')
@mock.patch('builtins.open')
def test_write_to_file_update_file_content_with_list_of_string(open, isdir):
# WARNING order is dependant on actual implementation
isdir.side_effect = [False, True]
open.return_value, fake_file = fake_open_for_write()
content = ['some', 'content']
filesystem.write_to_file('fake/file.txt', content)
expected_content = 'some\ncontent'
fake_file.write.assert_called_with(expected_content)
@mock.patch('os.path.isdir') @mock.patch('os.path.isdir')
@mock.patch('builtins.open') @mock.patch('builtins.open')
def test_write_to_file_raise_error_for_folder_used_as_file(open, isdir): def test_write_to_file_raise_error_for_folder_used_as_file(open, isdir):
@ -413,7 +438,6 @@ def test_chmod_recursive_update_folder_permissions(chmod, isdir, walk):
calls = [mock.call('folder', mode), calls = [mock.call('folder', mode),
mock.call('folder/subfolder', mode), mock.call('folder/subfolder', mode),
mock.call('folder/file.txt', mode)] mock.call('folder/file.txt', mode)]
chmod.assert_has_calls(calls) chmod.assert_has_calls(calls)
@ -432,8 +456,123 @@ def test_chmod_recursive_update_folder_permissions_with_fmode(chmod, isdir, walk
calls = [mock.call('folder', mode), calls = [mock.call('folder', mode),
mock.call('folder/subfolder', mode), mock.call('folder/subfolder', mode),
mock.call('folder/file.txt', fmode)] mock.call('folder/file.txt', fmode)]
chmod.assert_has_calls(calls) chmod.assert_has_calls(calls)
########################################################################
# Chagin owner of file or folder
########################################################################
def test_chown_cannot_change_owner_without_providing_uid_or_guid():
filename = 'file.txt'
with pytest.raises(ValueError):
filesystem.chown(filename)
@mock.patch('os.chown')
def test_chown_change_owner_of_file_with_given_uid_as_id(chown):
filename = 'file.txt'
uid = 1000
filesystem.chown(filename, uid=uid)
chown.assert_called_with(filename, uid, -1)
@mock.patch('pwd.getpwnam')
@mock.patch('os.chown')
def test_chown_change_owner_of_file_with_given_uid_as_name(chown, getpwnam):
filename = 'file.txt'
name = 'jdoe'
uid = 1000
getpwnam.return_value = mock.Mock(pw_uid=uid)
filesystem.chown(filename, uid=name)
chown.assert_called_with(filename, uid, -1)
@mock.patch('pwd.getpwnam')
@mock.patch('os.chown')
def test_cannot_change_owner_of_file_with_unknown_user_name(chown, getpwnam):
filename = 'file.txt'
name = 'jdoe'
getpwnam.side_effect = KeyError
with pytest.raises(MoulinetteError):
filesystem.chown(filename, uid=name)
@mock.patch('os.chown')
def test_chown_change_owner_of_file_with_given_gid_as_id(chown):
filename = 'file.txt'
gid = 1000
filesystem.chown(filename, gid=gid)
chown.assert_called_with(filename, -1, gid)
@mock.patch('grp.getgrnam')
@mock.patch('os.chown')
def test_chown_change_owner_of_file_with_given_gid_as_name(chown, getgrnam):
filename = 'file.txt'
name = 'jdoe'
gid = 1000
getgrnam.return_value = mock.Mock(gr_gid=gid)
filesystem.chown(filename, gid=name)
chown.assert_called_with(filename, -1, gid)
@mock.patch('grp.getgrnam')
@mock.patch('os.chown')
def test_cannot_change_owner_of_file_with_unknown_group_name(chown, getgrnam):
filename = 'file.txt'
name = 'jdoe'
getgrnam.side_effect = KeyError
with pytest.raises(MoulinetteError):
filesystem.chown(filename, gid=name)
@mock.patch('os.chown')
def test_chown_cannot_change_owner_of_file_without_permission(chown):
filename = 'file.txt'
gid = 1000
chown.side_effect = PermissionError
with pytest.raises(MoulinetteError):
filesystem.chown(filename, gid=gid)
@mock.patch('os.chown')
def test_chown_cannot_change_owner_of_non_existant_file(chown):
filename = 'file.txt'
gid = 1000
chown.side_effect = FileNotFoundError
with pytest.raises(MoulinetteError):
filesystem.chown(filename, gid=gid)
@mock.patch('os.walk')
@mock.patch('os.path.isdir')
@mock.patch('os.chown')
def test_chown_recursive_update_folder_owner(chown, isdir, walk):
foldername = 'folder'
uid = 1000
gid = 1000
isdir.return_value = True # foldername is a folder
walk.return_value = [(foldername, ['subfolder'], ['file.txt'])]
filesystem.chown(foldername, uid=uid, gid=gid, recursive=True)
calls = [mock.call('folder', uid, gid),
mock.call('folder/subfolder', uid, gid),
mock.call('folder/file.txt', uid, gid)]
chown.assert_has_calls(calls)
# eof # eof