Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a test covering operation of the cat functionality file. #128

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mig/shared/accountstate.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

from __future__ import print_function
from __future__ import absolute_import
from past.builtins import basestring

import os
import time
Expand Down
8 changes: 8 additions & 0 deletions mig/shared/functionality/cat.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ def main(client_id, user_arguments_dict, environ=None):

(configuration, logger, output_objects, op_name) = \
initialize_main_variables(client_id)

return _main(configuration, logger, op_name=op_name, output_objects=output_objects, client_id=client_id, user_arguments_dict=user_arguments_dict)

def _main(configuration, logger, op_name='', output_objects=[], client_id=None, user_arguments_dict=None, environ=None):
if logger is None:
logger = configuration.logger

client_dir = client_id_dir(client_id)
defaults = signature()[1]
status = returnvalues.OK
Expand All @@ -71,6 +78,7 @@ def main(client_id, user_arguments_dict, environ=None):
client_id,
configuration,
allow_rejects=False,
environ=environ,
# NOTE: path can use wildcards, dst cannot
typecheck_overrides={'path': valid_path_pattern},
)
Expand Down
187 changes: 187 additions & 0 deletions tests/fixture/MiG-users.db--example.binary
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
(dp0
V/C=DK/ST=NA/L=NA/O=Test Org/OU=NA/CN=b'Test User'/emailAddress=dummy-user
p1
(dp2
Vfull_name
p3
Vb'Test User'
p4
sVorganization
p5
VTest Org
p6
sVstate
p7
VNA
p8
sVcountry
p9
VDK
p10
sVemail
p11
Vdummy-user
p12
sVcomment
p13
VThis is the create comment
p14
sVpassword
p15
V
p16
sVpassword_hash
p17
VPBKDF2$sha256$10000$b't0JM/JjkQ347th0Q'$b'QupJt53hA5KhESEeqDhTQTCPOrCBvZ6H'
p18
sVdistinguished_name
p19
V/C=DK/ST=NA/L=NA/O=Test Org/OU=NA/CN=b'Test User'/emailAddress=dummy-user
p20
sVlocality
p21
g16
sVorganizational_unit
p22
g16
sVexpire
p23
I1757925298
sVcreated
p24
F1726233828.2676349
sVunique_id
p25
VktyCKIRg9HvsVzXMQ22EaKS67t9atchv9JKTiJqrtBiGN3qksKrbTTYIH8mitY2K
p26
sVopenid_names
p27
(lp28
sVold_password_hash
p29
VPBKDF2$sha256$10000$b'GL7Qq92iLe/hZXBo'$b'ZwB/5IZqgU7onP+ZqZk9zcHVZOx7jmWz'
p30
sVrenewed
p31
F1726389298.7801197
ssV/C=DK/ST=NA/L=NA/O=Test Org/OU=NA/CN=Test User/emailAddress=dummy-user
p32
(dp33
Vfull_name
p34
VTest User
p35
sVorganization
p36
VTest Org
p37
sVstate
p38
VNA
p39
sVcountry
p40
VDK
p41
sVemail
p42
Vdummy-user
p43
sVcomment
p44
VThis is the create comment
p45
sVpassword
p46
g16
sVpassword_hash
p47
VPBKDF2$sha256$10000$b'kZ8WgLNH+wg3X11d'$b't1d08MV4g215WYW7S7EbkjHqDF+MCjMa'
albu-diku marked this conversation as resolved.
Show resolved Hide resolved
p48
sVdistinguished_name
p49
V/C=DK/ST=NA/L=NA/O=Test Org/OU=NA/CN=Test User/emailAddress=dummy-user
p50
sVlocality
p51
g16
sVorganizational_unit
p52
g16
sVexpire
p53
I1759243332
sVcreated
p54
F1726602273.7987707
sVunique_id
p55
VKdYHJ21t37jAoHUmBq6t8Xnsnih6JWR5i0QepHoVXfDpQxz9fQGnEmegoDNrPzbe
p56
sVopenid_names
p57
(lp58
sVold_password_hash
p59
VPBKDF2$sha256$10000$b'yObizsUepZvvJ0/r'$b'uKIt7n6Lf/7WXD6pKDGyvT30L2uowBnV'
p60
sVrenewed
p61
F1727707333.0969944
ssV/C=DK/ST=NA/L=NA/O=Test Org/OU=NA/CN=Test User/[email protected]
p62
(dp63
Vfull_name
p64
VTest User
p65
sVorganization
p66
VTest Org
p67
sVstate
p68
VNA
p69
sVcountry
p70
VDK
p71
sVemail
p72
[email protected]
p73
sVcomment
p74
VThis is the create comment
p75
sVpassword
p76
g16
sVpassword_hash
p77
VPBKDF2$sha256$10000$b'/TkhLk4yMGf6XhaY'$b'7HUeQ9iwCkE4YMQAaCd+ZdrN+y8EzkJH'
p78
sVdistinguished_name
p79
g62
sVlocality
p80
g16
sVorganizational_unit
p81
g16
sVexpire
p82
I1758970812
sVcreated
p83
F1727434813.0792377
sVunique_id
p84
VaTza92klrnN2wfylm6HnphCy9C3PReGpQ6jklJ7zF3xjeaUDw36tW95Avx43vtba
p85
sVopenid_names
p86
(lp87
ss.
39 changes: 33 additions & 6 deletions tests/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,17 @@ def is_path_within(path, start=None, _msg=None):
return not relative.startswith('..')


def fixturefile(relative_path, fixture_format=None):
def _ensuredirs(absolute_dir):
try:
os.makedirs(absolute_dir)
except OSError as oserr:
if oserr.errno != errno.EEXIST:
raise

return absolute_dir


def fixturefile(relative_path, fixture_format=None, include_path=False):
"""Support function for loading fixtures from their serialised format.

Doing so is a little more involved than it may seem because serialisation
Expand All @@ -296,12 +306,24 @@ def fixturefile(relative_path, fixture_format=None):
#_, extension = os.path.splitext(os.path.basename(tmp_path))
#assert fixture_format == extension, "fixture file does not match format"

if fixture_format == 'json':
return _fixturefile_json(tmp_path)
data = None

if fixture_format == 'binary':
with open(tmp_path, 'rb') as binfile:
data = binfile.read()
elif fixture_format == 'json':
data = _fixturefile_json(tmp_path)
else:
raise AssertionError(
"unsupported fixture format: %s" % (fixture_format,))

return (data, tmp_path) if include_path else data


def fixturefile_normname(relative_path, prefix=None):
normname, _ = relative_path.split('--')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From experience I know that splitting strings and silently assuming a fixed number of resulting parts like this is a bit fragile and tends to break sooner or later. I'd suggest adding a maxsplit=1 arg to split or use normname=blabla[0] if additional parts are _always_ irrelevant. Otherwise explicitly assert exactly one occurrence of '--'` or split and check part count before assignment.

return os.path.join(prefix, normname) if prefix else normname
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a pedantic note I'm generally not too fond of these one-liner conditionals as they tend to save little and make code less readable IMO. E.g in this case it could easily hide details like if you really meant to test prefix to be logically True or actually wanted to only target the default value with is not None. Here prefix='' would not really make a difference, but for the general sake it might.



_FIXTUREFILE_HINTAPPLIERS = {
'array_of_tuples': lambda value: [tuple(x) for x in value]
Expand Down Expand Up @@ -340,12 +362,17 @@ def temppath(relative_path, test_case, ensure_dir=False, skip_clean=False):
"""Get absolute temp path for relative_path"""
assert isinstance(test_case, MigTestCase)
tmp_path = os.path.join(TEST_OUTPUT_DIR, relative_path)
return _temppath(tmp_path, test_case, ensure_dir=ensure_dir, skip_clean=skip_clean)


def _temppath(tmp_path, test_case, ensure_dir=False, skip_clean=False):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry if this comes out blunt after spending a significant amount of time figuring this code bit out, but on a closer look I don't like this temppath + _temppath construct and the out-of-module use of the latter at all. Probably also because I don't really see the point of exposing this part of the existing temppath as a new semi-private function with a very similar name and then using it directly in test modules.
If the TEST_OUTPUT_DIR anchoring should be skipped in some use cases there, that could easily be achieved with, say, a skip_output_anchor argument instead. That would keep all external use to just temppath and avoid the naming and functionality confusion as well.
I'll see if I can figure that out during merge now that I'm already halfway through.

if ensure_dir:
try:
os.mkdir(tmp_path)
except FileExistsError:
raise AssertionError(
"ABORT: use of unclean output path: %s" % relative_path)
except OSError as oserr:
if oserr.errno == errno.EEXIST:
raise AssertionError(
"ABORT: use of unclean output path: %s" % tmp_path)
if not skip_clean:
test_case._cleanup_paths.add(tmp_path)
return tmp_path
Expand Down
95 changes: 95 additions & 0 deletions tests/test_mig_shared_functionality_cat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
#
# --- BEGIN_HEADER ---
#
# test_mig_shared_functionality_cat - cat functionality unit test
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
#
# This file is part of MiG.
#
# MiG 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 2 of the License, or
# (at your option) any later version.
#
# MiG 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
#
# --- END_HEADER ---
#

"""Unit tests of the MiG functionality file implementing the cat resource."""

from __future__ import print_function
import importlib
import os
import shutil
import sys

from tests.support import MIG_BASE, MigTestCase, testmain, \
fixturefile, fixturefile_normname, \
_ensuredirs, _temppath
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: private and semi-private functions like these (one or two leading underscores) should generally not be used outside the module itself.
While it's not a blocker they should really either be made fully public (i.e. strip leading underscore and make sure they are generally usable) or the import replaced by existing public functions. The shared.fileio already has the makedirs_rec function, which appears to overlap some 90% percent with _ensuredirs.
I'll take a look and either adjust during merge or add the follow-up marker here.


from mig.shared.base import client_id_dir
from mig.shared.functionality.cat import _main as main


def create_http_environ(configuration, wsgi_variables={}):
Copy link
Contributor

@jonasbardino jonasbardino Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: as mentioned elsewhere I'd like the WSGI and CGI references in this unit test module to go. From a previous response I understand the origin of this function and that's fine, but delivery method is and should be irrelevant here as we want to (and do) test the underlying functionality backend in general.
I'll probably just generalize the variable name or the function to not even have the wsgi_variables arg during merge.

"""Small helper that can create a minimum viable environ dict suitable
for passing to http-facing code for the supplied configuration."""

environ = {}
environ['MIG_CONF'] = configuration.config_file
environ['HTTP_HOST'] = wsgi_variables.get('http_host', 'localhost')
environ['PATH_INFO'] = wsgi_variables.get('path_info', '/')
environ['REMOTE_ADDR'] = wsgi_variables.get('remote_addr', '127.0.0.1')
environ['SCRIPT_URI'] = ''.join(('http://', environ['HTTP_HOST'], environ['PATH_INFO']))
return environ


class MigCgibinCat(MigTestCase):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: another CGI leftover to strip.
I'll rename to fit unit test module name during merge and add the missing docstring while at it.

TEST_CLIENT_ID = '/C=DK/ST=NA/L=NA/O=Test Org/OU=NA/CN=Test User/[email protected]'

def _provide_configuration(self):
return 'testconfig'

def before_each(self):
# ensure a user home directory for our test user
conf_user_home = self.configuration.user_home[:-1]
test_client_dir = client_id_dir(self.TEST_CLIENT_ID)
test_user_dir = os.path.join(conf_user_home, test_client_dir)

# ensure a user db that includes our test user
conf_user_db_home = _ensuredirs(self.configuration.user_db_home)
_temppath(conf_user_db_home, self)
db_fixture, db_fixture_file = fixturefile('MiG-users.db--example', fixture_format='binary', include_path=True)
test_db_file = _temppath(fixturefile_normname('MiG-users.db--example', prefix=conf_user_db_home), self)
shutil.copyfile(db_fixture_file, test_db_file)

# create the test user home directory
self.test_user_dir = _ensuredirs(test_user_dir)
_temppath(self.test_user_dir, self)
self.test_environ = create_http_environ(self.configuration)

def test_returns_file_output_with_single_file_match(self):
with open(os.path.join(self.test_user_dir, 'foobar.txt'), 'w'):
pass
payload = {
'path': ['foobar.txt'],
}

(output_objects, status) = main(self.configuration, self.logger, client_id=self.TEST_CLIENT_ID, user_arguments_dict=payload, environ=self.test_environ)
self.assertEqual(len(output_objects), 1)
output_obj = output_objects[0]
self.assertEqual(output_obj['object_type'], 'file_output')


if __name__ == '__main__':
testmain()
Loading