117 lines
8.0 KiB
TypeScript
117 lines
8.0 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
||
import { generateKeyPairSync } from 'node:crypto';
|
||
import { rootCertificates } from 'node:tls';
|
||
|
||
test('文件资产上传、账号选择、类型过滤及引用删除保护', async ({ page }) => {
|
||
test.setTimeout(90_000);
|
||
// Generated fixture credentials and a public trust certificate; no external SSH/TLS server is contacted.
|
||
const pair = generateKeyPairSync('ed25519');
|
||
const privateKey = pair.privateKey.export({ type: 'pkcs8', format: 'pem' }).toString();
|
||
const keyBytes = Buffer.from(pair.publicKey.export({ format: 'jwk' }).x!, 'base64url');
|
||
const part = (value: Buffer) => { const length = Buffer.alloc(4); length.writeUInt32BE(value.length); return Buffer.concat([length, value]); };
|
||
const publicKey = `ssh-ed25519 ${Buffer.concat([part(Buffer.from('ssh-ed25519')), part(keyBytes)]).toString('base64')} fixture`;
|
||
await page.goto('/');
|
||
const session = await (await page.request.get('/api/v1/session')).json();
|
||
const headers = { 'X-CSRF-Token': session.csrfToken };
|
||
const assets: any[] = [];
|
||
const targets: string[] = [], gateways: string[] = [];
|
||
const drawer = page.getByRole('dialog', { name: /^(新建|编辑)(资产|目标服务|网关服务)$/ });
|
||
const choose = async (label: string, name: string, excluded?: string) => {
|
||
await drawer.locator('.el-select').filter({ has: page.getByRole('combobox', { name: label, exact: true }) }).click();
|
||
if (excluded) await expect(page.getByRole('option', { name: excluded, exact: true })).toHaveCount(0);
|
||
await page.getByRole('option', { name, exact: true }).click();
|
||
};
|
||
try {
|
||
for (const [type, name, content] of [
|
||
['ssh_private_key', '文件测试私钥', privateKey],
|
||
['ssh_public_key', '文件测试公钥', publicKey],
|
||
['tls_certificate', '文件测试 CA', rootCertificates[0]],
|
||
]) {
|
||
const response = await page.request.post('/api/v1/assets', { headers, data: { type, name, content } });
|
||
expect(response.ok()).toBeTruthy();
|
||
const asset = await response.json();
|
||
expect(asset.config.content || '').toBe('');
|
||
assets.push(asset);
|
||
}
|
||
await page.reload();
|
||
await page.keyboard.press('Control+k');
|
||
const search = page.getByRole('dialog', { name: '搜索资源 · 当前项目与环境', exact: true });
|
||
await search.getByRole('combobox', { name: '搜索资源关键词', exact: true }).fill('文件测试私钥');
|
||
await search.getByRole('option').filter({ hasText: '文件测试私钥' }).click();
|
||
await expect(drawer.getByRole('combobox', { name: '资产类型', exact: true })).toHaveCount(0);
|
||
await expect(drawer.getByRole('radio', { name: 'SSH 私钥', exact: true })).toBeChecked();
|
||
await expect(drawer.getByRole('radio', { name: 'SSH 用户证书', exact: true })).toBeDisabled();
|
||
await drawer.getByLabel('上传 SSH 私钥 文件', { exact: true }).setInputFiles({ name: 'renewed-key.pem', mimeType: 'text/plain', buffer: Buffer.from(privateKey) });
|
||
await expect(drawer.getByText('已选择 renewed-key.pem,保存资产后生效')).toBeVisible();
|
||
const updated = page.waitForResponse(response => response.url().endsWith(`/assets/${assets[0].config.id}`) && response.request().method() === 'PUT');
|
||
await drawer.getByRole('button', { name: '保存资产' }).click();
|
||
expect((await updated).ok()).toBeTruthy();
|
||
await expect(drawer).not.toBeVisible();
|
||
|
||
const targetResponse = await page.request.post('/api/v1/targets', { headers, data: {
|
||
name: '文件测试 SSH', type: 'ssh', targetHost: '127.0.0.1', targetPort: 1,
|
||
accounts: [{ id: 'fixture', remark: '文件账号', username: 'fixture', password: 'fixture-only', authMethod: 'password' }], ssh: { ignoreHostKey: true },
|
||
} });
|
||
expect(targetResponse.ok()).toBeTruthy();
|
||
const target = (await targetResponse.json()).config;
|
||
targets.push(target.id);
|
||
await page.reload();
|
||
await page.getByRole('navigation').getByRole('button', { name: /目标服务/ }).click();
|
||
await page.locator('.target-card').filter({ hasText: '文件测试 SSH' }).getByRole('button', { name: '配置目标' }).click();
|
||
await drawer.getByRole('tab', { name: '账号', exact: true }).click();
|
||
await drawer.locator('.el-radio-button').filter({ hasText: /^私钥$/ }).click();
|
||
await expect(drawer.getByRole('combobox', { name: 'SSH 用户证书资产(可选)', exact: true })).toBeVisible();
|
||
await choose('账号私钥资产', '文件测试私钥', '文件测试 CA');
|
||
await drawer.getByRole('button', { name: '保存配置' }).click();
|
||
await expect(drawer).not.toBeVisible();
|
||
const targetSaved = await (await page.request.get(`/api/v1/targets/${target.id}`)).json();
|
||
expect(targetSaved.config.accounts[0].privateKeyAssetId).toBe(assets[0].config.id);
|
||
expect(JSON.stringify(targetSaved)).not.toContain(privateKey);
|
||
|
||
const gatewayResponse = await page.request.post('/api/v1/proxies', { headers, data: {
|
||
name: '文件测试网关', type: 'ssh', targetId: target.id, targetAccountId: 'fixture', listenHost: '127.0.0.1', listenPort: 22391,
|
||
gatewayAccounts: [{ id: 'client', username: 'client', password: 'fixture-password' }],
|
||
} });
|
||
expect(gatewayResponse.ok()).toBeTruthy();
|
||
const gateway = (await gatewayResponse.json()).config;
|
||
gateways.push(gateway.id);
|
||
await page.reload();
|
||
await page.getByRole('navigation').getByRole('button', { name: /网关服务/ }).click();
|
||
await page.locator('tr').filter({ hasText: '文件测试网关' }).getByRole('button', { name: /配置|编辑/ }).first().click();
|
||
await drawer.getByRole('tab', { name: '账号', exact: true }).click();
|
||
await drawer.locator('.gateway-accounts').getByRole('tab', { name: '公钥', exact: true }).click();
|
||
await choose('客户端公钥资产', '文件测试公钥', '文件测试私钥');
|
||
await drawer.getByRole('button', { name: '保存配置' }).click();
|
||
await expect(drawer).not.toBeVisible();
|
||
const savedGateways = await (await page.request.get('/api/v1/proxies')).json();
|
||
expect(savedGateways.find((row: any) => row.config.id === gateway.id).config.gatewayAccounts[0].authorizedKeysAssetId).toBe(assets[1].config.id);
|
||
|
||
await page.getByRole('navigation').getByRole('button', { name: /目标服务/ }).click();
|
||
await page.getByRole('button', { name: '+ 新建目标' }).click();
|
||
await drawer.getByPlaceholder('例如:生产数据库 · 只读分析').fill('文件测试 TLS');
|
||
await drawer.getByPlaceholder('数据库或服务器域名 / IP').fill('127.0.0.1');
|
||
await drawer.getByRole('tab', { name: 'TLS/SSL', exact: true }).click();
|
||
await choose('目标 CA 证书资产', '文件测试 CA', '文件测试私钥');
|
||
await drawer.getByRole('tab', { name: '账号', exact: true }).click();
|
||
await drawer.getByRole('textbox', { name: '账号备注', exact: true }).fill('fixture');
|
||
await drawer.getByRole('textbox', { name: '登录用户名', exact: true }).fill('fixture');
|
||
await drawer.getByRole('textbox', { name: '账号密码', exact: true }).fill('fixture-password');
|
||
const tlsCreated = page.waitForResponse(response => response.url().endsWith('/api/v1/targets') && response.request().method() === 'POST');
|
||
await drawer.getByRole('button', { name: '保存配置' }).click();
|
||
const tlsResponse = await tlsCreated;
|
||
expect(tlsResponse.ok()).toBeTruthy();
|
||
const tlsTarget = (await tlsResponse.json()).config;
|
||
targets.push(tlsTarget.id);
|
||
expect(tlsTarget.targetCAAssetId).toBe(assets[2].config.id);
|
||
await expect(drawer).not.toBeVisible();
|
||
for (const asset of assets) {
|
||
expect((await page.request.delete(`/api/v1/assets/${asset.config.id}`, { headers })).status()).toBe(409);
|
||
expect((await page.request.get(`/api/v1/assets/${asset.config.id}`)).ok()).toBeTruthy();
|
||
}
|
||
} finally {
|
||
for (const id of gateways) await page.request.delete(`/api/v1/proxies/${id}`, { headers });
|
||
for (const id of targets) await page.request.delete(`/api/v1/targets/${id}`, { headers });
|
||
for (const asset of assets) await page.request.delete(`/api/v1/assets/${asset.config.id}`, { headers });
|
||
}
|
||
});
|