Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion src/clis/xianyu/item.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
import { describe, expect, it } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import { AuthRequiredError, EmptyResultError, SelectorError } from '../../errors.js';
import { getRegistry } from '../../registry.js';
import type { IPage } from '../../types.js';
import { __test__ } from './item.js';
import './item.js';

function createPageMock(evaluateResult: unknown): IPage {
return {
goto: vi.fn().mockResolvedValue(undefined),
evaluate: vi.fn().mockResolvedValue(evaluateResult),
snapshot: vi.fn().mockResolvedValue(undefined),
click: vi.fn().mockResolvedValue(undefined),
typeText: vi.fn().mockResolvedValue(undefined),
pressKey: vi.fn().mockResolvedValue(undefined),
scrollTo: vi.fn().mockResolvedValue(undefined),
getFormState: vi.fn().mockResolvedValue({ forms: [], orphanFields: [] }),
wait: vi.fn().mockResolvedValue(undefined),
tabs: vi.fn().mockResolvedValue([]),
selectTab: vi.fn().mockResolvedValue(undefined),
networkRequests: vi.fn().mockResolvedValue([]),
consoleMessages: vi.fn().mockResolvedValue([]),
scroll: vi.fn().mockResolvedValue(undefined),
autoScroll: vi.fn().mockResolvedValue(undefined),
installInterceptor: vi.fn().mockResolvedValue(undefined),
getInterceptedRequests: vi.fn().mockResolvedValue([]),
getCookies: vi.fn().mockResolvedValue([]),
screenshot: vi.fn().mockResolvedValue(''),
waitForCapture: vi.fn().mockResolvedValue(undefined),
} as IPage;
}

describe('xianyu item helpers', () => {
it('normalizes numeric item ids', () => {
Expand All @@ -17,3 +46,22 @@ describe('xianyu item helpers', () => {
expect(() => __test__.normalizeNumericId('abc', 'item_id', '1040754408976')).toThrow();
});
});

describe('xianyu item command', () => {
const command = getRegistry().get('xianyu/item');

it('throws AuthRequiredError on login wall before mtop is available', async () => {
const page = createPageMock({ error: 'auth-required' });
await expect(command!.func!(page, { item_id: '1040754408976' })).rejects.toBeInstanceOf(AuthRequiredError);
});

it('throws EmptyResultError on verification or risk-control pages', async () => {
const page = createPageMock({ error: 'blocked' });
await expect(command!.func!(page, { item_id: '1040754408976' })).rejects.toBeInstanceOf(EmptyResultError);
});

it('keeps SelectorError for true mtop initialization failures', async () => {
const page = createPageMock({ error: 'mtop-not-ready' });
await expect(command!.func!(page, { item_id: '1040754408976' })).rejects.toBeInstanceOf(SelectorError);
});
});
17 changes: 17 additions & 0 deletions src/clis/xianyu/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ function buildFetchItemEvaluate(itemId: string): string {
return false;
};

const bodyText = document.body?.innerText || '';
if (/请先登录|登录后/.test(bodyText)) {
return { error: 'auth-required' };
}

if (/验证码|安全验证|异常访问/.test(bodyText)) {
return { error: 'blocked' };
}

await waitFor(() => window.lib?.mtop?.request);
if (!window.lib || !window.lib.mtop || typeof window.lib.mtop.request !== 'function') {
return { error: 'mtop-not-ready' };
Expand Down Expand Up @@ -123,6 +132,14 @@ cli({
item_id?: string;
} & Record<string, unknown>;

if (result?.error === 'auth-required') {
throw new AuthRequiredError('www.goofish.com', 'Xianyu item detail requires a logged-in browser session');
}

if (result?.error === 'blocked') {
throw new EmptyResultError('xianyu item', 'Xianyu item detail is blocked by verification or risk control');
}

if (result?.error === 'mtop-not-ready') {
throw new SelectorError('window.lib.mtop', '闲鱼页面未完成初始化,无法调用商品详情接口');
}
Expand Down