177 lines
7.5 KiB
TypeScript
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);
|
|
});
|