Skip to content

Commit 45f51fb

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 45f51fb

File tree

6 files changed

+200
-22
lines changed

6 files changed

+200
-22
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/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/picklesupp.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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-2025 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+
"""Pickle related details within the test support library."""
29+
30+
import pickle
31+
32+
from tests.support.suppconst import TEST_OUTPUT_DIR
33+
from tests.support.fixturesupp import _FIXTUREFILE_APPLIERS_ARGLESS
34+
35+
36+
class PickleAssertMixin:
37+
"""Assertions for working with pickled files to be used as a mixin."""
38+
39+
def assertPickledFile(self, pickle_file_path, apply_hints=None):
40+
"""
41+
Check a paricular pickled file exists and is loadable.
42+
43+
Any data contained within it is returned for further assertions
44+
having been optionally transformed by any requested hints.
45+
"""
46+
47+
with open(pickle_file_path, 'rb') as picklefile:
48+
pickled = pickle.load(picklefile)
49+
50+
if not apply_hints:
51+
return pickled
52+
53+
result = pickled
54+
for hint_name in apply_hints:
55+
if not hint_name in _FIXTUREFILE_APPLIERS_ARGLESS:
56+
raise NotImplementedError("unknown hint %s" % (hint_name,))
57+
hint_fn = _FIXTUREFILE_APPLIERS_ARGLESS[hint_name]
58+
result = hint_fn(pickled, modifier=None)
59+
return result

tests/test_mig_shared_useradm.py

Lines changed: 84 additions & 2 deletions
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,8 +83,87 @@
8083
)
8184

8285

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

86168
def before_each(self):
87169
"""The create_user call requires quite a few helper dirs"""

0 commit comments

Comments
 (0)