57 lines
4.3 KiB
Python
57 lines
4.3 KiB
Python
"""AC-74: standard folder round trips, unsafe paths and immutable file integrity."""
|
|
import base64
|
|
import io
|
|
import tempfile
|
|
import unittest
|
|
import zipfile
|
|
from pathlib import Path
|
|
from services import skill_packages as packages, assistant_store as store, component_registry
|
|
from services.project_assistant import ProjectAssistantService
|
|
from services.lease_case_library import LibraryError
|
|
|
|
class SkillPackageTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp=tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
|
|
self.root=Path(self.temp.name); self.folder=self.root/'packages'
|
|
self.service=ProjectAssistantService(self.root/'assistant.sqlite3')
|
|
text='---\nname: cashflow-check\ndescription: 核对对公承租人现金流来源\n---\n\n# 现金流核查\n缺证留空。\n'
|
|
self.files=[{'path':'cashflow-check/SKILL.md','content_base64':base64.b64encode(text.encode()).decode()},
|
|
{'path':'cashflow-check/references/checklist.md','content_base64':base64.b64encode('银行流水核验清单'.encode()).decode()},
|
|
{'path':'cashflow-check/scripts/never-run.py','content_base64':base64.b64encode(b'raise Exception("MUST NOT EXECUTE")').decode()}]
|
|
def tearDown(self): self.temp.cleanup()
|
|
def test_import_reopen_download_preserves_bytes(self):
|
|
with self.service.connect() as db:
|
|
project=store.get(db,'project','DEMO-A')
|
|
card=packages.import_package(db,project,'admin-a',self.files,'financial.operating_cashflow',self.folder)
|
|
self.assertEqual(card['origin'],'manual'); self.assertEqual(card['status'],'draft'); self.assertEqual(card['allowed_tools'],[])
|
|
with self.service.connect() as db:
|
|
current=store.get(db,'component_draft',card['id'])
|
|
content=packages.content(db,current,self.folder); self.assertEqual(len(content['files']),3)
|
|
name,binary=packages.archive(db,current,self.folder)
|
|
with zipfile.ZipFile(io.BytesIO(binary)) as z:
|
|
self.assertEqual(z.read(name+'/SKILL.md'),base64.b64decode(self.files[0]['content_base64']))
|
|
self.assertIn(name+'/scripts/never-run.py',z.namelist())
|
|
self.assertTrue((self.folder/content['id']/name/'SKILL.md').is_file())
|
|
with self.assertRaises(LibraryError): packages.import_package(db,project,'admin-a',self.files,None,self.folder)
|
|
def test_unsafe_paths_and_yaml_rejected(self):
|
|
for path in ('/cashflow-check/SKILL.md','cashflow-check/../SKILL.md','C:/test/SKILL.md','cashflow-check/CON','cashflow-check//SKILL.md','cashflow-check/SKILL.md.'):
|
|
with self.subTest(path=path),self.assertRaises(LibraryError): packages.validate_files([{**self.files[0],'path':path}])
|
|
for text in ('plain text','---\nname: wrong\ndescription: test\n---\nbody','---\nname: cashflow-check\ndescription: 123\n---\nbody','---\nname: cashflow-check\ndescription: !!python/object/apply:os.system [bad]\n---\nbody'):
|
|
with self.subTest(text=text),self.assertRaises(LibraryError): packages.validate_files([{**self.files[0],'content_base64':base64.b64encode(text.encode()).decode()}])
|
|
def test_limits_and_duplicates(self):
|
|
with self.assertRaises(LibraryError): packages.validate_files(self.files+[self.files[0]])
|
|
with self.assertRaises(LibraryError): packages.validate_files([])
|
|
with self.assertRaises(LibraryError): packages.validate_files([{**self.files[0],'content_base64':base64.b64encode(b'x'*(2*1024*1024+1)).decode()}])
|
|
def test_file_change_rejected(self):
|
|
with self.service.connect() as db:
|
|
card=packages.import_package(db,store.get(db,'project','DEMO-A'),'admin-a',self.files,None,self.folder)
|
|
meta=card['package']; (self.folder/meta['id']/meta['name']/'SKILL.md').write_text('changed',encoding='utf-8')
|
|
with self.assertRaises(LibraryError): packages.content(db,card,self.folder)
|
|
def test_system_skill_is_standard_and_idempotent(self):
|
|
with self.service.connect() as db:
|
|
skill=component_registry.resolve(db,'corporate.cashflow'); first=packages.content(db,skill,self.folder)
|
|
self.assertIn('name: corporate-cashflow',first['files'][0]['text'])
|
|
self.assertEqual(first['id'],packages.content(db,skill,self.folder)['id'])
|
|
|
|
if __name__=='__main__': unittest.main()
|