import os, json, urllib.request, urllib.error, concurrent.futures, threading
base=os.environ['HR_TEST_API_URL'].rstrip('/')
tenant=os.environ['HR_TEST_TENANT']
if 'test' not in tenant.lower():raise RuntimeError('Use a dedicated synthetic test tenant.')
headers={'Content-Type':'application/json','Accept':'application/json','X-Tenant-Key':tenant}
def post(path,data):
    request=urllib.request.Request(base+path,json.dumps(data).encode(),headers,method='POST')
    try:
        with urllib.request.urlopen(request,timeout=30) as r:return r.status,json.load(r)
    except urllib.error.HTTPError as e:return e.code,json.load(e)
status,auth=post('/auth/login',{'email':os.environ['HR_TEST_EMAIL'],'password':os.environ['HR_TEST_PASSWORD']})
if status!=200:raise RuntimeError('Synthetic test login failed: '+str(status))
def token(v):
    if isinstance(v,dict):
        for k in ('token','access_token'):
            if isinstance(v.get(k),str):return v[k]
        for item in v.values():
            found=token(item)
            if found:return found
    return None
bearer=token(auth)
if not bearer:raise RuntimeError('No token from synthetic test login')
headers['Authorization']='Bearer '+bearer
ids=[]
for _ in range(2):
    status,data=post('/actions/payroll',{'month':os.environ['HR_TEST_MONTH'],'selected_user_ids':[int(os.environ['HR_TEST_EMPLOYEE_ID'])]})
    if status!=201:raise RuntimeError('Draft creation failed: '+str(status)+' '+str(data))
    ids.append(data['data']['movement']['id'])
barrier=threading.Barrier(2)
def submit(mid):
    barrier.wait();status,data=post('/actions/payroll/movements/'+str(mid)+'/submit',{'version':1})
    return {'movement_id':mid,'status':status,'errors':data.get('errors')}
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as pool:results=list(pool.map(submit,ids))
print(json.dumps({'test':'Simultaneous payroll submissions','simultaneous_submissions':results},ensure_ascii=False))
assert sorted(x['status'] for x in results)==[200,422],results
