229 lines
9.3 KiB
TypeScript
229 lines
9.3 KiB
TypeScript
import type { Page, Route } from '@playwright/test';
|
|
import { expect, test } from '@playwright/test';
|
|
|
|
const permissions = ['admin:user:view', 'admin:user:create', 'admin:update:view', 'admin:update:execute'];
|
|
|
|
function envelope(data: unknown) {
|
|
return { data, requestId: '01M00000000000000000000090' };
|
|
}
|
|
|
|
function session() {
|
|
return {
|
|
authenticated: true,
|
|
user: {
|
|
publicId: '01M00000000000000000000001',
|
|
username: 'admin',
|
|
displayName: '系统管理员',
|
|
departmentName: '信息中心',
|
|
mustChangePassword: false,
|
|
},
|
|
roles: [{ code: 'SYSTEM_ADMIN', name: '系统管理员', workbenchRoute: '/governance/settings' }],
|
|
activeRole: 'SYSTEM_ADMIN',
|
|
permissions,
|
|
};
|
|
}
|
|
|
|
function updateView(state: string, checked: boolean, enabled = true, recoveryPending = false) {
|
|
const busy = [
|
|
'DOWNLOAD_QUEUED',
|
|
'INSTALL_QUEUED',
|
|
'VERIFYING',
|
|
'DOWNLOADING',
|
|
'BACKING_UP',
|
|
'INSTALLING',
|
|
'RUNNING',
|
|
].includes(state);
|
|
return {
|
|
enabled,
|
|
currentVersion: '1.0.0-preview.1',
|
|
latestVersion: checked ? '1.0.0-preview.2' : null,
|
|
updateAvailable: checked && !['CURRENT', 'SUCCEEDED'].includes(state),
|
|
state,
|
|
message: !enabled
|
|
? '在线更新未配置'
|
|
: recoveryPending
|
|
? '回滚未完成,更新服务将重试'
|
|
: state === 'DOWNLOAD_QUEUED'
|
|
? '下载请求已排队'
|
|
: state === 'INSTALL_QUEUED'
|
|
? '安装请求已排队'
|
|
: state === 'READY'
|
|
? '更新包已下载并通过签名校验'
|
|
: state === 'SUCCEEDED'
|
|
? '新版本已通过健康检查'
|
|
: checked
|
|
? '发现新版本'
|
|
: '尚未执行在线更新',
|
|
releaseNotes: checked ? 'Preview update' : null,
|
|
publishedAt: checked ? '2026-08-16T00:00:00Z' : null,
|
|
checkedAt: checked ? '2026-08-16T00:01:00Z' : null,
|
|
statusUpdatedAt: null,
|
|
allowedActions:
|
|
!enabled || recoveryPending || busy
|
|
? []
|
|
: state === 'READY'
|
|
? ['CHECK', 'INSTALL']
|
|
: checked
|
|
? ['CHECK', 'DOWNLOAD']
|
|
: ['CHECK'],
|
|
};
|
|
}
|
|
|
|
async function installFixture(
|
|
page: Page,
|
|
downloadBodies: unknown[],
|
|
installBodies: unknown[],
|
|
enabled = true,
|
|
recoveryPending = false,
|
|
completeAfterInstall = false,
|
|
initialState?: string,
|
|
completeBusyAfterFirstRead = false,
|
|
) {
|
|
let checked = recoveryPending || Boolean(initialState);
|
|
let state = recoveryPending ? 'FAILED' : initialState || 'IDLE';
|
|
const history: Array<Record<string, unknown>> = [];
|
|
const recordHistory = (actionCode: string, targetVersion = '1.0.0-preview.2') => {
|
|
history.unshift({
|
|
publicId: `01M00000000000000000000${String(history.length + 1).padStart(3, '0')}`,
|
|
eventSequence: history.length + 1,
|
|
requestId: '01M00000000000000000000092',
|
|
username: 'admin',
|
|
activeRole: 'SYSTEM_ADMIN',
|
|
actionCode,
|
|
objectType: 'SYSTEM_UPDATE',
|
|
objectPublicId: 'SYSTEM_UPDATE',
|
|
resultCode: 'SUCCESS',
|
|
reason: actionCode === 'SYSTEM_UPDATE_REQUEST' ? '管理员确认立即重启' : null,
|
|
beforeJson: null,
|
|
afterJson: JSON.stringify({ targetVersion }),
|
|
occurredAt: '2026-08-16T00:02:00Z',
|
|
allowedActions: [],
|
|
});
|
|
};
|
|
await page.route('**/api/v1/**', async (route: Route) => {
|
|
const request = route.request();
|
|
const pathname = new URL(request.url()).pathname;
|
|
if (pathname === '/api/v1/auth/session') return route.fulfill({ json: envelope(session()) });
|
|
if (pathname === '/api/v1/auth/profile') return route.fulfill({ json: envelope(session().user) });
|
|
if (pathname === '/api/v1/auth/roles') return route.fulfill({ json: envelope(session().roles) });
|
|
if (pathname === '/api/v1/auth/csrf') {
|
|
return route.fulfill({
|
|
headers: { 'set-cookie': 'XSRF-TOKEN=update-csrf; Path=/; SameSite=Lax' },
|
|
json: envelope({ token: 'update-csrf' }),
|
|
});
|
|
}
|
|
if (pathname === '/api/v1/admin/users') {
|
|
return route.fulfill({
|
|
json: {
|
|
data: [],
|
|
meta: { page: 1, size: 20, totalElements: 0, totalPages: 0 },
|
|
requestId: '01M00000000000000000000091',
|
|
},
|
|
});
|
|
}
|
|
if (pathname === '/api/v1/admin/system-update' && request.method() === 'GET') {
|
|
const response = updateView(state, checked, enabled, recoveryPending);
|
|
if (completeBusyAfterFirstRead && state === 'INSTALLING') state = 'SUCCEEDED';
|
|
return route.fulfill({ json: envelope(response) });
|
|
}
|
|
if (pathname === '/api/v1/admin/system-update/check' && request.method() === 'POST') {
|
|
checked = true;
|
|
recordHistory('SYSTEM_UPDATE_CHECK');
|
|
return route.fulfill({ json: envelope(updateView(state, checked, enabled, recoveryPending)) });
|
|
}
|
|
if (pathname === '/api/v1/admin/system-update/download' && request.method() === 'POST') {
|
|
downloadBodies.push(request.postDataJSON());
|
|
recordHistory('SYSTEM_UPDATE_DOWNLOAD_REQUEST');
|
|
state = 'READY';
|
|
return route.fulfill({ json: envelope(updateView('DOWNLOAD_QUEUED', true, enabled)) });
|
|
}
|
|
if (pathname === '/api/v1/admin/system-update/install' && request.method() === 'POST') {
|
|
installBodies.push(request.postDataJSON());
|
|
recordHistory('SYSTEM_UPDATE_REQUEST');
|
|
const queued = updateView('INSTALL_QUEUED', true, enabled);
|
|
state = completeAfterInstall ? 'SUCCEEDED' : 'INSTALL_QUEUED';
|
|
return route.fulfill({ json: envelope(queued) });
|
|
}
|
|
if (pathname === '/api/v1/audit/logs' && request.method() === 'GET') {
|
|
return route.fulfill({
|
|
json: {
|
|
data: history,
|
|
meta: { page: 1, size: 20, totalElements: history.length, totalPages: history.length ? 1 : 0 },
|
|
requestId: '01M00000000000000000000093',
|
|
},
|
|
});
|
|
}
|
|
if (request.method() === 'GET') return route.fulfill({ json: envelope([]) });
|
|
return route.fulfill({ status: 204 });
|
|
});
|
|
}
|
|
|
|
test('system administrator checks and queues a signed online update', async ({ page }) => {
|
|
const downloadBodies: unknown[] = [];
|
|
const installBodies: unknown[] = [];
|
|
await installFixture(page, downloadBodies, installBodies);
|
|
await page.goto('/governance/update');
|
|
|
|
await expect(page.locator('h1.page-title-sr-only')).toHaveText('系统更新');
|
|
await expect(page.locator('.version-item').filter({ hasText: '当前版本' })).toContainText('1.0.0-preview.1');
|
|
await page.getByRole('button', { name: '获取更新', exact: true }).click();
|
|
await expect(page.locator('.version-item').filter({ hasText: '最新版本' })).toContainText('1.0.0-preview.2');
|
|
await expect(page.getByRole('button', { name: '立即重启', exact: true })).toBeVisible({ timeout: 5_000 });
|
|
expect(downloadBodies).toEqual([{ version: '1.0.0-preview.2' }]);
|
|
await expect(page.getByText('下载更新包', { exact: true })).toBeVisible();
|
|
|
|
await page.getByRole('button', { name: '立即重启', exact: true }).click();
|
|
|
|
await expect(page.getByText('安装请求已排队', { exact: true })).toBeVisible();
|
|
expect(installBodies).toEqual([{ version: '1.0.0-preview.2', reason: '管理员确认立即重启' }]);
|
|
});
|
|
|
|
test('brand logo returns the active identity to its workbench', async ({ page }) => {
|
|
await installFixture(page, [], []);
|
|
await page.goto('/governance/update');
|
|
|
|
await page.locator('.brand-logo').click();
|
|
|
|
await expect(page).toHaveURL(/\/governance\/settings$/);
|
|
await expect(page.getByText('选择工作身份', { exact: true })).toHaveCount(0);
|
|
});
|
|
|
|
test('disabled online updates expose status without an executable check action', async ({ page }) => {
|
|
await installFixture(page, [], [], false);
|
|
await page.goto('/governance/update');
|
|
|
|
await expect(page.getByText('在线更新未配置', { exact: true })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: '获取更新', exact: true })).toBeDisabled();
|
|
await expect(page.getByRole('button', { name: '立即重启', exact: true })).toHaveCount(0);
|
|
});
|
|
|
|
test('rollback recovery keeps the failure visible without an install action', async ({ page }) => {
|
|
await installFixture(page, [], [], true, true);
|
|
await page.goto('/governance/update');
|
|
|
|
await expect(page.getByText('回滚未完成,更新服务将重试', { exact: true })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: '立即重启', exact: true })).toHaveCount(0);
|
|
});
|
|
|
|
test('successful installation starts the ten-second automatic refresh countdown', async ({ page }) => {
|
|
await installFixture(page, [], [], true, false, true);
|
|
await page.goto('/governance/update');
|
|
|
|
await page.getByRole('button', { name: '获取更新', exact: true }).click();
|
|
await page.getByRole('button', { name: '立即重启', exact: true }).click({ timeout: 5_000 });
|
|
|
|
await expect(page.getByText('新版本已通过健康检查,页面将在 10 秒后自动刷新。', { exact: true })).toBeVisible({
|
|
timeout: 5_000,
|
|
});
|
|
});
|
|
|
|
test('reloading during installation resumes polling and starts the refresh countdown', async ({ page }) => {
|
|
await installFixture(page, [], [], true, false, false, 'INSTALLING', true);
|
|
await page.goto('/governance/update');
|
|
|
|
await expect(page.getByText('安装中', { exact: true })).toBeVisible();
|
|
await expect(page.getByText('新版本已通过健康检查,页面将在 10 秒后自动刷新。', { exact: true })).toBeVisible({
|
|
timeout: 6_000,
|
|
});
|
|
});
|