"""Offline release selection and integrity tests with synthetic files only.""" import json from pathlib import Path import shutil import subprocess import sys import tempfile import unittest import zipfile from scripts import offline_release class OfflineReleaseTests(unittest.TestCase): def setUp(self): temporary = tempfile.TemporaryDirectory() self.addCleanup(temporary.cleanup) self.root = Path(temporary.name) subprocess.run(['git', 'init', '-q'], cwd=self.root, check=True, capture_output=True) (self.root / '.gitignore').write_text('.env\nmodels/\nknowledge/prepared/\noutput/\n') (self.root / '.env').write_text('SYNTHETIC_SECRET=never_package\n') (self.root / 'src').mkdir() (self.root / 'src/app.py').write_text('print("synthetic")\n') (self.root / 'config').mkdir() (self.root / 'config/dms_schema.json').write_text('{"synthetic":true}') (self.root / 'scripts').mkdir() for name in ('offline_release.py', 'offline_release_bootstrap.py'): shutil.copyfile(Path(offline_release.__file__).parent / name, self.root / 'scripts' / name) self.snapshot = self.root / 'output/source.zip' self.snapshot.parent.mkdir() self.snapshot.write_bytes(b'synthetic knowledge snapshot') (self.root / 'models/bge-large-zh-v1.5').mkdir(parents=True) (self.root / 'models/bge-large-zh-v1.5/model.bin').write_bytes(b'synthetic model') (self.root / 'models/deepseek-tokenizer').mkdir(parents=True) (self.root / offline_release.TOKENIZER_TARGET).write_bytes(b'synthetic tokenizer') manifest = self.root / 'knowledge/prepared/ingestion/manifest.json' manifest.parent.mkdir(parents=True) manifest.write_text(json.dumps({'complete': True, 'model_files': {'model.bin': 'synthetic'}})) self.bundle = self.root / 'release.zip' def test_one_archive_contains_code_knowledge_and_required_models_only(self): offline_release.pack(self.root, self.snapshot, self.bundle) self.assertEqual(offline_release.verify(self.bundle)['files'], 10) runnable = subprocess.run([sys.executable, str(self.bundle), 'verify', str(self.bundle)], capture_output=True, text=True, check=True) self.assertTrue(json.loads(runnable.stdout)['ok']) with zipfile.ZipFile(self.bundle) as archive: names = set(archive.namelist()) self.assertIn('src/app.py', names) self.assertIn('__main__.py', names) self.assertIn(offline_release.SNAPSHOT_TARGET, names) self.assertIn('models/bge-large-zh-v1.5/model.bin', names) self.assertIn(offline_release.TOKENIZER_TARGET, names) self.assertIn('.env', names) self.assertIn('config/dms_schema.json', names) self.assertNotIn('knowledge/prepared/ingestion/manifest.json', names) target = self.root / 'server' offline_release.extract(self.bundle, target) self.assertEqual((target / offline_release.SNAPSHOT_TARGET).read_bytes(), self.snapshot.read_bytes()) self.assertEqual((target / '.env').read_text(), 'SYNTHETIC_SECRET=never_package\n') with self.assertRaisesRegex(ValueError, 'release_target_not_empty'): offline_release.extract(self.bundle, target) def test_rejects_archive_with_extra_file(self): offline_release.pack(self.root, self.snapshot, self.bundle) with zipfile.ZipFile(self.bundle, 'a') as archive: archive.writestr('unexpected.txt', 'extra') with self.assertRaisesRegex(ValueError, 'unexpected_release_members'): offline_release.verify(self.bundle) def test_missing_model_and_existing_output_fail(self): (self.root / 'models/bge-large-zh-v1.5/model.bin').unlink() with self.assertRaisesRegex(ValueError, 'missing_release_file'): offline_release.pack(self.root, self.snapshot, self.bundle) (self.root / 'models/bge-large-zh-v1.5/model.bin').write_bytes(b'synthetic model') offline_release.pack(self.root, self.snapshot, self.bundle) with self.assertRaisesRegex(ValueError, 'release_already_exists'): offline_release.pack(self.root, self.snapshot, self.bundle) def test_missing_dms_schema_fails(self): (self.root / 'config/dms_schema.json').unlink() with self.assertRaisesRegex(ValueError, 'missing_release_file'): offline_release.pack(self.root, self.snapshot, self.bundle)