Skip to content

Commit 836ed71

Browse files
committed
Add test covering the useradm create_user function.
Leverage the opportunity to make sure that a call to create_user writes the same content as has been declared as our on-disk user fixture. Note, create_user is mostly about writing rather than about the population of the user dictionary with most of that important work (in terms of the written values) being done by the createuser calling script. This means that the we are only comparing written data as of now, meaning it is something of a loose test, but this towards additional rigour.
1 parent cdbfbe5 commit 836ed71

File tree

8 files changed

+208
-32
lines changed

8 files changed

+208
-32
lines changed

envhelp/makeconfig.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,10 @@
4040

4141
from mig.shared.conf import get_configuration_object
4242
from mig.shared.install import MIG_BASE, generate_confs
43+
from mig.shared.useradm import _ensure_dirs_needed_for_userdb
4344

4445
_LOCAL_ENVHELP_OUTPUT_DIR = os.path.join(_LOCAL_MIG_BASE, "envhelp/output")
4546
_MAKECONFIG_ALLOWED = ["local", "test"]
46-
_USERADM_PATH_KEYS = ('user_cache', 'user_db_home', 'user_home',
47-
'user_settings', 'mrsl_files_dir', 'resource_pending')
4847

4948

5049
def _at(sequence, index=-1, default=None):
@@ -55,18 +54,6 @@ def _at(sequence, index=-1, default=None):
5554
return default
5655

5756

58-
def _ensure_dirs_needed_for_userdb(configuration):
59-
"""Provision the basic directories needed for the operation of the
60-
userdb deriving paths from the supplied configuration object."""
61-
62-
for config_key in _USERADM_PATH_KEYS:
63-
dir_path = getattr(configuration, config_key).rstrip(os.path.sep)
64-
try:
65-
os.makedirs(dir_path, exist_ok=True)
66-
except OSError as exc:
67-
pass
68-
69-
7057
def write_testconfig(env_name, is_docker=False):
7158
is_predefined = env_name == 'test'
7259
confs_name = '%sconfs' % (env_name,)

mig/shared/useradm.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,22 @@
102102
https_authdigests = user_db_filename
103103

104104

105+
_USERADM_PATH_KEYS = ('user_db_home', 'user_home', 'user_settings',
106+
'user_cache', 'mrsl_files_dir', 'resource_pending')
107+
108+
109+
def _ensure_dirs_needed_for_userdb(configuration):
110+
"""Provision the basic directories needed for the operation of the
111+
userdb deriving paths from the supplied configuration object."""
112+
113+
for config_key in _USERADM_PATH_KEYS:
114+
dir_path = getattr(configuration, config_key).rstrip(os.path.sep)
115+
try:
116+
os.makedirs(dir_path, exist_ok=True)
117+
except OSError as exc:
118+
pass
119+
120+
105121
def init_user_adm(dynamic_db_path=True):
106122
"""Shared init function for all user administration scripts.
107123
The optional dynamic_db_path argument toggles dynamic user db path lookup
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[ONWRITE]
2-
convert_dict_bytes_to_strings_kv = True
2+
convert_dict_strings_to_bytes_kv = True

tests/support/__init__.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343

4444
from tests.support.configsupp import FakeConfiguration
4545
from tests.support.fixturesupp import _PreparedFixture
46-
from tests.support.suppconst import MIG_BASE, TEST_BASE, \
46+
from tests.support.pathsupp import is_path_within
47+
from tests.support.suppconst import MIG_BASE, TEST_BASE, TEST_FIXTURE_DIR, \
4748
TEST_DATA_DIR, TEST_OUTPUT_DIR, ENVHELP_OUTPUT_DIR
4849

4950
from tests.support._env import MIG_ENV, PY2
@@ -383,16 +384,6 @@ def _provision_test_user(testcase, distinguished_name):
383384
return test_user_dir
384385

385386

386-
def is_path_within(path, start=None, _msg=None):
387-
"""Check if path is within start directory"""
388-
try:
389-
assert os.path.isabs(path), _msg
390-
relative = os.path.relpath(path, start=start)
391-
except:
392-
return False
393-
return not relative.startswith('..')
394-
395-
396387
def ensure_dirs_exist(absolute_dir):
397388
"""A simple helper to create absolute_dir and any parents if missing"""
398389
try:

tests/support/fixturesupp.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,33 @@ def _hints_apply_today_relative(value, modifier):
101101
raise NotImplementedError("unspported today_relative modifier")
102102

103103

104-
def _hints_apply_dict_bytes_to_strings_kv(input_dict):
104+
def _hints_apply_dict_bytes_to_strings_kv(input_dict, modifier):
105+
assert modifier is None
106+
107+
output_dict = {}
108+
109+
for k, v in input_dict.items():
110+
key_to_use = k
111+
if isinstance(k, bytes):
112+
key_to_use = str(k, 'utf8')
113+
114+
if isinstance(v, dict):
115+
output_dict[key_to_use] = _hints_apply_dict_bytes_to_strings_kv(v, modifier)
116+
continue
117+
118+
val_to_use = v
119+
if isinstance(v, bytes):
120+
val_to_use = str(v, 'utf8')
121+
122+
output_dict[key_to_use] = val_to_use
123+
124+
return output_dict
125+
126+
127+
128+
def _hints_apply_dict_strings_to_bytes_kv(input_dict, modifier):
129+
assert modifier is None
130+
105131
output_dict = {}
106132

107133
for k, v in input_dict.items():
@@ -110,7 +136,7 @@ def _hints_apply_dict_bytes_to_strings_kv(input_dict):
110136
key_to_use = bytes(k, 'utf8')
111137

112138
if isinstance(v, dict):
113-
output_dict[key_to_use] = _hints_apply_dict_bytes_to_strings_kv(v)
139+
output_dict[key_to_use] = _hints_apply_dict_strings_to_bytes_kv(v, modifier)
114140
continue
115141

116142
val_to_use = v
@@ -122,14 +148,22 @@ def _hints_apply_dict_bytes_to_strings_kv(input_dict):
122148
return output_dict
123149

124150

125-
_FIXTUREFILE_APPLIERS_ATTRIBUTES = {
151+
_FIXTUREFILE_APPLIERS_ARGLESS = {
126152
'array_of_tuples': _hints_apply_array_of_tuples,
127153
'today_relative': _hints_apply_today_relative,
154+
'convert_dict_bytes_to_strings_kv': _hints_apply_dict_bytes_to_strings_kv,
155+
'convert_dict_strings_to_bytes_kv': _hints_apply_dict_strings_to_bytes_kv,
156+
}
157+
158+
159+
_FIXTUREFILE_APPLIERS_ATTRIBUTES = {
160+
'array_of_tuples': _FIXTUREFILE_APPLIERS_ARGLESS['array_of_tuples'],
161+
'today_relative': _FIXTUREFILE_APPLIERS_ARGLESS['today_relative'],
128162
}
129163

130164

131165
_FIXTUREFILE_APPLIERS_ONWRITE = {
132-
'convert_dict_bytes_to_strings_kv': _hints_apply_dict_bytes_to_strings_kv,
166+
'convert_dict_strings_to_bytes_kv': _FIXTUREFILE_APPLIERS_ARGLESS['convert_dict_strings_to_bytes_kv'],
133167
}
134168

135169

@@ -301,7 +335,7 @@ def write_to_dir(self, target_dir, output_format=None):
301335
continue
302336

303337
apply_conversion = _FIXTUREFILE_APPLIERS_ONWRITE[item_name]
304-
output_data = apply_conversion(output_data)
338+
output_data = apply_conversion(output_data, None)
305339

306340
if output_format == 'binary':
307341
with open(fixture_file_target, 'wb') as fixture_outputfile:

tests/support/pathsupp.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# --- BEGIN_HEADER ---
5+
#
6+
# __init__ - package marker and core package functions
7+
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
8+
#
9+
# This file is part of MiG.
10+
#
11+
# MiG is free software: you can redistribute it and/or modify
12+
# it under the terms of the GNU General Public License as published by
13+
# the Free Software Foundation; either version 2 of the License, or
14+
# (at your option) any later version.
15+
#
16+
# MiG is distributed in the hope that it will be useful,
17+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
# GNU General Public License for more details.
20+
#
21+
# You should have received a copy of the GNU General Public License
22+
# along with this program; if not, write to the Free Software
23+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24+
#
25+
# -- END_HEADER ---
26+
#
27+
28+
"""Path related details within the test support library."""
29+
30+
import os
31+
32+
33+
def is_path_within(path, start=None, _msg=None):
34+
"""Check if path is within start directory"""
35+
try:
36+
assert os.path.isabs(path), _msg
37+
relative = os.path.relpath(path, start=start)
38+
except AssertionError:
39+
return False
40+
return not relative.startswith('..')

tests/support/picklesupp.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import pickle
2+
3+
from tests.support.suppconst import TEST_OUTPUT_DIR
4+
from tests.support.fixturesupp import _FIXTUREFILE_APPLIERS_ARGLESS
5+
from tests.support.pathsupp import is_path_within
6+
7+
8+
class PickleAssertMixin:
9+
def assertPickledFile(self, pickle_file_path, apply_hints=None):
10+
#assert is_path_within(pickle_file_path, TEST_OUTPUT_DIR)
11+
12+
with open(pickle_file_path, 'rb') as picklefile:
13+
pickled = pickle.load(picklefile)
14+
15+
if not apply_hints:
16+
return pickled
17+
18+
result = pickled
19+
for hint_name in apply_hints:
20+
if not hint_name in _FIXTUREFILE_APPLIERS_ARGLESS:
21+
raise NotImplementedError("unknown hint %s" % (hint_name,))
22+
hint_fn = _FIXTUREFILE_APPLIERS_ARGLESS[hint_name]
23+
result = hint_fn(pickled, modifier=None)
24+
return result

tests/test_mig_shared_useradm.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,13 @@
3838

3939
from tests.support import MIG_BASE, TEST_OUTPUT_DIR, MigTestCase, \
4040
FakeConfiguration, testmain, cleanpath, is_path_within
41+
from tests.support.fixturesupp import FixtureAssertMixin
42+
from tests.support.picklesupp import PickleAssertMixin
4143

4244
from mig.shared.defaults import keyword_auto, htaccess_filename, \
4345
DEFAULT_USER_ID_FORMAT
44-
from mig.shared.useradm import assure_current_htaccess
46+
from mig.shared.useradm import assure_current_htaccess, create_user, \
47+
_ensure_dirs_needed_for_userdb
4548

4649
DUMMY_USER = 'dummy-user'
4750
DUMMY_STALE_USER = 'dummy-stale-user'
@@ -80,6 +83,87 @@
8083
)
8184

8285

86+
class TestMigSharedUsedadm_create_user(MigTestCase,
87+
FixtureAssertMixin,
88+
PickleAssertMixin):
89+
def before_each(self):
90+
configuration = self.configuration
91+
92+
_ensure_dirs_needed_for_userdb(self.configuration)
93+
94+
self.expected_user_db_home = configuration.user_db_home[0:-1]
95+
self.expected_user_db_file = os.path.join(
96+
self.expected_user_db_home, 'MiG-users.db')
97+
98+
def _provide_configuration(self):
99+
return 'testconfig'
100+
101+
def test_user_db_is_created(self):
102+
user_dict = {}
103+
user_dict['full_name'] = "Test User"
104+
user_dict['organization'] = "Test Org"
105+
user_dict['state'] = "NA"
106+
user_dict['country'] = "DK"
107+
user_dict['email'] = "user@example.com"
108+
user_dict['comment'] = "This is the create comment"
109+
user_dict['password'] = "password"
110+
create_user(user_dict, self.configuration,
111+
keyword_auto, default_renew=True)
112+
113+
# presence of user home
114+
path_kind = MigTestCase._absolute_path_kind(self.expected_user_db_home)
115+
self.assertEqual(path_kind, 'dir')
116+
117+
# presence of user db
118+
path_kind = MigTestCase._absolute_path_kind(self.expected_user_db_file)
119+
self.assertEqual(path_kind, 'file')
120+
121+
def test_user_entry_is_recorded(self):
122+
def _adjust_user_dict_for_compare(user_obj):
123+
obj = dict(user_obj)
124+
obj['created'] = 9999999999.9999999
125+
obj['expire'] = 9999999999.9999999
126+
obj['unique_id'] = '__UNIQUE_ID__'
127+
return obj
128+
129+
def _generate_salt():
130+
return b'CCCC12344321CCCC'
131+
132+
expected_user_id = '/C=DK/ST=NA/L=NA/O=Test Org/OU=NA/CN=Test User/emailAddress=test@example.com'
133+
expected_user_password_hash = "PBKDF2$sha256$10000$XMZGaar/pU4PvWDr$w0dYjezF6JGtSiYPexyZMt3lM2134uix"
134+
135+
user_dict = {}
136+
user_dict['full_name'] = "Test User"
137+
user_dict['organization'] = "Test Org"
138+
user_dict['state'] = "NA"
139+
user_dict['country'] = "DK"
140+
user_dict['email'] = "test@example.com"
141+
user_dict['comment'] = "This is the create comment"
142+
user_dict['locality'] = ""
143+
user_dict['organizational_unit'] = ""
144+
user_dict['password'] = ""
145+
user_dict['password_hash'] = expected_user_password_hash
146+
147+
create_user(user_dict, self.configuration,
148+
keyword_auto, default_renew=True)
149+
150+
pickled = self.assertPickledFile(self.expected_user_db_file,
151+
apply_hints=['convert_dict_bytes_to_strings_kv'])
152+
self.assertIn(expected_user_id, pickled)
153+
154+
prepared = self.prepareFixtureAssert('MiG-users.db--example',
155+
fixture_format='json')
156+
157+
# TODO: remove resetting the handful of keys here done because changes
158+
# to make them assertion frienfly values will increase the size
159+
# of the diff which, at time of commit, are best minimised.
160+
actual_user_object = _adjust_user_dict_for_compare(pickled[expected_user_id])
161+
expected_user_object = _adjust_user_dict_for_compare(prepared.fixture_data[expected_user_id])
162+
163+
self.maxDiff = None
164+
self.assertEqual(actual_user_object, expected_user_object)
165+
166+
83167
class MigSharedUseradm__assure_current_htaccess(MigTestCase):
84168
"""Unit test helper for the migrid code pointed to in class name"""
85169

0 commit comments

Comments
 (0)