Integrate faction and random settings menus, generated objectives, scouts, convoys, air support, mortar stations and body-intel recovery. Add identified-body aggression, finite hunter UAZ deployment, dedicated objective defence and maximum AI skill with separate reduced aim accuracy. Include policy tests, native probes, evidence and documentation.
73 lines
4.7 KiB
Python
73 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Exercise exact dependency closure, resource ownership and isolated profile inputs."""
|
|
import copy
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'tools'))
|
|
import external_addons as ext
|
|
ROOT=Path(__file__).resolve().parents[1]
|
|
|
|
class ExternalAddonsTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp=tempfile.TemporaryDirectory();self.addCleanup(self.temp.cleanup)
|
|
self.home=Path(self.temp.name);self.source=self.home/'installed';self.source.mkdir()
|
|
self.addons=self.home/'profile'/'addons';self.addons.mkdir(parents=True)
|
|
self.cache=self.home/'profile'/'cache.json';self.value=ext.policy(ROOT)
|
|
for guid,deps in ext.ALLOWED.items():
|
|
folder=self.source/guid;folder.mkdir()
|
|
manifest='GameProject { GUID "'+guid+'" Dependencies { '+' '.join('"'+d+'"' for d in sorted(deps))+' } }\n'
|
|
(folder/'addon.gproj').write_text(manifest)
|
|
self.value['addons'][guid]['project_sha256']=hashlib.sha256(manifest.encode()).hexdigest()
|
|
data=b'RDB synthetic fixture\0'
|
|
for path,entry in self.value['resources'].items():
|
|
if entry['addon']==guid:data+=path.encode()+b'\0\x06\0\0\0\0\0'+bytes.fromhex(entry['guid'])[::-1]
|
|
(folder/'resourceDatabase.rdb').write_bytes(data);(folder/'data.pak').write_bytes(b'fixture '+guid.encode())
|
|
def test_resource_identity_requires_visible_owner(self):
|
|
for path,item in self.value['resources'].items():
|
|
self.assertTrue(ext.resource_allowed(self.value,{item['addon']},item['guid'],path))
|
|
self.assertFalse(ext.resource_allowed(self.value,{'core'},item['guid'],path))
|
|
self.assertFalse(ext.resource_allowed(self.value,{item['addon']},'0000000000000000',path))
|
|
self.assertEqual(ext.direct_for('core'),set(ext.DIRECT));self.assertEqual(ext.direct_for('chat'),set())
|
|
def test_policy_rejects_original_transitive_dependency(self):
|
|
root=self.home/'root';(root/'docs').mkdir(parents=True)
|
|
value=copy.deepcopy(self.value);value['addons'][ext.DIRECT[0]]['dependencies'].append('5F16D7E4A1CBE075')
|
|
(root/'docs/external-addons.json').write_text(json.dumps(value))
|
|
with self.assertRaisesRegex(ValueError,'transitive'):ext.policy(root)
|
|
def test_only_exact_authorized_closure_is_exposed_and_hashed(self):
|
|
unrelated=self.source/'original';unrelated.mkdir();(unrelated/'addon.gproj').write_text('GameProject { GUID "5F16D7E4A1CBE075" }')
|
|
records=ext.prepare_profile(self.addons,self.source,self.value,self.cache)
|
|
self.assertEqual(set(records),set(ext.ALLOWED))
|
|
self.assertEqual({p.name for p in self.addons.iterdir()},{'ERRI_'+g for g in ext.ALLOWED})
|
|
self.assertTrue(all(p.is_symlink() for p in self.addons.iterdir()))
|
|
self.assertTrue(ext.verify_profile(records,self.source,self.value,self.cache,self.addons))
|
|
self.assertEqual(ext.prepare_profile(self.addons,self.source,self.value,self.cache),records)
|
|
(self.source/ext.DIRECT[0]/'data.pak').write_bytes(b'mutated runtime')
|
|
self.assertFalse(ext.verify_profile(records,self.source,self.value,self.cache,self.addons))
|
|
def test_missing_and_duplicate_identity_fail(self):
|
|
guid=ext.DIRECT[0];shutil.copytree(self.source/guid,self.source/'duplicate')
|
|
with self.assertRaisesRegex(ValueError,'Duplicate'):ext.installed(self.source,self.value)
|
|
shutil.rmtree(self.source/'duplicate');shutil.rmtree(self.source/guid)
|
|
with self.assertRaisesRegex(ValueError,'Missing installed'):ext.installed(self.source,self.value)
|
|
def test_manifest_and_database_mutations_fail(self):
|
|
guid=ext.DIRECT[0];manifest=self.source/guid/'addon.gproj';old=manifest.read_text();manifest.write_text(old+'// changed')
|
|
with self.assertRaisesRegex(ValueError,'manifest changed'):ext.installed(self.source,self.value)
|
|
manifest.write_text(old)
|
|
resource=next(iter(self.value['resources'].values()));(self.source/resource['addon']/'resourceDatabase.rdb').write_bytes(b'wrong GUID')
|
|
with self.assertRaisesRegex(ValueError,'resource identity'):ext.installed(self.source,self.value)
|
|
def test_unrelated_profile_addon_and_wrong_link_fail(self):
|
|
(self.addons/'original').mkdir()
|
|
with self.assertRaisesRegex(ValueError,'Unrelated'):ext.prepare_profile(self.addons,self.source,self.value,self.cache)
|
|
(self.addons/'original').rmdir();guid=ext.DIRECT[0]
|
|
(self.addons/('ERRI_'+guid)).symlink_to(self.source/ext.DIRECT[1],target_is_directory=True)
|
|
with self.assertRaisesRegex(ValueError,'Unexpected private'):ext.prepare_profile(self.addons,self.source,self.value,self.cache)
|
|
def test_symlinked_runtime_file_fails(self):
|
|
pak=self.source/ext.DIRECT[0]/'data.pak';pak.unlink();pak.symlink_to(self.source/ext.DIRECT[1]/'data.pak')
|
|
with self.assertRaisesRegex(ValueError,'regular file'):ext.installed(self.source,self.value)
|
|
|
|
if __name__=='__main__':unittest.main()
|