summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorSimeon Simeonov2022-03-04 20:48:20 +0100
committerSimeon Simeonov2022-03-04 20:48:20 +0100
commit907f80e1889781a69edded00126b3e78ddeb7b8e (patch)
tree20be15e81762d7a72e4caa40bda8b4301339efe4 /tests
parent4914b0c74ed669d6631897950ff1660ce91ef918 (diff)
Add the -P param, bash completion script and improve the README.md
Diffstat (limited to 'tests')
-rw-r--r--tests/conftest.py31
-rw-r--r--tests/test_cli.py97
-rw-r--r--tests/test_envtoolkit_instance.py8
-rw-r--r--tests/test_envtoolkit_instance_static.py43
4 files changed, 155 insertions, 24 deletions
diff --git a/tests/conftest.py b/tests/conftest.py
index 1f70259..0770adb 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,5 +1,5 @@
1# etoolkit 1# etoolkit
2# Copyright (C) 2021 Simeon Simeonov 2# Copyright (C) 2021-2022 Simeon Simeonov
3 3
4# This program is free software: you can redistribute it and/or modify 4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by 5# it under the terms of the GNU General Public License as published by
@@ -14,6 +14,8 @@
14# You should have received a copy of the GNU General Public License 14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>. 15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16"""Common fixtures""" 16"""Common fixtures"""
17import json
18
17import pytest 19import pytest
18 20
19 21
@@ -23,8 +25,8 @@ def config_data():
23 return { 25 return {
24 'general': { 26 'general': {
25 'MASTER_PASSWORD_HASH': ( 27 'MASTER_PASSWORD_HASH': (
26 'pbkdf2_sha256$100000$kFOQkAPtStZ/Ny/O4501ygHGQnqh5Y+ySxF9qVHr' 28 'pbkdf2_sha256$100000$uYpZM1VfAGq0CDZL2duITs076CQj+hIFEgx+F4m'
27 'iv8=$3BujuWzn3CfDnw4yiD9m3F+GjeW1MHHW40R/ThHNcn0=' 29 'n80o=$h3PSPLCd37fP15zKdW4CBGn7CXE+q5UiydaF3vbeZHo='
28 ) 30 )
29 }, 31 },
30 'instances': { 32 'instances': {
@@ -51,9 +53,26 @@ def config_data():
51 53
52 54
53@pytest.fixture 55@pytest.fixture
56def config_file(tmp_path, config_data):
57 """temporary config file for testing that includes config_data"""
58 cf = tmp_path / "etoolkit.json"
59 cf.write_text(json.dumps(config_data))
60 return str(cf)
61
62
63@pytest.fixture
64def non_random_bytes_32():
65 """always use the same bytes instead of os.urandom(32)"""
66 return (
67 b'\xb9\x8aY3U_\x00j\xb4\x086K\xd9\xdb\x88N'
68 b'\xcd;\xe8$#\xfa\x12\x05\x12\x0c~\x17\x89\xa7\xf3J'
69 )
70
71
72@pytest.fixture
54def password_hash(): 73def password_hash():
55 """password hash for testing, corresponding to 'the very secret passwd'""" 74 """password hash for testing, corresponding to 'The very secret passwd'"""
56 return ( 75 return (
57 'pbkdf2_sha256$100000$kFOQkAPtStZ/Ny/O4501ygHGQnqh5Y+ySxF9qVHr' 76 'pbkdf2_sha256$100000$uYpZM1VfAGq0CDZL2duITs076CQj+hIFEgx+F4mn80o=$h3'
58 'iv8=$3BujuWzn3CfDnw4yiD9m3F+GjeW1MHHW40R/ThHNcn0=' 77 'PSPLCd37fP15zKdW4CBGn7CXE+q5UiydaF3vbeZHo='
59 ) 78 )
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 1bd9be1..884e676 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,5 +1,5 @@
1# etoolkit 1# etoolkit
2# Copyright (C) 2021 Simeon Simeonov 2# Copyright (C) 2021-2022 Simeon Simeonov
3 3
4# This program is free software: you can redistribute it and/or modify 4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by 5# it under the terms of the GNU General Public License as published by
@@ -14,12 +14,86 @@
14# You should have received a copy of the GNU General Public License 14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>. 15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16"""Tests for the CLI (etoolkit.__main__""" 16"""Tests for the CLI (etoolkit.__main__"""
17import errno
18import os
19import unittest.mock
20
17import pytest 21import pytest
18 22
19import etoolkit 23import etoolkit
20from etoolkit.__main__ import main 24from etoolkit.__main__ import main
21 25
22 26
27@unittest.mock.patch('os.urandom')
28@unittest.mock.patch('builtins.input')
29def test_decrypt(binput, urandom, capsys, non_random_bytes_32, config_file):
30 """Tests encryption via the CLI interface"""
31 urandom.return_value = non_random_bytes_32
32 binput.return_value = (
33 'enc-val$1$uYpZM1VfAGq0CDZL2duITs076CQj+'
34 'hIFEgx+F4mn80o=$xdF/1S+R2MGlEQMCOLG6OjEuzw=='
35 )
36 with unittest.mock.patch.dict(
37 os.environ, {'ETOOLKIT_MASTER_PASSWORD': 'the very secret passwd'}
38 ):
39 with pytest.raises(SystemExit) as exit_info:
40 main(['-c', f'{config_file}', '-d'])
41 assert exit_info.type == SystemExit
42 assert exit_info.value.code == 0
43 assert capsys.readouterr().out.strip() == 'Decrypted value: bar'
44
45
46@unittest.mock.patch('os.urandom')
47@unittest.mock.patch('builtins.input', lambda *args: 'bar')
48def test_encrypt_with_echo(urandom, capsys, non_random_bytes_32, config_file):
49 """Tests encryption via the CLI interface"""
50 urandom.return_value = non_random_bytes_32
51 with unittest.mock.patch.dict(
52 os.environ, {'ETOOLKIT_MASTER_PASSWORD': 'the very secret passwd'}
53 ):
54 with pytest.raises(SystemExit) as exit_info:
55 main(['-c', f'{config_file}', '-e', '-E'])
56 assert exit_info.type == SystemExit
57 assert exit_info.value.code == 0
58 assert capsys.readouterr().out.strip() == (
59 'Encrypted value: enc-val$1$uYpZM1VfAGq0CDZL2duITs076CQj+'
60 'hIFEgx+F4mn80o=$xdF/1S+R2MGlEQMCOLG6OjEuzw=='
61 )
62
63
64@unittest.mock.patch('os.urandom')
65@unittest.mock.patch('getpass.getpass', lambda *args: 'bar')
66def test_encrypt_without_echo(gpass, capsys, non_random_bytes_32, config_file):
67 """Tests encryption via the CLI interface"""
68 gpass.return_value = non_random_bytes_32
69 with unittest.mock.patch.dict(
70 os.environ, {'ETOOLKIT_MASTER_PASSWORD': 'the very secret passwd'}
71 ):
72 with pytest.raises(SystemExit) as exit_info:
73 main(['-c', f'{config_file}', '-e'])
74 assert exit_info.type == SystemExit
75 assert exit_info.value.code == 0
76 assert capsys.readouterr().out.strip() == (
77 'Encrypted value: enc-val$1$uYpZM1VfAGq0CDZL2duITs076CQj+'
78 'hIFEgx+F4mn80o=$xdF/1S+R2MGlEQMCOLG6OjEuzw=='
79 )
80
81
82def test_list(capsys, config_file):
83 """Tests list via the CLI interface"""
84 with pytest.raises(SystemExit) as exit_info:
85 main(['-l'])
86 assert exit_info.type == SystemExit
87 assert exit_info.value.code == errno.EIO
88 with pytest.raises(SystemExit) as exit_info:
89 main(['-c', f'{config_file}', '-l'])
90 assert exit_info.type == SystemExit
91 assert exit_info.value.code == 0
92 assert capsys.readouterr().out.strip() == (
93 f'default{os.linesep}dev{os.linesep}secret'
94 )
95
96
23def test_help(capsys): 97def test_help(capsys):
24 """Dummy test checking if the CLI is available at all""" 98 """Dummy test checking if the CLI is available at all"""
25 with pytest.raises(SystemExit) as exit_info: 99 with pytest.raises(SystemExit) as exit_info:
@@ -29,6 +103,27 @@ def test_help(capsys):
29 assert capsys.readouterr().out.startswith('usage: etoolkit') 103 assert capsys.readouterr().out.startswith('usage: etoolkit')
30 104
31 105
106@unittest.mock.patch('os.urandom')
107@unittest.mock.patch('getpass.getpass')
108def test_generate_master_password_hash(
109 gpass,
110 urandom,
111 capsys,
112 non_random_bytes_32,
113):
114 """Tests master password hash generation via the CLI interface"""
115 urandom.return_value = non_random_bytes_32
116 gpass.return_value = 'The very secret passwd'
117 with pytest.raises(SystemExit) as exit_info:
118 main(['--generate-master-password-hash'])
119 assert exit_info.type == SystemExit
120 assert exit_info.value.code == 0
121 assert capsys.readouterr().out.strip() == (
122 'Master password hash: pbkdf2_sha256$100000$uYpZM1VfAGq0CDZL2duITs076'
123 'CQj+hIFEgx+F4mn80o=$h3PSPLCd37fP15zKdW4CBGn7CXE+q5UiydaF3vbeZHo='
124 )
125
126
32def test_version(capsys): 127def test_version(capsys):
33 """Dummy test checking if the CLI is available at all""" 128 """Dummy test checking if the CLI is available at all"""
34 with pytest.raises(SystemExit) as exit_info: 129 with pytest.raises(SystemExit) as exit_info:
diff --git a/tests/test_envtoolkit_instance.py b/tests/test_envtoolkit_instance.py
index 2ffacd2..9061dc8 100644
--- a/tests/test_envtoolkit_instance.py
+++ b/tests/test_envtoolkit_instance.py
@@ -1,5 +1,5 @@
1# etoolkit 1# etoolkit
2# Copyright (C) 2021 Simeon Simeonov 2# Copyright (C) 2021-22 Simeon Simeonov
3 3
4# This program is free software: you can redistribute it and/or modify 4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by 5# it under the terms of the GNU General Public License as published by
@@ -19,7 +19,7 @@ import pytest
19import etoolkit 19import etoolkit
20 20
21 21
22def test_instantiation(config_data, password_hash): 22def test_instantiation(config_data):
23 """Tests for object instatiation""" 23 """Tests for object instatiation"""
24 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: 24 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info:
25 instance = etoolkit.EtoolkitInstance('devv', config_data) 25 instance = etoolkit.EtoolkitInstance('devv', config_data)
@@ -32,7 +32,9 @@ def test_instantiation(config_data, password_hash):
32 assert 'DB_CONNECTION' not in instance.sensitive_env_variables 32 assert 'DB_CONNECTION' not in instance.sensitive_env_variables
33 assert 'PASSWORD' in instance.sensitive_env_variables 33 assert 'PASSWORD' in instance.sensitive_env_variables
34 assert instance.name == 'secret' 34 assert instance.name == 'secret'
35 assert instance.master_password_hash == password_hash 35 assert instance.master_password_hash == (
36 config_data['general']['MASTER_PASSWORD_HASH']
37 )
36 assert instance.master_password is None 38 assert instance.master_password is None
37 39
38 40
diff --git a/tests/test_envtoolkit_instance_static.py b/tests/test_envtoolkit_instance_static.py
index d7ac857..73a5f36 100644
--- a/tests/test_envtoolkit_instance_static.py
+++ b/tests/test_envtoolkit_instance_static.py
@@ -1,5 +1,5 @@
1# etoolkit 1# etoolkit
2# Copyright (C) 2021 Simeon Simeonov 2# Copyright (C) 2021-2022 Simeon Simeonov
3 3
4# This program is free software: you can redistribute it and/or modify 4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by 5# it under the terms of the GNU General Public License as published by
@@ -24,14 +24,14 @@ import etoolkit
24@unittest.mock.patch('getpass.getpass') 24@unittest.mock.patch('getpass.getpass')
25def test_confirm_password_prompt(getpass, password_hash): 25def test_confirm_password_prompt(getpass, password_hash):
26 """Tests the static EtoolkitInstance.confirm_password_prompt method""" 26 """Tests the static EtoolkitInstance.confirm_password_prompt method"""
27 getpass.return_value = 'the very secret passwd' 27 getpass.return_value = 'The very secret passwd'
28 assert ( 28 assert (
29 etoolkit.EtoolkitInstance.confirm_password_prompt(password_hash) 29 etoolkit.EtoolkitInstance.confirm_password_prompt(password_hash)
30 == 'the very secret passwd' 30 == 'The very secret passwd'
31 ) 31 )
32 assert ( 32 assert (
33 etoolkit.EtoolkitInstance.confirm_password_prompt(password_hash, False) 33 etoolkit.EtoolkitInstance.confirm_password_prompt(password_hash, False)
34 == 'the very secret passwd' 34 == 'The very secret passwd'
35 ) 35 )
36 36
37 37
@@ -39,10 +39,10 @@ def test_decrypt():
39 """Tests the static EtoolkitInstance.decrypt method""" 39 """Tests the static EtoolkitInstance.decrypt method"""
40 assert ( 40 assert (
41 etoolkit.EtoolkitInstance.decrypt( 41 etoolkit.EtoolkitInstance.decrypt(
42 'the very secret passwd', 42 'The very secret passwd',
43 ( 43 (
44 'enc-val$1$Y/TBb1F3siHTw6qZg9ERzZfA8PLPf2CwGSQLpu9jYWw=$FT5tS9' 44 'enc-val$1$/cXpEMoZrTlb9yokGhw8tLTSUkqnqJ4ZoAkurNgMYx'
45 'o+ABvsxogIXpJim16Gz5SVtV8=' 45 'w=$1VdkSMcZnLRwLiu1M8VlYcbelwmiVNY='
46 ), 46 ),
47 ) 47 )
48 == 'secret1' 48 == 'secret1'
@@ -51,11 +51,11 @@ def test_decrypt():
51 # now test with modified edata 51 # now test with modified edata
52 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: 52 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info:
53 edata = ( 53 edata = (
54 'enc-val$1$Y/TBb1F3siHTw6qZg9ERzZfA8PLPf2CwGSQLpu9jYWw=$FT5tS9' 54 'enc-val$1$/cXpEMoZrTlb9yokGhw8tLTSUkqnqJ5ZoAkurNgMYx'
55 'o+ABvsxogIXpJim17Gz5SVtV8=' 55 'w=$1VdkSMcZnLRwLiu1M8VlYcbelwmiVNY='
56 ) 56 )
57 etoolkit.EtoolkitInstance.decrypt( 57 etoolkit.EtoolkitInstance.decrypt(
58 'the very secret passwd', edata 58 'The very secret passwd', edata
59 ) == 'secret1' 59 ) == 'secret1'
60 assert exc_info.type is etoolkit.EtoolkitInstanceError 60 assert exc_info.type is etoolkit.EtoolkitInstanceError
61 assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}' 61 assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}'
@@ -70,17 +70,32 @@ def test_encrypt():
70 assert edata != etoolkit.EtoolkitInstance.encrypt('foo', 'bar') 70 assert edata != etoolkit.EtoolkitInstance.encrypt('foo', 'bar')
71 71
72 72
73@unittest.mock.patch('os.urandom')
74def test_encrypt_staticly(urandom, non_random_bytes_32):
75 """Tests the EtoolkitInstance.encrypt method always with the same salt"""
76 urandom.return_value = non_random_bytes_32
77 edata = etoolkit.EtoolkitInstance.encrypt('The very secret passwd', 'bar')
78 assert edata == (
79 'enc-val$1$uYpZM1VfAGq0CDZL2duITs076CQj+hIFEgx+F4mn80'
80 'o=$HjPFNv6xC5hbMrFc0L5lSkWdfQ=='
81 )
82 assert edata == etoolkit.EtoolkitInstance.encrypt(
83 'The very secret passwd',
84 'bar',
85 )
86
87
73def test_get_new_password_hash(): 88def test_get_new_password_hash():
74 """Tests the static EtoolkitInstance.get_new_password_hash method""" 89 """Tests the static EtoolkitInstance.get_new_password_hash method"""
75 new_hash = etoolkit.EtoolkitInstance.get_new_password_hash( 90 new_hash = etoolkit.EtoolkitInstance.get_new_password_hash(
76 'the very secret passwd' 91 'The very secret passwd'
77 ) 92 )
78 # all pbkdf2 params are the same / hardcoded for the time being 93 # all pbkdf2 params are the same / hardcoded for the time being
79 assert new_hash.startswith('pbkdf2_sha256$100000$') 94 assert new_hash.startswith('pbkdf2_sha256$100000$')
80 assert len(new_hash) == 110 95 assert len(new_hash) == 110
81 # the hash should always be different because of random salting 96 # the hash should always be different because of random salting
82 assert new_hash != etoolkit.EtoolkitInstance.get_new_password_hash( 97 assert new_hash != etoolkit.EtoolkitInstance.get_new_password_hash(
83 'the very secret passwd' 98 'The very secret passwd'
84 ) 99 )
85 100
86 101
@@ -95,8 +110,8 @@ def test_parse_value():
95def test_password_matches(password_hash): 110def test_password_matches(password_hash):
96 """Tests the static EtoolkitInstance.password_matches method""" 111 """Tests the static EtoolkitInstance.password_matches method"""
97 assert etoolkit.EtoolkitInstance.password_matches( 112 assert etoolkit.EtoolkitInstance.password_matches(
98 'the very secret passwd', password_hash 113 'The very secret passwd', password_hash
99 ) 114 )
100 assert not etoolkit.EtoolkitInstance.password_matches( 115 assert not etoolkit.EtoolkitInstance.password_matches(
101 'the very secret passwdo', password_hash 116 'The very secret passwdo', password_hash
102 ) 117 )