summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimeon Simeonov2024-04-30 15:39:29 +0200
committerSimeon Simeonov2024-04-30 15:39:29 +0200
commitbb9a844e22134a2537652ea14f93e82acb4ee380 (patch)
treebdc8921ba20bf7c9f3d31e8cad4dd4b2abe86df8
parentc31fa58c85e866b3a5ab04882c7aff655f0b5477 (diff)
Improve tests
-rw-r--r--src/etoolkit/__main__.py14
-rw-r--r--src/etoolkit/etoolkit.py19
-rw-r--r--tests/conftest.py35
-rw-r--r--tests/test_cli.py67
-rw-r--r--tests/test_envtoolkit_instance.py16
-rw-r--r--tests/test_envtoolkit_instance_static.py83
6 files changed, 151 insertions, 83 deletions
diff --git a/src/etoolkit/__main__.py b/src/etoolkit/__main__.py
index fba9dc7..8b5bd55 100644
--- a/src/etoolkit/__main__.py
+++ b/src/etoolkit/__main__.py
@@ -319,7 +319,13 @@ def main(inargs=None):
319 sys.exit(0) 319 sys.exit(0)
320 320
321 inst = etoolkit.EtoolkitInstance(args.instance, config_dict) 321 inst = etoolkit.EtoolkitInstance(args.instance, config_dict)
322 inst.prompt_func = etoolkit.EtoolkitInstance.confirm_password_prompt 322 if (
323 args.master_password_prompt
324 or os.environ.get('ETOOLKIT_MASTER_PASSWORD') is None
325 ):
326 inst.prompt_func = (
327 etoolkit.EtoolkitInstance.confirm_password_prompt
328 )
323 env = inst.get_environ() 329 env = inst.get_environ()
324 330
325 if args.dump_output: 331 if args.dump_output:
@@ -328,9 +334,11 @@ def main(inargs=None):
328 os.environ.update(env) 334 os.environ.update(env)
329 335
330 if args.spawn: 336 if args.spawn:
331 subprocess.run(args.spawn.split(), check=True) 337 subprocess.run(args.spawn.split(), check=False)
332 else: 338 else:
333 subprocess.run(os.environ.get('SHELL', 'bash').split(), check=True) 339 subprocess.run(
340 os.environ.get('SHELL', 'bash').split(), check=False
341 )
334 except KeyboardInterrupt: 342 except KeyboardInterrupt:
335 logger.debug('KeyboardInterrupt') 343 logger.debug('KeyboardInterrupt')
336 print(os.linesep) 344 print(os.linesep)
diff --git a/src/etoolkit/etoolkit.py b/src/etoolkit/etoolkit.py
index 870a4be..a2e0d7a 100644
--- a/src/etoolkit/etoolkit.py
+++ b/src/etoolkit/etoolkit.py
@@ -385,7 +385,7 @@ class EtoolkitInstance:
385 if self._parent is not None 385 if self._parent is not None
386 else '' 386 else ''
387 ) 387 )
388 if isinstance(value, str) and value.startswith('enc-val$1$'): 388 if isinstance(value, str) and value.startswith('enc-val$'):
389 value = self._decrypt_value(value) 389 value = self._decrypt_value(value)
390 if isinstance(value, str) and value.endswith(':'): 390 if isinstance(value, str) and value.endswith(':'):
391 # if 'value' ends with ':', append the existing value of 391 # if 'value' ends with ':', append the existing value of
@@ -440,11 +440,16 @@ class EtoolkitInstance:
440 """ 440 """
441 if self._master_password is None: 441 if self._master_password is None:
442 if self._prompt_func is None: 442 if self._prompt_func is None:
443 raise EtoolkitInstanceError( 443 if (
444 'Neither password or prompt function set' 444 mp_from_env := os.environ.get('ETOOLKIT_MASTER_PASSWORD')
445 ) is None:
446 raise EtoolkitInstanceError(
447 'Neither password or prompt function set'
448 )
449 self.master_password = mp_from_env
450 else:
451 # use master_password setter in order to propagate to parent
452 self.master_password = self._prompt_func(
453 self._master_password_hash, confirm=False
445 ) 454 )
446 # use master_password setter in order to propagate to parent
447 self.master_password = self._prompt_func(
448 self._master_password_hash, confirm=False
449 )
450 return EtoolkitInstance.decrypt(self._master_password, evalue) 455 return EtoolkitInstance.decrypt(self._master_password, evalue)
diff --git a/tests/conftest.py b/tests/conftest.py
index e555a49..cbf7152 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -27,26 +27,27 @@ def config_data():
27 return { 27 return {
28 'general': { 28 'general': {
29 'MASTER_PASSWORD_HASH': ( 29 'MASTER_PASSWORD_HASH': (
30 'pbkdf2_sha256$100000$uYpZM1VfAGq0CDZL2duITs076CQj+hIFEgx+F4m' 30 'pbkdf2_sha256$500000$UY3o78KUM1Btzxk3k3JCsijnwtJ2lx+hH9Newp'
31 'n80o=$h3PSPLCd37fP15zKdW4CBGn7CXE+q5UiydaF3vbeZHo=' 31 'VKxo8=$tHwDm8OVKanC4DoYTigTCb0R3lQIa/CbBYj0B3TZtHg='
32 ) 32 )
33 }, 33 },
34 'instances': { 34 'instances': {
35 '_default': { 35 '_default': {
36 'ETOOLKIT_PROMPT': '(%i)', 36 'ETOOLKIT_PROMPT': '(%i)',
37 'PYTHONPATH': '/home/foo/%i/python', 37 'ETOOLKIT_TEST_PYTHONPATH': '/home/foo/%i/python',
38 }, 38 },
39 'dev': { 39 'dev': {
40 'ETOOLKIT_PARENT': '_default', 40 'ETOOLKIT_PARENT': '_default',
41 'PYTHONPATH': '%p:/home/user/%i/.pythonpath', 41 'ETOOLKIT_TEST_PYTHONPATH': '%p:/home/user/%i/.pythonpath',
42 }, 42 },
43 'secret': { 43 'secret': {
44 'ETOOLKIT_PARENT': '_default', 44 'ETOOLKIT_PARENT': '_default',
45 'ETOOLKIT_SENSITIVE': ['PASSWORD'], 45 'ETOOLKIT_SENSITIVE': ['ETOOLKIT_TEST_PASSWORD'],
46 'GNUPGHOME': '%h/private/.gnupg', 46 'GNUPGHOME': '%h/private/.gnupg',
47 'PASSWORD': ( 47 'ETOOLKIT_TEST_PASSWORD': (
48 'enc-val$1$vIBcoCNiYrsDLtF41uLuSEnppBjhliD0B8jwcBJcj/c=$Kw' 48 'enc-val$2$v6F2M7LeUDbQWNLg6WW5mUcbuYYo7aGynSxzWAENVBI=$'
49 'OGe/y1dlxktDaCnJPIVNuaQ4Q7yNo=' 49 'ZcyWzf9Kp0aYI8N+biKMSmu4RGGi199ayq'
50 'EYdJl+qdq7b1HSutwYlC7UR2GsSofu4Xo='
50 ), 51 ),
51 }, 52 },
52 }, 53 },
@@ -55,7 +56,7 @@ def config_data():
55 56
56@pytest.fixture() 57@pytest.fixture()
57def config_file(tmp_path, config_data): 58def config_file(tmp_path, config_data):
58 """temporary config file for testing that includes config_data""" 59 """Temporary config file for testing that includes config_data"""
59 60
60 cf = tmp_path / 'etoolkit.json' 61 cf = tmp_path / 'etoolkit.json'
61 cf.write_text(json.dumps(config_data)) 62 cf.write_text(json.dumps(config_data))
@@ -63,6 +64,12 @@ def config_file(tmp_path, config_data):
63 64
64 65
65@pytest.fixture() 66@pytest.fixture()
67def master_password():
68 """Master passord"""
69 return 'The very secret passwd'
70
71
72@pytest.fixture()
66def non_random_bytes_32(): 73def non_random_bytes_32():
67 """always use the same bytes instead of os.urandom(32)""" 74 """always use the same bytes instead of os.urandom(32)"""
68 75
@@ -95,6 +102,12 @@ def password_hash():
95 """password hash for testing, corresponding to 'The very secret passwd'""" 102 """password hash for testing, corresponding to 'The very secret passwd'"""
96 103
97 return ( 104 return (
98 'pbkdf2_sha256$100000$uYpZM1VfAGq0CDZL2duITs076CQj+hIFEgx+F4mn80o=$h3' 105 'pbkdf2_sha256$500000$UY3o78KUM1Btzxk3k3JCsijnwtJ2lx+hH9NewpVKxo8=$'
99 'PSPLCd37fP15zKdW4CBGn7CXE+q5UiydaF3vbeZHo=' 106 'tHwDm8OVKanC4DoYTigTCb0R3lQIa/CbBYj0B3TZtHg='
100 ) 107 )
108
109
110@pytest.fixture()
111def wrong_master_password():
112 """Wrong master passord"""
113 return 'the very secret passwd'
diff --git a/tests/test_cli.py b/tests/test_cli.py
index e5db244..1693a2b 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -26,14 +26,15 @@ from etoolkit.__main__ import main
26 26
27 27
28@unittest.mock.patch('builtins.input') 28@unittest.mock.patch('builtins.input')
29def test_decrypt_v1(binput, capsys, config_file): 29def test_decrypt_v1(binput, capsys, config_file, master_password):
30 """Tests v1 decryption via the CLI interface""" 30 """Tests v1 decryption via the CLI interface"""
31
31 binput.return_value = ( 32 binput.return_value = (
32 'enc-val$1$uYpZM1VfAGq0CDZL2duITs076CQj+' 33 'enc-val$1$rye0sMGEnd35gOWyISE1FQa6dzS+8/jf6aopMO5tPr4=$'
33 'hIFEgx+F4mn80o=$xdF/1S+R2MGlEQMCOLG6OjEuzw==' 34 'RjnRY0bUJWFOiejTlM3OhKNimQ=='
34 ) 35 )
35 with unittest.mock.patch.dict( 36 with unittest.mock.patch.dict(
36 os.environ, {'ETOOLKIT_MASTER_PASSWORD': 'the very secret passwd'} 37 os.environ, {'ETOOLKIT_MASTER_PASSWORD': master_password}
37 ): 38 ):
38 with pytest.raises(SystemExit) as exit_info: 39 with pytest.raises(SystemExit) as exit_info:
39 main(['-c', f'{config_file}', '-d']) 40 main(['-c', f'{config_file}', '-d'])
@@ -43,15 +44,16 @@ def test_decrypt_v1(binput, capsys, config_file):
43 44
44 45
45@unittest.mock.patch('builtins.input') 46@unittest.mock.patch('builtins.input')
46def test_decrypt_v2(binput, capsys, config_file): 47def test_decrypt_v2(binput, capsys, config_file, master_password):
47 """Tests v2 decryption via the CLI interface""" 48 """Tests v2 decryption via the CLI interface"""
49
48 binput.return_value = ( 50 binput.return_value = (
49 'enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviq' 51 'enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviq'
50 'rLTBxM=$VW3UZ6l12yDtyaqWHb7i0QEDiS9s9np' 52 'rLTBxM=$+Yo6Ya2MAVcBLTQHuATkyFc+dzYsL/E'
51 '7huAACK54BtZVV7RZoIhbu4K6zZuz+LRCyio=' 53 'SvA6ofOUDsiKZvIff35cUHAmoNxVuGG+MXv4='
52 ) 54 )
53 with unittest.mock.patch.dict( 55 with unittest.mock.patch.dict(
54 os.environ, {'ETOOLKIT_MASTER_PASSWORD': 'the very secret passwd'} 56 os.environ, {'ETOOLKIT_MASTER_PASSWORD': master_password}
55 ): 57 ):
56 with pytest.raises(SystemExit) as exit_info: 58 with pytest.raises(SystemExit) as exit_info:
57 main(['-c', f'{config_file}', '-d']) 59 main(['-c', f'{config_file}', '-d'])
@@ -61,12 +63,16 @@ def test_decrypt_v2(binput, capsys, config_file):
61 63
62 64
63@unittest.mock.patch('os.urandom') 65@unittest.mock.patch('os.urandom')
64@unittest.mock.patch('builtins.input', lambda *args: 'bar') 66@unittest.mock.patch('builtins.input')
65def test_encrypt_with_echo(urandom, capsys, non_random_bytes_61, config_file): 67def test_encrypt_with_echo(
68 binput, urandom, capsys, non_random_bytes_61, config_file, master_password
69):
66 """Tests encryption via the CLI interface""" 70 """Tests encryption via the CLI interface"""
71
72 binput.return_value = 'bar'
67 urandom.return_value = non_random_bytes_61 73 urandom.return_value = non_random_bytes_61
68 with unittest.mock.patch.dict( 74 with unittest.mock.patch.dict(
69 os.environ, {'ETOOLKIT_MASTER_PASSWORD': 'the very secret passwd'} 75 os.environ, {'ETOOLKIT_MASTER_PASSWORD': master_password}
70 ): 76 ):
71 with pytest.raises(SystemExit) as exit_info: 77 with pytest.raises(SystemExit) as exit_info:
72 main(['-c', f'{config_file}', '-e', '-E']) 78 main(['-c', f'{config_file}', '-e', '-E'])
@@ -74,18 +80,22 @@ def test_encrypt_with_echo(urandom, capsys, non_random_bytes_61, config_file):
74 assert exit_info.value.code == 0 80 assert exit_info.value.code == 0
75 assert capsys.readouterr().out.strip() == ( 81 assert capsys.readouterr().out.strip() == (
76 'Encrypted value: enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviq' 82 'Encrypted value: enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviq'
77 'rLTBxM=$VW3UZ6l12yDtyaqWHb7i0QEDiS9s9np' 83 'rLTBxM=$+Yo6Ya2MAVcBLTQHuATkyFc+dzYsL/E'
78 '7huAACK54BtZVV7RZoIhbu4K6zZuz+LRCyio=' 84 'SvA6ofOUDsiKZvIff35cUHAmoNxVuGG+MXv4='
79 ) 85 )
80 86
81 87
82@unittest.mock.patch('os.urandom') 88@unittest.mock.patch('os.urandom')
83@unittest.mock.patch('getpass.getpass', lambda *args: 'bar') 89@unittest.mock.patch('getpass.getpass')
84def test_encrypt_without_echo(gpass, capsys, non_random_bytes_61, config_file): 90def test_encrypt_without_echo(
91 getpass, urandom, capsys, non_random_bytes_61, config_file, master_password
92):
85 """Tests encryption via the CLI interface""" 93 """Tests encryption via the CLI interface"""
86 gpass.return_value = non_random_bytes_61 94
95 getpass.return_value = 'bar'
96 urandom.return_value = non_random_bytes_61
87 with unittest.mock.patch.dict( 97 with unittest.mock.patch.dict(
88 os.environ, {'ETOOLKIT_MASTER_PASSWORD': 'the very secret passwd'} 98 os.environ, {'ETOOLKIT_MASTER_PASSWORD': master_password}
89 ): 99 ):
90 with pytest.raises(SystemExit) as exit_info: 100 with pytest.raises(SystemExit) as exit_info:
91 main(['-c', f'{config_file}', '-e']) 101 main(['-c', f'{config_file}', '-e'])
@@ -93,13 +103,25 @@ def test_encrypt_without_echo(gpass, capsys, non_random_bytes_61, config_file):
93 assert exit_info.value.code == 0 103 assert exit_info.value.code == 0
94 assert capsys.readouterr().out.strip() == ( 104 assert capsys.readouterr().out.strip() == (
95 'Encrypted value: enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviq' 105 'Encrypted value: enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviq'
96 'rLTBxM=$VW3UZ6l12yDtyaqWHb7i0QEDiS9s9np' 106 'rLTBxM=$+Yo6Ya2MAVcBLTQHuATkyFc+dzYsL/E'
97 '7huAACK54BtZVV7RZoIhbu4K6zZuz+LRCyio=' 107 'SvA6ofOUDsiKZvIff35cUHAmoNxVuGG+MXv4='
98 ) 108 )
99 109
100 110
111def test_fetch_encrypted_value(config_file, master_password):
112 """Tests decryption of encrypted value"""
113
114 with unittest.mock.patch.dict(
115 os.environ, {'ETOOLKIT_MASTER_PASSWORD': master_password}
116 ):
117 assert os.environ.get('ETOOLKIT_TEST_PASSWORD') is None
118 main(['-c', f'{config_file}', '-q', '-s', '/bin/false', 'secret'])
119 assert os.environ.get('ETOOLKIT_TEST_PASSWORD') == 'bar'
120
121
101def test_list(capsys, config_file, nonexistent_config_file): 122def test_list(capsys, config_file, nonexistent_config_file):
102 """Tests list via the CLI interface""" 123 """Tests list via the CLI interface"""
124
103 with pytest.raises(SystemExit) as exit_info: 125 with pytest.raises(SystemExit) as exit_info:
104 main(['-c', nonexistent_config_file, '-l']) 126 main(['-c', nonexistent_config_file, '-l'])
105 assert exit_info.type == SystemExit 127 assert exit_info.type == SystemExit
@@ -113,6 +135,7 @@ def test_list(capsys, config_file, nonexistent_config_file):
113 135
114def test_help(capsys): 136def test_help(capsys):
115 """Dummy test checking if the CLI is available at all""" 137 """Dummy test checking if the CLI is available at all"""
138
116 with pytest.raises(SystemExit) as exit_info: 139 with pytest.raises(SystemExit) as exit_info:
117 main(['-h']) 140 main(['-h'])
118 assert exit_info.type == SystemExit 141 assert exit_info.type == SystemExit
@@ -123,11 +146,12 @@ def test_help(capsys):
123@unittest.mock.patch('os.urandom') 146@unittest.mock.patch('os.urandom')
124@unittest.mock.patch('getpass.getpass') 147@unittest.mock.patch('getpass.getpass')
125def test_generate_master_password_hash( 148def test_generate_master_password_hash(
126 gpass, urandom, capsys, non_random_bytes_32 149 getpass, urandom, capsys, non_random_bytes_32
127): 150):
128 """Tests master password hash generation via the CLI interface""" 151 """Tests master password hash generation via the CLI interface"""
152
153 getpass.return_value = 'The very secret passwd'
129 urandom.return_value = non_random_bytes_32 154 urandom.return_value = non_random_bytes_32
130 gpass.return_value = 'The very secret passwd'
131 with pytest.raises(SystemExit) as exit_info: 155 with pytest.raises(SystemExit) as exit_info:
132 main(['--generate-master-password-hash']) 156 main(['--generate-master-password-hash'])
133 assert exit_info.type == SystemExit 157 assert exit_info.type == SystemExit
@@ -140,6 +164,7 @@ def test_generate_master_password_hash(
140 164
141def test_version(capsys): 165def test_version(capsys):
142 """Dummy test checking if the CLI is available at all""" 166 """Dummy test checking if the CLI is available at all"""
167
143 with pytest.raises(SystemExit) as exit_info: 168 with pytest.raises(SystemExit) as exit_info:
144 main(['-v']) 169 main(['-v'])
145 assert exit_info.type == SystemExit 170 assert exit_info.type == SystemExit
diff --git a/tests/test_envtoolkit_instance.py b/tests/test_envtoolkit_instance.py
index 73faed6..0147c6f 100644
--- a/tests/test_envtoolkit_instance.py
+++ b/tests/test_envtoolkit_instance.py
@@ -22,6 +22,7 @@ import etoolkit
22 22
23def test_instantiation(config_data): 23def test_instantiation(config_data):
24 """Tests for object instatiation""" 24 """Tests for object instatiation"""
25
25 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: 26 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info:
26 instance = etoolkit.EtoolkitInstance('devv', config_data) 27 instance = etoolkit.EtoolkitInstance('devv', config_data)
27 assert exc_info.type is etoolkit.EtoolkitInstanceError 28 assert exc_info.type is etoolkit.EtoolkitInstanceError
@@ -31,7 +32,7 @@ def test_instantiation(config_data):
31 assert 'ETOOLKIT_PARENT' not in instance.raw_env_variables 32 assert 'ETOOLKIT_PARENT' not in instance.raw_env_variables
32 assert 'ETOOLKIT_SENSITIVE' not in instance.raw_env_variables 33 assert 'ETOOLKIT_SENSITIVE' not in instance.raw_env_variables
33 assert 'DB_CONNECTION' not in instance.sensitive_env_variables 34 assert 'DB_CONNECTION' not in instance.sensitive_env_variables
34 assert 'PASSWORD' in instance.sensitive_env_variables 35 assert 'ETOOLKIT_TEST_PASSWORD' in instance.sensitive_env_variables
35 assert instance.name == 'secret' 36 assert instance.name == 'secret'
36 assert ( 37 assert (
37 instance.master_password_hash 38 instance.master_password_hash
@@ -40,15 +41,16 @@ def test_instantiation(config_data):
40 assert instance.master_password is None 41 assert instance.master_password is None
41 42
42 43
43def test_get_environ(config_data): 44def test_get_environ(config_data, master_password, wrong_master_password):
44 """Tests the EtoolkitInstance.get_environ method""" 45 """Tests the EtoolkitInstance.get_environ method"""
46
45 instance = etoolkit.EtoolkitInstance('secret', config_data) 47 instance = etoolkit.EtoolkitInstance('secret', config_data)
46 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: 48 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info:
47 env = instance.get_environ() 49 env = instance.get_environ()
48 assert exc_info.type is etoolkit.EtoolkitInstanceError 50 assert exc_info.type is etoolkit.EtoolkitInstanceError
49 assert exc_info.value.args[0] == 'Neither password or prompt function set' 51 assert exc_info.value.args[0] == 'Neither password or prompt function set'
50 52
51 instance.master_password = 'The very secret passwd' # wrong passwd 53 instance.master_password = wrong_master_password
52 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: 54 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info:
53 env = instance.get_environ() 55 env = instance.get_environ()
54 assert exc_info.type is etoolkit.EtoolkitInstanceError 56 assert exc_info.type is etoolkit.EtoolkitInstanceError
@@ -56,22 +58,24 @@ def test_get_environ(config_data):
56 'Invalid tag when decrypting: enc-val$' 58 'Invalid tag when decrypting: enc-val$'
57 ) 59 )
58 60
59 instance.master_password = 'the very secret passwd' # correct passwd 61 instance.master_password = master_password
60 env = instance.get_environ() 62 env = instance.get_environ()
61 assert isinstance(env, dict) 63 assert isinstance(env, dict)
62 assert env['PASSWORD'] == 'secret2' 64 assert env['ETOOLKIT_TEST_PASSWORD'] == 'bar'
63 65
64 66
65def test_get_full_name(config_data): 67def test_get_full_name(config_data):
66 """Tests the EtoolkitInstance.get_full_name method""" 68 """Tests the EtoolkitInstance.get_full_name method"""
69
67 instance = etoolkit.EtoolkitInstance('secret', config_data) 70 instance = etoolkit.EtoolkitInstance('secret', config_data)
68 assert instance.get_full_name('->') == '_default->secret' 71 assert instance.get_full_name('->') == '_default->secret'
69 72
70 73
71def test_parent_vars(config_data): 74def test_parent_vars(config_data):
72 """Tests the EtoolkitInstance.get_full_name method""" 75 """Tests the EtoolkitInstance.get_full_name method"""
76
73 instance = etoolkit.EtoolkitInstance('dev', config_data) 77 instance = etoolkit.EtoolkitInstance('dev', config_data)
74 assert instance.get_full_name('->') == '_default->dev' 78 assert instance.get_full_name('->') == '_default->dev'
75 assert instance.get_environ()['PYTHONPATH'] == ( 79 assert instance.get_environ()['ETOOLKIT_TEST_PYTHONPATH'] == (
76 '/home/foo/_default/python:/home/user/dev/.pythonpath' 80 '/home/foo/_default/python:/home/user/dev/.pythonpath'
77 ) 81 )
diff --git a/tests/test_envtoolkit_instance_static.py b/tests/test_envtoolkit_instance_static.py
index 5fb9451..b3b24f0 100644
--- a/tests/test_envtoolkit_instance_static.py
+++ b/tests/test_envtoolkit_instance_static.py
@@ -23,24 +23,26 @@ import etoolkit
23 23
24 24
25@unittest.mock.patch('getpass.getpass') 25@unittest.mock.patch('getpass.getpass')
26def test_confirm_password_prompt(getpass, password_hash): 26def test_confirm_password_prompt(getpass, password_hash, master_password):
27 """Tests the static EtoolkitInstance.confirm_password_prompt method""" 27 """Tests the static EtoolkitInstance.confirm_password_prompt method"""
28 getpass.return_value = 'The very secret passwd' 28
29 getpass.return_value = master_password
29 assert ( 30 assert (
30 etoolkit.EtoolkitInstance.confirm_password_prompt(password_hash) 31 etoolkit.EtoolkitInstance.confirm_password_prompt(password_hash)
31 == 'The very secret passwd' 32 == master_password
32 ) 33 )
33 assert ( 34 assert (
34 etoolkit.EtoolkitInstance.confirm_password_prompt(password_hash, False) 35 etoolkit.EtoolkitInstance.confirm_password_prompt(password_hash, False)
35 == 'The very secret passwd' 36 == master_password
36 ) 37 )
37 38
38 39
39def test_decrypt_v1(): 40def test_decrypt_v1(master_password):
40 """Tests the static EtoolkitInstance.decrypt method""" 41 """Tests the static EtoolkitInstance.decrypt method"""
42
41 assert ( 43 assert (
42 etoolkit.EtoolkitInstance.decrypt( 44 etoolkit.EtoolkitInstance.decrypt(
43 'The very secret passwd', 45 master_password,
44 ( 46 (
45 'enc-val$1$/cXpEMoZrTlb9yokGhw8tLTSUkqnqJ4ZoAkurNgMYx' 47 'enc-val$1$/cXpEMoZrTlb9yokGhw8tLTSUkqnqJ4ZoAkurNgMYx'
46 'w=$1VdkSMcZnLRwLiu1M8VlYcbelwmiVNY=' 48 'w=$1VdkSMcZnLRwLiu1M8VlYcbelwmiVNY='
@@ -55,16 +57,17 @@ def test_decrypt_v1():
55 'w=$1VdkSMcZnLRwLiu1M8VlYcbelwmiVNY=' 57 'w=$1VdkSMcZnLRwLiu1M8VlYcbelwmiVNY='
56 ) 58 )
57 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: 59 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info:
58 etoolkit.EtoolkitInstance.decrypt('The very secret passwd', edata) 60 etoolkit.EtoolkitInstance.decrypt(master_password, edata)
59 assert exc_info.type is etoolkit.EtoolkitInstanceError 61 assert exc_info.type is etoolkit.EtoolkitInstanceError
60 assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}' 62 assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}'
61 63
62 64
63def test_decrypt_v2_no_padding(): 65def test_decrypt_v2_no_padding(master_password):
64 """Tests the static EtoolkitInstance.decrypt method for v2 - no padding""" 66 """Tests the static EtoolkitInstance.decrypt method for v2 - no padding"""
67
65 assert ( 68 assert (
66 etoolkit.EtoolkitInstance.decrypt( 69 etoolkit.EtoolkitInstance.decrypt(
67 'The very secret passwd', 70 master_password,
68 ( 71 (
69 'enc-val$2$Wer5lECGyeZhhYS58N18WVx5Zzy+rrC+BPlq3Dw89wQ=$' 72 'enc-val$2$Wer5lECGyeZhhYS58N18WVx5Zzy+rrC+BPlq3Dw89wQ=$'
70 'SQc0ox6Emf2m5rrumsiptpIZEujdpXXSR/' 73 'SQc0ox6Emf2m5rrumsiptpIZEujdpXXSR/'
@@ -81,16 +84,17 @@ def test_decrypt_v2_no_padding():
81 '1VcfEZeBz4+KDSagr9ID+bkc4R2yFdxHnhig1eqQ8=' 84 '1VcfEZeBz4+KDSagr9ID+bkc4R2yFdxHnhig1eqQ8='
82 ) 85 )
83 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: 86 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info:
84 etoolkit.EtoolkitInstance.decrypt('The very secret passwd', edata) 87 etoolkit.EtoolkitInstance.decrypt(master_password, edata)
85 assert exc_info.type is etoolkit.EtoolkitInstanceError 88 assert exc_info.type is etoolkit.EtoolkitInstanceError
86 assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}' 89 assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}'
87 90
88 91
89def test_decrypt_v2_with_padding(): 92def test_decrypt_v2_with_padding(master_password):
90 """Tests the static EtoolkitInstance.decrypt method for v2 with padding""" 93 """Tests the static EtoolkitInstance.decrypt method for v2 with padding"""
94
91 assert ( 95 assert (
92 etoolkit.EtoolkitInstance.decrypt( 96 etoolkit.EtoolkitInstance.decrypt(
93 'The very secret passwd', 97 master_password,
94 ( 98 (
95 'enc-val$2$//kzyUbDEWNoPC5dyukhB8de8+IVaLR2ngx2HwkfOuM=$' 99 'enc-val$2$//kzyUbDEWNoPC5dyukhB8de8+IVaLR2ngx2HwkfOuM=$'
96 'rhRona4wP9nhnXjcHqwkjFDsiVVVjYanAs' 100 'rhRona4wP9nhnXjcHqwkjFDsiVVVjYanAs'
@@ -106,39 +110,44 @@ def test_decrypt_v2_with_padding():
106 'rhRona4wP8nhnXjcHqwkjFDsiVVVjYanAsN4kknNkgC0ix4RtJQHYDeTzw1rrR1vb2w=' 110 'rhRona4wP8nhnXjcHqwkjFDsiVVVjYanAsN4kknNkgC0ix4RtJQHYDeTzw1rrR1vb2w='
107 ) 111 )
108 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info: 112 with pytest.raises(etoolkit.EtoolkitInstanceError) as exc_info:
109 etoolkit.EtoolkitInstance.decrypt('The very secret passwd', edata) 113 etoolkit.EtoolkitInstance.decrypt(master_password, edata)
110 assert exc_info.type is etoolkit.EtoolkitInstanceError 114 assert exc_info.type is etoolkit.EtoolkitInstanceError
111 assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}' 115 assert exc_info.value.args[0] == f'Invalid tag when decrypting: {edata}'
112 116
113 117
114def test_encrypt_no_padding(): 118def test_encrypt_no_padding(master_password):
115 """Tests the static EtoolkitInstance.encrypt method with a long string""" 119 """Tests the static EtoolkitInstance.encrypt method with a long string"""
120
116 edata = etoolkit.EtoolkitInstance.encrypt( 121 edata = etoolkit.EtoolkitInstance.encrypt(
117 'foo', 'Nobody expects the Spanish inquisition' 122 master_password, 'Nobody expects the Spanish inquisition'
118 ) 123 )
119 assert edata.startswith('enc-val$2$') 124 assert edata.startswith('enc-val$2$')
120 assert len(edata) == 131 125 assert len(edata) == 131
121 # the edata should always be different because of random salting 126 # the edata should always be different because of random salting
122 assert edata != etoolkit.EtoolkitInstance.encrypt( 127 assert edata != etoolkit.EtoolkitInstance.encrypt(
123 'foo', 'Nobody expects the Spanish inquisition' 128 master_password, 'Nobody expects the Spanish inquisition'
124 ) 129 )
125 130
126 131
127def test_encrypt_with_padding(): 132def test_encrypt_with_padding(master_password):
128 """Tests the static EtoolkitInstance.encrypt method with a short string""" 133 """Tests the static EtoolkitInstance.encrypt method with a short string"""
129 edata = etoolkit.EtoolkitInstance.encrypt('foo', 'bar') 134
135 edata = etoolkit.EtoolkitInstance.encrypt(master_password, 'bar')
130 assert edata.startswith('enc-val$2$') 136 assert edata.startswith('enc-val$2$')
131 assert len(edata) == 123 137 assert len(edata) == 123
132 # the edata should always be different because of random salting 138 # the edata should always be different because of random salting
133 assert edata != etoolkit.EtoolkitInstance.encrypt('foo', 'bar') 139 assert edata != etoolkit.EtoolkitInstance.encrypt(master_password, 'bar')
134 140
135 141
136@unittest.mock.patch('os.urandom') 142@unittest.mock.patch('os.urandom')
137def test_encrypt_staticly_no_padding(urandom, non_random_bytes_32): 143def test_encrypt_staticly_no_padding(
144 urandom, master_password, non_random_bytes_32
145):
138 """Tests the EtoolkitInstance.encrypt method always with the same salt""" 146 """Tests the EtoolkitInstance.encrypt method always with the same salt"""
147
139 urandom.return_value = non_random_bytes_32 148 urandom.return_value = non_random_bytes_32
140 edata = etoolkit.EtoolkitInstance.encrypt( 149 edata = etoolkit.EtoolkitInstance.encrypt(
141 'The very secret passwd', 'Nobody expects the Spanish inquisition' 150 master_password, 'Nobody expects the Spanish inquisition'
142 ) 151 )
143 assert edata == ( 152 assert edata == (
144 'enc-val$2$uYpZM1VfAGq0CDZL2duITs076CQj+hIFEgx+F4mn80o=$' 153 'enc-val$2$uYpZM1VfAGq0CDZL2duITs076CQj+hIFEgx+F4mn80o=$'
@@ -147,51 +156,55 @@ def test_encrypt_staticly_no_padding(urandom, non_random_bytes_32):
147 ) 156 )
148 assert len(edata) == 131 157 assert len(edata) == 131
149 assert edata == etoolkit.EtoolkitInstance.encrypt( 158 assert edata == etoolkit.EtoolkitInstance.encrypt(
150 'The very secret passwd', 'Nobody expects the Spanish inquisition' 159 master_password, 'Nobody expects the Spanish inquisition'
151 ) 160 )
152 161
153 162
154@unittest.mock.patch('os.urandom') 163@unittest.mock.patch('os.urandom')
155def test_encrypt_staticly_with_padding(urandom, non_random_bytes_61): 164def test_encrypt_staticly_with_padding(
165 urandom, master_password, non_random_bytes_61
166):
156 """Tests the EtoolkitInstance.encrypt method always with the same salt""" 167 """Tests the EtoolkitInstance.encrypt method always with the same salt"""
168
157 urandom.return_value = non_random_bytes_61 169 urandom.return_value = non_random_bytes_61
158 edata = etoolkit.EtoolkitInstance.encrypt('The very secret passwd', 'bar') 170 edata = etoolkit.EtoolkitInstance.encrypt(master_password, 'bar')
159 assert edata == ( 171 assert edata == (
160 'enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviqrLTBxM=$' 172 'enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviqrLTBxM=$'
161 '+Yo6Ya2MAVcBLTQHuATkyFc+dzYsL/ESvA6ofOUDsiKZvIff35cUHAmoNxVuGG+MXv4=' 173 '+Yo6Ya2MAVcBLTQHuATkyFc+dzYsL/ESvA6ofOUDsiKZvIff35cUHAmoNxVuGG+MXv4='
162 ) 174 )
163 assert edata == etoolkit.EtoolkitInstance.encrypt( 175 assert edata == etoolkit.EtoolkitInstance.encrypt(master_password, 'bar')
164 'The very secret passwd', 'bar'
165 )
166 176
167 177
168def test_get_new_password_hash(): 178def test_get_new_password_hash(master_password):
169 """Tests the static EtoolkitInstance.get_new_password_hash method""" 179 """Tests the static EtoolkitInstance.get_new_password_hash method"""
170 new_hash = etoolkit.EtoolkitInstance.get_new_password_hash( 180
171 'The very secret passwd' 181 new_hash = etoolkit.EtoolkitInstance.get_new_password_hash(master_password)
172 )
173 # all pbkdf2 params are the same / hardcoded for the time being 182 # all pbkdf2 params are the same / hardcoded for the time being
174 assert new_hash.startswith('pbkdf2_sha256$500000$') 183 assert new_hash.startswith('pbkdf2_sha256$500000$')
175 assert len(new_hash) == 110 184 assert len(new_hash) == 110
176 # the hash should always be different because of random salting 185 # the hash should always be different because of random salting
177 assert new_hash != etoolkit.EtoolkitInstance.get_new_password_hash( 186 assert new_hash != etoolkit.EtoolkitInstance.get_new_password_hash(
178 'The very secret passwd' 187 master_password
179 ) 188 )
180 189
181 190
182def test_parse_value(): 191def test_parse_value():
183 """Tests the static EtoolkitInstance.parse_value method""" 192 """Tests the static EtoolkitInstance.parse_value method"""
193
184 assert ( 194 assert (
185 etoolkit.EtoolkitInstance.parse_value('t%bs%t', {'%b': 'e', '%t': 't'}) 195 etoolkit.EtoolkitInstance.parse_value('t%bs%t', {'%b': 'e', '%t': 't'})
186 == 'test' 196 == 'test'
187 ) 197 )
188 198
189 199
190def test_password_matches(password_hash): 200def test_password_matches(
201 password_hash, master_password, wrong_master_password
202):
191 """Tests the static EtoolkitInstance.password_matches method""" 203 """Tests the static EtoolkitInstance.password_matches method"""
204
192 assert etoolkit.EtoolkitInstance.password_matches( 205 assert etoolkit.EtoolkitInstance.password_matches(
193 'The very secret passwd', password_hash 206 master_password, password_hash
194 ) 207 )
195 assert not etoolkit.EtoolkitInstance.password_matches( 208 assert not etoolkit.EtoolkitInstance.password_matches(
196 'The very secret passwdo', password_hash 209 wrong_master_password, password_hash
197 ) 210 )