#!/usr/bin/env python3
"""Synthetic regression cases for redaction and allowlisted archive validation."""
from pathlib import Path
import base64,hashlib,io,json,stat,tempfile,unittest,zipfile
from privacy import findings,scrub_text,scan_archive
R=Path(__file__).resolve().parents[1]
class PrivacyTests(unittest.TestCase):
 def setUp(self):
  (R/'.build').mkdir(exist_ok=True);self.tmp=tempfile.TemporaryDirectory(dir=R/'.build');self.root=Path(self.tmp.name)
 def tearDown(self):self.tmp.cleanup()
 def archive(self,items):
  p=self.root/'test.zip'
  with zipfile.ZipFile(p,'w') as z:
   for name,data in items:z.writestr(name,data)
  return p
 def record(self,data,reviewed=False):
  return {'bytes':len(data),'sha256':hashlib.sha256(data).hexdigest(),'reviewed_binary':reviewed}
 def test_safe_text(self):self.assertFalse(findings('Lunis ist Zeit. 40 × 150 = 6.000 Euro netto.'))
 def test_contact(self):self.assertIn('email',findings('Demo'+'@'+'example.invalid'))
 def test_encoded_contact(self):self.assertIn('email',findings('Demo'+'%40'+'example.invalid'))
 def test_entity_contact(self):self.assertIn('email',findings('Demo'+'&#64;'+'example.invalid'))
 def test_credential(self):self.assertIn('credential',findings('gh'+'p_'+'a'*28))
 def test_phone(self):self.assertIn('telephone',findings('+'+'43'+' 650 '+'1234567'))
 def test_private_path(self):self.assertIn('home_path',findings('/'+'Users'+'/'+'sample'+'/file.txt'))
 def test_scrub_copy(self):
  original='Person '+'demo'+'@'+'example.invalid';out,counts=scrub_text(original);self.assertNotEqual(original,out);self.assertFalse(findings(out));self.assertEqual(counts['email'],1)
 def test_markup_is_not_unescaped(self):
  original='<p>&lt;script&gt; &amp; 20%25</p>';clean,_=scrub_text(original);self.assertEqual(original,clean)
 def test_encoded_residual_stays_rejected(self):
  clean,_=scrub_text('person'+'%40'+'example.invalid');self.assertIn('email',findings(clean))
 def test_optional_terms_not_in_logs(self):
  clean,counts=scrub_text('Alpha Person spricht.',{'Alpha Person':'Rolle A'});self.assertEqual(clean,'Rolle A spricht.');self.assertNotIn('Alpha Person',str(counts))
 def test_embedded_text(self):
  payload=base64.b64encode(('person'+'@'+'example.invalid').encode()).decode();self.assertIn('email',findings('data:text/plain;base64,'+payload))
 def test_exact_allowlist(self):
  data=b'Safe report';p=self.archive([('endbericht/safe.txt',data)]);self.assertEqual(scan_archive(p,{'endbericht/safe.txt':self.record(data)})['status'],'PASS')
 def test_unknown_member(self):
  p=self.archive([('unknown.txt',b'unknown')]);self.assertEqual(scan_archive(p,{})['status'],'FAIL')
 def test_integrity(self):
  p=self.archive([('safe.txt',b'changed')]);self.assertEqual(scan_archive(p,{'safe.txt':self.record(b'original')})['status'],'FAIL')
 def test_traversal(self):
  p=self.archive([('../safe.txt',b'bad')]);self.assertEqual(scan_archive(p,{'../safe.txt':self.record(b'bad')})['status'],'FAIL')
 def test_raw_format(self):
  p=self.archive([('data.db',b'raw')]);self.assertEqual(scan_archive(p,{'data.db':self.record(b'raw')})['status'],'FAIL')
 def test_symlink(self):
  entry=zipfile.ZipInfo('link.txt');entry.create_system=3;entry.external_attr=(stat.S_IFLNK|0o777)<<16;p=self.archive([(entry,b'target')]);self.assertEqual(scan_archive(p,{'link.txt':self.record(b'target')})['status'],'FAIL')
 def test_nested_archive(self):
  raw=b'good';stream=io.BytesIO()
  with zipfile.ZipFile(stream,'w') as z:z.writestr('endbericht/safe.txt',raw)
  inner=stream.getvalue();p=self.archive([('endbericht/inner.zip',inner)])
  records={'endbericht/inner.zip':self.record(inner),'endbericht/safe.txt':self.record(raw)}
  self.assertEqual(scan_archive(p,records)['status'],'PASS')
 def test_nested_unknown_blocked(self):
  stream=io.BytesIO()
  with zipfile.ZipFile(stream,'w') as z:z.writestr('not-allowed.txt',b'bad')
  inner=stream.getvalue();p=self.archive([('inner.zip',inner)])
  self.assertEqual(scan_archive(p,{'inner.zip':self.record(inner)})['status'],'FAIL')
 def test_unreviewed_image_blocked(self):
  from PIL import Image
  stream=io.BytesIO();Image.new('RGB',(2,2),'white').save(stream,'PNG');data=stream.getvalue();p=self.archive([('image.png',data)])
  self.assertEqual(scan_archive(p,{'image.png':self.record(data)})['status'],'FAIL')
 def test_reviewed_image_accepted(self):
  from PIL import Image
  stream=io.BytesIO();Image.new('RGB',(2,2),'white').save(stream,'PNG');data=stream.getvalue();p=self.archive([('image.png',data)])
  self.assertEqual(scan_archive(p,{'image.png':self.record(data,True)})['status'],'PASS')
if __name__=='__main__':
 result=unittest.TextTestRunner(verbosity=2).run(unittest.defaultTestLoader.loadTestsFromTestCase(PrivacyTests))
 record={'status':'PASS' if result.wasSuccessful() else 'FAIL','tests_run':result.testsRun,'failures':len(result.failures),'errors':len(result.errors),'scope':'Synthetic regression cases only; not proof of universal anonymization'}
 (R/'proof/privacy-tests.json').write_text(json.dumps(record,indent=2));raise SystemExit(not result.wasSuccessful())
