Files
LinkPort/web/tests/file-assets.spec.ts

117 lines
8.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 });
}
});