Files
kaidi/frontend/e2e/archive-files.e2e.ts
2026-08-17 14:08:40 +08:00

177 lines
7.5 KiB
TypeScript

import type { Page, Route } from '@playwright/test';
import { expect, test } from '@playwright/test';
const borrowId = '01M00000000000000000000401';
const fileId = '01M00000000000000000000402';
const projectId = '01M00000000000000000000403';
function envelope(data: unknown, totalElements?: number) {
return {
data,
...(typeof totalElements === 'number'
? { meta: { page: 1, size: 20, totalElements, totalPages: totalElements ? 1 : 0 } }
: {}),
requestId: '01M00000000000000000000499',
};
}
function session() {
return {
authenticated: true,
user: {
publicId: '01M00000000000000000000404',
username: 'archive-e2e',
displayName: '资料管理员',
departmentName: '档案部',
mustChangePassword: false,
},
roles: [{ code: 'ARCHIVE_MANAGER', name: '资料管理员', workbenchRoute: '/workbench/archive' }],
activeRole: 'ARCHIVE_MANAGER',
permissions: ['archive:file:view', 'archive:borrow:approve', 'archive:borrow:reject', 'archive:borrow:return'],
};
}
function borrow(overrides: Record<string, unknown> = {}) {
return {
publicId: borrowId,
businessNo: 'BOR-E2E-018',
filePublicId: fileId,
fileName: '项目合同原件.pdf',
projectPublicId: projectId,
projectName: '档案借阅验收项目',
purpose: '项目审计核对原件',
dueAt: '2099-12-31T12:00:00Z',
status: 'APPLIED',
applicantName: '财务申请员',
approvedByName: null,
approvedAt: null,
reviewOpinion: null,
returnCondition: null,
returnedByName: null,
returnedAt: null,
version: 0,
allowedActions: ['APPROVE', 'REJECT'],
...overrides,
};
}
interface FixtureOptions {
approveRequests: Array<Record<string, unknown>>;
returnRequests: Array<Record<string, unknown>>;
commandHeaders: Array<Record<string, string>>;
borrowQueries: URLSearchParams[];
}
async function installFixture(page: Page, options: FixtureOptions) {
const userSession = session();
let currentBorrow = borrow();
await page.route('**/api/v1/**', async (route: Route) => {
const request = route.request();
const url = new URL(request.url());
const pathname = url.pathname;
if (pathname === '/api/v1/auth/session') return route.fulfill({ json: envelope(userSession) });
if (pathname === '/api/v1/auth/profile') return route.fulfill({ json: envelope(userSession.user) });
if (pathname === '/api/v1/auth/roles') return route.fulfill({ json: envelope(userSession.roles) });
if (pathname === '/api/v1/auth/csrf') {
return route.fulfill({
headers: { 'set-cookie': 'XSRF-TOKEN=archive-e2e-csrf; Path=/' },
json: envelope({ token: 'archive-e2e-csrf' }),
});
}
if (request.method() === 'GET' && pathname === '/api/v1/archives/borrows') {
options.borrowQueries.push(new URLSearchParams(url.searchParams));
return route.fulfill({ json: envelope([currentBorrow], 1) });
}
if (request.method() === 'POST' && pathname === `/api/v1/archives/borrows/${borrowId}/approve`) {
options.approveRequests.push(request.postDataJSON() as Record<string, unknown>);
options.commandHeaders.push(request.headers());
currentBorrow = borrow({
status: 'BORROWED',
approvedByName: '资料管理员',
approvedAt: '2026-08-16T09:00:00Z',
reviewOpinion: '用途和期限符合要求',
version: 1,
allowedActions: ['RETURN'],
});
return route.fulfill({ json: envelope(currentBorrow) });
}
if (request.method() === 'POST' && pathname === `/api/v1/archives/borrows/${borrowId}/return`) {
const payload = request.postDataJSON() as Record<string, unknown>;
options.returnRequests.push(payload);
options.commandHeaders.push(request.headers());
currentBorrow = borrow({
status: 'RETURNED',
approvedByName: '资料管理员',
approvedAt: '2026-08-16T09:00:00Z',
reviewOpinion: '用途和期限符合要求',
returnCondition: payload.returnCondition,
returnedByName: '资料管理员',
returnedAt: '2026-08-16T10:00:00Z',
version: 2,
allowedActions: [],
});
return route.fulfill({ json: envelope(currentBorrow) });
}
if (request.method() === 'GET') return route.fulfill({ json: envelope([], 0) });
return route.fulfill({ status: 204 });
});
}
test('PAGE-18 approves and returns an archive borrow with versioned commands', async ({ page }) => {
const approveRequests: Array<Record<string, unknown>> = [];
const returnRequests: Array<Record<string, unknown>> = [];
const commandHeaders: Array<Record<string, string>> = [];
const borrowQueries: URLSearchParams[] = [];
await installFixture(page, { approveRequests, returnRequests, commandHeaders, borrowQueries });
await page.goto('/archives/files?resource=borrows');
await expect(page.getByRole('heading', { name: '文件检索与借阅', exact: true })).toBeVisible();
await expect(page.getByText('BOR-E2E-018', { exact: true }).first()).toBeVisible();
await expect.poll(() => borrowQueries.length).toBeGreaterThan(0);
expect(borrowQueries[0].get('page')).toBe('1');
expect(borrowQueries[0].get('size')).toBe('20');
const initialListRequests = borrowQueries.length;
await page.getByRole('button', { name: '批准', exact: true }).click();
const approveDialog = page.locator('.t-dialog').filter({ hasText: '批准借阅' });
await approveDialog.getByRole('button', { name: '确认', exact: true }).click();
await expect(approveDialog.getByText('请填写至少 2 个字符的处理意见', { exact: true })).toBeVisible();
expect(approveRequests).toHaveLength(0);
await approveDialog.locator('textarea').fill('用途和期限符合要求');
await approveDialog.getByRole('button', { name: '确认', exact: true }).click();
await expect.poll(() => approveRequests.length).toBe(1);
expect(approveRequests[0]).toEqual({
version: 0,
opinion: '用途和期限符合要求',
});
expect(commandHeaders[0]['x-xsrf-token']).toBe('archive-e2e-csrf');
expect(commandHeaders[0]['idempotency-key']).toBeTruthy();
await expect(approveDialog).toBeHidden();
await expect.poll(() => borrowQueries.length).toBeGreaterThan(initialListRequests);
await expect(page.getByText('借出中', { exact: true })).toBeVisible();
const afterApprovalListRequests = borrowQueries.length;
await page.getByRole('button', { name: '登记归还', exact: true }).click();
const returnDialog = page.locator('.t-dialog').filter({ hasText: '登记归还' });
await returnDialog.getByRole('button', { name: '确认', exact: true }).click();
await expect(returnDialog.getByText('请填写至少 2 个字符的处理意见', { exact: true })).toBeVisible();
expect(returnRequests).toHaveLength(0);
await returnDialog.locator('textarea').fill('纸质原件完好,页数一致');
await returnDialog.getByRole('button', { name: '确认', exact: true }).click();
await expect.poll(() => returnRequests.length).toBe(1);
expect(returnRequests[0]).toEqual({
version: 1,
returnCondition: '纸质原件完好,页数一致',
});
expect(commandHeaders[1]['x-xsrf-token']).toBe('archive-e2e-csrf');
expect(commandHeaders[1]['idempotency-key']).toBeTruthy();
await expect(returnDialog).toBeHidden();
await expect.poll(() => borrowQueries.length).toBeGreaterThan(afterApprovalListRequests);
await expect(page.getByText('已归还', { exact: true })).toBeVisible();
await expect(page.getByRole('button', { name: '登记归还', exact: true })).toHaveCount(0);
});