Manifest: transform path before checking against the zip file list #171
@ -102,7 +102,7 @@ EXPECTED_VALIDATION_ERRORS = {
|
||||
'invalid-theme-multiple-xmls.zip': {'source': ['A theme should have exactly one XML file.']},
|
||||
'invalid-missing-wheels.zip': {
|
||||
'source': [
|
||||
'A declared wheel is missing in the zip file, expected path: addon/./wheels/test-wheel-whatever.whl'
|
||||
'A declared wheel is missing in the zip file, expected path: addon/wheels/test-wheel-whatever.whl'
|
||||
]
|
||||
},
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, ANY
|
||||
import dataclasses
|
||||
import tempfile
|
||||
|
||||
from django.test import TestCase
|
||||
@ -11,6 +12,7 @@ from files.utils import (
|
||||
find_path_by_name,
|
||||
get_thumbnail_upload_to,
|
||||
make_thumbnails,
|
||||
validate_file_list,
|
||||
)
|
||||
|
||||
# Reusing test files from the extensions app
|
||||
@ -171,3 +173,84 @@ class UtilsTest(TestCase):
|
||||
mock_ffmpeg.return_value.option.return_value.input.assert_any_call(
|
||||
'path/to/source/video.mp4'
|
||||
)
|
||||
|
||||
def test_validate_file_list(self):
|
||||
@dataclasses.dataclass
|
||||
class TestParams:
|
||||
name: str
|
||||
toml_content: dict
|
||||
manifest_filepath: dict
|
||||
file_list: list
|
||||
expected: list
|
||||
|
||||
for test in [
|
||||
TestParams(
|
||||
name='valid add-on',
|
||||
toml_content={'type': 'add-on'},
|
||||
manifest_filepath='blender_manifest.toml',
|
||||
file_list=['__init__.py'],
|
||||
expected=[],
|
||||
),
|
||||
TestParams(
|
||||
name='add-on missing init',
|
||||
toml_content={'type': 'add-on'},
|
||||
manifest_filepath='blender_manifest.toml',
|
||||
file_list=[],
|
||||
expected=['invalid_missing_init'],
|
||||
),
|
||||
TestParams(
|
||||
name='valid theme',
|
||||
toml_content={'type': 'theme'},
|
||||
manifest_filepath='blender_manifest.toml',
|
||||
file_list=['theme.xml'],
|
||||
expected=[],
|
||||
),
|
||||
TestParams(
|
||||
name='missing theme file',
|
||||
toml_content={'type': 'theme'},
|
||||
manifest_filepath='blender_manifest.toml',
|
||||
file_list=[],
|
||||
expected=['missing_or_multiple_theme_xml'],
|
||||
),
|
||||
TestParams(
|
||||
name='multiple theme files',
|
||||
toml_content={'type': 'theme'},
|
||||
manifest_filepath='blender_manifest.toml',
|
||||
file_list=['theme.xml', 'theme/2.xml'],
|
||||
expected=['missing_or_multiple_theme_xml'],
|
||||
),
|
||||
TestParams(
|
||||
name='valid nested wheels',
|
||||
toml_content={'type': 'add-on', 'wheels': ['wheels/1.whl']},
|
||||
manifest_filepath='addon/blender_manifest.toml',
|
||||
file_list=['addon/__init__.py', 'addon/wheels/1.whl'],
|
||||
expected=[],
|
||||
),
|
||||
TestParams(
|
||||
name='valid nested wheels with ./',
|
||||
toml_content={'type': 'add-on', 'wheels': ['./wheels/1.whl']},
|
||||
manifest_filepath='blender_manifest.toml',
|
||||
file_list=['__init__.py', 'wheels/1.whl'],
|
||||
expected=[],
|
||||
),
|
||||
TestParams(
|
||||
name='missing wheel',
|
||||
toml_content={'type': 'add-on', 'wheels': ['./wheels/1.whl', './wheels/2.whl']},
|
||||
manifest_filepath='blender_manifest.toml',
|
||||
file_list=['__init__.py', 'wheels/1.whl'],
|
||||
expected=[{'code': 'missing_wheel', 'params': {'path': 'wheels/2.whl'}}],
|
||||
),
|
||||
TestParams(
|
||||
name='incorrect wheel nesting',
|
||||
toml_content={'type': 'add-on', 'wheels': ['./wheels/1.whl']},
|
||||
manifest_filepath='add-on/blender_manifest.toml',
|
||||
file_list=['add-on/__init__.py', 'wheels/1.whl'],
|
||||
expected=[{'code': 'missing_wheel', 'params': {'path': 'add-on/wheels/1.whl'}}],
|
||||
),
|
||||
]:
|
||||
with self.subTest(**dataclasses.asdict(test)):
|
||||
self.assertEqual(
|
||||
test.expected,
|
||||
validate_file_list(test.toml_content, test.manifest_filepath, test.file_list),
|
||||
test.name,
|
||||
)
|
||||
|
@ -147,7 +147,14 @@ def read_manifest_from_zip(archive_path):
|
||||
error_codes.append('invalid_manifest_toml')
|
||||
return None, error_codes
|
||||
|
||||
# If manifest was parsed successfully, do additional type-specific validation
|
||||
file_list_error_codes = validate_file_list(toml_content, manifest_filepath, file_list)
|
||||
error_codes.extend(file_list_error_codes)
|
||||
return toml_content, error_codes
|
||||
|
||||
|
||||
def validate_file_list(toml_content, manifest_filepath, file_list):
|
||||
"""Check the files in in the archive against manifest."""
|
||||
error_codes = []
|
||||
type_slug = toml_content['type']
|
||||
if type_slug == 'theme':
|
||||
theme_xmls = filter_paths_by_ext(file_list, '.xml')
|
||||
@ -155,22 +162,36 @@ def read_manifest_from_zip(archive_path):
|
||||
error_codes.append('missing_or_multiple_theme_xml')
|
||||
elif type_slug == 'add-on':
|
||||
# __init__.py is expected to be next to the manifest
|
||||
expected_init_path = os.path.join(os.path.dirname(manifest_filepath), '__init__.py')
|
||||
expected_init_path = _canonical_path('__init__.py', manifest_filepath)
|
||||
init_filepath = find_exact_path(file_list, expected_init_path)
|
||||
if not init_filepath:
|
||||
error_codes.append('invalid_missing_init')
|
||||
|
||||
wheels = toml_content.get('wheels')
|
||||
if wheels:
|
||||
for wheel in wheels:
|
||||
expected_wheel_path = os.path.join(os.path.dirname(manifest_filepath), wheel)
|
||||
expected_wheel_path = _canonical_path(wheel, manifest_filepath)
|
||||
wheel_filepath = find_exact_path(file_list, expected_wheel_path)
|
||||
if not wheel_filepath:
|
||||
error_codes.append(
|
||||
{'code': 'missing_wheel', 'params': {'path': expected_wheel_path}}
|
||||
)
|
||||
return error_codes
|
||||
|
||||
return toml_content, error_codes
|
||||
|
||||
def _canonical_path(path, manifest_filepath):
|
||||
"""Transform path before checking against the zip file list.
|
||||
|
||||
We expect to support other manifest fields (e.g. in the [build] section) that will potentially
|
||||
point to directories, including the "current" directory, which has to be denoted as "./".
|
||||
To avoid inconsistencies in file path notations supported for different fields, we process all
|
||||
paths values in manifest in a uniform way, allowing the leading "./" in all file paths.
|
||||
|
||||
All paths mentioned in manifest are treated as relative for the directory that contains
|
||||
manifest_filepath.
|
||||
"""
|
||||
if path.startswith('./'):
|
||||
path = path[2:]
|
||||
return os.path.join(os.path.dirname(manifest_filepath), path)
|
||||
|
||||
|
||||
def guess_mimetype_from_ext(file_name: str) -> str:
|
||||
|
Loading…
Reference in New Issue
Block a user