test: prepare phase 9 requalification harness and blocker register
MariaDB persistence qualification / mariadb (push) Failing after 7s
MariaDB persistence qualification / mariadb (push) Failing after 7s
This commit is contained in:
@@ -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 });
|
||||
Reference in New Issue
Block a user