import json, urllib.request, sqlite3, uuid
B='http://127.0.0.1:8090/api'; db=sqlite3.connect('api/storage/keel.db')
TOK=db.execute("select token from sessions where kind='user' order by created_at desc limit 1").fetchone()[0]
cid=db.execute("select id from companies where is_sandbox=1").fetchone()[0]
sid=db.execute("select id from companies where name like 'Sahyadri%'").fetchone()[0]
def call(m, path, body=None, tok=None, raw=None):
    req=urllib.request.Request(B+path, method=m, data=(raw if raw is not None else (json.dumps(body).encode() if body is not None else None)), headers={'Content-Type':'application/json', **({'Authorization':'Bearer '+tok} if tok else {})})
    try: r=urllib.request.urlopen(req); return r.status, json.loads(r.read() or b'{}')
    except urllib.error.HTTPError as e: 
        try: return e.code, json.loads(e.read())
        except Exception: return e.code, {}
T=lambda *a,**k: call(*a, tok=TOK, **k)
print('no auth', call('GET','/console')[0], call('GET',f'/companies/{cid}')[0])
print('bad token', call('GET','/console',tok='ab'*32)[0])
print('bad json', T('POST',f'/companies/{cid}/note',raw=b'{oops'))
print('huge note', T('POST',f'/companies/{cid}/note',{'note':'x'*100000})[0])
print('xss note', T('POST',f'/companies/{cid}/note',{'note':'<script>alert(1)</script>'})[0], '<script' in json.dumps(T('GET',f'/companies/{cid}')[1]['note']))
print('sqli', T('GET','/companies/x%27%20OR%20%271%27=%271')[0], T('POST','/verify',{'reference':"KEEL-AAAAAA-0000-AAAAAA' OR 1=1--"}))
print('empty allot', T('POST',f'/companies/{cid}/events/preview',{'type':'allotment','form':{}})[1]['errors'])
print('neg shares', T('POST',f'/companies/{cid}/events/preview',{'type':'allotment','form':{'allottees':[{'name':'X','shares':-5}],'issue_price':10}})[1]['errors'])
print('future date', T('POST',f'/companies/{cid}/events/preview',{'type':'allotment','form':{'allotment_date':'2099-01-01','allottees':[{'name':'X','shares':5}],'issue_price':10}})[1]['errors'])
print('price below par', T('POST',f'/companies/{cid}/events/preview',{'type':'allotment','form':{'allottees':[{'name':'X','shares':5}],'issue_price':5}})[1]['errors'])
print('unknown type', T('POST',f'/companies/{cid}/events/preview',{'type':'zzz','form':{}}))
print('stale head', T('POST',f'/companies/{cid}/events',{'type':'allotment','form':{'allottees':[{'name':'X','shares':5}],'issue_price':10},'head_hash':'0'*64}))
# double submit race: two identical posts with the same head
pv=T('POST',f'/companies/{cid}/events/preview',{'type':'appointment','form':{'person':{'name':'Dup Director','din':'55555555'},'designation':'director'}})[1]
r1=T('POST',f'/companies/{cid}/events',{'type':'appointment','form':{'person':{'name':'Dup Director','din':'55555555'},'designation':'director'},'head_hash':pv['head_hash']}); r2=T('POST',f'/companies/{cid}/events',{'type':'appointment','form':{'person':{'name':'Dup Director','din':'55555555'},'designation':'director'},'head_hash':pv['head_hash']})
print('double submit', r1[0], r2[0], r2[1].get('message','')[:60])
print('resign to 1 director', T('POST',f'/companies/{sid}/events/preview',{'type':'resignation','form':{'person_id':db.execute("select id from persons where company_id=? and name='Anil Patil'",(sid,)).fetchone()[0]}})[1]['errors'])
print('transfer too many', T('POST',f'/companies/{cid}/events/preview',{'type':'transfer','form':{'transferor_person_id':db.execute("select id from persons where company_id=? and name='Aarav Sood'",(cid,)).fetchone()[0],'shares':999999,'transferee':{'name':'Z'}}})[1]['errors'])
print('srn bad', T('POST',f"/companies/{cid}/calendar/{db.execute('select id from calendar_items where company_id=? and form is not null limit 1',(cid,)).fetchone()[0]}/srn",{'srn':'nope'}))
print('meeting past notice', T('POST',f'/companies/{cid}/meetings',{'meeting_date':'2026-09-28'})[0])
mid=db.execute("select id from meetings where company_id=? order by created_at desc limit 1",(cid,)).fetchone()[0]
print('short notice', T('POST',f'/companies/{cid}/meetings/{mid}/notice',{'notice_date':'2026-09-27'}))
print('sign before edits', T('POST',f'/companies/{cid}/meetings/{mid}/sign',{'method':'typed','typed_name':'x','consent':1}))
print('enter before sign', T('POST',f'/companies/{cid}/meetings/{mid}/enter',{}))
print('doc bad class', T('POST',f'/companies/{cid}/documents',{'class':'nope','body':'x'*30}))
print('doc no signer', T('POST',f'/companies/{cid}/documents',{'class':'ctc','body':'x'*30,'title':'t'}))
print('seat bad role', T('POST',f'/companies/{cid}/seats',{'role':'hacker','email':'a@b.c','name':'X'}))
print('auditor no window', T('POST',f'/companies/{cid}/seats',{'role':'auditor','email':'a@b.c','name':'X'}))
print('founder not director', T('POST',f'/companies/{cid}/seats',{'role':'founder','email':'a@b.c','name':'X','person_id':'nope'}))
print('other firm company', call('GET',f'/companies/{cid}',tok=db.execute("select token from sessions where kind='seat' order by created_at desc limit 1").fetchone()[0])[0])
# seat cannot hit firm routes
ST=db.execute("select token from sessions where kind='seat' order by created_at desc limit 1").fetchone()[0]
print('seat→firm routes', call('GET','/console',tok=ST)[0], call('POST',f'/companies/{cid}/events',{'type':'allotment','form':{}},tok=ST)[0], call('GET','/firm',tok=ST)[0])
print('admin as partner', T('GET','/admin/firms')[0], T('POST','/admin/anchors',{'month':'2026-07'})[0])
print('mig on attested', T('POST',f'/companies/{sid}/migration/map',{'mapping':{}}))
print('reset non-sandbox', T('POST',f'/companies/{sid}/sandbox/reset',{}))
print('verify formats', T('POST','/verify',{'reference':'garbage'})[0], T('POST','/verify',{'reference':'A'*64})[1]['result'])
print('upload get unauth', call('GET',f"/upload/{db.execute('select id from uploads limit 1').fetchone()[0]}")[0])
print('404 route', T('GET','/nope')[0])
print('cross-origin', call('POST','/verify',{'reference':'x'}) )
req=urllib.request.Request(B+'/verify', method='POST', data=b'{"reference":"x"}', headers={'Content-Type':'application/json','Origin':'https://evil.example'})
try: urllib.request.urlopen(req)
except urllib.error.HTTPError as e: print('evil origin', e.code)
# rate limit /verify 120/hr
codes=set()
for i in range(125): codes.add(call('POST','/verify',{'reference':'KEEL-AAAAAA-0000-AAAAAA'})[0])
print('verify ratelimit', codes)
# password change wrong current
print('pw wrong', T('POST','/auth/password',{'current':'x','new':'y'*12})[0])
# facts update recompute
print('facts', T('POST',f'/companies/{cid}/facts',{'accepts_deposits_or_loans':True})[0], [r['rule_id'] for r in T('GET',f'/companies/{cid}/calendar')[1]['items'] if r['rule_id']=='DPT-3'][:1])
# correction with wrong payload
ev=T('GET',f'/companies/{cid}/events?type=appointment')[1]['events'][0]
print('correction bad payload', T('POST',f'/companies/{cid}/events/preview',{'type':'correction','form':{'event_id':ev['id'],'reason':'typo','payload':{'nothing':1}}})[1]['errors'][:2])
full=T('GET',f"/companies/{cid}/events/{ev['id']}")[1]['event']; p=dict(full['payload']); p['designation']='additional'
r=T('POST',f'/companies/{cid}/events',{'type':'correction','form':{'event_id':ev['id'],'reason':'designation was additional director per board minutes','payload':p}}); print('correction ok', r[0], r[1].get('entry'))
print('trail verify', T('GET',f'/companies/{cid}/trail')[1]['verify']['ok'])
