test: 补充诊断导出与帮助弹窗防护用例

同步更新 AuthHandler 断言与模块文档说明。
This commit is contained in:
Abner
2026-07-17 20:53:32 +08:00
parent 76cba05ef1
commit 2eda195a89
4 changed files with 108 additions and 1 deletions

View File

@@ -58,6 +58,15 @@ giffgaffAPI.clearCache();
#### `clipboard.js`
Shared clipboard helper. Re-exported by Giffgaff/Simyo `utils.js` so existing import paths stay stable.
#### `diagnostics.js`
Issue 诊断导出。业务页初始化后可在控制台执行:
```js
copyEsimDiagnostics() // 复制脱敏 JSON 到剪贴板,便于粘贴到 GitHub Issue
```
不包含 token / 密码 / cookie / LPA 等敏感字段。
#### `resource-hints.js`
> **状态:未挂载生产页面**。路径已对齐原生 ES 模块静态托管,但 HTML 入口尚未 `import` 本模块。
> 生产页面当前依赖浏览器默认加载与各页已写死的 script/link。

View File

@@ -0,0 +1,76 @@
'use strict';
/**
* 诊断信息导出测试
*/
import {
collectDiagnostics,
formatDiagnostics,
copyDiagnostics,
installDiagnosticsGlobal
} from '../../src/js/modules/diagnostics.js';
describe('diagnostics', () => {
beforeEach(() => {
jest.clearAllMocks();
delete window.copyEsimDiagnostics;
delete window.__esimDiagnostics;
});
it('collectDiagnostics 应脱敏敏感字段', () => {
const snap = collectDiagnostics({
app: 'simyo',
state: {
sessionToken: 'secret-token',
phoneNumber: '0612345678',
password: 'should-not-appear',
currentStep: 2
}
});
expect(snap.app).toBe('simyo');
expect(snap.state.sessionToken).toBe('[redacted]');
expect(snap.state.password).toBe('[redacted]');
expect(snap.state.phoneNumber).toBe('0612345678');
expect(snap.state.currentStep).toBe(2);
expect(snap.page.hostname).toBeDefined();
});
it('formatDiagnostics 应输出可粘贴 markdown 代码块', () => {
const text = formatDiagnostics({ app: 'giffgaff', state: { accessToken: 'tok' } });
expect(text).toContain('```json');
expect(text).toContain('[redacted]');
expect(text).not.toContain('"accessToken": "tok"');
});
it('copyDiagnostics 应调用剪贴板', async () => {
navigator.clipboard.writeText.mockResolvedValueOnce(undefined);
Object.defineProperty(window, 'isSecureContext', {
configurable: true,
value: true
});
const text = await copyDiagnostics({ app: 'home' });
expect(navigator.clipboard.writeText).toHaveBeenCalled();
expect(text).toContain('```json');
});
it('installDiagnosticsGlobal 应挂载 copyEsimDiagnostics', async () => {
navigator.clipboard.writeText.mockResolvedValueOnce(undefined);
Object.defineProperty(window, 'isSecureContext', {
configurable: true,
value: true
});
installDiagnosticsGlobal({
app: 'simyo',
getState: () => ({ sessionToken: 'abc', phoneNumber: '0611111111' })
});
expect(typeof window.copyEsimDiagnostics).toBe('function');
const text = await window.copyEsimDiagnostics();
expect(text).toContain('[redacted]');
expect(text).toContain('0611111111');
});
});

View File

@@ -39,7 +39,8 @@ describe('Simyo AuthHandler', () => {
expect(result.sessionToken).toBe('sess-abc');
expect(stateManager.get('sessionToken')).toBe('sess-abc');
expect(stateManager.get('phoneNumber')).toBe('0612345678');
expect(stateManager.get('password')).toBe('');
expect(stateManager.get('password')).toBeUndefined();
expect(Object.prototype.hasOwnProperty.call(stateManager.getState(), 'password')).toBe(false);
expect(authHandler.isLoggedIn()).toBe(true);
});

View File

@@ -3,6 +3,7 @@
*/
import { openHelp } from '../../src/simyo/js/modules/utils.js';
import * as i18n from '../../src/js/modules/i18n.js';
describe('Simyo openHelp()', () => {
beforeEach(() => {
@@ -31,5 +32,25 @@ describe('Simyo openHelp()', () => {
overlay.click();
expect(document.querySelector('[data-help-overlay="simyo-help"]')).toBeNull();
});
it('帮助文案应作为文本渲染,不执行 HTML', () => {
jest.spyOn(i18n, 't').mockImplementation((key) => {
if (key === 'simyo.help.title') return '<img src=x onerror=alert(1)>XSS';
if (key.endsWith('.heading')) return 'Heading';
if (key.endsWith('.content')) return '<script>alert(1)</script>';
if (key === 'simyo.help.close') return 'Close';
return key;
});
openHelp();
const overlay = document.querySelector('[data-help-overlay="simyo-help"]');
expect(overlay.querySelector('img[src="x"]')).toBeNull();
expect(overlay.querySelector('script')).toBeNull();
expect(overlay.textContent).toContain('<img src=x onerror=alert(1)>XSS');
expect(overlay.textContent).toContain('<script>alert(1)</script>');
i18n.t.mockRestore();
});
});