test: prepare phase 9 requalification harness and blocker register
MariaDB persistence qualification / mariadb (push) Failing after 7s

This commit is contained in:
cesnimda
2026-10-24 13:20:47 +02:00
parent 780b9bc64f
commit 1a1b7c8221
29 changed files with 7637 additions and 4 deletions
@@ -0,0 +1,77 @@
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();}
});
@@ -0,0 +1,4 @@
import { defineConfig } from '../../../../../job-tracker-ui/node_modules/@playwright/test';
import base from '../../../../../job-tracker-ui/playwright.mariadb.config';
export default defineConfig({ ...base, testDir: '.', testMatch: 'persistence.spec.ts', workers: 1, retries: 0 });