85 lines
5.5 KiB
TypeScript
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);
|
|
});
|