feat(admin): add safe processing monitor and operation correlation

This commit is contained in:
cesnimda
2026-10-28 19:31:32 +01:00
parent 1f87301ee9
commit d52403dba8
19 changed files with 1064 additions and 6 deletions
@@ -0,0 +1,47 @@
import { expect, test, type APIRequestContext } from '@playwright/test';
import path from 'node:path';
const api = 'http://localhost:5302/api';
const password = 'E2ePassword123!';
async function login(client: APIRequestContext, email: string) {
expect((await client.post(`${api}/auth/login`, { data: { email, password } })).status()).toBe(200);
const me = await (await client.get(`${api}/auth/me`)).json();
return { id: me.id as string, headers: { 'X-CSRF-TOKEN': (await client.storageState()).cookies.find(cookie => cookie.name === 'XSRF-TOKEN')!.value, 'X-Session-Context': me.sessionContext as string } };
}
test('real Docling produces owner-scoped administrator history without exposing source text', async ({ page, playwright }, info) => {
await page.context().route('**/*', route => ['localhost', '127.0.0.1'].includes(new URL(route.request().url()).hostname) ? route.continue() : route.abort());
const admin = await playwright.request.newContext();
try {
const administrator = await login(admin, 'e2e@example.test');
const email = `processing-docling-${Date.now()}@example.test`;
expect((await admin.post(`${api}/users`, { headers: administrator.headers, data: { email, password, roles: ['Premium'] } })).status()).toBe(200);
const owner = await login(page.request, email);
await page.goto('/career?section=import');
await page.locator('input[type=file][accept*=".pdf"]').setInputFiles(path.resolve(process.env.CV_IMPORT_FIXTURE_ROOT || '../.artifacts/cv-import/corpus-02', 'dev-en-simple.pdf'));
await expect.poll(async () => (await (await page.request.get(`${api}/profile-cv/runs`)).json())[0]?.operation?.status, { timeout: 90000 }).toBe('succeeded');
const run = (await (await page.request.get(`${api}/profile-cv/runs`)).json())[0];
const id = run.operation.id;
expect(run.status).toBe('pending_review');
expect((await admin.get(`${api}/profile-cv/runs/${run.id}/source`)).status()).toBe(404);
const history = await (await admin.get(`${api}/admin/processing/${id}/events`)).json();
expect(history.items.map((item: { kind: string }) => item.kind)).toEqual(['Created', 'Claimed', ...Array(7).fill('StageChanged'), 'Completed']);
expect(history.items.filter((item: { kind: string }) => item.kind === 'StageChanged').map((item: { stage: string }) => item.stage)).toEqual(['cv_validating_file', 'cv_extracting', 'cv_identifying_sections', 'cv_analysing', 'cv_validating_details', 'cv_preparing_review', 'cv_review_ready']);
const detail = await (await admin.get(`${api}/admin/processing/${id}`)).json();
expect(detail.owner.id).toBe(owner.id);
expect(detail.document.name).toBe('dev-en-simple.pdf');
expect(detail.document.pages).toBeGreaterThan(0);
expect(detail.worker).toBe('document');
expect(detail.workerEnabled).toBe(true);
for (const field of ['rawExtractedText', 'documentJson', 'structuredProfileJson', 'leaseToken', 'failureMessage', 'resultReference']) expect(detail).not.toHaveProperty(field);
await login(page.request, 'e2e@example.test');
await page.goto('/admin/processing');
await page.getByLabel('Owner ID', { exact: true }).fill(owner.id);
await page.getByRole('button', { name: 'Apply filters', exact: true }).click();
await page.getByRole('button', { name: `View processing details ${id}`, exact: true }).click();
await expect(page.getByText(/^Processing completed/)).toBeVisible();
await expect(page.getByLabel('Correlation ID', { exact: true })).toHaveValue(id);
await page.screenshot({ path: info.outputPath('real-docling-processing-history.png'), fullPage: true });
await info.attach('real-parser-evidence-limit', { body: 'Actual pinned Docling conversion and deterministic interpretation; no generative model inference or model accuracy acceptance. Administrator source-download access remains denied by the ordinary ownership API.', contentType: 'text/plain' });
} finally { await admin.dispose(); }
});
@@ -16,6 +16,7 @@ for (const scenario of [
{ language: 'nb', mode: 'dark', width: 390, file: 'dev-nb-education.docx' },
] as const) {
test(`real Docling ${scenario.language}/${scenario.mode}: upload, refresh, review, correct, durable profile, editor and PDF`, async ({ page, context, playwright }, info) => {
await context.route('**/*', route => ['localhost', '127.0.0.1'].includes(new URL(route.request().url()).hostname) ? route.continue() : route.abort());
const t = translations[scenario.language];
const admin = await playwright.request.newContext();
const adminHeaders = await login(admin, 'e2e@example.test');