Skip to content

Commit

Permalink
Validator features for v1.3.0 Specification (#20)
Browse files Browse the repository at this point in the history
- add spec v1.3.0 Tag-Manifests-Allowed and Manifests-Allowed validation
- add spec v1.3.0 Bag-Info tag "description" property validation
- add tests for spec v1.3.0 features
- fix basestring undefined in Python 3 & simplify string type checking
  • Loading branch information
tdilauro authored and ruebot committed Dec 12, 2019
1 parent b39c1a0 commit 7246a73
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 9 deletions.
66 changes: 58 additions & 8 deletions bagit_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@
import sys
from fnmatch import fnmatch
from os import listdir, walk
from os.path import exists, isdir, isfile, join, relpath, split
from os.path import basename, exists, isdir, isfile, join, relpath, split

if sys.version_info > (3,):
basestring = str
from urllib.request import urlopen # pylint: no-name-in-module
else:
basestring = basestring
from urllib import urlopen # pylint: disable=no-name-in-module

# Define an exceptin class for use within this module.
Expand Down Expand Up @@ -124,6 +126,8 @@ def validate(self, bag):
"Required tag manifests not found",
None,
),
(self.validate_payload_manifests_allowed, "Disallowed payload manifests present", (1, 3, 0)),
(self.validate_tag_manifests_allowed, "Disallowed tag manifests present", (1, 3, 0)),
(self.validate_tag_files_required, "Required tag files not found", None),
(
self.validate_allow_fetch,
Expand Down Expand Up @@ -170,6 +174,7 @@ def validate_bagit_profile(self, profile):
self.profile_version_info = tuple(int(i) for i in profile_version.split("."))
self.validate_bagit_profile_info(profile)
self.validate_bagit_profile_accept_bagit_versions(profile)
self.validate_bagit_profile_bag_info(profile)

# Check self.profile['bag-profile-info'] to see if "Source-Organization",
# "External-Description", "Version" and "BagIt-Profile-Identifier" are present.
Expand Down Expand Up @@ -200,18 +205,23 @@ def validate_bagit_profile_accept_bagit_versions(self, profile):
if "Accept-BagIt-Version" in profile:
for version_number in profile["Accept-BagIt-Version"]:
# pylint: disable=undefined-variable
if (
sys.version_info < (3, 0)
and not isinstance(version_number, basestring)
) or (
sys.version_info >= (3, 0) and not isinstance(version_number, str)
):
if not isinstance(version_number, basestring):
raise ProfileValidationError(
'Version number "%s" in "Accecpt-BagIt-Version" is not a string!'
'Version number "%s" in "Accept-BagIt-Version" is not a string!'
% version_number
)
return True

def validate_bagit_profile_bag_info(self, profile):
if 'Bag-Info' in profile:
for tag in profile['Bag-Info']:
config = profile['Bag-Info'][tag]
if self.profile_version_info >= (1, 3, 0) and \
'description' in config and not isinstance(config['description'], basestring):
self._fail("%s: Profile Bag-Info '%s' tag 'description' property, when present, must be a string." %
(profile, tag))
return True

# Validate tags in self.profile['Bag-Info'].
def validate_bag_info(self, bag):
# First, check to see if bag-info.txt exists.
Expand Down Expand Up @@ -287,6 +297,46 @@ def validate_tag_manifests_required(self, bag):
)
return True

@staticmethod
def manifest_algorithms(manifest_files):
for filepath in manifest_files:
filename = basename(filepath)
if filename.startswith("tagmanifest-"):
prefix = "tagmanifest-"
else:
prefix = "manifest-"
algorithm = filename.replace(prefix, "").replace(".txt", "")
yield algorithm

def validate_tag_manifests_allowed(self, bag):
return self._validate_allowed_manifests(bag, manifest_type="tag",
manifests_present=self.manifest_algorithms(bag.tagmanifest_files()),
allowed_attribute="Tag-Manifests-Allowed",
required_attribute="Tag-Manifests-Required")

def validate_payload_manifests_allowed(self, bag):
return self._validate_allowed_manifests(bag, manifest_type="payload",
manifests_present=self.manifest_algorithms(bag.manifest_files()),
allowed_attribute="Manifests-Allowed",
required_attribute="Manifests-Required")

def _validate_allowed_manifests(self, bag, manifest_type=None, manifests_present=None,
allowed_attribute=None, required_attribute=None):
if allowed_attribute not in self.profile:
return True
allowed = self.profile[allowed_attribute]
required = self.profile[required_attribute] if required_attribute in self.profile else []

required_but_not_allowed = [alg for alg in required if alg not in allowed]
if required_but_not_allowed:
self._fail("%s: Required %s manifest type(s) %s not allowed by %s" %
(bag, manifest_type, [str(a) for a in required_but_not_allowed], allowed_attribute))
present_but_not_allowed = [alg for alg in manifests_present if alg not in allowed]
if present_but_not_allowed:
self._fail("%s: Unexpected %s manifest type(s) '%s' present, but not allowed by %s" %
(bag, manifest_type, [str(a) for a in present_but_not_allowed], allowed_attribute))
return True

def validate_tag_files_allowed(self, bag):
"""
Validate the ``Tag-Files-Allowed`` tag.
Expand Down
89 changes: 88 additions & 1 deletion test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from unittest import TestCase, main

from bagit import Bag
from bagit_profile import Profile, find_tag_files
from bagit_profile import Profile, ProfileValidationError, find_tag_files

PROFILE_URL = (
"https://raw.github.com/bagit-profiles/bagit-profiles/master/bagProfileBar.json"
Expand Down Expand Up @@ -56,6 +56,93 @@ def test_existing_not_allowed(self):
self.assertTrue("Existing tag file" in profile.report.errors[0].value)


class BagitProfileV1_3_0_Tests(TestCase):

def tearDown(self):
if isdir(self.bagdir):
rmtree(self.bagdir)

def setUp(self):
self.bagdir = join("/tmp", "bagit-profile-test-bagdir")
if isdir(self.bagdir):
rmtree(self.bagdir)
copytree("./fixtures/test-tag-files-allowed/bag", self.bagdir)
with open(join("./fixtures/test-tag-files-allowed/profile.json"), "r") as f:
self.profile_dict = json.loads(f.read())
self.profile_dict["BagIt-Profile-Info"]["BagIt-Profile-Version"] = "1.3.0"

def test_BagInfoTagDescriptionShouldNotBeABoolean(self):
self.profile_dict["Bag-Info"] = {"FakeTag": {"description": False}}
with self.assertRaises(ProfileValidationError) as context:
profile = Profile("TEST", self.profile_dict)
self.assertTrue("tag description property, when present, must be a string", context.exception.value)

def test_BagInfoTagDescriptionShouldNotBeANumber(self):
self.profile_dict["Bag-Info"] = {"FakeTag": {"description": 0}}
with self.assertRaises(ProfileValidationError) as context:
profile = Profile("TEST", self.profile_dict)
self.assertTrue("tag description property, when present, must be a string", context.exception.value)

def test_BagInfoTagDescriptionShouldBeAString(self):
self.profile_dict["Bag-Info"] = {"FakeTag": {"description": "some string"}}
profile = Profile("TEST", self.profile_dict)
result = profile.validate(Bag(self.bagdir))
self.assertTrue(result)
self.assertEqual(len(profile.report.errors), 0)

def test_PayloadManifestAllowedRequiredConsistency(self):
self.profile_dict["Manifests-Allowed"] = ["foo"]
self.profile_dict["Manifests-Required"] = ["sha256"]
profile = Profile("TEST", self.profile_dict)
result = profile.validate(Bag(self.bagdir))
self.assertFalse(result)
self.assertEqual(len(profile.report.errors), 1)
self.assertTrue("Required payload manifest type(s)" in profile.report.errors[0].value)

def test_TagManifestAllowedRequiredConsistency(self):
self.profile_dict["Tag-Manifests-Allowed"] = ["foo"]
self.profile_dict["Tag-Manifests-Required"] = ["sha256"]
profile = Profile("TEST", self.profile_dict)
result = profile.validate(Bag(self.bagdir))
self.assertFalse(result)
self.assertEqual(len(profile.report.errors), 1)
self.assertTrue("Required tag manifest type(s)" in profile.report.errors[0].value)

def test_DisallowedPayloadManifestInBag(self):
self.profile_dict["Manifests-Allowed"] = ["foo"]
self.profile_dict["Manifests-Required"] = []
profile = Profile("TEST", self.profile_dict)
result = profile.validate(Bag(self.bagdir))
self.assertFalse(result)
self.assertEqual(len(profile.report.errors), 1)
self.assertTrue("Unexpected payload manifest type(s)" in profile.report.errors[0].value)

def test_DisallowedTagManifestInBag(self):
self.profile_dict["Tag-Manifests-Allowed"] = ["foo"]
self.profile_dict["Tag-Manifests-Required"] = []
profile = Profile("TEST", self.profile_dict)
result = profile.validate(Bag(self.bagdir))
self.assertFalse(result)
self.assertEqual(len(profile.report.errors), 1)
self.assertTrue("Unexpected tag manifest type(s)" in profile.report.errors[0].value)

def test_AllowMorePayloadManifestsThanRequired(self):
self.profile_dict["Manifests-Allowed"] = ["sha256", "sha512"]
self.profile_dict["Manifests-Required"] = ["sha256"]
profile = Profile("TEST", self.profile_dict)
result = profile.validate(Bag(self.bagdir))
self.assertTrue(result)
self.assertEqual(len(profile.report.errors), 0)

def test_AllowMoreTagManifestsThanRequired(self):
self.profile_dict["Tag-Manifests-Allowed"] = ["sha256", "sha512"]
self.profile_dict["Tag-Manifests-Required"] = ["sha256"]
profile = Profile("TEST", self.profile_dict)
result = profile.validate(Bag(self.bagdir))
self.assertTrue(result)
self.assertEqual(len(profile.report.errors), 0)


class BagitProfileConstructorTest(TestCase):
def setUp(self):
with open("./fixtures/bagProfileBar.json", "rb") as f:
Expand Down

0 comments on commit 7246a73

Please sign in to comment.