import re import unittest from pathlib import Path ROOT = Path(__file__).resolve().parent.parent NAME_VERSION = re.compile(r'^([A-Za-z0-9_.-]+)==([^\s\\]+)', re.MULTILINE) def normalized(name): return re.sub(r'[-_.]+', '-', name).lower() class DependencyLockTests(unittest.TestCase): @classmethod def setUpClass(cls): cls.direct_text = (ROOT / 'infra/requirements.txt').read_text() cls.lock_text = (ROOT / 'infra/requirements.lock').read_text() def test_every_direct_pin_matches_lock(self): direct = {normalized(name): version for name, version in NAME_VERSION.findall(self.direct_text)} locked = {normalized(name): version for name, version in NAME_VERSION.findall(self.lock_text)} self.assertTrue(direct) self.assertEqual({name: locked.get(name) for name in direct}, direct) def test_every_locked_package_has_sha256_hash(self): matches = list(NAME_VERSION.finditer(self.lock_text)) self.assertTrue(matches) for index, match in enumerate(matches): end = matches[index + 1].start() if index + 1 < len(matches) else len(self.lock_text) block = self.lock_text[match.start():end] hashes = re.findall(r'--hash=sha256:([0-9a-f]{64})(?:\s|\\)', block) self.assertTrue(hashes, f'{match.group(1)} has no SHA-256 artifact hash') def test_image_build_requires_the_lock_and_hashes(self): dockerfile = (ROOT / 'infra/Dockerfile').read_text() self.assertIn('requirements.lock', dockerfile) self.assertIn('--require-hashes -r infra/requirements.lock', dockerfile) def test_reproducible_generator_is_recorded(self): self.assertIn('./local/lock_dependencies.sh', self.lock_text[:300]) if __name__ == '__main__': unittest.main()