This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user