generated from jric11/baseProject
66 lines
4.2 KiB
Python
66 lines
4.2 KiB
Python
|
|
import json
|
||
|
|
import secrets
|
||
|
|
import subprocess
|
||
|
|
from application_harness import Application,ROOT,PHP
|
||
|
|
|
||
|
|
app=Application();workers=[]
|
||
|
|
def race(operation,records,extra=[]):
|
||
|
|
group=[]
|
||
|
|
for record in records:
|
||
|
|
process=subprocess.Popen(PHP+['tests/maintenance_worker.php',operation,app.database,str(record)]+list(map(str,extra)),cwd=ROOT,env=app.env,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True)
|
||
|
|
workers.append(process);group.append(process)
|
||
|
|
for process in group:
|
||
|
|
if process.stdout.readline().strip()!='READY':raise AssertionError(process.stderr.read())
|
||
|
|
for process in group:process.stdin.write('go\n');process.stdin.flush()
|
||
|
|
results=[]
|
||
|
|
for process in group:
|
||
|
|
output,error=process.communicate(timeout=20)
|
||
|
|
if process.returncode:raise AssertionError(error+output)
|
||
|
|
results.append(json.loads(output))
|
||
|
|
return results
|
||
|
|
try:
|
||
|
|
app.login()
|
||
|
|
app.post('Dropdowns.addDropdowntype',step='add',dropdowntype_name='Concurrent',dropdowntype_title='Concurrent',dropdowntype_table='*none',dropdowntype_column='')
|
||
|
|
kind=app.fixture('inspect')['dropdowntypes'][0]['dropdowntype_serial']
|
||
|
|
ids=[]
|
||
|
|
for value in ['First','Second']:
|
||
|
|
app.post('Dropdowns.addDropdown',step='add',dropdowntype_serial=kind,dropdown_value=value)
|
||
|
|
ident=app.fixture('inspect')['dropdowns'][-1]['dropdown_serial'];ids.append(ident);app.post('Dropdowns.updateDropdownActive',dropdown_serial=ident,dropdown_checked='true')
|
||
|
|
result=race('default',ids,[kind])
|
||
|
|
assert all(item['ok'] for item in result),result
|
||
|
|
assert sum(int(row['dropdown_default']) for row in app.fixture('inspect')['dropdowns'])==1
|
||
|
|
print('PASS: Concurrent dropdown default changes preserve one default.')
|
||
|
|
app.post('Roster.workspace',step='club',club_serial=0,club_name='Concurrent ledger')
|
||
|
|
club=app.fixture('inspect')['clubs'][0]['club_serial']
|
||
|
|
app.post('Roster.workspace',step='member',club_serial=club,identifier='testadmin',membership_status='active',joined_on='2026-01-01')
|
||
|
|
app.post('Roster.workspace',step='season',club_serial=club,season_name='Season',starts_on='2026-01-01',ends_on='2026-12-31')
|
||
|
|
data=app.fixture('inspect');membership=data['club_memberships'][0]['membership_serial'];season=data['club_seasons'][0]['season_serial']
|
||
|
|
app.post('Roster.workspace',step='charge',club_serial=club,membership_serial=membership,season_serial=season,amount='100.00')
|
||
|
|
due=app.fixture('inspect')['club_dues'][0]['due_serial']
|
||
|
|
result=race('payment',[due,due],[club,'80.00'])
|
||
|
|
assert sorted(row['ok'] for row in result)==[False,True],result
|
||
|
|
assert len(app.fixture('inspect')['club_payments'])==1
|
||
|
|
print('PASS: Concurrent payments cannot exceed the outstanding balance.')
|
||
|
|
payment=app.fixture('inspect')['club_payments'][0]['payment_serial']
|
||
|
|
result=race('reverse',[payment,payment],[club,secrets.token_hex(32)])
|
||
|
|
assert all(row['ok'] for row in result),result
|
||
|
|
assert len(app.fixture('inspect')['club_payment_reversals'])==1
|
||
|
|
print('PASS: Concurrent reversal retries create only one reversal.')
|
||
|
|
result=race('adjust',[due,due],[club,secrets.token_hex(32)])
|
||
|
|
assert all(row['ok'] for row in result),result
|
||
|
|
assert len(app.fixture('inspect')['club_due_adjustments'])==1
|
||
|
|
print('PASS: Concurrent dues correction retries create only one adjustment.')
|
||
|
|
response=app.post('Users.addUser',step='add',user_name='second_admin',user_first_name='Second',user_last_name='Administrator',user_email='second@example.test',user_role='Administrator')
|
||
|
|
assert response[0]==200,response[1]
|
||
|
|
ids=[row['user_serial'] for row in app.fixture('inspect')['users'] if row['user_role']=='Administrator']
|
||
|
|
result=race('disable',ids)
|
||
|
|
assert sorted(item['ok'] for item in result)==[False,True],result
|
||
|
|
assert next(item for item in result if not item['ok'])['status']==409,result
|
||
|
|
assert sum(row['user_role']=='Administrator' and row['user_status']=='active' for row in app.fixture('inspect')['users'])==1
|
||
|
|
print('PASS: Concurrent administrator changes cannot disable the final administrator.')
|
||
|
|
print('5 concurrency scenarios passed.')
|
||
|
|
finally:
|
||
|
|
for process in workers:
|
||
|
|
if process.poll() is None:process.terminate();process.wait(timeout=10)
|
||
|
|
app.close()
|