Skip to content

Commit

Permalink
Layout tests
Browse files Browse the repository at this point in the history
  • Loading branch information
zimeon committed Oct 13, 2024
1 parent 350d9e4 commit da0c17c
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extensionName": "good_param",
"param": "yay!"
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NOT a JSON file!
59 changes: 40 additions & 19 deletions ocfl/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ class Layout:
This base class includes some common implementations where that makes
sense for certain methods, other methods must be replace and will
throw an exception if called.
Attributes:
NAME: string for name of the extension
DESCRIPTION: sting with longer description of extension
PARAMS: None if the extension has no parameters, otherwise a dict
with keys that are the parameter names, and values that are
the methods used to parse/check the paremeter.
"""

def __init__(self):
Expand All @@ -53,8 +60,13 @@ def config_file(self):

@property
def config(self):
"""Dictionary with config.json configuration for the layout extenstion."""
raise LayoutException("No yet implemented")
"""Dictionary with config.json configuration for the layout extenstion.
Returns a dict with values based on the current attributes (to be
serialized with json.dump()), else None indicates that there is no
config.json this layout.
"""
return None

def strip_root(self, path, root):
"""Remove root from path, throw exception on failure."""
Expand Down Expand Up @@ -83,11 +95,10 @@ def read_layout_params(self, root_fs=None, params_required=False):
"""Look for and read and layout configuration parameters.
Arguments:
root_fs - the storage root fs object
params_required - if True then throw exception for params file not present
root_fs: the storage root fs object
params_required: if True then throw exception for params file not present
Returns:
params - dict of params read
Returns None
"""
config = None
if root_fs.exists(self.config_file):
Expand All @@ -101,14 +112,23 @@ def read_layout_params(self, root_fs=None, params_required=False):
elif params_required:
raise LayoutException("Storage root extension config %s expected but not present" % (self.config_file))
if config is not None:
self.check_and_set_layout_params(config, require_extension_name=True)

def check_and_set_layout_params(self, config, require_extension_name=False):
"""Check the layout extension params and set for this layout object."""
# Check the extensionName
if not require_extension_name and 'extensionName' not in config:
# Fine if we don't require the extension name
pass
self.check_and_set_layout_params(config)

def check_and_set_layout_params(self, config, require_extension_name=True):
"""Check the layout extension params and set for this layout object.
Arguments:
config: dict representing the parse JSON config.json file
require_extension_name: boolean, True by default. If set False then
the extensionName paramater is not required
For each parameter that is recognizedm, the appropriate check and set
method in self.PARAMS is called. The methods set instance attributes.
"""
# Check the extensionName if required and/or specified
if 'extensionName' not in config:
if require_extension_name:
raise LayoutException("Storage root extension config missing extensionName")
elif config.get('extensionName') != self.NAME:
raise LayoutException("Storage root extension config extensionName is %s, expected %s" % (config.get('extensionName'), self.NAME))
# Read and check the parameters (ignore any extra params)
Expand All @@ -118,16 +138,17 @@ def check_and_set_layout_params(self, config, require_extension_name=False):
def write_layout_params(self, root_fs=None):
"""Write the config.json file with layout parameters if need for this layout.
Does nothing if there is no config.json required for this payout.
Does nothing if there is no config.json content defined for this layout.
"""
if self.PARAMS is None:
# Nothing to write if there are no params
config = self.config
if config is None:
# Nothing to write if there is no config defined
return
if root_fs.exists(self.config_file):
raise LayoutException("Storage root extension layout config %s already exists" % (self.config_file))
try:
root_fs.makedirs(os.path.dirname(self.config_file))
with root_fs.open(self.config_file, 'w') as fh:
json.dump(self.config, fh, indent=2)
json.dump(config, fh, indent=2)
except Exception as e:
raise LayoutException("Storage root extension config file %s exists but can't be read/parsed (%s)" % (self.config_file, str(e)))
raise LayoutException("Storage root extension config file %s couldn't be written (%s)" % (self.config_file, str(e)))
2 changes: 1 addition & 1 deletion ocfl/storage_root.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def initialize(self, spec_version=None, layout_params=None):
config = json.loads(layout_params)
except Exception as e:
raise StorageRootException("Bad layout params supplied: %s" % (str(e)))
self.layout.check_and_set_layout_params(config=config)
self.layout.check_and_set_layout_params(config=config, require_extension_name=False)
self.check_spec_version(spec_version=spec_version)
# Now create the storage root
(parent, root_dir) = fs.path.split(self.root)
Expand Down
119 changes: 98 additions & 21 deletions tests/test_layout.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,106 @@
"""Digest tests."""
import unittest
from ocfl.layout import Layout
import unittest.mock
from ocfl.layout import Layout, LayoutException
from ocfl.pyfs import open_fs


class TestAll(unittest.TestCase):
"""TestAll class to run tests."""

def test_almost_everything(self):
def test_init_and_config(self):
"""Test almost everything, just a little."""
d = Layout()
self.assertEqual(type(d), Layout)
self.assertEqual(d.strip_root('a/b', 'a'), 'b')
self.assertEqual(d.strip_root('a/b', ''), 'a/b')
self.assertEqual(d.strip_root('a/b/c', 'a/b/c'), '.')
self.assertEqual(d.strip_root('a/b/c', 'a/b/'), 'c')
self.assertEqual(d.strip_root('a/b/c/', 'a/b'), 'c')
self.assertRaises(Exception, d.strip_root, 'a', 'b')
self.assertRaises(Exception, d.strip_root, 'a', 'a/b')
self.assertRaises(Exception, d.strip_root, '', 'a/b')
self.assertTrue(d.is_valid(''))
self.assertTrue(d.is_valid('anything'))
self.assertEqual(d.encode(''), '')
self.assertEqual(d.encode('something'), 'something')
self.assertEqual(d.encode('http://a.b.c'), 'http%3A%2F%2Fa.b.c')
self.assertEqual(d.decode(''), '')
self.assertEqual(d.decode('something-else'), 'something-else')
self.assertEqual(d.decode('http%3a%2f%2Fa.b.c'), 'http://a.b.c')
self.assertRaises(Exception, d.identifier_to_path, 'id')
layout = Layout()
self.assertEqual(type(layout), Layout)
self.assertEqual(layout.config_file, "extensions/BASE/config.json")
self.assertEqual(layout.config, None)

def test_strip_root(self):
"""Test strip_root."""
layout = Layout()
self.assertEqual(layout.strip_root('a/b', 'a'), 'b')
self.assertEqual(layout.strip_root('a/b', ''), 'a/b')
self.assertEqual(layout.strip_root('a/b/c', 'a/b/c'), '.')
self.assertEqual(layout.strip_root('a/b/c', 'a/b/'), 'c')
self.assertEqual(layout.strip_root('a/b/c/', 'a/b'), 'c')
self.assertRaises(LayoutException, layout.strip_root, 'a', 'b')
self.assertRaises(LayoutException, layout.strip_root, 'a', 'a/b')
self.assertRaises(LayoutException, layout.strip_root, '', 'a/b')

def test_is_valid(self):
"""Test is_valid method."""
layout = Layout()
self.assertTrue(layout.is_valid(''))
self.assertTrue(layout.is_valid('anything'))

def test_encodea_and_decode(self):
"""Test encode and decode methods."""
layout = Layout()
self.assertEqual(layout.encode(''), '')
self.assertEqual(layout.encode('something'), 'something')
self.assertEqual(layout.encode('http://a.b.c'), 'http%3A%2F%2Fa.b.c')
self.assertEqual(layout.decode(''), '')
self.assertEqual(layout.decode('something-else'), 'something-else')
self.assertEqual(layout.decode('http%3a%2f%2Fa.b.c'), 'http://a.b.c')
self.assertRaises(LayoutException, layout.identifier_to_path, 'id')

def text_read_layout_params(self):
"""Test read_layout_params."""
root_fs = open_fs("extra_fixtures/extension_configs")
layout = Layout()
layout.NAME = "good_param"
layout.param = None

def parse_param(value):
layout.param = value
layout.PARAMS = {"param": parse_param}
layout.read_layout_params(root_fs=root_fs)
self.assertEqual(layout.param, "yay!")
# Error cases
# No config file
layout.NAME = "no_config"
self.assertRaises(LayoutException, layout.read_layout_params, root_fs=root_fs)
layout.NAME = "not_json"
self.assertRaises(LayoutException, layout.read_layout_params, root_fs=root_fs)

def test_check_and_set_layout_params(self):
"""Test check_and_set_layout_params."""
layout = Layout()
layout.NAME = "mylayout"
layout.param = None

def parse_param(value):
layout.param = value
layout.PARAMS = {"param": parse_param}
self.assertRaises(LayoutException,
layout.check_and_set_layout_params, config={})
self.assertRaises(LayoutException,
layout.check_and_set_layout_params,
config={"extensionName": "wrong-name"})
# Good case
layout.check_and_set_layout_params(
config={"extensionName": "mylayout",
"param": "42",
"extraParam": "whatever"})
self.assertEqual(layout.param, "42")

def test_write_layout_params(self):
"""Test write_layout_params."""
root_fs = open_fs("mem://")
layout = Layout()
# No config_file will return none, so simply exits
self.assertEqual(layout.write_layout_params(root_fs=root_fs), None)
# File exists
layout.NAME = "a_name"
with unittest.mock.patch("ocfl.layout.Layout.config", new_callable=unittest.mock.PropertyMock) as mock_config:
mock_config.return_value = {"hello": "you"}
# First write works....
layout.write_layout_params(root_fs=root_fs)
# Second hits file exists
self.assertRaises(LayoutException, layout.write_layout_params,
root_fs=root_fs)
# Change name, make bad JSON causing write exception
layout.NAME = "new_name"
mock_config.return_value = {unittest: "key in json.dump must not be module name!"}
self.assertRaises(LayoutException, layout.write_layout_params,
root_fs=root_fs)

0 comments on commit da0c17c

Please sign in to comment.