import modulus.build_context import os import pathlib import tempfile import unittest import unittest.mock import urllib.error import modulus.artifacts import modulus.config class ArtifactTests(unittest.TestCase): def setUp(self): self.temporary = tempfile.TemporaryDirectory() self.addCleanup(self.temporary.cleanup) self.root = pathlib.Path(self.temporary.name) config = modulus.config.normalize({'package': {'name': 'example', 'artifact': {'enabled': True, 'targets': ['arm_64_gcc12_release']}}}) self.context = modulus.build_context.build_context(self.root, config) self.directory = self.root / 'build/packages' self.directory.mkdir(parents=True) self.archive = self.directory / modulus.artifacts.archive_name('example', 'arm_64_gcc12_release') self.archive.write_bytes(b'package') self.checksum = modulus.artifacts.write_checksum(self.archive) self.enterContext(unittest.mock.patch.dict(os.environ, {'PACKAGE_REGISTRY_USER': 'test', 'PACKAGE_REGISTRY_TOKEN': 'secret'})) def test_discovery_uses_the_same_names_as_creation(self): self.assertEqual(self.archive.name, 'example_arm_64_gcc12_release.tar.gz') self.assertEqual(modulus.artifacts.discover_artifacts(self.directory, 'example', ['arm_64_gcc12_release']), [self.archive, self.checksum]) def test_missing_or_invalid_checksums_never_touch_registry(self): with unittest.mock.patch.object(modulus.artifacts, 'request') as request: self.archive.write_bytes(b'corrupt') with self.assertRaisesRegex(SystemExit, 'checksum mismatch'): modulus.artifacts.main(self.context, []) self.checksum.unlink() with self.assertRaisesRegex(SystemExit, 'missing artifact'): modulus.artifacts.main(self.context, []) request.assert_not_called() def test_replacement_and_cleanup_are_ordered_after_validation(self): events = [] with unittest.mock.patch.object(modulus.artifacts, 'list_versions', return_value=['latest', 'old']), \ unittest.mock.patch.object(modulus.artifacts, 'delete_version', side_effect=lambda *args: events.append(('delete', args[-2]))), \ unittest.mock.patch.object(modulus.artifacts, 'upload_file', side_effect=lambda *args: events.append(('upload', args[2].name))): self.assertEqual(modulus.artifacts.main(self.context, []), 0) self.assertEqual(events, [('delete', 'latest'), ('upload', self.archive.name), ('upload', self.checksum.name), ('delete', 'old')]) def test_failed_upload_does_not_delete_other_slots(self): with unittest.mock.patch.object(modulus.artifacts, 'list_versions', return_value=['latest', 'old']), \ unittest.mock.patch.object(modulus.artifacts, 'delete_version') as delete, \ unittest.mock.patch.object(modulus.artifacts, 'upload_file', side_effect=SystemExit('upload failed')): with self.assertRaisesRegex(SystemExit, 'upload failed'): modulus.artifacts.main(self.context, []) self.assertEqual([call.args[-2] for call in delete.call_args_list], ['latest']) def test_request_errors_and_credentials_are_not_logged(self): for code in (401, 500): with unittest.mock.patch.object(modulus.artifacts.urllib.request, 'urlopen', side_effect=urllib.error.HTTPError('https://example.test', code, 'failed', {}, None)): with self.assertRaises(SystemExit) as caught: modulus.artifacts.request('PUT', 'https://example.test', 'secret') self.assertNotIn('secret', str(caught.exception))