From 2ed5dc8f73c72043a8d239fca306ac490ddd248c Mon Sep 17 00:00:00 2001 From: Raphael Marvie Date: Thu, 4 Oct 2018 16:16:50 +0200 Subject: [PATCH] Tests for utils.filesystem.write_to_file - non existent folder - non readable file - writable file with content Introducing a fake_open_for_write helper function to easily use mocking plumbery for mocking the opening and writing of text files in tests. Renamed fake_open helper to fake_open_for_read (one helper similarly named for read and for write) --- moulinette/utils/filesystem.py | 8 ++- tests/moulinette/utils/test_filesystem.py | 86 ++++++++++++++++++++--- 2 files changed, 83 insertions(+), 11 deletions(-) diff --git a/moulinette/utils/filesystem.py b/moulinette/utils/filesystem.py index 1117634f..68ce3c1a 100644 --- a/moulinette/utils/filesystem.py +++ b/moulinette/utils/filesystem.py @@ -103,9 +103,13 @@ def write_to_file(file_path, data, file_mode="w"): by append_to_file to avoid duplicating the code of this function. """ assert isinstance(data, str) or isinstance(data, list) - assert not os.path.isdir(file_path) - assert os.path.isdir(os.path.dirname(file_path)) + path = os.path.dirname(file_path) + if not os.path.isdir(path): + raise MoulinetteError(errno.ENOENT, + m18n.g('file_not_exist', path=path)) + + # FIXME could be replaced by a writelines # If data is a list, check elements are strings and build a single string if not isinstance(data, str): for element in data: diff --git a/tests/moulinette/utils/test_filesystem.py b/tests/moulinette/utils/test_filesystem.py index 81765b0d..ec7499ae 100644 --- a/tests/moulinette/utils/test_filesystem.py +++ b/tests/moulinette/utils/test_filesystem.py @@ -30,7 +30,7 @@ def test_read_file_raise_error_for_non_existant_file(isfile): @mock.patch('os.path.isfile') @mock.patch('builtins.open') -def test_read_file_raise_error_for_file_with_bad_permission(open, isfile): +def test_read_file_raise_error_for_non_openable_file(open, isfile): isfile.return_value = True # the file exists open.side_effect = IOError() # it cannot be opened @@ -38,6 +38,16 @@ def test_read_file_raise_error_for_file_with_bad_permission(open, isfile): filesystem.read_file('non_openable_file.txt') +@mock.patch('os.path.isfile') +@mock.patch('builtins.open') +def test_read_file_raise_error_for_non_readable_file(open, isfile): + isfile.return_value = True # the file exists + open.side_effect = Exception() # it cannot be read + + with pytest.raises(MoulinetteError): + filesystem.read_file('non_openable_file.txt') + + @mock.patch('os.path.isfile') @mock.patch('builtins.open') def test_read_file_return_file_content(open, isfile): @@ -132,16 +142,74 @@ def test_read_yaml_raise_error_on_bad_content(open, isfile): # Test writing a file ######################################################################## -#@mock.patch('builtins.open') -#def test_write_to_file_update_file_content(open): -# file_content = 'file content' -# open.return_value = fake_open_for_write() -# -# filesystem.write_file('fake_file.txt') -# -# assert content == file_content, 'read_file returned expected content' +@mock.patch('os.path.isdir') +@mock.patch('builtins.open') +def test_write_to_file_update_file_content(open, isdir): + isdir.return_value = True + open.return_value, fake_file = fake_open_for_write() + content = 'some content\n' + + filesystem.write_to_file('fake_file.txt', content) + + fake_file.write.assert_called_with(content) +@mock.patch('os.path.isdir') +@mock.patch('builtins.open') +def test_write_to_file_raise_error_for_improper_path(open, isdir): + isdir.return_value = False + content = 'some content\n' + + with pytest.raises(MoulinetteError): + filesystem.write_to_file('fake_file.txt', content) + + +@mock.patch('os.path.isdir') +@mock.patch('builtins.open') +def test_write_to_file_raise_error_when_file_cannot_be_open(open, isdir): + isdir.return_value = True # the folder exists + open.side_effect = IOError() # it cannot be opened + content = 'some content\n' + + with pytest.raises(MoulinetteError): + filesystem.write_to_file('non_openable_file.txt', content) + + +@mock.patch('os.path.isdir') +@mock.patch('builtins.open') +def test_write_to_file_raise_error_when_file_cannot_be_written(open, isdir): + isdir.return_value = True # the folder exists + # FIXME it could be write that raises Exception + open.side_effect = Exception() # it cannot be written + content = 'some content\n' + + with pytest.raises(MoulinetteError): + filesystem.write_to_file('non_writable_file.txt', content) + + +def fake_open_for_write(): + """Return a mock for opening a file to be writen to as well as the fake file + + This helper function is for mocking open() when used in a context manager. + + @mock.patch('builtins.open') + def test(open): + open.return_value, fake_file = fake_open_for_write() + function_using_open('filename.txt', 'content') + fake_file.write.assert_called('content') + + def function_using_open(filename, content): + with open(filename, 'w') as f: + content = f.write(content) + """ + fake_file = mock.Mock(write=mock.Mock()) + # open is used as a context manager + # - so we fake __enter__ to return the fake file + # - so we fake __exit__ to do nothing + return (mock.Mock( + __enter__=mock.Mock(return_value=fake_file), + __exit__=mock.Mock()), + fake_file) #