import { test, expect, type APIRequestContext } from '../../../../../job-tracker-ui/node_modules/@playwright/test'; import { sql } from '../performance/mariadb-proof.mjs'; const api='http://localhost:5302/api'; const password='E2ePassword123!'; async function login(request: APIRequestContext, email: string) { expect((await request.post(`${api}/auth/login`,{data:{email,password}})).status()).toBe(200); const me=await (await request.get(`${api}/auth/me`)).json(); return {id:me.id as string, headers:{'X-CSRF-TOKEN':(await request.storageState()).cookies.find(c=>c.name==='XSRF-TOKEN')!.value, 'X-Session-Context':me.sessionContext as string}}; } async function seed(admin: APIRequestContext, request: APIRequestContext) { const administrator=await login(admin,'e2e@example.test'); const email=`requal-observation-${Date.now()}@example.test`; expect((await admin.post(`${api}/users`,{headers:administrator.headers,data:{email,password,roles:[]}})).status()).toBe(200); const owner=await login(request,email); const company=await request.post(`${api}/companies`,{headers:owner.headers,data:{name:'Requalification synthetic company'}}); expect(company.status()).toBe(201); return {owner,email,companyId:(await company.json()).id as number}; } async function stored(id: number) { expect(Number.isSafeInteger(id)&&id>0).toBe(true); return JSON.parse(await sql(`SELECT JSON_OBJECT('id',a.Id,'version',a.Version,'title',a.JobTitle,'notes',a.Notes, 'opportunityTitle',j.JobTitle,'events',(SELECT COUNT(*) FROM JobEvents e WHERE e.JobApplicationId=a.Id)) FROM JobApplications a JOIN Jobs j ON j.Id=a.JobId WHERE a.Id=${id}`)); } test('MariaDB real HTTP job saves reject stale and unversioned drafts; independent SQL and a new session retain the acknowledged winner',async({playwright},info)=>{ const contexts=await Promise.all(Array.from({length:4},()=>playwright.request.newContext())); const [admin,first,second,fresh]=contexts; try { expect(await sql('SELECT VERSION()')).toMatch(/^12\.2\.2-MariaDB(?:-|$)/); const {owner,email,companyId}=await seed(admin,first); const otherSession=await login(second,email); const created=await first.post(`${api}/jobapplications`,{headers:owner.headers,data:{companyId,jobTitle:'Original title',notes:'Original notes',status:'Prospect'}}); expect(created.status()).toBe(201); const id=(await created.json()).id as number; const a=await (await first.get(`${api}/jobapplications/${id}`)).json(); const b=await (await second.get(`${api}/jobapplications/${id}`)).json(); expect(b).toEqual(a); const saved=await first.put(`${api}/jobapplications/${id}`,{headers:owner.headers,data:{...a,jobTitle:'Acknowledged winner',expectedVersion:a.version}}); expect(saved.status()).toBe(204); const version=Number(saved.headers()['x-job-version']); expect(version).toBe(a.version+1); const acknowledged=await stored(id); expect(acknowledged).toMatchObject({title:'Acknowledged winner',opportunityTitle:'Acknowledged winner',notes:'Original notes',version}); expect((await second.put(`${api}/jobapplications/${id}`,{headers:otherSession.headers,data:{...b,notes:'Rejected stale notes',expectedVersion:b.version}})).status()).toBe(409); expect(await stored(id)).toEqual(acknowledged); expect((await second.put(`${api}/jobapplications/${id}`,{headers:otherSession.headers,data:{...b,notes:'Legacy unversioned draft'}})).status()).toBe(409); expect(await stored(id)).toEqual(acknowledged); await login(fresh,email); expect(await (await fresh.get(`${api}/jobapplications/${id}`)).json()).toMatchObject({jobTitle:'Acknowledged winner',notes:'Original notes',version}); const foreign=await seed(admin,admin); expect((await admin.get(`${api}/jobapplications/${id}`)).status()).toBe(404); expect((await admin.put(`${api}/jobapplications/${id}`,{headers:foreign.owner.headers,data:{...a,expectedVersion:version}})).status()).toBe(404); expect(await stored(id)).toEqual(acknowledged); await info.attach('durable-job-acknowledgement',{body:JSON.stringify({id,acknowledged,stale:409,missing:409,foreign:404,newSession:true}),contentType:'application/json'}); } finally {await Promise.all(contexts.map(context=>context.dispose()));} }); test('Characterization only: repeated creation bodies create separate persisted records; no creation-intent idempotency guarantee is claimed',async({playwright},info)=>{ const admin=await playwright.request.newContext(); const request=await playwright.request.newContext(); try { expect(await sql('SELECT VERSION()')).toMatch(/^12\.2\.2-MariaDB(?:-|$)/); const {owner,companyId}=await seed(admin,request); const body={companyId,jobTitle:'Identical creation body',notes:'Synthetic intent ambiguity',status:'Prospect'}; const first=await request.post(`${api}/jobapplications`,{headers:owner.headers,data:body}); const second=await request.post(`${api}/jobapplications`,{headers:owner.headers,data:body}); expect(first.status()).toBe(201); expect(second.status()).toBe(201); const ids=[(await first.json()).id as number,(await second.json()).id as number]; expect(ids[0]).not.toBe(ids[1]); expect(await stored(ids[0])).toMatchObject({id:ids[0],title:body.jobTitle,notes:body.notes}); expect(await stored(ids[1])).toMatchObject({id:ids[1],title:body.jobTitle,notes:body.notes}); await info.attach('creation-intent-limit',{body:JSON.stringify({ids,statuses:[201,201],classification:'Characterization, not safety acceptance', limit:'Identical JSON cannot distinguish two intentional creations from a retry after an unknown response. A separately specified creation-intent contract remains required; no silent deduplication was added.'}),contentType:'application/json'}); } finally {await admin.dispose();await request.dispose();} });