Files
LinkPort/web/tests/operations.spec.ts

85 lines
5.5 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { readFile } from 'node:fs/promises';
test('配置管理页面加密导出、导入备份并恢复历史版本', async ({ page }) => {
await page.goto('/');
const session = await (await page.request.get('/api/v1/session')).json();
const headers = { 'X-CSRF-Token': session.csrfToken };
const name = '运维备份独立网关';
const password = 'operations-original-secret';
const created = await page.request.post('/api/v1/proxies', { headers, data: {
name, type: 'mysql', listenHost: '127.0.0.1', listenPort: 24406,
gatewayAccounts: [{ id: 'operations', username: 'operations', password }],
} });
expect(created.ok()).toBeTruthy();
const config = (await created.json()).config;
const endpoint = `/api/v1/proxies/${config.id}`;
try {
await page.reload();
await page.getByRole('navigation').getByRole('button', { name: '配置管理', exact: true }).click();
await expect(page.getByRole('heading', { name: '配置备份', exact: true })).toBeVisible();
await page.getByRole('button', { name: '导出配置', exact: true }).click();
const exportDialog = page.getByRole('dialog', { name: '导出配置', exact: true });
await exportDialog.getByPlaceholder('导出口令(至少 12 字节)', { exact: true }).fill('operations-backup-passphrase');
const downloading = page.waitForEvent('download');
await exportDialog.getByRole('button', { name: '确认导出', exact: true }).click();
const backup = await downloading;
await expect(exportDialog).not.toBeVisible();
expect(backup.suggestedFilename()).toBe('linkport-backup.json');
const backupPath = (await backup.path())!;
const backupText = await readFile(backupPath, 'utf8');
expect(JSON.parse(backupText).ciphertext).toBeTruthy();
expect(backupText).not.toContain(password);
const changed = { name: `${name}已修改`, gatewayAccounts: [
{ id: 'operations', username: 'operations', password: 'operations-changed-secret' },
] };
expect((await page.request.put(endpoint, { headers, data: changed })).ok()).toBeTruthy();
await page.getByPlaceholder('导入口令(加密文件必填)', { exact: true }).fill('operations-backup-passphrase');
await page.getByLabel('导入配置文件').setInputFiles(backupPath);
await page.getByRole('button', { name: '导入配置', exact: true }).click();
const importing = page.waitForResponse(r => r.url().endsWith('/configuration/import') && r.request().method() === 'POST');
await page.getByRole('dialog', { name: '导入配置' }).getByRole('button', { name: /确定|确认|OK/ }).click();
expect((await importing).ok()).toBeTruthy();
await expect(page.getByText('配置已导入', { exact: true })).toBeVisible();
const imported = (await (await page.request.get('/api/v1/proxies')).json()).find((g: any) => g.config.id === config.id);
expect(imported.config.name).toBe(name);
expect(imported.secretStatus['gatewayAccount:operations:password']).toBe(true);
expect((await page.request.put(endpoint, { headers, data: changed })).ok()).toBeTruthy();
const revisions = await (await page.request.get(`/api/v1/configuration/history?kind=gateways&id=${config.id}`)).json();
const revision = Math.max(...revisions.map((r: any) => r.revision));
await page.getByText('选择配置', { exact: true }).click();
await page.getByRole('option', { name, exact: true }).click();
const revisionRow = page.getByRole('row').filter({ has: page.getByRole('cell', { name: String(revision), exact: true }) });
await revisionRow.getByRole('button', { name: '查看差异 / 恢复', exact: true }).click();
const diffDialog = page.getByRole('dialog', { name: '配置变更对比' });
await expect(diffDialog).toBeVisible();
await expect(diffDialog).not.toContainText(password);
await expect(diffDialog).not.toContainText('operations-changed-secret');
await diffDialog.getByRole('button', { name: '恢复此版本', exact: true }).click();
const restoring = page.waitForResponse(r => r.url().endsWith('/configuration/restore') && r.request().method() === 'POST');
await page.getByRole('dialog', { name: '恢复版本' }).getByRole('button', { name: /确定|确认|OK/ }).click();
expect((await restoring).ok()).toBeTruthy();
await expect(page.getByText('版本已恢复', { exact: true })).toBeVisible();
const restored = (await (await page.request.get('/api/v1/proxies')).json()).find((g: any) => g.config.id === config.id);
expect(restored.config.name).toBe(name);
expect(restored.secretStatus['gatewayAccount:operations:password']).toBe(true);
const revealed = await page.request.post(`${endpoint}/reveal-password`, { headers, data: { field: 'gatewayAccount:operations:password', password: '' } });
expect(revealed.ok()).toBeTruthy();
expect((await revealed.json()).value).toBe(password);
} finally {
await page.request.delete(endpoint, { headers }).catch(() => {});
}
});
test('个人模式移除用户角色页面及接口', async ({ page }) => {
await page.goto('/');
const session = await (await page.request.get('/api/v1/session')).json();
expect(session.role).toBeUndefined();
await expect(page.getByRole('navigation').getByRole('button', { name: '管理用户', exact: true })).toHaveCount(0);
expect((await page.request.get('/api/v1/users')).status()).toBe(404);
expect((await page.request.post('/api/v1/users', { headers: { 'X-CSRF-Token': session.csrfToken }, data: { username: 'removed', role: 'admin' } })).status()).toBe(404);
});