diff --git a/extensions/tests/test_manifest.py b/extensions/tests/test_manifest.py index e3bd23e4..33715835 100644 --- a/extensions/tests/test_manifest.py +++ b/extensions/tests/test_manifest.py @@ -400,11 +400,8 @@ class ValidateManifestFields(TestCase): data['blender_version_min'] = '2.9' with self.assertRaises(ValidationError) as e: ManifestValidator(data) - self.assertEqual(1, len(e.exception.messages)) - self.assertIn('blender_version_min', e.exception.messages[0]) - self.assertIn('4.2.0', e.exception.messages[0]) data['blender_version_min'] = '4.1.0' with self.assertRaises(ValidationError) as e: @@ -419,12 +416,8 @@ class ValidateManifestFields(TestCase): data['blender_version_min'] = '4.2.0-beta' with self.assertRaises(ValidationError) as e: ManifestValidator(data) - self.assertEqual( - e.exception.messages, - [ - 'Manifest value error: blender_version_min should be at least "4.2.0"', - ], - ) + self.assertIn('blender_version_min', e.exception.messages[0]) + self.assertIn('should follow', e.exception.messages[0]) def test_blender_version_max(self): data = { @@ -443,6 +436,52 @@ class ValidateManifestFields(TestCase): self.assertEqual(1, len(e.exception.messages)) self.assertIn('blender_version_max', e.exception.messages[0]) + def test_non_semver_versions(self): + data = { + **self.mandatory_fields, + **self.optional_fields, + } + + for field, value in ( + ('blender_version_min', '4.2'), + ('blender_version_min', '4.2.1+alpha'), + ('blender_version_min', '5'), + ('blender_version_max', '4.3'), + ('blender_version_max', '4.3.0+alpha'), + ('blender_version_max', '5'), + ('schema_version', '1.0'), + ('schema_version', '1'), + ('schema_version', '1.0.0.0'), + ): + value_original = data[field] + data[field] = value + + with self.assertRaises(ValidationError) as e: + ManifestValidator(data) + self.assertEqual(1, len(e.exception.messages)) + self.assertIn(field, e.exception.messages[0]) + + data[field] = value_original + + def test_version(self): + data = { + **self.mandatory_fields, + **self.optional_fields, + } + + data['version'] = '1.2.0' + ManifestValidator(data) + + data['version'] = '1.2.0+alpha' + ManifestValidator(data) + + for bad_version in ['1.2', '1.2.0.0', '1']: + data['version'] = bad_version + with self.assertRaises(ValidationError) as e: + ManifestValidator(data) + self.assertEqual(1, len(e.exception.messages)) + self.assertIn('version', e.exception.messages[0]) + def test_licenses(self): data = { **self.mandatory_fields, diff --git a/files/validators.py b/files/validators.py index 73056ce3..d1431aff 100644 --- a/files/validators.py +++ b/files/validators.py @@ -1,5 +1,6 @@ from semantic_version import Version import logging +import re from django.core.exceptions import ValidationError from django.core.validators import URLValidator, validate_unicode_slug @@ -20,6 +21,10 @@ import files.models logger = logging.getLogger(__name__) +# Define the regex pattern for major.minor.patch (do not include any additional label). +# https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string +version_regex_pattern = re.compile(r'^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$') + @deconstructible class FileMIMETypeValidator: @@ -536,7 +541,35 @@ class VersionValidator: ) -class SchemaVersionValidator(VersionValidator): +class SemVerNoLabelValidator(StringValidator): + """These version are a sub-set of the semantic version + + They only support major.minor.patch. + """ + + @staticmethod + def is_version_string(version): + return version_regex_pattern.match(version) + + @classmethod + def validate(cls, *, name: str, value: str, manifest: dict) -> str: + """Return error message if cannot validate, otherwise returns nothing""" + err_message = mark_safe( + f'Manifest value error: {name} should follow a ' + f'semantic version ' + f'with only major.minor.patch ' + f'e.g., "{cls.example}"' + ) + + if super().validate(name=name, value=value, manifest=manifest): + return err_message + + # Make sure we only have major.minor.patch. + if not cls.is_version_string(version=value): + return err_message + + +class SchemaVersionValidator(SemVerNoLabelValidator): example = '1.0.0' @classmethod @@ -558,7 +591,7 @@ class SchemaVersionValidator(VersionValidator): ) -class VersionMinValidator(VersionValidator): +class VersionMinValidator(SemVerNoLabelValidator): example = '4.2.0' @classmethod @@ -574,7 +607,7 @@ class VersionMinValidator(VersionValidator): ) -class VersionMaxValidator(VersionValidator): +class VersionMaxValidator(SemVerNoLabelValidator): example = '4.3.0' @classmethod @@ -591,15 +624,7 @@ class VersionMaxValidator(VersionValidator): # assuming that VersionMinValidator has caught this and reported properly return - try: - max = Version(value) - except (ValueError, TypeError): - return mark_safe( - f'Manifest value error: {name} should follow a ' - f'semantic version. ' - f'e.g., "{cls.example}"' - ) - + max = Version(value) if max <= min: return mark_safe( 'Manifest value error: blender_version_max should be greater than '