Files
huntingclubroster/tests/ticket_paging_http.py
T

44 lines
4.5 KiB
Python

import re,urllib.parse
from application_harness import Application
app=Application();checks=[]
def check(ok,message):
if not ok:raise AssertionError(message)
checks.append(message);print('PASS: '+message)
def get(club,**fields):return app.request('/main.php?'+urllib.parse.urlencode({'Xaction':'Roster.tickets','club_serial':club,**fields}))
def expect(response,status,message):
check(response[0]==status,message+' (HTTP '+str(response[0])+')');return response[1]
def listed(body):
part=re.search(rb'<ul class="ticket-list">(.*?)</ul>',body,re.S);return re.findall(rb'ticket_serial=([0-9]+)',part[1])
try:
ids=app.fixture('tenantSeed');a,b=ids['clubA'],ids['clubB'];seed=app.fixture('ticketPagingSeed',str(a),str(b),str(ids['membera']),str(ids['shared']),str(ids['memberb']))
app.login('membera@example.test','Tenant test password 123!')
body=expect(get(a),200,'Member opens first ticket page');check(len(listed(body))==25 and b'Tickets 1-25 of 102. Page 1 of 5.' in body,'First page is bounded and count includes only personal tickets')
seen=[]
for page in range(1,6):
body=expect(get(a,ticket_page=page),200,'Ticket page '+str(page)+' loads');seen+=listed(body)
check(b'Private neighbor ticket' not in body and b'Archive foreign club ticket' not in body,'Page '+str(page)+' excludes private and foreign tickets')
check(len(seen)==102 and len(set(seen))==102,'Paging returns every personal ticket once in stable serial order')
check(str(seed['oldest']).encode() in seen,'Ticket older than the previous 100-ticket ceiling is discoverable')
body=expect(get(a,ticket_page=9999),200,'Excessive page number is safely clamped');check(b'Page 5 of 5.' in body and len(listed(body))==2,'Last page shows the correct final two records')
for page in ['0','-1','1 OR 1=1','1.5']:
expect(get(a,ticket_page=page),422,'Malformed ticket page is rejected: '+page)
for literal in ['%','_','!']:
body=expect(get(a,ticket_search=literal),200,'Literal wildcard search works: '+literal);check(listed(body)==[str(seed['literal']).encode()],'Search treats '+literal+' as literal text rather than a wildcard')
body=expect(get(a,ticket_search='Archive ticket 001'),200,'Search finds an old ticket');check(listed(body)==[str(seed['oldest']).encode()] and b'Page 1 of 1.' in body,'Search count and rows use the same authorization filter')
body=expect(get(a,ticket_search='%'),200,'Stored HTML subject is safely searchable');check(b'&lt;b&gt;needle&lt;/b&gt;' in body and b'<b>needle</b>' not in body,'Ticket subject markup is escaped in search results')
body=expect(get(a,ticket_search='"><script>alert(1)</script>'),200,'Search handles HTML-shaped text');check(b'<script>alert(1)</script>' not in body,'Search text cannot inject HTML into the form')
expect(get(a,ticket_search='x'*101),422,'Overlong search gets a validation error')
expect(get(b,ticket_page=2,ticket_search='Archive'),403,'Changing club ID during paging or search is denied')
expect(get(a,ticket_serial=seed['private'],ticket_page=5),404,'Paging cannot open another member private ticket by ID')
# A reply retains its old-ticket list context, while refreshing is a read-only GET.
response=app.post('Roster.tickets',step='reply',club_serial=a,ticket_serial=seed['oldest'],ticket_page=5,ticket_search='Archive',message='Reply to older ticket')
body=expect(response,200,'Replying to an older ticket preserves navigation');check(b'Page 5 of 5.' in body and b'value="Archive"' in body and b'Reply to older ticket' in body,'Reply redirect preserves search and page context')
app.logout();app.login('shared@example.test','Tenant test password 123!')
body=expect(get(a,ticket_page=5),200,'Other member has a separately scoped ticket list');check(b'of 1. Page 1 of 1.' in body and b'Archive ticket' not in body,'Other member counts do not expose personal ticket totals')
body=expect(get(a,ticket_search='Archive'),200,'Other member search stays private');check(b'Tickets 0-0 of 0.' in body,'Search counts do not reveal other member matches')
app.logout();app.login('admina2@example.test','Tenant test password 123!')
body=expect(get(a,ticket_page=5),200,'Club administrator can page through all club tickets');check(b'of 103. Page 5 of 5.' in body and str(seed['oldest']).encode() in listed(body),'Administrator can find older tickets from every club member')
expect(get(b,ticket_search='Archive'),403,'Administrator cannot search another tenant')
finally:app.close()
print(str(len(checks))+' ticket paging HTTP checks passed.')