"""HTTP scope and conversion tests against an explicitly named synthetic fixture."""
import http.cookiejar,json,os,subprocess
from pathlib import Path
from urllib.request import build_opener,HTTPCookieProcessor,Request
from urllib.error import HTTPError
base=os.environ.get('SECURITY_TEST_BASE_URL','');assert base=='http://127.0.0.1:8092';assert 'lodge_security_test_' in os.environ.get('SECURITY_FIXTURE_CONFIG','')
password='Fixture-password-only-2026';checks=0
def expect(v,m):
 global checks
 assert v,m;checks+=1
def fixture(action):
 r=subprocess.run([os.environ['PHP_BINARY'],str(Path(__file__).with_name('security_http_fixture.php')),action],capture_output=True,text=True,env=os.environ);assert r.returncode==0,r.stderr
class Client:
 def __init__(self):self.jar=http.cookiejar.CookieJar();self.opener=build_opener(HTTPCookieProcessor(self.jar));self.csrf=''
 def call(self,path,body=None):
  headers={'Accept':'application/json'}
  if body is not None:headers.update({'Content-Type':'application/json','X-CSRF-Token':self.csrf})
  try:r=self.opener.open(Request(base+path,None if body is None else json.dumps(body).encode(),headers),timeout=15)
  except HTTPError as e:r=e
  with r:
   raw=r.read();d=json.loads(raw) if 'application/json' in r.headers.get('Content-Type','') else raw
   if isinstance(d,dict):self.csrf=d.get('csrfToken',self.csrf)
   return r.status,d
 def login(self,user,unit):self.call('/admin/api/auth/status.php?unit='+unit);return self.call('/admin/api/auth/login.php',{'email':f'security{user}@example.test','password':password,'unit':unit})
a=Client();expect(a.login(4,'test-unit-a')[0]==200,'Unit A login')
status,d=a.call('/admin/api/people/save.php?unit=test-unit-a',{'tab':'candidates','firstName':'Casey','surname':'Candidate','email':'candidate@example.test','orderKey':'craft','stage':'approved','contactMethod':'email','sponsorName':'Sponsor','reviewDate':'2026-12-01'});expect(status==200,'Candidate created');candidate_person=d['personId']
status,d=a.call('/admin/api/people/list.php?unit=test-unit-a&tab=candidates');expect(status==200 and any(int(x['person_id'])==candidate_person for x in d['rows']),'Candidate listed');candidate=next(x for x in d['rows'] if int(x['person_id'])==candidate_person)
expect(a.call('/admin/api/people/detail.php?unit=test-unit-a&id=999999')[0] in (403,404),'Guessed person denied')
b=Client();expect(b.login(5,'test-unit-b')[0]==200,'Unit B login');expect(b.call(f'/admin/api/people/detail.php?unit=test-unit-b&id={candidate_person}')[0] in (403,404),'Cross-unit profile denied')
status,d=a.call('/admin/api/people/admit.php?unit=test-unit-a',{'candidateId':candidate['relation_id'],'ceremonyDate':'2026-09-01','officerConfirmed':True});expect(status==200 and d['terminology']=='initiated','Admission recorded with Order terminology');expect(a.call('/admin/api/people/admit.php?unit=test-unit-a',{'candidateId':candidate['relation_id'],'ceremonyDate':'2026-09-01','officerConfirmed':True})[0] in (403,404,409),'Duplicate conversion denied')
status,d=b.call('/admin/api/people/save.php?unit=test-unit-b',{'tab':'members','firstName':'Jordan','surname':'Joining','email':'joining@example.test','orderKey':'craft','membershipType':'Joining member','admissionRoute':'joining','startDate':'2026-01-01','state':'active'});expect(status==200,'Joining member created');joining_person=d['personId'];status,d=b.call(f'/admin/api/people/detail.php?unit=test-unit-b&id={joining_person}');expect(status==200 and not d.get('milestones'),'Joining member has no false ceremony')
expect(a.call('/admin/api/people/bulk.php?unit=test-unit-a',{'tab':'members','personIds':[candidate_person,joining_person],'state':'inactive'})[0] in (403,404),'Cross-unit bulk edit denied')
fixture('people_contact');status,d=a.call('/admin/api/people/list.php?unit=test-unit-a&tab=contacts&filter=active');expect(status==200 and any(int(x['person_id'])==9100 for x in d['rows']),'Explicit consent contact listed');expect(a.call('/admin/api/people/save.php?unit=test-unit-a',{'tab':'contacts','personId':9100,'firstName':'Consented','surname':'Guest','email':'consented@example.test','contactMethod':'email','withdraw':True})[0]==200,'Consent withdrawal saved');status,d=a.call('/admin/api/people/list.php?unit=test-unit-a&tab=contacts&filter=active');expect(all(int(x['person_id'])!=9100 for x in d['rows']),'Withdrawn contact unavailable for active reuse')
status,_=a.call('/admin/api/people/export.php?unit=test-unit-a&tab=candidates');expect(status==200,'Scoped export allowed')
print(f'People HTTP: {checks} positive/negative scope, consent, conversion, bulk and export assertions passed.')
