fix(rd21): expose committed revisions and refresh answer sources
This commit is contained in:
@@ -89,6 +89,72 @@ async function createJob(request: APIRequestContext, owner: Owner) {
|
||||
data: { companyId: company.id, jobTitle: 'Original job title', status: 'Saved', notes: 'Retain original notes', location: 'Trondheim' } }), 201);
|
||||
}
|
||||
|
||||
test('RD21 cross-origin browser exposes committed revisions and native Answers saves advance consecutively', async ({ page, owner }, info) => {
|
||||
const job = await createJob(page.request, owner);
|
||||
await page.goto(`/jobs/${job.id}?section=application&sub=answers`);
|
||||
const answer = page.getByRole('textbox', { name: t.assetsApplicationAnswer, exact: true });
|
||||
await expect(answer).toBeEditable();
|
||||
const browserCommit = await page.evaluate(async ({ api, job, headers }) => {
|
||||
const response = await fetch(`${api}/jobapplications/${job.id}/application-drafts`, { method: 'PUT', credentials: 'include',
|
||||
headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({ applicationAnswerDraft: 'Browser-visible commit', recruiterMessageDraft: '', expectedVersion: job.version }) });
|
||||
return { status: response.status, revision: response.headers.get('X-Job-Version') };
|
||||
}, { api, job, headers: owner.headers });
|
||||
expect(browserCommit.status).toBe(204);
|
||||
expect(db(info, owner, job.id).job).toMatchObject({ Version: job.version + 1, Notes: expect.stringContaining('<<<APPLICATION_ANSWER_DRAFT>>>\nBrowser-visible commit\n<<<END_APPLICATION_ANSWER_DRAFT>>>') });
|
||||
expect(browserCommit.revision).toBe(String(job.version + 1));
|
||||
await page.reload();
|
||||
await expect(answer).toHaveValue('Browser-visible commit');
|
||||
for (const [index, text] of ['First native answer', 'Second native answer'].entries()) {
|
||||
await answer.fill(text);
|
||||
const responsePromise = page.waitForResponse(response => response.url().endsWith(`/jobapplications/${job.id}/application-drafts`) && response.request().method() === 'PUT');
|
||||
await page.getByRole('button', { name: t.assetsSaveDrafts, exact: true }).click();
|
||||
const response = await responsePromise;
|
||||
expect(response.status()).toBe(204);
|
||||
expect(Number(response.headers()['x-job-version'])).toBe(job.version + index + 2);
|
||||
await expect(page.getByRole('button', { name: t.assetsSaveDrafts, exact: true })).toBeDisabled();
|
||||
await expect(page.getByText('Missing committed job revision.', { exact: true })).toHaveCount(0);
|
||||
expect(db(info, owner, job.id).job).toMatchObject({ Version: job.version + index + 2, Notes: expect.stringContaining(`<<<APPLICATION_ANSWER_DRAFT>>>\n${text}\n<<<END_APPLICATION_ANSWER_DRAFT>>>`) });
|
||||
}
|
||||
await attach(info, 'rd21-cross-origin-native-answers', { browserCommit, database: db(info, owner, job.id) });
|
||||
});
|
||||
|
||||
test('RD21 cover-letter save then Answers entry waits for fresh coherent source before a native save', async ({ page, owner }, info) => {
|
||||
const job = await createJob(page.request, owner);
|
||||
await page.goto(`/jobs/${job.id}?section=application&sub=letter`);
|
||||
const letter = page.getByRole('textbox', { name: t.jobDetailsCoverLetter, exact: true });
|
||||
await expect(letter).toBeEditable();
|
||||
await letter.fill('Acknowledged cover letter retained');
|
||||
const saved = page.waitForResponse(response => response.url().endsWith(`/jobapplications/${job.id}/cover-letter`) && response.request().method() === 'PUT');
|
||||
await page.getByRole('button', { name: t.save, exact: true }).click();
|
||||
const letterResponse = await saved;
|
||||
expect(letterResponse.status()).toBe(200);
|
||||
const letterRevision = (await letterResponse.json()).revision;
|
||||
await expect(page.getByRole('button', { name: t.save, exact: true })).toBeDisabled();
|
||||
expect(db(info, owner, job.id).job).toMatchObject({ Version: letterRevision, CoverLetterText: 'Acknowledged cover letter retained' });
|
||||
let release!: () => void;
|
||||
const gate = new Promise<void>(resolve => { release = resolve; });
|
||||
let started!: () => void;
|
||||
const requested = new Promise<void>(resolve => { started = resolve; });
|
||||
await page.route(`**/api/jobapplications/${job.id}/workspace`, async route => { started(); await gate; await route.continue(); });
|
||||
await page.getByRole('tab', { name: t.workspaceAnswers, exact: true }).click();
|
||||
await expect(page.getByRole('tab', { name: t.workspaceAnswers, exact: true })).toHaveAttribute('aria-selected', 'true');
|
||||
const answer = page.getByRole('textbox', { name: t.assetsApplicationAnswer, exact: true });
|
||||
try {
|
||||
await expect(answer).toHaveCount(0);
|
||||
await requested;
|
||||
} finally { release(); }
|
||||
await expect(answer).toBeEditable();
|
||||
await answer.fill('Answer after cover letter');
|
||||
const answerSaved = page.waitForResponse(response => response.url().endsWith(`/jobapplications/${job.id}/application-drafts`) && response.request().method() === 'PUT');
|
||||
await page.getByRole('button', { name: t.assetsSaveDrafts, exact: true }).click();
|
||||
const response = await answerSaved;
|
||||
expect(response.request().postDataJSON().expectedVersion).toBe(letterRevision);
|
||||
expect(response.status()).toBe(204);
|
||||
await expect(page.getByRole('button', { name: t.assetsSaveDrafts, exact: true })).toBeDisabled();
|
||||
expect(db(info, owner, job.id).job).toMatchObject({ Version: letterRevision + 1, CoverLetterText: 'Acknowledged cover letter retained', Notes: expect.stringContaining('<<<APPLICATION_ANSWER_DRAFT>>>\nAnswer after cover letter\n<<<END_APPLICATION_ANSWER_DRAFT>>>') });
|
||||
await attach(info, 'rd21-cover-letter-answers-entry', { letterRevision, committedRevision: response.headers()['x-job-version'], database: db(info, owner, job.id) });
|
||||
});
|
||||
|
||||
test('RD21 original two-session ordering rejects stale snapshots and preserves each acknowledged SQLite commit across navigation and a fresh session', async ({ page, owner, second, playwright, email }, info) => {
|
||||
await seedCareer(page.request, owner);
|
||||
const careerA = await json<Career>(await page.request.get(`${api}/career/profile`));
|
||||
|
||||
Reference in New Issue
Block a user