Occupy the integration slot for M1 live sign-off, cover two pgx pools plus four sessions, and add a Playwright spec that treats projection consistency as 200 rather than 404. Co-authored-by: Cursor <cursoragent@cursor.com>
115 lines
4.9 KiB
TypeScript
115 lines
4.9 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
/**
|
|
* M1 live: ledger-first GET. Projection missing must not become 404/409.
|
|
* Project 143 is read-only. Do not terminate or PUT workflow-plan.
|
|
*/
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const runAcceptance = process.env.RUN_UI_ACCEPTANCE === '1';
|
|
const ASSETS = process.env.UI_ACCEPTANCE_ASSETS_DIR
|
|
? path.resolve(process.env.UI_ACCEPTANCE_ASSETS_DIR)
|
|
: path.resolve(
|
|
__dirname,
|
|
'../../.doc/test-reports/assets/2026-09-14-document-reliability-pilot-live',
|
|
);
|
|
const SCREENSHOTS = path.join(ASSETS, 'screenshots');
|
|
fs.mkdirSync(SCREENSHOTS, { recursive: true });
|
|
|
|
const ADMIN_USER = process.env.ADMIN_USERNAME?.trim() || '';
|
|
const ADMIN_PASS = process.env.ADMIN_PASSWORD?.trim() || '';
|
|
const BASE = process.env.BASE_URL || 'http://127.0.0.1:8080';
|
|
const TARGET_DOC = 'doc_proj143_intake';
|
|
|
|
test.skip(!runAcceptance, 'set RUN_UI_ACCEPTANCE=1 against the rebuilt dev stack');
|
|
|
|
async function login(page: Page, username: string, password: string) {
|
|
await page.goto('/');
|
|
await page.fill('input[placeholder*="系统用户名"]', username);
|
|
await page.fill('input[placeholder*="登录密码"]', password);
|
|
await page.click('button:has-text("进入医疗协同工作台")');
|
|
await expect(page.getByText('新建立项会话')).toBeVisible({ timeout: 30_000 });
|
|
}
|
|
|
|
test.describe.serial('controlled document reload M1 live', () => {
|
|
test('project 143 content is 200 with consistency; anonymous is 401; unknown is 404', async ({
|
|
browser,
|
|
}) => {
|
|
if (!ADMIN_USER || !ADMIN_PASS) {
|
|
throw new Error('ADMIN_USERNAME and ADMIN_PASSWORD are required');
|
|
}
|
|
const page = await browser.newPage();
|
|
const contentHits: { status: number; consistency?: string; error?: string }[] = [];
|
|
page.on('response', async (res) => {
|
|
const u = res.url();
|
|
if (!u.includes(`/docgov/documents/${TARGET_DOC}/content`)) return;
|
|
let consistency: string | undefined;
|
|
let error: string | undefined;
|
|
try {
|
|
const json = (await res.json()) as { consistency?: string; error_code?: string };
|
|
consistency = json.consistency;
|
|
error = json.error_code;
|
|
} catch {
|
|
/* ignore non-json */
|
|
}
|
|
contentHits.push({ status: res.status(), consistency, error });
|
|
});
|
|
|
|
await login(page, ADMIN_USER, ADMIN_PASS);
|
|
await page.getByRole('complementary').getByTitle('对话协同会话', { exact: true }).click({ force: true });
|
|
await expect(page.getByRole('banner')).toContainText('对话协同会话', { timeout: 20_000 });
|
|
await page.screenshot({ path: path.join(SCREENSHOTS, '01-project-143.png'), fullPage: true });
|
|
|
|
const token = await page.evaluate(() => {
|
|
try {
|
|
const raw = window.localStorage.getItem('smdf_auth_session');
|
|
if (!raw) return '';
|
|
const session = JSON.parse(raw) as { token?: string };
|
|
return session.token || '';
|
|
} catch {
|
|
return '';
|
|
}
|
|
});
|
|
if (!token) {
|
|
throw new Error('login did not persist smdf_auth_session.token');
|
|
}
|
|
|
|
const authed = await page.request.get(`${BASE}/api/v1/docgov/documents/${TARGET_DOC}/content`, {
|
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
});
|
|
const authedBody = await authed.json();
|
|
expect(authed.status(), `content ${JSON.stringify(authedBody)}`).toBe(200);
|
|
expect(authedBody.markdown, 'ledger markdown required').toBeTruthy();
|
|
expect(['aligned', 'missing', 'drift', 'empty', 'ledger_missing']).toContain(authedBody.consistency);
|
|
expect(authedBody.error_code ?? '').not.toMatch(/PROJECTION/);
|
|
|
|
await page.reload();
|
|
await expect(page.getByRole('banner')).toContainText('对话协同会话', { timeout: 20_000 });
|
|
const afterReload = await page.request.get(`${BASE}/api/v1/docgov/documents/${TARGET_DOC}/content`, {
|
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
});
|
|
expect(afterReload.status()).toBe(200);
|
|
const reloadBody = await afterReload.json();
|
|
expect(reloadBody.markdown).toBeTruthy();
|
|
|
|
await page.screenshot({ path: path.join(SCREENSHOTS, '02-after-reload.png'), fullPage: true });
|
|
|
|
const anon = await page.request.get(`${BASE}/api/v1/docgov/documents/${TARGET_DOC}/content`);
|
|
expect(anon.status()).toBe(401);
|
|
|
|
const missing = await page.request.get(`${BASE}/api/v1/docgov/documents/doc_does_not_exist_m1/content`, {
|
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
});
|
|
expect(missing.status()).toBe(404);
|
|
const missingBody = await missing.json();
|
|
expect(missingBody.error_code).toBe('DOCUMENT_NOT_FOUND');
|
|
|
|
const projection409 = contentHits.filter(
|
|
(h) => h.status === 409 || String(h.error || '').includes('PROJECTION'),
|
|
);
|
|
expect(projection409, JSON.stringify(contentHits)).toHaveLength(0);
|
|
});
|
|
});
|