153 lines
9.7 KiB
TypeScript
153 lines
9.7 KiB
TypeScript
import { test, expect, type Locator } from '@playwright/test';
|
||
import { createCipheriv } from 'node:crypto';
|
||
|
||
async function expectChoice(panel: Locator, name: string, value: string) {
|
||
await expect(selectControl(panel, name)).toContainText(value);
|
||
}
|
||
function selectControl(panel: Locator, name: string) {
|
||
return panel.locator('.el-select').filter({ has: panel.page().getByRole('combobox', { name, exact: true }) });
|
||
}
|
||
|
||
test('Navicat 两种密码格式、输入校验、清空与离开页面', async ({ page }) => {
|
||
// Verify the clipboard payload without depending on desktop clipboard access.
|
||
await page.addInitScript(() => {
|
||
Object.defineProperty(navigator, 'clipboard', { value: {
|
||
writeText: async (text: string) => { (window as Window & { copiedPassword?: string }).copiedPassword = text; },
|
||
} });
|
||
});
|
||
await page.goto('/');
|
||
const navigation = page.getByRole('navigation');
|
||
await navigation.getByRole('button', { name: '密码解密', exact: true }).click();
|
||
const panel = page.getByRole('region', { name: '密码解密工具', exact: true });
|
||
await expectChoice(panel, '解密算法', 'AES');
|
||
await expectChoice(panel, '分组模式', 'CBC');
|
||
await expectChoice(panel, '填充方式', 'PKCS7');
|
||
await expect(panel.getByRole('textbox', { name: 'Key 密钥', exact: true })).toHaveValue('libcckeylibcckey');
|
||
await expect(panel.getByRole('textbox', { name: 'IV 向量', exact: true })).toHaveValue('libcciv libcciv ');
|
||
await expect(panel.getByTestId('iv-length')).toContainText('16 字节');
|
||
const ciphertext = panel.getByRole('textbox', { name: '十六进制密文', exact: true });
|
||
const result = panel.getByRole('textbox', { name: '解密结果', exact: true });
|
||
await ciphertext.fill(' \nf03a1418eb2928a948cc8c71d92edf1c\n');
|
||
await panel.getByRole('button', { name: '解密', exact: true }).click();
|
||
await expect(result).toHaveValue('密码🔑 test');
|
||
await panel.getByRole('button', { name: '复制密码', exact: true }).click();
|
||
await expect(page.getByText('解密密码已复制', { exact: true })).toBeVisible();
|
||
expect(await page.evaluate(() => (window as Window & { copiedPassword?: string }).copiedPassword)).toBe('密码🔑 test');
|
||
await page.screenshot({ path: 'test-results/navicat-desktop.png', fullPage: true });
|
||
|
||
await panel.getByText('Navicat 11(Blowfish)', { exact: true }).click();
|
||
await expect(result).toHaveCount(0);
|
||
await ciphertext.fill('9AA92D8A5BDCB3F746');
|
||
await panel.getByRole('button', { name: '解密', exact: true }).click();
|
||
await expect(result).toHaveValue('123456789');
|
||
await ciphertext.fill('xyz');
|
||
await expect(result).toHaveCount(0);
|
||
await panel.getByRole('button', { name: '解密', exact: true }).click();
|
||
await expect(panel.getByRole('alert')).toContainText('十六进制字符');
|
||
await ciphertext.fill('a');
|
||
await panel.getByRole('button', { name: '解密', exact: true }).click();
|
||
await expect(panel.getByRole('alert')).toContainText('偶数');
|
||
await panel.getByRole('button', { name: '清空', exact: true }).click();
|
||
await expect(ciphertext).toHaveValue('');
|
||
await expect(panel.getByRole('alert')).toHaveCount(0);
|
||
await panel.getByRole('button', { name: '解密', exact: true }).click();
|
||
await expect(panel.getByText('解密成功:密码为空。')).toBeVisible();
|
||
|
||
await panel.getByText('Navicat 12(AES)', { exact: true }).click();
|
||
await ciphertext.fill('0'.repeat(32));
|
||
await panel.getByRole('button', { name: '解密', exact: true }).click();
|
||
await expect(panel.getByRole('alert')).toContainText('解密失败');
|
||
await navigation.getByRole('button', { name: '网关服务', exact: false }).click();
|
||
await navigation.getByRole('button', { name: '密码解密', exact: true }).click();
|
||
await expect(ciphertext).toHaveValue('');
|
||
await expect(result).toHaveCount(0);
|
||
for (const viewport of [{ width: 320, height: 568 }, { width: 390, height: 844 }, { width: 768, height: 640 }, { width: 1920, height: 1080 }]) {
|
||
await page.setViewportSize(viewport);
|
||
await expect(panel).toBeVisible();
|
||
const choices = panel.getByRole('radiogroup');
|
||
expect(await choices.evaluate(el => el.scrollWidth <= el.clientWidth)).toBe(true);
|
||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
|
||
await navigation.getByRole('button', { name: '密码解密', exact: true }).click();
|
||
if (viewport.width === 390) await page.screenshot({ path: 'test-results/navicat-mobile.png', fullPage: true });
|
||
}
|
||
});
|
||
|
||
test('修改或清空输入后忽略迟到的解密结果', async ({ page }) => {
|
||
let release!: () => void;
|
||
const gate = new Promise<void>(resolve => { release = resolve; });
|
||
await page.route('**/api/v1/tools/decrypt', async route => {
|
||
await gate;
|
||
await route.fulfill({ json: { plaintext: 'stale-result' } });
|
||
});
|
||
await page.goto('/');
|
||
await page.getByRole('navigation').getByRole('button', { name: '密码解密', exact: true }).click();
|
||
const panel = page.getByRole('region', { name: '密码解密工具' });
|
||
await panel.getByRole('textbox', { name: '十六进制密文' }).fill('e191af42327478cc5f143ef279ec4d81');
|
||
const requested = page.waitForRequest('**/api/v1/tools/decrypt');
|
||
await panel.getByRole('button', { name: '解密', exact: true }).click();
|
||
await requested;
|
||
await panel.getByRole('button', { name: '清空', exact: true }).click();
|
||
const completed = page.waitForResponse('**/api/v1/tools/decrypt');
|
||
release();
|
||
await completed;
|
||
await expect(panel.getByRole('textbox', { name: '解密结果' })).toHaveCount(0);
|
||
});
|
||
|
||
test('自定义参数真实参与解密,切换 Navicat 12 恢复预设', async ({ page }) => {
|
||
const key = 'custom-key-12345';
|
||
const iv = 'custom-iv-123456';
|
||
const cipher = createCipheriv('aes-128-cbc', Buffer.from(key), Buffer.from(iv));
|
||
const encrypted = Buffer.concat([cipher.update('自定义参数 test'), cipher.final()]).toString('base64');
|
||
await page.goto('/');
|
||
const navigation = page.getByRole('navigation');
|
||
await navigation.getByRole('button', { name: '密码解密', exact: true }).click();
|
||
const panel = page.getByRole('region', { name: '密码解密工具' });
|
||
const select = async (name: string, option: string) => {
|
||
await selectControl(panel, name).click();
|
||
const listbox = await panel.getByRole('combobox', { name, exact: true }).getAttribute('aria-controls');
|
||
await page.locator(`[id="${listbox}"]`).getByRole('option', { name: option, exact: true }).click();
|
||
};
|
||
await panel.getByRole('textbox', { name: 'Key 密钥', exact: true }).fill(key);
|
||
await expect(panel.getByRole('radio', { name: '自定义', exact: true })).toBeChecked();
|
||
await panel.getByRole('textbox', { name: 'IV 向量', exact: true }).fill(iv);
|
||
await select('密文编码', 'Base64');
|
||
await panel.getByRole('textbox', { name: 'Base64 密文', exact: true }).fill(encrypted);
|
||
await panel.getByRole('button', { name: '解密', exact: true }).click();
|
||
await expect(panel.getByRole('textbox', { name: '解密结果', exact: true })).toHaveValue('自定义参数 test');
|
||
await panel.getByRole('textbox', { name: 'IV 向量', exact: true }).fill('short');
|
||
await expect(panel.getByRole('textbox', { name: '解密结果', exact: true })).toHaveCount(0);
|
||
await panel.getByRole('button', { name: '解密', exact: true }).click();
|
||
await expect(panel.getByRole('alert')).toContainText('IV 必须为 16 字节');
|
||
|
||
await select('分组模式', 'CTR');
|
||
await expectChoice(panel, '填充方式', 'NoPadding(无填充)');
|
||
await select('Key 编码', 'Hex(十六进制)');
|
||
await select('IV 编码', 'Hex(十六进制)');
|
||
await select('密文编码', 'Hex(十六进制)');
|
||
await select('结果编码', 'Hex(十六进制)');
|
||
await panel.getByRole('textbox', { name: 'Key 密钥', exact: true }).fill('2b7e151628aed2a6abf7158809cf4f3c');
|
||
await panel.getByRole('textbox', { name: 'IV 向量', exact: true }).fill('f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff');
|
||
await panel.getByRole('textbox', { name: '十六进制密文', exact: true }).fill('874d6191b620e3261bef6864990db6ce');
|
||
await panel.getByRole('button', { name: '解密', exact: true }).click();
|
||
await expect(panel.getByRole('textbox', { name: '解密结果', exact: true })).toHaveValue('6bc1bee22e409f96e93d7e117393172a');
|
||
await select('分组模式', 'ECB');
|
||
await expect(panel.getByRole('textbox', { name: 'IV 向量', exact: true })).toHaveCount(0);
|
||
await panel.getByRole('textbox', { name: '十六进制密文', exact: true }).fill('3ad77bb40d7a3660a89ecaf32466ef97');
|
||
await panel.getByRole('button', { name: '解密', exact: true }).click();
|
||
await expect(panel.getByRole('textbox', { name: '解密结果', exact: true })).toHaveValue('6bc1bee22e409f96e93d7e117393172a');
|
||
|
||
await panel.getByText('Navicat 12(AES)', { exact: true }).click();
|
||
await expectChoice(panel, '分组模式', 'CBC');
|
||
await expectChoice(panel, '填充方式', 'PKCS7');
|
||
await expect(panel.getByRole('textbox', { name: 'Key 密钥', exact: true })).toHaveValue('libcckeylibcckey');
|
||
await expect(panel.getByRole('textbox', { name: 'IV 向量', exact: true })).toHaveValue('libcciv libcciv ');
|
||
await panel.getByRole('textbox', { name: '十六进制密文', exact: true }).fill('f03a1418eb2928a948cc8c71d92edf1c');
|
||
await panel.getByRole('button', { name: '解密', exact: true }).click();
|
||
await expect(panel.getByRole('textbox', { name: '解密结果', exact: true })).toHaveValue('密码🔑 test');
|
||
expect(await page.evaluate(() => JSON.stringify(localStorage))).not.toContain(key);
|
||
await panel.getByRole('textbox', { name: 'Key 密钥', exact: true }).fill(key);
|
||
await navigation.getByRole('button', { name: /网关服务/ }).click();
|
||
await navigation.getByRole('button', { name: '密码解密', exact: true }).click();
|
||
await expect(panel.getByRole('textbox', { name: 'Key 密钥', exact: true })).toHaveValue('libcckeylibcckey');
|
||
});
|