#!/usr/bin/env python3
"""Schema and adversarial fail-closed tests; never production/browser evidence."""
import json, pathlib, subprocess, sys, importlib.util, tempfile, shutil
root=pathlib.Path(__file__).resolve().parent
spec=importlib.util.spec_from_file_location('auth_split_verify',root/'verify.py'); verifier=importlib.util.module_from_spec(spec); spec.loader.exec_module(verifier)
try:
 import jsonschema
except ImportError:
 print('SELFTEST=FAIL reason=jsonschema-validator-unavailable'); raise SystemExit(1)
schemas={p.name:json.loads(p.read_text()) for p in sorted((root/'schemas').glob('*.schema.json'))}
for name,schema in schemas.items():
 try: jsonschema.Draft202012Validator.check_schema(schema)
 except Exception as e: print(f'SELFTEST=FAIL schema={name} error={type(e).__name__}'); raise SystemExit(1)
print(f'SCHEMAS=PASS count={len(schemas)} draft=2020-12 checker=jsonschema-draft202012')
# Deliberately malformed and partial objects must be rejected by the real validators.
adversarial={
 'environment-input.schema.json':[{}, {'schema_version':'1.0.0','production_equivalent':False}],
 'case-oracle.schema.json':[{}, {'schema_version':'1.0.0','route':'/auth/unknown'}, {'canonical_outcome_sha256':'0'*64,'trace_ref':'trace','trace_sha256':'0'*64,'evidence_refs':[]}],
 'db-assertions.schema.json':[{}, {'schema_version':'1.0.0','case_id':'unrelated','expected_delta':{},'observed_delta':{}}],
 'sink-search.schema.json':[{}, {'schema_version':'1.0.0','marker_hits':0,'marker_classes':[]}],
 'redaction-manifest.schema.json':[{}, {'schema_version':'1.0.0','entries':[]}],
 'artifact-manifest.schema.json':[{}, {'schema_version':'1.0.0','files':[{'path':'../escape','sha256':'0'*64,'bytes':0}]}],
 'summary.schema.json':[{}, {'schema_version':'1.0.0','verdict':'PASS','counts':{'pass':999,'fail':0,'blocked':0}}]
}
for name,values in adversarial.items():
 v=jsonschema.Draft202012Validator(schemas[name],format_checker=jsonschema.FormatChecker())
 for i,value in enumerate(values):
  if v.is_valid(value): print(f'SELFTEST=FAIL accepted={name}:{i}'); raise SystemExit(1)
ids=verifier.expected_identities()
if not ids or len(ids)!=len(set(ids)):
 print('SELFTEST=FAIL expected-identities-not-closed'); raise SystemExit(1)
missing=set(ids); missing.pop()
if missing==ids or set([*ids,*ids])==ids and len([*ids,*ids])==len(ids):
 print('SELFTEST=FAIL identity-attack-undetected'); raise SystemExit(1)
print(f'IDENTITIES=PASS exact={len(ids)} missing-and-duplicate-negative=2')
p=subprocess.run([sys.executable,str(root/'verify.py'),'--input',str(root/'fixtures/self-test/absent-production'),'--manifest',str(root/'fixtures/self-test/absent-production/auth-sensitive-data-flow.json'),'--publication',str(root/'fixtures/self-test/absent-publication')],text=True,capture_output=True)
if p.returncode!=2 or not p.stdout.startswith('VERDICT=BLOCKED'):
 print('SELFTEST=FAIL fail_closed=0'); raise SystemExit(1)
print('ADVERSARIAL=PASS malformed=5 partial=5')
print('FAIL_CLOSED=PASS verifier_verdict=BLOCKED exit=2')
# Deterministic, visibly non-production fixtures exercise the whole public verifier.
factory=root/'generate_test_bundle.py'
with tempfile.TemporaryDirectory(prefix='auth-split-selftest-') as td:
 base=pathlib.Path(td)
 for scenario,expected_rc,reason in (
  ('valid',0,'FIXTURE_VERDICT=PASS'),
  ('fabricated-minimal',2,'expected-identity-set-mismatch'),
  ('disconnected-graph',2,'redaction-graph-root-count'),
 ):
  run=base/scenario; publication=run.with_name(run.name+'-publication')
  made=subprocess.run([sys.executable,str(factory),'--out',str(run),'--scenario',scenario],text=True,capture_output=True)
  if made.returncode:
   print(f'SELFTEST=FAIL fixture-generation={scenario} error={(made.stderr or made.stdout).strip()}'); raise SystemExit(1)
  checked=subprocess.run([sys.executable,str(root/'verify.py'),'--test-fixture','--input',str(run),'--manifest',str(run/'auth-sensitive-data-flow.json'),'--publication',str(publication)],text=True,capture_output=True)
  if checked.returncode!=expected_rc or reason not in checked.stdout:
   print(f'SELFTEST=FAIL fixture={scenario} rc={checked.returncode} output={checked.stdout.strip()}'); raise SystemExit(1)
  if scenario=='valid':
   production=subprocess.run([sys.executable,str(root/'verify.py'),'--input',str(run),'--manifest',str(run/'auth-sensitive-data-flow.json'),'--publication',str(publication)],text=True,capture_output=True)
   if production.returncode!=2 or 'non-production-test-key' not in production.stdout:
    print(f'SELFTEST=FAIL production-accepted-test-key rc={production.returncode} output={production.stdout.strip()}'); raise SystemExit(1)
 print('FIXTURE_VALID=PASS identities=209 sinks=32 signed=1 publication-copy=1 production-rejected=1')
 print('FIXTURE_ATTACKS=PASS prior-fabricated-cases=102 disconnected-signed-graph=1')
print('SELFTEST=PASS')
