From 4914b0c74ed669d6631897950ff1660ce91ef918 Mon Sep 17 00:00:00 2001 From: Simeon Simeonov Date: Thu, 23 Dec 2021 14:41:14 +0100 Subject: Initial commit and release for Christmas with love --- tests/conftest.py | 59 ++++++++++++++++++ tests/test_cli.py | 40 ++++++++++++ tests/test_envtoolkit_instance.py | 64 +++++++++++++++++++ tests/test_envtoolkit_instance_static.py | 102 +++++++++++++++++++++++++++++++ 4 files changed, 265 insertions(+) create mode 100644 tests/conftest.py create mode 100644 tests/test_cli.py create mode 100644 tests/test_envtoolkit_instance.py create mode 100644 tests/test_envtoolkit_instance_static.py (limited to 'tests') diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..1f70259 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,59 @@ +# etoolkit +# Copyright (C) 2021 Simeon Simeonov + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""Common fixtures""" +import pytest + + +@pytest.fixture +def config_data(): + """config_data for testing""" + return { + 'general': { + 'MASTER_PASSWORD_HASH': ( + 'pbkdf2_sha256$100000$kFOQkAPtStZ/Ny/O4501ygHGQnqh5Y+ySxF9qVHr' + 'iv8=$3BujuWzn3CfDnw4yiD9m3F+GjeW1MHHW40R/ThHNcn0=' + ) + }, + 'instances': { + 'default': {'ETOOLKIT_PROMPT': '(%i)'}, + 'dev': { + 'ETOOLKIT_PARENT': 'default', + 'PYTHONPATH': ':/home/user/.pythonpath', + 'DB_CONNECTION': ( + 'enc-val$1$Y/TBb1F3siHTw6qZg9ERzZfA8PLPf2CwGSQLpu9jYWw=$FT' + '5tS9o+ABvsxogIXpJim16Gz5SVtV8=' + ), + }, + 'secret': { + 'ETOOLKIT_PARENT': 'default', + 'ETOOLKIT_SENSITIVE': ['PASSWORD'], + 'GNUPGHOME': '%h/private/.gnupg', + 'PASSWORD': ( + 'enc-val$1$vIBcoCNiYrsDLtF41uLuSEnppBjhliD0B8jwcBJcj/c=$Kw' + 'OGe/y1dlxktDaCnJPIVNuaQ4Q7yNo=' + ), + }, + }, + } + + +@pytest.fixture +def password_hash(): + """password hash for testing, corresponding to 'the very secret passwd'""" + return ( + 'pbkdf2_sha256$100000$kFOQkAPtStZ/Ny/O4501ygHGQnqh5Y+ySxF9qVHr' + 'iv8=$3BujuWzn3CfDnw4yiD9m3F+GjeW1MHHW40R/ThHNcn0=' + ) diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..1bd9be1 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,40 @@ +# etoolkit +# Copyright (C) 2021 Simeon Simeonov + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""Tests for the CLI (etoolkit.__main__""" +import pytest + +import etoolkit +from etoolkit.__main__ import main + + +def test_help(capsys): + """Dummy test checking if the CLI is available at all""" + with pytest.raises(SystemExit) as exit_info: + main(['-h']) + assert exit_info.type == SystemExit + assert exit_info.value.code == 0 + assert capsys.readouterr().out.startswith('usage: etoolkit') + + +def test_version(capsys): + """Dummy test checking if the CLI is available at all""" + with pytest.raises(SystemExit) as exit_info: + main(['-v']) + assert exit_info.type == SystemExit + assert exit_info.value.code == 0 + assert capsys.readouterr().out.startswith( + f'etoolkit {etoolkit.__version__}' + ) diff --git a/tests/test_envtoolkit_instance.py b/tests/test_envtoolkit_instance.py new file mode 100644 index 0000000..2ffacd2 --- /dev/null +++ b/tests/test_envtoolkit_instance.py @@ -0,0 +1,64 @@ +# etoolkit +# Copyright (C) 2021 Simeon Simeonov + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""Tests for etoolkit.EtoolkitInstance""" +import pytest + +import etoolkit + + +def test_instantiation(config_data, password_hash): + """Tests for object instatiation""" + with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: + instance = etoolkit.EtoolkitInstance('devv', config_data) + assert exc_info.type is etoolkit.EtoolkitInstanceError + assert exc_info.value.args[0] == 'Unknown instance "devv"' + + instance = etoolkit.EtoolkitInstance('secret', config_data) + assert 'ETOOLKIT_PARENT' not in instance.raw_env_variables + assert 'ETOOLKIT_SENSITIVE' not in instance.raw_env_variables + assert 'DB_CONNECTION' not in instance.sensitive_env_variables + assert 'PASSWORD' in instance.sensitive_env_variables + assert instance.name == 'secret' + assert instance.master_password_hash == password_hash + assert instance.master_password is None + + +def test_get_environ(config_data): + """Tests the EtoolkitInstance.get_environ method""" + instance = etoolkit.EtoolkitInstance('secret', config_data) + with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: + env = instance.get_environ() + assert exc_info.type is etoolkit.EtoolkitInstanceError + assert exc_info.value.args[0] == 'Neither password or prompt function set' + + instance.master_password = 'The very secret passwd' # wrong passwd + with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: + env = instance.get_environ() + assert exc_info.type is etoolkit.EtoolkitInstanceError + assert exc_info.value.args[0].startswith( + 'Invalid tag when decrypting: enc-val$' + ) + + instance.master_password = 'the very secret passwd' # correct passwd + env = instance.get_environ() + assert isinstance(env, dict) + assert env['PASSWORD'] == 'secret2' + + +def test_get_full_name(config_data): + """Tests the EtoolkitInstance.get_full_name method""" + instance = etoolkit.EtoolkitInstance('secret', config_data) + assert instance.get_full_name('->') == 'default->secret' diff --git a/tests/test_envtoolkit_instance_static.py b/tests/test_envtoolkit_instance_static.py new file mode 100644 index 0000000..d7ac857 --- /dev/null +++ b/tests/test_envtoolkit_instance_static.py @@ -0,0 +1,102 @@ +# etoolkit +# Copyright (C) 2021 Simeon Simeonov + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""Tests for etoolkit.EtoolkitInstance static methods""" +import unittest.mock + +import pytest + +import etoolkit + + +@unittest.mock.patch('getpass.getpass') +def test_confirm_password_prompt(getpass, password_hash): + """Tests the static EtoolkitInstance.confirm_password_prompt method""" + getpass.return_value = 'the very secret passwd' + assert ( + etoolkit.EtoolkitInstance.confirm_password_prompt(password_hash) + == 'the very secret passwd' + ) + assert ( + etoolkit.EtoolkitInstance.confirm_password_prompt(password_hash, False) + == 'the very secret passwd' + ) + + +def test_decrypt(): + """Tests the static EtoolkitInstance.decrypt method""" + assert ( + etoolkit.EtoolkitInstance.decrypt( + 'the very secret passwd', + ( + 'enc-val$1$Y/TBb1F3siHTw6qZg9ERzZfA8PLPf2CwGSQLpu9jYWw=$FT5tS9' + 'o+ABvsxogIXpJim16Gz5SVtV8=' + ), + ) + == 'secret1' + ) + + # now test with modified edata + with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: + edata = ( + 'enc-val$1$Y/TBb1F3siHTw6qZg9ERzZfA8PLPf2CwGSQLpu9jYWw=$FT5tS9' + 'o+ABvsxogIXpJim17Gz5SVtV8=' + ) + etoolkit.EtoolkitInstance.decrypt( + 'the very secret passwd', edata + ) == 'secret1' + assert exc_info.type is etoolkit.EtoolkitInstanceError + assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}' + + +def test_encrypt(): + """Tests the static EtoolkitInstance.encrypt method""" + edata = etoolkit.EtoolkitInstance.encrypt('foo', 'bar') + assert edata.startswith('enc-val$') + assert len(edata) == 83 + # the edata should always be different because of random salting + assert edata != etoolkit.EtoolkitInstance.encrypt('foo', 'bar') + + +def test_get_new_password_hash(): + """Tests the static EtoolkitInstance.get_new_password_hash method""" + new_hash = etoolkit.EtoolkitInstance.get_new_password_hash( + 'the very secret passwd' + ) + # all pbkdf2 params are the same / hardcoded for the time being + assert new_hash.startswith('pbkdf2_sha256$100000$') + assert len(new_hash) == 110 + # the hash should always be different because of random salting + assert new_hash != etoolkit.EtoolkitInstance.get_new_password_hash( + 'the very secret passwd' + ) + + +def test_parse_value(): + """Tests the static EtoolkitInstance.parse_value method""" + assert ( + etoolkit.EtoolkitInstance.parse_value('t%bs%t', {'%b': 'e', '%t': 't'}) + == 'test' + ) + + +def test_password_matches(password_hash): + """Tests the static EtoolkitInstance.password_matches method""" + assert etoolkit.EtoolkitInstance.password_matches( + 'the very secret passwd', password_hash + ) + assert not etoolkit.EtoolkitInstance.password_matches( + 'the very secret passwdo', password_hash + ) -- cgit v1.3