generated from jric11/baseProject
44 lines
4.5 KiB
Python
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'<b>needle</b>' 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.')
|