"""Exercise real staff sessions, role guards and CSRF with disposable CI accounts."""
import http.cookiejar, re, subprocess, time, urllib.request, urllib.parse, urllib.error
from pathlib import Path
root=Path(__file__).resolve().parents[1]
log=open(root/'backend/runtime/http-smoke.log','w')
server=subprocess.Popen(['php','-S','127.0.0.1:8099','-t','backend/web'],cwd=root,stdout=log,stderr=log)
class Client:
 def __init__(self):self.opener=urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
 def request(self,route='',data=None,params=None):
  url='http://127.0.0.1:8099/index.php?'+urllib.parse.urlencode({'r':route,**(params or {})})
  try:
   with self.opener.open(url,None if data is None else urllib.parse.urlencode(data).encode(),timeout=10) as r:return r.status,r.read().decode(errors='replace'),r.url
  except urllib.error.HTTPError as e:return e.code,e.read().decode(),e.url
 def csrf(self,html):
  m=re.search(r'name="_csrf-backend" value="([^"]+)"',html)
  assert m,'CSRF token missing';return m.group(1)
 def login(self,name):
  code,html,_=self.request('site/login');assert code==200,html
  return self.request('site/login',{'_csrf-backend':self.csrf(html),'StaffLoginForm[username]':name,'StaffLoginForm[password]':'Test-only password 2026!'})
try:
 for i in range(30):
  try:urllib.request.urlopen('http://127.0.0.1:8099/index.php?r=site/login',timeout=1);break
  except (OSError,urllib.error.HTTPError):time.sleep(.2)
 officer=Client();code,html,url=officer.request('administration/branch/index');assert 'site%2Flogin' in url or 'site/login' in url,url
 code,html,_=officer.login('officer');assert code==200 and 'Management dashboard' in html,html
 assert 'Royal Lutanda' in html and 'Oasis Mall' not in html,'Branch scope leaked'
 assert officer.request('administration/staff/index')[0]==403,'Officer has staff admin access'
 assert officer.request('administration/branch/index')[0]==403,'Officer has branch admin access'
 assert officer.request('site/logout')[0]==405,'GET logout accepted'
 assert officer.request('clients/default/index')[0]==200,'Client registry unavailable'
 code,html,_=officer.request('clients/default/create');assert code==200,html
 token=officer.csrf(html)
 data={'_csrf-backend':token,'Client[branch_id]':'2','Client[full_name]':'HTTP Client','Client[identity_number]':'HTTP-NRC-001','Client[phone]':'0970000010','Client[address]':'Test address'}
 code,html,url=officer.request('clients/default/create',data);assert code==200 and 'HTTP Client' in html and 'Record saved.' in html,html
 client_id=urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)['id'][0]
 code,html,_=officer.request('collateral/default/create');assert code==200,html
 payload={'_csrf-backend':officer.csrf(html),'CollateralItem[branch_id]':'2','CollateralItem[client_id]':client_id,'CollateralItem[category]':'Electronics','CollateralItem[description]':'HTTP laptop','CollateralItem[condition_notes]':'Good','CollateralItem[ownership_evidence]':'Receipt inspected','CollateralItem[estimated_value]':'500','CollateralItem[storage_location]':'Safe C','CollateralItem[received_at]':time.strftime('%Y-%m-%d'),'CollateralItem[receipt_reference]':'HTTP-RECEIPT-001'}
 code,html,url=officer.request('collateral/default/create',payload);assert code==200 and 'HTTP laptop' in html and 'Record saved.' in html,html
 collateral_id=urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)['id'][0]
 code,html,_=officer.request('lending/default/create');assert code==200 and 'Save draft' in html,html
 application={'_csrf-backend':officer.csrf(html),'LoanApplicationForm[client_id]':client_id,'LoanApplicationForm[principal]':'100.00','LoanApplicationForm[term_weeks]':'1','LoanApplicationForm[payment_mode]':'single','LoanApplicationForm[purpose]':'HTTP test loan','LoanApplicationForm[collateral_ids][]':collateral_id}
 code,html,url=officer.request('lending/default/create',application);assert code==200 and 'APP-' in html and 'Application draft saved.' in html,html
 application_id=urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)['id'][0]
 # Incomplete drafts may be saved, but submission still requires collateral.
 code,draft_form,_=officer.request('lending/default/create',params={'client_id':client_id})
 css_url=re.search(r'href="([^"]*css/lendford\.css\?v=\d+)"',draft_form)
 assert css_url,'Button stylesheet is not cache-busted'
 with officer.opener.open(urllib.parse.urljoin('http://127.0.0.1:8099/index.php',css_url.group(1))) as response:
  css=response.read().decode()
 assert 'padding: .3125rem .625rem;' in css and 'border-radius: 3px;' in css,'Direct button styles missing'
 draft_payload=application.copy();draft_payload.update({'_csrf-backend':officer.csrf(draft_form),'LoanApplicationForm[collateral_ids][]':'','LoanApplicationForm[collateral_ids]':'','LoanApplicationForm[purpose]':'Draft awaiting collateral'})
 draft_payload.pop('LoanApplicationForm[collateral_ids][]')
 code,draft_page,draft_url=officer.request('lending/default/create',draft_payload)
 assert code==200 and 'Application draft saved.' in draft_page,draft_page
 draft_id=urllib.parse.parse_qs(urllib.parse.urlsplit(draft_url).query)['id'][0]
 submit_url='http://127.0.0.1:8099/index.php?'+urllib.parse.urlencode({'r':'lending/default/submit','id':draft_id})
 with officer.opener.open(submit_url,urllib.parse.urlencode({'_csrf-backend':officer.csrf(draft_page)}).encode()) as response:
  blocked=response.read().decode()
 assert 'Collateral is required.' in blocked and 'Draft' in blocked,'Unsecured application submitted'
 bad=draft_payload.copy();bad.update({'_csrf-backend':officer.csrf(draft_form),'LoanApplicationForm[collateral_ids][]':'invalid'})
 bad.pop('LoanApplicationForm[collateral_ids]')
 code,bad_page,_=officer.request('lending/default/create',bad)
 assert code==200 and 'Collateral' in bad_page and 'Application draft saved.' not in bad_page,bad_page
 assert 'Collateral Ids' not in bad_page and 'Client Id' not in bad_page,'Technical validation labels leaked'
 # Client context is retained from the profile; collateral responses contain only their own items.
 code,profile,_=officer.request('clients/default/view',params={'id':client_id});assert code==200 and ('client_id='+client_id) in profile,'Client application link omitted context'
 code,scoped,_=officer.request('lending/default/create',params={'client_id':client_id});assert code==200 and ('value="'+client_id+'" selected') in scoped and 'HTTP laptop' in scoped,scoped
 assert 'Test laptop' not in scoped and 'Transfer test laptop' not in scoped,'Other client collateral rendered on application'
 code,unselected,_=officer.request('lending/default/create');assert code==200 and 'HTTP laptop' not in unselected,'Unselected application exposed collateral'
 import json
 code,options,_=officer.request('lending/default/client-collateral',params={'client_id':client_id});items=json.loads(options);assert code==200 and [i['id'] for i in items['items']]==[int(collateral_id)] and items['branch']=='Royal Lutanda',options
 assert officer.request('lending/default/client-collateral',params={'client_id':'not-an-id'})[0]==400,'Invalid collateral client ID accepted'
 # The original foundation client is different but in the same branch; their collateral must never mix.
 code,options,_=officer.request('lending/default/client-collateral',params={'client_id':'1'});assert code==200 and all(i['id']!=int(collateral_id) for i in json.loads(options)['items']),'Collateral mixed across clients'
 code,scoped,_=officer.request('lending/default/create',params={'client_id':client_id})
 inline={'_csrf-backend':officer.csrf(scoped),'LoanApplicationForm[client_id]':client_id,'LoanApplicationForm[principal]':'100','LoanApplicationForm[term_weeks]':'1','LoanApplicationForm[payment_mode]':'single','LoanApplicationForm[purpose]':'Inline branch regression','LoanApplicationForm[add_collateral]':'0','LoanApplicationForm[collateral_ids]':'','CollateralItem[client_id]':'1','CollateralItem[branch_id]':'1','CollateralItem[category]':'Electronics','CollateralItem[description]':'Inline client branch test','CollateralItem[condition_notes]':'Good','CollateralItem[ownership_evidence]':'Ownership checked','CollateralItem[estimated_value]':'500'}
 code,inline_html,inline_url=officer.request('lending/default/create',inline);assert code==200 and 'Application draft saved.' in inline_html,inline_html
 code,options,_=officer.request('lending/default/client-collateral',params={'client_id':client_id});owned=json.loads(options)['items'];created=[i for i in owned if 'Inline client branch test' in i['label']];assert code==200 and len(created)==1 and 'Royal Lutanda' in created[0]['label'],options
 code,inline_item,_=officer.request('collateral/default/view',params={'id':created[0]['id']});assert code==200 and 'HTTP Client' in inline_item and 'Royal Lutanda' in inline_item and 'pledged' in inline_item,inline_item
 # Failed application validation must roll back collateral saved earlier in the transaction.
 code,failed_form,_=officer.request('lending/default/create',params={'client_id':client_id})
 failed_inline=inline.copy();failed_inline.update({'_csrf-backend':officer.csrf(failed_form),'LoanApplicationForm[principal]':'0','CollateralItem[description]':'Rollback-only collateral'})
 failed_inline.pop('LoanApplicationForm[add_collateral]')
 code,failed_page,_=officer.request('lending/default/create',failed_inline)
 assert code==200 and 'Principal must be greater than zero.' in failed_page,failed_page
 code,after_failure,_=officer.request('lending/default/client-collateral',params={'client_id':client_id})
 assert 'Rollback-only collateral' not in after_failure,'Failed draft left an orphan collateral record'
 code,scoped,_=officer.request('lending/default/create')
 new_inline=inline.copy();new_inline.update({'_csrf-backend':officer.csrf(scoped),'LoanApplicationForm[new_client]':'1','LoanApplicationForm[client_id]':'','Client[branch_id]':'2','Client[full_name]':'Inline new client branch test','Client[identity_number]':'HTTP-INLINE-NEW','Client[phone]':'0970000019','Client[address]':'Synthetic address','CollateralItem[description]':'New client branch collateral'})
 code,new_html,new_url=officer.request('lending/default/create',new_inline);assert code==200 and 'Application draft saved.' in new_html and 'Inline new client branch test' in new_html,new_html
 new_item_link=re.search(r'href="[^"]*collateral[^"]*view[^"]*id=(\d+)"',new_html)
 assert new_item_link,'New client collateral link missing'
 code,new_item_page,_=officer.request('collateral/default/view',params={'id':new_item_link.group(1)});assert code==200 and 'Inline new client branch test' in new_item_page and 'Royal Lutanda' in new_item_page,new_item_page


 assert officer.request('lending/default/submit',params={'id':application_id})[0]==405, 'GET submit accepted'
 # Use an explicit URL for action parameters, preserving the real cookie session.
 def action(client,route,identifier,data):
  url='http://127.0.0.1:8099/index.php?'+urllib.parse.urlencode({'r':route,'id':identifier})
  try:
   with client.opener.open(url,urllib.parse.urlencode(data).encode(),timeout=10) as response:return response.status,response.read().decode(),response.url
  except urllib.error.HTTPError as e:return e.code,e.read().decode(),e.url
 assert action(officer,'lending/default/submit',application_id,{})[0]==400,'Application CSRF bypass'
 code,html,_=action(officer,'lending/default/submit',application_id,{'_csrf-backend':officer.csrf(html)});assert code==200 and 'Submitted' in html,html
 assert action(officer,'lending/default/decide',application_id,{'_csrf-backend':officer.csrf(html),'decision':'approve'})[0]==403,'Officer decision endpoint allowed'
 assert officer.request('lending/default/settings')[0]==403,'Officer settings access'

 code,html,_=officer.request('clients/default/create');data['_csrf-backend']=officer.csrf(html);data['Client[branch_id]']='1';data['Client[identity_number]']='HTTP-NRC-DENIED'
 assert officer.request('clients/default/create',data)[0]==403,'Cross-branch create allowed'
 admin=Client();code,html,_=admin.login('support');assert code==200 and 'Management dashboard' in html,html
 assert admin.request('clients/default/create')[0]==403,'Support gained client registration authority'
 code,html,_=admin.request('administration/branch/create');assert code==200,html
 token=admin.csrf(html)
 code,_,_=admin.request('administration/branch/create',{'Branch[code]':'NO-CSRF','Branch[name]':'Rejected'});assert code==400,'CSRF bypass'
 code,html,_=admin.request('administration/branch/create',{'_csrf-backend':token,'Branch[code]':'BR-004','Branch[name]':'Test Branch','Branch[status]':'1','Branch[address]':'<script>alert(1)</script>'});assert code==200 and 'Test Branch' in html,html
 assert '<script>alert(1)</script>' not in html and '&lt;script&gt;' in html,'Stored XSS'
 ceo=Client();code,html,_=ceo.login('chief');assert code==200,html
 code,html,_=ceo.request('lending/default/index');assert code==200,html
 code,html,_=ceo.request('clients/default/create');foreign={'_csrf-backend':ceo.csrf(html),'Client[branch_id]':'1','Client[full_name]':'Other branch context test','Client[identity_number]':'HTTP-CONTEXT-OTHER','Client[phone]':'0970000099','Client[address]':'Synthetic address'}
 code,html,foreign_url=ceo.request('clients/default/create',foreign);assert code==200 and 'Record saved.' in html,html
 foreign_id=urllib.parse.parse_qs(urllib.parse.urlsplit(foreign_url).query)['id'][0]
 assert officer.request('lending/default/create',params={'client_id':foreign_id})[0]==404,'Application preselection leaked cross-branch client'
 assert officer.request('lending/default/client-collateral',params={'client_id':foreign_id})[0]==404,'Collateral endpoint leaked cross-branch client'
 assert admin.request('lending/default/client-collateral',params={'client_id':client_id})[0]==403,'Support gained application preparation endpoint'

 # Open the submitted application to retrieve a CEO-session CSRF token.
 url='http://127.0.0.1:8099/index.php?'+urllib.parse.urlencode({'r':'lending/default/view','id':application_id})
 with ceo.opener.open(url) as response:html=response.read().decode()
 assert 'CEO application decision' in html,html
 code,html,_=action(ceo,'lending/default/decide',application_id,{'_csrf-backend':ceo.csrf(html),'decision':'approve','comment':'Approved test'});assert code==200 and 'Mark as paid out' in html,html
 assert ceo.request('lending/default/settings')[0]==200,'CEO settings unavailable'
 assert officer.request('lending/default/reloans')[0]==200,'Reloan register unavailable'
 assert officer.request('lending/default/decide-reloan',params={'id':'1'})[0]==403,'Officer gained reloan approval'
 assert ceo.request('lending/default/decide-reloan',params={'id':'1'})[0]==405,'GET reloan decision accepted'
 code,source_page,_=officer.request('lending/default/loan',params={'id':'1'})
 assert code==200 and 'Replaced after partial repayment' in source_page,source_page
 # Submit a real multipart repayment with evidence, then verify through a separate CEO session.
 code,payment_form,_=officer.request('payments/default/create',params={'loan_id':'3'})
 assert code==200 and 'Submit for CEO verification' in payment_form,payment_form
 boundary='LendfordSmokeBoundary2026'
 fields={'_csrf-backend':officer.csrf(payment_form),'RepaymentForm[loan_id]':'3','RepaymentForm[amount]':'20.00','RepaymentForm[paid_on]':time.strftime('%Y-%m-%d'),'RepaymentForm[channel]':'Cash','RepaymentForm[reference]':'HTTP-REPAYMENT-001','RepaymentForm[early_settlement]':'0'}
 body=b''
 for key,value in fields.items():body+=('--'+boundary+'\r\nContent-Disposition: form-data; name="'+key+'"\r\n\r\n'+value+'\r\n').encode()
 import base64
 evidence=base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aXs8AAAAASUVORK5CYII=')
 body+=('--'+boundary+'\r\nContent-Disposition: form-data; name="RepaymentForm[proof]"; filename="payment.png"\r\nContent-Type: image/png\r\n\r\n').encode()+evidence+('\r\n--'+boundary+'--\r\n').encode()
 request=urllib.request.Request('http://127.0.0.1:8099/index.php?r=payments/default/create',data=body,headers={'Content-Type':'multipart/form-data; boundary='+boundary})
 with officer.opener.open(request) as response:payment_html=response.read().decode();payment_url=response.url
 assert 'Repayment submitted for CEO verification.' in payment_html,payment_html
 repayment_id=urllib.parse.parse_qs(urllib.parse.urlsplit(payment_url).query)['id'][0]
 assert officer.request('payments/default/decide',params={'id':repayment_id})[0]==403,'Officer gained payment approval'
 assert ceo.request('payments/default/decide',params={'id':repayment_id})[0]==405,'GET repayment decision allowed'
 assert officer.request('payments/default/receipt',params={'id':repayment_id})[0]==404,'Pending payment issued a receipt'
 code,payment_html,_=ceo.request('payments/default/view',params={'id':repayment_id})
 assert code==200 and 'CEO verification' in payment_html,payment_html
 code,payment_html,_=action(ceo,'payments/default/decide',repayment_id,{'_csrf-backend':ceo.csrf(payment_html),'decision':'verify','reason':'HTTP evidence verified'})
 assert code==200 and 'Recorded allocation' in payment_html,payment_html
 assert officer.request('payments/default/receipt',params={'id':repayment_id})[0]==200,'Verified receipt unavailable'
 code,listing,_=officer.request('payments/default/index')
 assert code==200 and 'data-record-url' in listing,'Repayment listing failed'
 code,loan_html,_=officer.request('lending/default/loan',params={'id':'3'})
 assert code==200 and 'Total debt:' in loan_html and 'Repayment history' in loan_html,loan_html

 # Exercise a custody release with real multipart evidence and CEO approval.
 assert officer.request('collateral/default/requests')[0]==200,'Custody queue unavailable'
 code,html,_=officer.request('collateral/default/create')
 payload['_csrf-backend']=officer.csrf(html);payload['CollateralItem[receipt_reference]']='HTTP-CUSTODY-INTAKE';payload['CollateralItem[description]']='HTTP release item'
 code,html,url=officer.request('collateral/default/create',payload);assert code==200 and 'Record saved.' in html,html
 release_item_id=urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)['id'][0]
 code,html,_=officer.request('collateral/default/request',params={'id':release_item_id});assert code==200,html
 code,html,url=action(officer,'collateral/default/request',release_item_id,{'_csrf-backend':officer.csrf(html),'CustodyRequestForm[kind]':'release','CustodyRequestForm[reason]':'Unpledged client collection'})
 assert code==200 and 'Custody request submitted for CEO approval.' in html,html
 custody_id=urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)['id'][0]
 assert officer.request('collateral/default/decide',params={'id':custody_id})[0]==403,'Officer gained custody approval'
 assert ceo.request('collateral/default/decide',params={'id':custody_id})[0]==405,'GET custody decision accepted'
 assert officer.request('collateral/default/complete',params={'id':custody_id})[0]==405,'GET physical handover accepted'
 assert action(officer,'collateral/default/complete',custody_id,{})[0]==400,'Custody CSRF bypass'
 code,html,_=ceo.request('collateral/default/request-view',params={'id':custody_id})
 code,html,_=action(ceo,'collateral/default/decide',custody_id,{'_csrf-backend':ceo.csrf(html),'decision':'approve','reason':'CEO authorises collection'});assert code==200 and 'Custody decision recorded.' in html,html
 code,html,_=officer.request('collateral/default/request-view',params={'id':custody_id});assert 'Confirm client collection' in html,html
 fields={'_csrf-backend':officer.csrf(html),'CustodyCompletionForm[occurred_on]':time.strftime('%Y-%m-%d'),'CustodyCompletionForm[reference]':'HTTP-CUSTODY-COLLECTION','CustodyCompletionForm[recipient]':'HTTP Client'}
 body=b''
 for key,value in fields.items():body+=('--'+boundary+'\r\nContent-Disposition: form-data; name="'+key+'"\r\n\r\n'+value+'\r\n').encode()
 body+=('--'+boundary+'\r\nContent-Disposition: form-data; name="CustodyCompletionForm[proof]"; filename="collection.png"\r\nContent-Type: image/png\r\n\r\n').encode()+evidence+('\r\n--'+boundary+'--\r\n').encode()
 request=urllib.request.Request('http://127.0.0.1:8099/index.php?'+urllib.parse.urlencode({'r':'collateral/default/complete','id':custody_id}),data=body,headers={'Content-Type':'multipart/form-data; boundary='+boundary})
 with officer.opener.open(request) as response:html=response.read().decode()
 assert 'Physical custody event recorded.' in html and 'Download proof' in html,html
 code,html,_=officer.request('collateral/default/view',params={'id':release_item_id});assert code==200 and 'released' in html,html

 # Exercise the recovery approval, collection, handover and surplus workflow with real uploads.
 import json,tempfile
 fixture=json.loads((Path(tempfile.gettempdir())/'lendford-recovery-smoke.json').read_text())
 def upload(client,route,identifier,fields,file_field):
  boundary='RecoverySmokeBoundary2026';body=b''
  for key,value in fields.items():body+=('--'+boundary+'\r\nContent-Disposition: form-data; name="'+key+'"\r\n\r\n'+str(value)+'\r\n').encode()
  body+=('--'+boundary+'\r\nContent-Disposition: form-data; name="'+file_field+'"; filename="proof.png"\r\nContent-Type: image/png\r\n\r\n').encode()+evidence+('\r\n--'+boundary+'--\r\n').encode()
  params={'r':route}
  if identifier is not None:params['id']=identifier
  req=urllib.request.Request('http://127.0.0.1:8099/index.php?'+urllib.parse.urlencode(params),data=body,headers={'Content-Type':'multipart/form-data; boundary='+boundary})
  with client.opener.open(req) as response:return response.status,response.read().decode(),response.url
 assert officer.request('collateral/default/sales')[0]==200,'Recovery register unavailable'
 code,html,_=officer.request('collateral/default/sale-create',params={'loan_id':fixture['loan_id']});assert code==200,html
 fields={'_csrf-backend':officer.csrf(html),'RecoverySaleForm[loan_id]':fixture['loan_id'],'RecoverySaleForm[collateral_id]':fixture['collateral_id'],'RecoverySaleForm[buyer]':'HTTP Buyer','RecoverySaleForm[gross]':'200.00','RecoverySaleForm[cost]':'20.00','RecoverySaleForm[reason]':'HTTP overdue recovery'}
 code,html,url=upload(officer,'collateral/default/sale-create',None,fields,'RecoverySaleForm[proof]');assert code==200 and 'Recovery sale submitted for CEO approval.' in html,html
 sale_id=urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)['id'][0]
 assert officer.request('collateral/default/sale-decide',params={'id':sale_id})[0]==403,'Officer approved sale'
 assert ceo.request('collateral/default/sale-decide',params={'id':sale_id})[0]==405,'GET sale decision allowed'
 assert officer.request('collateral/default/sale-payment',params={'id':sale_id})[0]==405,'GET payment capture allowed'
 assert action(officer,'collateral/default/sale-payment',sale_id,{})[0]==400,'Recovery CSRF bypass'
 code,html,_=ceo.request('collateral/default/sale',params={'id':sale_id})
 code,html,_=action(ceo,'collateral/default/sale-decide',sale_id,{'_csrf-backend':ceo.csrf(html),'decision':'approve','reason':'CEO accepts documented recovery price'});assert code==200 and 'Recovery decision recorded.' in html,html
 code,html,_=officer.request('collateral/default/sale',params={'id':sale_id})
 fields={'_csrf-backend':officer.csrf(html),'RecoveryPaymentForm[amount]':'200.00','RecoveryPaymentForm[paid_on]':time.strftime('%Y-%m-%d'),'RecoveryPaymentForm[channel]':'Cash','RecoveryPaymentForm[reference]':'HTTP-RECOVERY-GROSS'}
 code,html,_=upload(officer,'collateral/default/sale-payment',sale_id,fields,'RecoveryPaymentForm[proof]');assert code==200 and 'Sale proceeds submitted for CEO verification.' in html,html
 code,html,_=ceo.request('collateral/default/sale',params={'id':sale_id})
 code,html,_=action(ceo,'collateral/default/sale-decide',sale_id,{'_csrf-backend':ceo.csrf(html),'decision':'verify','reason':'CEO verifies gross cash and selling costs'});assert code==200 and 'Posted recovery' in html and 'K20.00' in html,html
 code,html,_=officer.request('collateral/default/sale',params={'id':sale_id})
 fields={'_csrf-backend':officer.csrf(html),'CustodyCompletionForm[occurred_on]':time.strftime('%Y-%m-%d'),'CustodyCompletionForm[recipient]':'HTTP Buyer','CustodyCompletionForm[reference]':'HTTP-RECOVERY-HANDOVER'}
 code,html,_=upload(officer,'collateral/default/sale-handover',sale_id,fields,'CustodyCompletionForm[proof]');assert code==200 and 'Buyer collection recorded.' in html,html
 code,html,_=officer.request('collateral/default/sale',params={'id':sale_id})
 fields={'_csrf-backend':officer.csrf(html),'RecoveryPaymentForm[amount]':'20.00','RecoveryPaymentForm[paid_on]':time.strftime('%Y-%m-%d'),'RecoveryPaymentForm[channel]':'Cash','RecoveryPaymentForm[reference]':'HTTP-CLIENT-SURPLUS','RecoveryPaymentForm[recipient]':'Registry Test','RecoveryPaymentForm[reason]':'Return verified sale surplus'}
 code,html,_=upload(officer,'collateral/default/surplus-return',sale_id,fields,'RecoveryPaymentForm[proof]');assert code==200 and 'Surplus return submitted for CEO verification.' in html,html
 code,html,_=ceo.request('collateral/default/sale',params={'id':sale_id})
 return_id=re.search(r'action="[^"]*return-decide[^\"]*[?&amp;]id=(\d+)',html)
 if not return_id:return_id=re.search(r'action="[^"]*return-decide[^\"]*id=(\d+)',html)
 assert return_id,'Surplus decision form missing'
 code,html,_=action(ceo,'collateral/default/return-decide',return_id.group(1),{'_csrf-backend':ceo.csrf(html),'decision':'verify','reason':'CEO verifies client acknowledgement'});assert code==200 and 'Surplus decision recorded.' in html,html
 for route,identifier in [('collateral/default/sale-proof',sale_id),('collateral/default/return-proof',return_id.group(1))]:
  url='http://127.0.0.1:8099/index.php?'+urllib.parse.urlencode({'r':route,'id':identifier})
  with officer.opener.open(url) as response:assert response.status==200 and response.read()==evidence,'Downloaded evidence differs from upload'

 # Report filters, exports and the notification centre use the same branch scope as records.
 for kind in ['portfolio','collections','recoveries','surplus','custody']:
  code,html,_=officer.request('reports/default/index',params={'kind':kind,'branch_id':2});assert code==200,html
 assert officer.request('reports/default/index',params={'branch_id':1})[0]==403,'Report branch bypass'
 assert officer.request('reports/default/index',params={'as_of':'invalid'})[0]==400,'Invalid report date accepted'
 code,csv,_=officer.request('reports/default/index',params={'kind':'collections','export':'csv'});assert code==200 and 'Generated at' in csv,csv
 assert officer.request('collections/default/index')[0]==200,'Collections queue unavailable'
 assert officer.request('collections/default/follow-ups')[0]==200,'Follow-up register unavailable'
 code,html,_=ceo.request('notifications/index');assert code==200 and 'Notifications' in html,html
 code,count,_=ceo.request('notifications/count');assert code==200 and 'unread' in count,count
 assert ceo.request('notifications/read')[0]==405,'GET notification mutation allowed'
 assert ceo.request('notifications/read',{'token':'invalid'})[0]==400,'Notification CSRF bypass'
 code,html,_=officer.request('clients/default/create');assert code==200 and '<select id="client-nationality"' in html and 'data-country-search' in html and 'value="Zambia" selected' in html,html
 code,html,_=officer.request('lending/default/create');assert code==200 and '<select id="client-nationality"' in html and 'Nationality (country)' in html,html
 # General expense preparation, independent approval, payment verification and exports.
 code,html,_=officer.request('expenses/default/create');assert code==200,html
 fields={'_csrf-backend':officer.csrf(html),'ExpenseForm[branch_id]':'2','ExpenseForm[category_id]':'1','ExpenseForm[payee]':'HTTP Landlord','ExpenseForm[description]':'Synthetic operating expense','ExpenseForm[amount]':'50.00','ExpenseForm[incurred_on]':time.strftime('%Y-%m-%d')}
 code,html,url=upload(officer,'expenses/default/create',None,fields,'ExpenseForm[proof]');assert code==200 and 'Expense draft saved.' in html and 'data-auto-dismiss="5000"' in html,html
 expense_id=urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)['id'][0]
 assert officer.request('expenses/default/submit',params={'id':expense_id})[0]==405,'GET expense submit allowed'
 assert action(officer,'expenses/default/submit',expense_id,{})[0]==400,'Expense CSRF bypass'
 assert officer.request('expenses/default/decide',params={'id':expense_id})[0]==403,'Officer approved expense'
 code,html,_=action(officer,'expenses/default/submit',expense_id,{'_csrf-backend':officer.csrf(html)});assert code==200 and 'Expense submitted for CEO approval.' in html,html
 assert officer.request('expenses/default/update',params={'id':expense_id})[0]==403,'Submitted expense editable'
 code,html,_=ceo.request('notifications/index');assert code==200 and 'Expense approval required' in html,html
 code,html,_=ceo.request('expenses/default/view',params={'id':expense_id})
 code,html,_=action(ceo,'expenses/default/decide',expense_id,{'_csrf-backend':ceo.csrf(html),'decision':'approve','reason':'CEO reviewed invoice'});assert code==200 and 'Expense decision recorded.' in html,html
 code,html,_=officer.request('expenses/default/view',params={'id':expense_id})
 fields={'_csrf-backend':officer.csrf(html),'ExpensePaymentForm[amount]':'50.00','ExpensePaymentForm[paid_on]':time.strftime('%Y-%m-%d'),'ExpensePaymentForm[channel]':'Cash'}
 code,html,_=upload(officer,'expenses/default/pay',expense_id,fields,'ExpensePaymentForm[proof]');assert code==200 and 'Expense payment submitted for CEO verification.' in html,html
 code,html,_=ceo.request('expenses/default/view',params={'id':expense_id})
 match=re.search(r'action="[^"]*payment-decide[^"]*id=(\d+)',html);assert match,'Expense verification form missing'
 payment_id=match.group(1)
 code,html,_=action(ceo,'expenses/default/payment-decide',payment_id,{'_csrf-backend':ceo.csrf(html),'decision':'verify','reason':'CEO verified actual payment'});assert code==200 and 'paid' in html,html
 for kind,identifier in [('expense',expense_id),('payment',payment_id)]:
  url='http://127.0.0.1:8099/index.php?'+urllib.parse.urlencode({'r':'expenses/default/proof','id':identifier,'kind':kind})
  with officer.opener.open(url) as response:assert response.status==200 and response.read()==evidence,'Expense evidence download mismatch'
 assert officer.request('expenses/default/report',params={'branch_id':1})[0]==403,'Expense report branch bypass'
 assert officer.request('expenses/default/report',params={'from':'invalid'})[0]==400,'Invalid expense date accepted'
 code,html,_=officer.request('expenses/default/report',params={'branch_id':2});assert code==200 and 'Operating expense payment movements' in html,html
 code,csv,_=officer.request('expenses/default/report',params={'export':'csv'});assert code==200 and 'HTTP Landlord' in csv,csv
 assert admin.request('lending/default/settings')[0]==403,'Support financial settings access'
 assert ceo.request('administration/staff/create')[0]==200,'CEO staff management unavailable'
 assert ceo.request('administration/audit/index')[0]==200,'CEO audit unavailable'
 # Readable business history never exposes raw payloads to the CEO.
 code,history,_=ceo.request('administration/audit/index');assert code==200 and 'Activity history' in history and 'RAW-ONLY-MARKER' not in history,history
 assert ceo.request('administration/audit/raw')[0]==403,'CEO raw audit URL bypass'
 assert officer.request('administration/audit/raw')[0]==403,'Officer raw audit access'
 code,raw,_=admin.request('administration/audit/raw',params={'search':'RAW-ONLY-MARKER','action':'repayment.verify'});assert code==200 and 'RAW-ONLY-MARKER' in raw,raw
 assert admin.request('administration/audit/raw',params={'from':'invalid'})[0]==400,'Raw audit invalid filter accepted'
 fixtures=json.loads(Path('/tmp/lendford-cash-smoke.json').read_text());source=fixtures['source']
 for route in ['cash/index','cash/transfers','cash/reconciliations']:
  code,html,_=officer.request(route);assert code==200,html
 assert officer.request('cash/index',params={'branch_id':1})[0]==403,'Cash branch filter bypass'
 assert officer.request('cash/index',params={'to':'2000-01-01'})[0]==200,'Historical cash date rejected'
 assert officer.request('cash/account',params={'id':fixtures['destination']})[0]==404,'Destination balance exposed'
 code,csv,_=officer.request('cash/account',params={'id':source,'export':'csv'});assert code==200 and 'account movements' in csv,csv
 assert officer.request('cash/decide-account',params={'id':source})[0]==403,'Cash approval role bypass'
 assert ceo.request('cash/decide-account',params={'id':source})[0]==405,'GET cash decision accepted'
 # Propose and approve a separate bank account through the real multipart form.
 code,html,_=officer.request('cash/create-account');assert code==200,html
 fields={'_csrf-backend':officer.csrf(html),'CashForm[branch_id]':'2','CashForm[name]':'HTTP bank','CashForm[channel]':'Bank transfer','CashForm[opening_on]':time.strftime('%Y-%m-%d'),'CashForm[opening_amount]':'0'}
 code,html,url=upload(officer,'cash/create-account',None,fields,'CashForm[proof]');assert code==200 and 'submitted for CEO approval' in html,html
 bank_id=urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)['id'][0]
 code,html,_=ceo.request('cash/account',params={'id':bank_id});assert code==200,html
 code,html,_=action(ceo,'cash/decide-account',bank_id,{'_csrf-backend':ceo.csrf(html),'decision':'approve','reason':'Opening evidence checked'});assert code==200 and 'Cash-control decision recorded.' in html and 'active' in html,html
 code,html,_=officer.request('cash/reconcile',params={'account_id':bank_id});assert code==200,html
 fields={'_csrf-backend':officer.csrf(html),'CashForm[account_id]':bank_id,'CashForm[as_of]':time.strftime('%Y-%m-%d'),'CashForm[counted_amount]':'0','CashForm[explanation]':'Bank statement reviewed'}
 code,html,url=upload(officer,'cash/reconcile',None,fields,'CashForm[proof]');assert code==200 and 'submitted for CEO review' in html,html
 reconciliation_id=urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)['id'][0]
 code,html,_=ceo.request('cash/reconciliation',params={'id':reconciliation_id});assert code==200,html
 code,html,_=action(ceo,'cash/decide-reconciliation',reconciliation_id,{'_csrf-backend':ceo.csrf(html),'decision':'approve','reason':'Statement and book agree'});assert code==200 and 'approved' in html,html
 # Targets, commission policy and exports are private to CEO/admin.
 for route in ['performance/index','performance/commissions','performance/rules','performance/rule','performance/edit']:
  assert officer.request(route)[0]==403,'Officer private performance access: '+route
 assert officer.request('performance/commissions',params={'export':'csv'})[0]==403,'Officer private export access'
 assert admin.request('performance/rule')[0]==403,'Administrator sets financial commission policy'
 assert admin.request('performance/edit')[0]==403,'Administrator changes targets'
 for client in [ceo,admin]:
  for route in ['performance/index','performance/commissions','performance/rules']:
   code,html,_=client.request(route);assert code==200,html
  code,csv,_=client.request('performance/commissions',params={'export':'csv'});assert code==200 and 'Earned commission movements' in csv,csv
 assert ceo.request('performance/index',params={'month':'invalid'})[0]==400,'Invalid target month accepted'
 fixture=json.loads(Path('/tmp/lendford-performance-smoke.json').read_text())
 assert officer.request('performance/view',params={'id':fixture['target']})[0]==403,'Target direct URL bypass'
 code,html,_=ceo.request('performance/view',params={'id':fixture['target']});assert code==200 and 'Target revision history' in html,html
 code,html,_=ceo.request('performance/rule');assert code==200,html
 assert ceo.request('performance/rule',{'PerformanceForm[rate]':'10'})[0]==400,'Commission policy CSRF bypass'
 fields={'_csrf-backend':ceo.csrf(html),'PerformanceForm[effective_on]':time.strftime('%Y-%m-%d'),'PerformanceForm[rate]':'15','PerformanceForm[recipient]':'originating','PerformanceForm[enabled]':'1','PerformanceForm[include_recovery]':'0','PerformanceForm[include_reloans]':'0','PerformanceForm[confirmed]':'1','PerformanceForm[reason]':'HTTP CEO policy confirmation'}
 code,html,_=ceo.request('performance/rule',fields);assert code==200 and 'CEO commission policy saved' in html,html
 code,html,_=officer.request('notifications/index');assert code==200 and 'Repayment rejected by CEO' in html and 'Evidence does not match' in html,html
 # The actual HTTP approvals above must also create officer result messages.
 assert 'Application approved by CEO' in html and 'Expense payment verified by CEO' in html,'Approval results absent from loan-officer notifications'
 assert 'Mark read' not in html and 'Mark all read' not in html,'Officer notifications require manual acknowledgement'
 code,count,_=officer.request('notifications/count');assert code==200 and json.loads(count)['unread']==0,'Viewing officer updates did not mark them seen'
 # CEO staff management, readable profile roles and private report links.
 code,html,_=ceo.request('administration/staff/index');assert code==200,html
 code,html,_=ceo.request('administration/staff/create');assert code==200 and 'Admin Superuser' not in html,html
 fields={'_csrf-backend':ceo.csrf(html),'StaffForm[username]':'httpceostaff','StaffForm[full_name]':'HTTP CEO Officer','StaffForm[email]':'httpceostaff@example.test','StaffForm[password]':'Test-only password 2026!','StaffForm[status]':'10','StaffForm[roles][]':'loanOfficer'}
 code,html,url=ceo.request('administration/staff/create',fields);assert code==200 and 'Staff account saved.' in html,html
 employee_id=urllib.parse.parse_qs(urllib.parse.urlsplit(url).query)['id'][0]
 assert 'Loan Officer' in html and 'Performance report' in html and 'Commission report' in html,'Staff profile omits role/reports'
 assert ceo.request('administration/branch/index')[0]==200,'CEO branch overview list unavailable'
 assert ceo.request('administration/branch/create')[0]==403,'CEO gained branch settings'
 assert ceo.request('administration/audit/raw')[0]==403,'CEO gained raw audit'
 code,html,_=ceo.request('performance/edit');assert code==200 and 'name="PerformanceForm[branch_id]"' not in html,html
 match=re.search(r'<select[^>]*name="PerformanceForm\[officer_id\]"[^>]*>(.*?)</select>',html,re.S);assert match,html
 assert 'HTTP CEO Officer' in match.group(1) and '>Support<' not in match.group(1) and '>Chief<' not in match.group(1),'Target officer options include non-officers'
 # Delegation and payroll direct IDs, exports, proof and CEO branch reports.
 import json
 fx=json.loads(Path('/tmp/lendford-delegation-fixtures.json').read_text())
 manager=Client();assert manager.login('delegatedmanager')[0]==200
 for route in ['administration/staff/index','performance/index','performance/commissions','payroll/default/index']:
  code,body,_=manager.request(route);assert code==200,(route,body)
 assert manager.request('administration/staff/view',params={'id':fx['other_staff']})[0]==404
 assert manager.request('performance/view',params={'id':fx['other_target']})[0]==404
 assert manager.request('payroll/default/view',params={'id':fx['salary']})[0]==404
 assert manager.request('payroll/default/proof',params={'id':fx['salary']})[0]==404
 assert manager.request('payroll/default/proof',params={'id':fx['payroll']})[0]==200
 code,body,_=manager.request('payroll/default/view',params={'id':fx['payroll']});assert code==200 and 'Payment evidence history' in body,body
 assert manager.request('payroll/default/proof',params={'id':fx['payroll'],'attempt':fx['salary_attempt']})[0]==404
 code,body,_=manager.request('performance/view',params={'id':fx['target']});assert code==200 and 'Branch target review' in body,body
 assert manager.request('performance/review',params={'id':fx['target']})[0]==405
 code,body,_=manager.request('performance/review',{'_csrf-backend':manager.csrf(body),'rating':'exceeds','note':'HTTP branch performance review'},params={'id':fx['target']});assert code==200 and 'HTTP branch performance review' in body,body
 assert manager.request('performance/rules')[0]==403
 assert manager.request('delegation/index')[0]==403
 assert manager.request('reports/default/branches')[0]==403
 code,body,_=manager.request('payroll/default/index',params={'export':'csv'});assert code==200 and fx['salary_reference'] not in body
 assert manager.request('performance/index',params={'branch_id':1,'export':'csv'})[0]==403
 assert manager.request('administration/staff/create')[0]==200
 code,body,_=manager.request('administration/staff/create');assert 'Branch Manager' not in body and 'Admin Superuser' not in body
 assert officer.request('payroll/default/index')[0]==403
 assert officer.request('payroll/default/proof',params={'id':fx['payroll']})[0]==403
 for route in ['delegation/index','reports/default/branches','payroll/default/index']:
  code,body,_=ceo.request(route);assert code==200,(route,body)
 code,body,_=ceo.request('reports/default/branches',params={'branch_id':2,'export':'csv'});assert code==200 and 'Royal Lutanda' in body and 'Oasis Mall' not in body
 code,body,_=ceo.request('delegation/index')
 assert ceo.request('delegation/save',{'manager_id':fx['manager'],'branch_id':2})[0]==400
 code,body,_=ceo.request('delegation/save',{'_csrf-backend':ceo.csrf(body),'manager_id':fx['manager'],'branch_id':2});assert code==200,body
 assert manager.request('administration/staff/index')[0]==403
 assert manager.request('payroll/default/proof',params={'id':fx['payroll']})[0]==403
 assert manager.request('performance/index')[0]==403
 # Consignments use real branch-scoped sessions, evidence and CEO financial verification.
 consignment=json.loads((root/'backend/runtime/consignment-http.json').read_text())
 consign=Client();assert consign.login('consignmentOfficer')[0]==200
 outsider=Client();assert outsider.login('consignmentOther')[0]==200
 code,page,_=consign.request('assetSales/default/view',params={'id':consignment['mandate']});assert code==200 and 'Consignment Owner' in page,page
 assert consign.request('assetSales/default/index')[0]==200,'Consignment register unavailable'
 assert consign.request('assetSales/default/create')[0]==200,'Signed mandate form unavailable'
 assert consign.request('assetSales/default/export')[0]==200,'Consignment CSV unavailable'
 for kind,key in [('mandate','mandate'),('sale','sale'),('cost','sale'),('handover','sale'),('payment','payment')]:
  assert consign.request('assetSales/default/proof',params={'id':consignment[key],'kind':kind})[0]==200,'Consignment proof unavailable: '+kind
  assert outsider.request('assetSales/default/proof',params={'id':consignment[key],'kind':kind})[0]==404,'Consignment proof leaked: '+kind
 assert outsider.request('assetSales/default/view',params={'id':consignment['mandate']})[0]==404,'Consignment detail leaked'
 assert outsider.request('assetSales/default/export',params={'ConsignmentFilter[branch_id]':'2'})[0]==403,'Consignment export leaked'
 assert ceo.request('assetSales/default/decide-sale',params={'id':consignment['pending_sale']})[0]==405,'GET consignment verification accepted'
 assert action(ceo,'assetSales/default/decide-sale',consignment['pending_sale'],{})[0]==400,'Consignment CSRF bypass'
 code,page,_=consign.request('assetSales/default/view',params={'id':consignment['pending']})
 assert action(consign,'assetSales/default/decide-sale',consignment['pending_sale'],{'_csrf-backend':consign.csrf(page),'decision':'verify','reason':'Officer'})[0]==403,'Officer verified sale'
 code,page,_=ceo.request('assetSales/default/view',params={'id':consignment['pending']})
 code,page,_=action(ceo,'assetSales/default/decide-sale',consignment['pending_sale'],{'_csrf-backend':ceo.csrf(page),'decision':'verify','reason':'HTTP evidence checked'})
 assert code==200 and 'CEO decision recorded.' in page,page
 code,form,_=consign.request('assetSales/default/handover',params={'id':consignment['pending_sale']})
 code,page,_=upload(consign,'assetSales/default/handover',consignment['pending_sale'],{'_csrf-backend':consign.csrf(form),'ConsignmentForm[occurred_on]':time.strftime('%Y-%m-%d'),'ConsignmentForm[note]':'Buyer signed receipt'},'ConsignmentForm[proof]')
 assert code==200 and 'Consignment action recorded.' in page,page
 assert not re.search(r'>[^<]*\bIds?\b[^<]*<',page,re.I),'Technical reference label in consignment screen'

 # Referrals use real sessions and CEO review; financial payouts reuse expense evidence.
 assert admin.request('referrals/default/create')[0]==403,'Support gained referral preparation'
 code,ref_form,_=officer.request('referrals/default/create');assert code==200 and 'Record referral' in ref_form,ref_form
 ref_payload={'_csrf-backend':officer.csrf(ref_form),'ReferralForm[client_id]':client_id,'ReferralForm[application_id]':application_id,'ReferralForm[referrer_name]':'HTTP referral person','ReferralForm[referrer_phone]':'0977330011','ReferralForm[source]':'HTTP campaign','ReferralForm[notes]':'Qualify manually with CEO review'}
 code,ref_page,ref_url=officer.request('referrals/default/create',ref_payload);assert code==200 and 'Referral draft saved.' in ref_page,ref_page
 ref_id=urllib.parse.parse_qs(urllib.parse.urlsplit(ref_url).query)['id'][0]
 assert officer.request('referrals/default/submit',params={'id':ref_id})[0]==405,'GET referral submit accepted'
 assert action(officer,'referrals/default/submit',ref_id,{})[0]==400,'Referral CSRF bypass'
 code,ref_page,_=action(officer,'referrals/default/submit',ref_id,{'_csrf-backend':officer.csrf(ref_page)});assert code==200 and 'Submitted' in ref_page,ref_page
 assert officer.request('referrals/default/update',params={'id':ref_id})[0]==403,'Submitted referral editable'
 assert officer.request('referrals/default/decide',params={'id':ref_id})[0]==403,'Officer gained reward approval'
 code,ref_page,_=ceo.request('referrals/default/view',params={'id':ref_id});assert code==200 and 'CEO eligibility and reward decision' in ref_page,ref_page
 code,ref_page,_=action(ceo,'referrals/default/decide',ref_id,{'_csrf-backend':ceo.csrf(ref_page),'decision':'approve','amount':'25','reason':'HTTP eligibility confirmed and K25 reward'});assert code==200 and 'Approved, awaiting payment' in ref_page and '25.00' in ref_page,ref_page
 code,notices,_=officer.request('notifications/index');assert code==200 and 'Referral reward approved by CEO' in notices,notices
 for user in [officer,ceo]:
  code,ref_report,_=user.request('referrals/default/index');assert code==200 and 'HTTP referral person' in ref_report,ref_report
 code,ref_csv,_=officer.request('referrals/default/export',params={'ReferralFilter[branch_id]':'2'});assert code==200 and 'HTTP referral person' in ref_csv and 'Oasis Mall' not in ref_csv,ref_csv
 assert officer.request('referrals/default/export',params={'ReferralFilter[branch_id]':'1'})[0]==403,'Referral export branch leakage'
 ref_payload['_csrf-backend']=officer.csrf(ref_form)
 code,duplicate_ref,_=officer.request('referrals/default/create',ref_payload);assert code==200 and 'already has a referral' in duplicate_ref,duplicate_ref
 # Foreign-branch referral cannot be opened or used as payout input.
 foreign_session=Client();assert foreign_session.login('referralother')[0]==200
 assert foreign_session.request('referrals/default/view',params={'id':ref_id})[0]==404
 code,foreign_listing,_=foreign_session.request('referrals/default/index');assert code==200 and 'HTTP referral person' not in foreign_listing
 code,ref_page,_=officer.request('referrals/default/view',params={'id':ref_id})
 boundary='ReferralPayoutBoundary2026'
 fields={'_csrf-backend':officer.csrf(ref_page)}
 upload=b''
 for key,value in fields.items():upload+=('--'+boundary+'\r\nContent-Disposition: form-data; name="'+key+'"\r\n\r\n'+value+'\r\n').encode()
 upload+=('--'+boundary+'\r\nContent-Disposition: form-data; name="proof"; filename="referral.png"\r\nContent-Type: image/png\r\n\r\n').encode()+evidence+('\r\n--'+boundary+'--\r\n').encode()
 request=urllib.request.Request('http://127.0.0.1:8099/index.php?'+urllib.parse.urlencode({'r':'referrals/default/payout','id':ref_id}),data=upload,headers={'Content-Type':'multipart/form-data; boundary='+boundary})
 with officer.opener.open(request) as response:payout_page=response.read().decode();payout_url=response.url
 assert 'Referral payout expense submitted for CEO approval.' in payout_page and 'Referral reward:' in payout_page and 'HTTP referral person' in payout_page,payout_page
 payout_id=urllib.parse.parse_qs(urllib.parse.urlsplit(payout_url).query)['id'][0]
 code,payout_page,_=ceo.request('expenses/default/view',params={'id':payout_id});assert code==200,payout_page
 code,payout_page,_=action(ceo,'expenses/default/decide',payout_id,{'_csrf-backend':ceo.csrf(payout_page),'decision':'approve','reason':'Reward expense matches CEO decision'});assert code==200 and 'approved' in payout_page,payout_page
 code,ref_page,_=officer.request('referrals/default/view',params={'id':ref_id});assert code==200 and 'Prepare payout expense' not in ref_page,ref_page
 assert admin.request('account/password')[0]==200,'Password change screen failed'
 code,html,_=admin.request('account/profile');assert code==200 and 'staff-account-menu' in html and 'dropdown-menu-end' in html,html
 token=admin.csrf(html)
 code,_,_=admin.request('account/profile',{'DynamicModel[full_name]':'No CSRF','DynamicModel[email]':'support@example.com'});assert code==400,'Profile CSRF bypass'
 code,html,_=admin.request('account/profile',{'_csrf-backend':token,'DynamicModel[full_name]':'<script>Profile</script>','DynamicModel[email]':'support.updated@example.com','DynamicModel[status]':'9','DynamicModel[id]':'1'});assert code==200 and 'Your profile has been updated.' in html,html
 assert '&lt;script&gt;Profile&lt;/script&gt;' in html and '<script>Profile</script>' not in html,'Profile output not escaped'
 assert admin.request('administration/staff/index')[0]==200,'Profile changed staff authority'
 token=admin.csrf(html)
 code,html,_=admin.request('account/profile',{'_csrf-backend':token,'DynamicModel[full_name]':'Support','DynamicModel[email]':'not-an-email'});assert code==200 and 'not a valid email address' in html,html
 code,html,_=admin.request('site/logout',{'_csrf-backend':admin.csrf(html)});assert code==200 and 'Sign in' in html,'POST logout failed' 
 # Branch overviews, direct downloads and grouped navigation use real role-scoped sessions.
 assert admin.login('support')[0]==200,'Administrator session for scope check unavailable'
 code,branch_page,_=ceo.request('administration/branch/view',params={'id':2,'period':'weekly','date':'2024-12-31'})
 assert code==200 and '2024-12-30 through 2025-01-05' in branch_page and 'What is happening now' in branch_page,branch_page
 code,branch_list,_=manager.request('administration/branch/index');assert code==200 and 'Royal Lutanda' in branch_list and 'Oasis Mall' not in branch_list,branch_list
 assert manager.request('administration/branch/view',params={'id':2})[0]==200,'Manager branch overview unavailable'
 assert manager.request('administration/branch/view',params={'id':1})[0]==403,'Other branch overview leaked'
 assert manager.request('administration/branch/update',params={'id':2})[0]==403,'Manager acquired branch editing'
 assert officer.request('administration/branch/view',params={'id':2})[0]==403,'Officer read branch management report'
 assert admin.request('administration/branch/view',params={'id':2})[0]==403,'Technical administrator read financial report'
 for format,mime,signature in [('pdf','application/pdf',b'%PDF-'),('xlsx','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',b'PK'),('csv','text/csv',b'Branch')]:
  params={'r':'administration/branch/export','id':2,'period':'monthly','date':time.strftime('%Y-%m-%d'),'format':format}
  address='http://127.0.0.1:8099/index.php?'+urllib.parse.urlencode(params)
  with manager.opener.open(address) as response:
   body=response.read();assert mime in response.headers['Content-Type'] and body.startswith(signature),(format,response.headers,body[:150])
   assert 'attachment;' in response.headers['Content-Disposition'],'Report not downloaded as attachment'
  assert manager.request('administration/branch/export',params={'id':1,'format':format})[0]==403,'Cross-branch export leaked: '+format
 assert ceo.request('administration/branch/export',params={'id':2,'format':'html'})[0]==400,'Unknown report format accepted'
 assert ceo.request('administration/branch/view',params={'id':2,'period':'daily'})[0]==400,'Unknown report period accepted'
 assert ceo.request('administration/branch/view',params={'id':2,'date':'2024-02-30'})[0]==400,'Invalid report date accepted'
 from html.parser import HTMLParser
 class MenuParser(HTMLParser):
  def __init__(self):super().__init__();self.stack=[];self.labels={};self.link=None
  def handle_starttag(self,tag,attrs):
   attrs=dict(attrs);classes=set(attrs.get('class','').split())
   if tag in ['a','button']:
    in_menu=any('dropdown-menu' in e['classes'] for e in self.stack)
    parent=next((e.get('label') for e in reversed(self.stack) if 'dropdown' in e['classes']),None) if in_menu else None
    self.link={'parent':parent,'text':''}
   if tag not in ['area','base','br','col','embed','hr','img','input','link','meta','param','source','track','wbr']:self.stack.append({'tag':tag,'classes':classes})
  def handle_data(self,text):
   if self.link:self.link['text']+=text
  def handle_endtag(self,tag):
   if tag in ['a','button'] and self.link:
    label=self.link['text'].strip();self.labels[label]=self.link['parent']
    if self.link['parent'] is None:
     for e in reversed(self.stack):
      if 'dropdown' in e['classes']:e['label']=label;break
    self.link=None
   index=next((i for i in range(len(self.stack)-1,-1,-1) if self.stack[i]['tag']==tag),None)
   if index is not None:self.stack=self.stack[:index]
 menu=MenuParser();menu.feed(branch_page)
 for label,parent in [('Applications','Loans'),('Running loans','Loans'),('Repayments','Loans'),('Delegation','Staff'),('Settings','More'),('Audit history','More')]:
  assert menu.labels.get(label)==parent,(label,menu.labels)
 import zipfile,io,xml.etree.ElementTree as ET
 expected=json.loads((root/'backend/runtime/branch-overview-expected.json').read_text())
 with zipfile.ZipFile(root/'backend/runtime/branch-overview.xlsx') as archive:
  assert archive.testzip() is None,'Excel CRC mismatch'
  for name in archive.namelist():ET.fromstring(archive.read(name))
  sheet=ET.fromstring(archive.read('xl/worksheets/sheet1.xml'));ns={'s':'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
  assert not sheet.findall('.//s:f',ns),'Unexpected executable Excel formula'
  texts=[e.text or '' for e in sheet.findall('.//s:t',ns)]
  assert expected['branch'] in texts and 'Royal Lutanda' in texts and 'Oasis Mall' not in texts,'Excel branch scope incorrect'
  values=[e.text for e in sheet.findall('.//s:v',ns)]
  assert values==[r['value'] for r in expected['rows']],'Excel numeric values differ from report'
 pdf=(root/'backend/runtime/branch-overview.pdf').read_bytes()
 assert pdf.startswith(b'%PDF-1.4') and pdf.endswith(b'%%EOF\n'),'Invalid PDF envelope'
 xref=int(re.search(rb'startxref\n(\d+)\n',pdf).group(1));assert pdf[xref:xref+4]==b'xref','Invalid PDF cross-reference offset'
 entries=re.search(rb'xref\n0 (\d+)\n(.*?)trailer',pdf,re.S);offsets=entries.group(2).splitlines()[1:]
 for number,line in enumerate(offsets,1):assert pdf[int(line[:10]):].startswith(str(number).encode()+b' 0 obj'),'Invalid PDF object offset'
 assert expected['branch'].encode() in pdf and b'Oasis Mall' not in pdf,'PDF branch scope incorrect'

 # Editing collateral awaiting receipt must preserve new intake values and record custody once.
 edit_fixture=json.loads((root/'backend/runtime/collateral-edit-http.json').read_text());edit_id=edit_fixture['id']
 code,edit_form,_=officer.request('collateral/default/update',params={'id':edit_id});assert code==200 and 'awaiting receipt' in edit_form,edit_form
 edit_fields={'_csrf-backend':officer.csrf(edit_form),'CollateralItem[description]':'HTTP pledge edit before receipt','CollateralItem[storage_location]':'','CollateralItem[received_at]':''}
 code,edit_page,_=action(officer,'collateral/default/update',edit_id,edit_fields);assert code==200 and 'Record saved.' in edit_page and 'pledged' in edit_page,edit_page
 code,edit_form,_=officer.request('collateral/default/update',params={'id':edit_id});edit_fields.update({'_csrf-backend':officer.csrf(edit_form),'CollateralItem[storage_location]':'HTTP edit safe','CollateralItem[received_at]':''})
 code,edit_page,_=action(officer,'collateral/default/update',edit_id,edit_fields);assert code==200 and 'Date received cannot be blank.' in edit_page and 'Record saved.' not in edit_page,edit_page
 edit_fields.update({'_csrf-backend':officer.csrf(edit_page),'CollateralItem[received_at]':time.strftime('%Y-%m-%d'),'CollateralItem[receipt_reference]':'HTTP-EDIT-DOCUMENT','CollateralItem[branch_id]':'1','CollateralItem[client_id]':'999'})
 code,edit_page,_=action(officer,'collateral/default/update',edit_id,edit_fields);assert code==200 and 'Record saved.' in edit_page and 'held' in edit_page and 'HTTP edit safe' in edit_page and 'HTTP-EDIT-DOCUMENT' in edit_page,edit_page
 assert 'Confirm physical receipt' not in edit_page,'Edit did not confirm possession'
 assert outsider.request('collateral/default/update',params={'id':edit_id})[0]==404,'Other branch edited intake'
 code,held_form,_=officer.request('collateral/default/update',params={'id':edit_id})
 for field in ['storage_location','received_at','receipt_reference']:
  tag=re.search(r'<input[^>]*name="CollateralItem\['+field+r'\]"[^>]*>',held_form);assert tag and 'disabled' in tag.group(0),'Recorded intake still appears editable: '+field
 edit_fields.update({'_csrf-backend':officer.csrf(held_form),'CollateralItem[description]':'HTTP held description edit','CollateralItem[storage_location]':'Ignored replacement','CollateralItem[received_at]':'2000-01-01','CollateralItem[receipt_reference]':'Ignored replacement'})
 code,held_page,_=action(officer,'collateral/default/update',edit_id,edit_fields);assert code==200 and 'Record saved.' in held_page and 'HTTP held description edit' in held_page and 'HTTP edit safe' in held_page and 'Ignored replacement' not in held_page,held_page

 print('HTTP smoke passed: guest redirect, login, branch scope, RBAC, logout method, CSRF, branch create, escaping and audit.')
finally:
 server.terminate();server.wait(timeout=5);log.close()












