Combine Workshop addon and fix prison startup
Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
This commit is contained in:
@@ -20,7 +20,7 @@ class ExternalPackageTests(unittest.TestCase):
|
||||
self.source=self.fixture.external_fixture()
|
||||
self.root=self.fixture.source;self.session=self.fixture.session
|
||||
self.steam=self.fixture.steam;self.compat=self.fixture.compat
|
||||
self.context=n.external_request('chat',self.source,self.root)
|
||||
self.context=n.external_request('core',self.source,self.root)
|
||||
def prepare(self):
|
||||
self.authored=n.copy_payload(self.root,self.session)
|
||||
n.capture_external(self.context,self.session)
|
||||
@@ -28,7 +28,7 @@ class ExternalPackageTests(unittest.TestCase):
|
||||
profile=self.session/'profiles'/project;(profile/'addons').mkdir(parents=True)
|
||||
(profile/'addons/ArmaReforger').symlink_to(self.steam/'steamapps/common/Arma Reforger/addons/data',target_is_directory=True)
|
||||
return profile
|
||||
def native_double(self,project='ai',mutation=None,error='',native_exit=0,cleanup=True,write_status=True):
|
||||
def native_double(self,project='core',mutation=None,error='',native_exit=0,cleanup=True,write_status=True):
|
||||
child=Mock();child.poll.return_value=0
|
||||
def spawn(command,**kwargs):
|
||||
self.assertNotIn('WINEPREFIX',kwargs['env'])
|
||||
@@ -44,10 +44,10 @@ class ExternalPackageTests(unittest.TestCase):
|
||||
patch.object(n.subprocess,'check_output',side_effect=lambda args,**kw:'[]' if 'sink-inputs' in args else '7'), \
|
||||
patch.object(n.subprocess,'run'),patch.object(n,'cleanup_owned_run',return_value={'cleanup_confirmed':cleanup,'errors':[]}) as cleaned:
|
||||
report=n.run_project(self.steam,self.compat,self.session,project,30,self.authored,
|
||||
external=self.context if project in {'ai','chat'} else None)
|
||||
external=self.context if project in {'core'} else None)
|
||||
launch.assert_called_once();cleaned.assert_called_once()
|
||||
return report
|
||||
def args(self,project='chat',external=True,continuation=True):
|
||||
def args(self,project='core',external=True,continuation=True):
|
||||
session=Path('/tmp')/('erri-external-package-'+uuid.uuid4().hex)
|
||||
self.addCleanup(lambda:shutil.rmtree(session,ignore_errors=True))
|
||||
return argparse.Namespace(project=project,external_addons=self.source if external else None,
|
||||
@@ -60,25 +60,22 @@ class ExternalPackageTests(unittest.TestCase):
|
||||
child.assert_not_called()
|
||||
return code,json.loads((args.session/'package-report.json').read_text()),runs.call_args_list
|
||||
def test_option_is_required_only_for_external_projects_before_native_work(self):
|
||||
for project in ('ai','chat'):
|
||||
for project in ('core',):
|
||||
with self.subTest(project=project),patch.object(n,'assert_no_game') as game,patch.object(n,'copy_payload') as copy:
|
||||
with self.assertRaisesRegex(ValueError,'require --external-addons'):n.run(self.args(project,False),self.root)
|
||||
game.assert_not_called();copy.assert_not_called()
|
||||
for project in ('core','intel','map'):
|
||||
with self.subTest(project=project),patch.object(n,'assert_no_game') as game:
|
||||
with self.assertRaisesRegex(ValueError,'vanilla-only'):n.run(self.args(project),self.root)
|
||||
game.assert_not_called()
|
||||
self.assertIsNone(n.external_request(project,None,self.root))
|
||||
for project in ('ai','intel','map','chat'):
|
||||
with self.assertRaises(ValueError):n.project_order(project)
|
||||
def test_cli_passes_explicit_installed_path_without_launch(self):
|
||||
with patch.object(n,'run',return_value=1) as run:
|
||||
self.assertEqual(n.main(['--project','ai','--session','/tmp/not-created','--compat',str(self.compat),
|
||||
self.assertEqual(n.main(['--project','core','--session','/tmp/not-created','--compat',str(self.compat),
|
||||
'--external-addons',str(self.source)]),1)
|
||||
self.assertEqual(run.call_args.args[0].external_addons,self.source)
|
||||
def test_exact_profiles_expose_only_reviewed_six_not_workshop_parent(self):
|
||||
unrelated=self.source/'original';unrelated.mkdir();(unrelated/'addon.gproj').write_text('GameProject { GUID "5F16D7E4A1CBE075" }')
|
||||
self.prepare()
|
||||
for project in ('core','ai','intel','map','chat'):
|
||||
profile=self.profile(project);context=self.context if project in ('ai','chat') else None
|
||||
for project in ('core',):
|
||||
profile=self.profile(project);context=self.context if project in ('core',) else None
|
||||
records=n.expose_external(project,profile,self.steam,context)
|
||||
expected={'ArmaReforger'}|({'ERRI_'+guid for guid in ext.ALLOWED} if context else set())
|
||||
self.assertEqual({p.name for p in (profile/'addons').iterdir()},expected)
|
||||
@@ -94,8 +91,8 @@ class ExternalPackageTests(unittest.TestCase):
|
||||
def test_native_helper_rejects_missing_or_wrong_context_before_creating_profile(self):
|
||||
self.prepare()
|
||||
with patch.object(n.subprocess,'Popen') as child:
|
||||
with self.assertRaises(ValueError):n.run_project(self.steam,self.compat,self.session,'ai',30,self.authored)
|
||||
with self.assertRaises(ValueError):n.run_project(self.steam,self.compat,self.session,'core',30,self.authored,external=self.context)
|
||||
with self.assertRaises(ValueError):n.run_project(self.steam,self.compat,self.session,'core',30,self.authored)
|
||||
with self.assertRaises(ValueError):n.run_project(self.steam,self.compat,self.session,'ai',30,self.authored,external=self.context)
|
||||
child.assert_not_called()
|
||||
self.assertFalse((self.session/'profiles').exists())
|
||||
def test_missing_manifest_changed_policy_or_foreign_dependency_refuse(self):
|
||||
@@ -110,7 +107,7 @@ class ExternalPackageTests(unittest.TestCase):
|
||||
shutil.rmtree(self.source/ext.DIRECT[0])
|
||||
with self.assertRaisesRegex(ValueError,'Missing installed'):n.capture_external(self.context,self.session)
|
||||
def test_unrelated_addon_wrong_link_and_whole_tree_link_are_refused(self):
|
||||
self.prepare();profile=self.profile('ai');records=n.expose_external('ai',profile,self.steam,self.context)
|
||||
self.prepare();profile=self.profile('core');records=n.expose_external('core',profile,self.steam,self.context)
|
||||
addon=profile/'addons';extra=addon/'Original';extra.symlink_to(self.source,target_is_directory=True)
|
||||
with self.assertRaisesRegex(ValueError,'unrelated'):n.verify_package_profile(profile,self.steam,records)
|
||||
extra.unlink();link=addon/('ERRI_'+ext.DIRECT[0]);link.unlink();link.symlink_to(self.source,target_is_directory=True)
|
||||
@@ -121,7 +118,7 @@ class ExternalPackageTests(unittest.TestCase):
|
||||
self.prepare();report=self.native_double()
|
||||
self.assertTrue(report['candidate_created']);self.assertTrue(report['external_addons_unchanged_after_run'])
|
||||
self.assertTrue(report['profile_addons_unchanged']);self.assertEqual(report['external_addon_count'],6)
|
||||
evidence=self.session/'evidence/ai'
|
||||
evidence=self.session/'evidence/core'
|
||||
before=json.loads((evidence/'external-addons-before.json').read_text())
|
||||
after=json.loads((evidence/'external-addons-after.json').read_text())
|
||||
self.assertTrue(after['unchanged']);self.assertEqual(after['records'],before)
|
||||
@@ -134,7 +131,7 @@ class ExternalPackageTests(unittest.TestCase):
|
||||
self.assertFalse(report['candidate_created']);self.assertFalse(report['external_addons_unchanged_after_run'])
|
||||
self.assertTrue(report['copied_authored_unchanged']);self.assertTrue(report['cleanup_confirmed'])
|
||||
self.assertTrue(any('External input validation failed' in e for e in report['errors']))
|
||||
after=json.loads((self.session/'evidence/ai/external-addons-after.json').read_text())
|
||||
after=json.loads((self.session/'evidence/core/external-addons-after.json').read_text())
|
||||
self.assertFalse(after['unchanged']);self.assertNotEqual(after['records'],self.context['records'])
|
||||
def test_cache_tampering_cannot_relabel_changed_payload_as_unchanged(self):
|
||||
self.prepare()
|
||||
@@ -144,11 +141,11 @@ class ExternalPackageTests(unittest.TestCase):
|
||||
self.prepare();path=self.context['policy_path']
|
||||
report=self.native_double(mutation=lambda:path.write_text(path.read_text()+'\n'))
|
||||
self.assertFalse(report['candidate_created']);self.assertFalse(report['external_addons_unchanged_after_run'])
|
||||
after=json.loads((self.session/'evidence/ai/external-addons-after.json').read_text());self.assertIsNone(after['records'])
|
||||
after=json.loads((self.session/'evidence/core/external-addons-after.json').read_text());self.assertIsNone(after['records'])
|
||||
def test_relinked_private_dependency_fails_without_altering_installed_inputs(self):
|
||||
self.prepare()
|
||||
def mutate():
|
||||
link=self.session/'profiles/ai/addons'/('ERRI_'+ext.DIRECT[0]);link.unlink();link.symlink_to(self.source,target_is_directory=True)
|
||||
link=self.session/'profiles/core/addons'/('ERRI_'+ext.DIRECT[0]);link.unlink();link.symlink_to(self.source,target_is_directory=True)
|
||||
report=self.native_double(mutation=mutate)
|
||||
self.assertFalse(report['candidate_created']);self.assertFalse(report['profile_addons_unchanged'])
|
||||
self.assertTrue(report['external_addons_unchanged_after_run'])
|
||||
@@ -163,10 +160,10 @@ class ExternalPackageTests(unittest.TestCase):
|
||||
def test_context_only_passed_to_ai_and_chat_in_full_build(self):
|
||||
args=self.args()
|
||||
def result(*pos,**kw):
|
||||
self.assertEqual(kw['external'] is not None,pos[3] in ('ai','chat'))
|
||||
self.assertEqual(kw['external'] is not None,pos[3] in ('core',))
|
||||
return base.NativePackageTests.completed_report(True)
|
||||
code,summary,calls=self.run_double(args,result)
|
||||
self.assertEqual(code,0);self.assertEqual([c.args[3] for c in calls],['core','ai','intel','map','chat'])
|
||||
self.assertEqual(code,0);self.assertEqual([c.args[3] for c in calls],['core'])
|
||||
self.assertEqual(summary['external_addon_count'],6);self.assertTrue(summary['external_addons_unchanged_after_run'])
|
||||
self.assertFalse(summary['release_qualified']);self.assertFalse(summary['published'])
|
||||
self.assertTrue((args.session/'external-addons-after.json').is_file())
|
||||
@@ -179,12 +176,11 @@ class ExternalPackageTests(unittest.TestCase):
|
||||
self.assertEqual(code,1);self.assertEqual(seen,['core']);self.assertEqual(len(calls),1)
|
||||
self.assertFalse(summary['external_addons_unchanged_after_run']);self.assertFalse(summary['all_candidates_created'])
|
||||
self.assertIn('external dependency inputs changed',summary['stopped_reason'])
|
||||
self.assertEqual(summary['skipped_projects'],['ai','intel','map','chat'])
|
||||
self.assertEqual(summary['skipped_projects'],[])
|
||||
def test_diagnostic_continuation_cannot_ignore_unverified_external_report(self):
|
||||
args=self.args();reports=[base.NativePackageTests.completed_report(True),
|
||||
base.NativePackageTests.completed_report(True,external_addons_unchanged_after_run=False)]
|
||||
args=self.args();reports=[base.NativePackageTests.completed_report(True,external_addons_unchanged_after_run=False)]
|
||||
code,summary,calls=self.run_double(args,lambda *a,**k:reports.pop(0))
|
||||
self.assertEqual(code,1);self.assertEqual(len(calls),2);self.assertIn('external dependency',summary['stopped_reason'])
|
||||
self.assertEqual(code,1);self.assertEqual(len(calls),1);self.assertIn('external dependency',summary['stopped_reason'])
|
||||
self.assertFalse(summary['all_candidates_created'])
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user