9b395f5cc3
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
125 lines
6.4 KiB
JavaScript
125 lines
6.4 KiB
JavaScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { ArgumentError, AuthRequiredError, CommandExecutionError } from '@jackwener/opencli/errors';
|
|
import { __test__ } from './ask.js';
|
|
import { askCommand } from './ask.js';
|
|
describe('yuanbao ask helpers', () => {
|
|
describe('isOnYuanbao', () => {
|
|
const fakePage = (url) => ({ evaluate: () => url instanceof Error ? Promise.reject(url) : Promise.resolve(url) });
|
|
it('returns true for yuanbao.tencent.com URLs', async () => {
|
|
expect(await __test__.isOnYuanbao(fakePage('https://yuanbao.tencent.com/'))).toBe(true);
|
|
expect(await __test__.isOnYuanbao(fakePage('https://yuanbao.tencent.com/chat/abc'))).toBe(true);
|
|
});
|
|
it('returns false for non-yuanbao domains', async () => {
|
|
expect(await __test__.isOnYuanbao(fakePage('https://example.com/?next=yuanbao.tencent.com'))).toBe(false);
|
|
expect(await __test__.isOnYuanbao(fakePage('about:blank'))).toBe(false);
|
|
});
|
|
it('returns false when evaluate throws', async () => {
|
|
expect(await __test__.isOnYuanbao(fakePage(new Error('detached')))).toBe(false);
|
|
});
|
|
});
|
|
it('removes echoed prompt prefixes from transcript additions', () => {
|
|
expect(__test__.sanitizeYuanbaoResponseText('你好\n你好,我是元宝。', '你好')).toBe('你好,我是元宝。');
|
|
});
|
|
it('filters transient in-progress assistant placeholders', () => {
|
|
expect(__test__.sanitizeYuanbaoResponseText('正在搜索资料', '张雪机车相关的股票有哪些?')).toBe('');
|
|
});
|
|
it('normalizes boolean flags with explicit defaults', () => {
|
|
expect(__test__.normalizeBooleanFlag(undefined, true)).toBe(true);
|
|
expect(__test__.normalizeBooleanFlag(undefined, false)).toBe(false);
|
|
expect(__test__.normalizeBooleanFlag('true', false)).toBe(true);
|
|
expect(__test__.normalizeBooleanFlag('1', false)).toBe(true);
|
|
expect(__test__.normalizeBooleanFlag('yes', false)).toBe(true);
|
|
expect(__test__.normalizeBooleanFlag('false', true)).toBe(false);
|
|
});
|
|
it('ignores baseline lines and echoed prompts when collecting additions', () => {
|
|
const response = __test__.collectYuanbaoTranscriptAdditions(['旧消息'], ['旧消息', '你好', '你好\n你好,我是元宝。'], '你好');
|
|
expect(response).toBe('你好,我是元宝。');
|
|
});
|
|
it('prefers fresh assistant messages over echoed prompts and older messages', () => {
|
|
const response = __test__.pickLatestYuanbaoAssistantCandidate(['旧回复', '你好', '你好!我是元宝,由腾讯推出的AI助手。'], 1, '你好');
|
|
expect(response).toBe('你好!我是元宝,由腾讯推出的AI助手。');
|
|
});
|
|
it('converts assistant html tables to markdown tables via turndown', () => {
|
|
const markdown = __test__.convertYuanbaoHtmlToMarkdown(`
|
|
<h3>核心产业链概念股一览</h3>
|
|
<table>
|
|
<thead>
|
|
<tr><th>细分赛道</th><th>核心标的</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td>光模块</td><td>中际旭创</td></tr>
|
|
</tbody>
|
|
</table>
|
|
`);
|
|
expect(markdown).toContain('### 核心产业链概念股一览');
|
|
expect(markdown).toContain('| 细分赛道 | 核心标的 |');
|
|
expect(markdown).toContain('| --- | --- |');
|
|
expect(markdown).toContain('| 光模块 | 中际旭创 |');
|
|
});
|
|
it('tracks stabilization by incrementing repeats and resetting on changes', () => {
|
|
expect(__test__.updateStableState('', 0, '第一段')).toEqual({
|
|
previousText: '第一段',
|
|
stableCount: 0,
|
|
});
|
|
expect(__test__.updateStableState('第一段', 0, '第一段')).toEqual({
|
|
previousText: '第一段',
|
|
stableCount: 1,
|
|
});
|
|
expect(__test__.updateStableState('第一段', 1, '第二段')).toEqual({
|
|
previousText: '第二段',
|
|
stableCount: 0,
|
|
});
|
|
});
|
|
});
|
|
function createAskPageMock(overrides = {}) {
|
|
const currentUrl = overrides.currentUrl ?? 'https://yuanbao.tencent.com/';
|
|
const hasLoginGate = overrides.hasLoginGate ?? false;
|
|
const sendResult = overrides.sendResult;
|
|
return {
|
|
goto: vi.fn().mockResolvedValue(undefined),
|
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
evaluate: vi.fn().mockImplementation(async (script) => {
|
|
if (script === 'window.location.href')
|
|
return currentUrl;
|
|
if (script.includes('微信扫码登录'))
|
|
return hasLoginGate;
|
|
if (script.includes('[dt-button-id="internet_search"]'))
|
|
return { found: false, enabled: false };
|
|
if (script.includes('[dt-button-id="deep_think"]'))
|
|
return { found: false, enabled: false };
|
|
if (script.includes('.agent-chat__list__item--ai'))
|
|
return [];
|
|
if (script.includes('const stopLines = new Set(['))
|
|
return [];
|
|
if (script.includes('Failed to insert the prompt into the Yuanbao composer.')) {
|
|
return sendResult ?? { ok: true, action: 'click' };
|
|
}
|
|
throw new Error(`Unexpected evaluate script in test: ${script.slice(0, 80)}`);
|
|
}),
|
|
};
|
|
}
|
|
describe('yuanbao ask command', () => {
|
|
it('throws AuthRequiredError when Yuanbao shows a login gate before sending', async () => {
|
|
const page = createAskPageMock({ hasLoginGate: true });
|
|
await expect(askCommand.func(page, { prompt: '你好', timeout: 60, search: true, think: false }))
|
|
.rejects.toBeInstanceOf(AuthRequiredError);
|
|
});
|
|
it('throws CommandExecutionError when the prompt cannot be sent', async () => {
|
|
const page = createAskPageMock({
|
|
sendResult: {
|
|
ok: false,
|
|
reason: 'Yuanbao composer was not found.',
|
|
},
|
|
});
|
|
await expect(askCommand.func(page, { prompt: '你好', timeout: 60, search: true, think: false }))
|
|
.rejects.toBeInstanceOf(CommandExecutionError);
|
|
});
|
|
it('throws TimeoutError when no response arrives before timeout', async () => {
|
|
const page = createAskPageMock({
|
|
sendResult: { ok: true, action: 'click' },
|
|
});
|
|
await expect(askCommand.func(page, { prompt: '你好', timeout: -1, search: true, think: false }))
|
|
.rejects.toBeInstanceOf(ArgumentError);
|
|
});
|
|
});
|