Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 85f81c87f2 | |||
| 6b2ccbd5b8 |
@@ -38,14 +38,14 @@ describe('apple-podcasts search command', () => {
|
||||
'https://itunes.apple.com/search?term=machine%20learning&media=podcast&limit=5',
|
||||
);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
expect.objectContaining({
|
||||
id: 42,
|
||||
title: 'Machine Learning Guide',
|
||||
author: 'OpenCLI',
|
||||
episodes: 12,
|
||||
genre: 'Technology',
|
||||
url: '',
|
||||
},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -55,6 +55,30 @@ describe('apple-podcasts top command', () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('adds a timeout signal to chart fetches', async () => {
|
||||
const cmd = getRegistry().get('apple-podcasts/top');
|
||||
expect(cmd?.func).toBeTypeOf('function');
|
||||
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({
|
||||
feed: {
|
||||
results: [
|
||||
{ id: '100', name: 'Top Show', artistName: 'Host A' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
});
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
await cmd!.func!(null as any, { country: 'US', limit: 1 });
|
||||
|
||||
const [, options] = fetchMock.mock.calls[0] ?? [];
|
||||
expect(options).toBeDefined();
|
||||
expect(options.signal).toBeDefined();
|
||||
expect(options.signal).toHaveProperty('aborted', false);
|
||||
});
|
||||
|
||||
it('uses the canonical Apple charts host and maps ranked results', async () => {
|
||||
const cmd = getRegistry().get('apple-podcasts/top');
|
||||
expect(cmd?.func).toBeTypeOf('function');
|
||||
@@ -76,6 +100,9 @@ describe('apple-podcasts top command', () => {
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'https://rss.marketingtools.apple.com/api/v2/us/podcasts/top/2/podcasts.json',
|
||||
expect.objectContaining({
|
||||
signal: expect.any(Object),
|
||||
}),
|
||||
);
|
||||
expect(result).toEqual([
|
||||
{ rank: 1, title: 'Top Show', author: 'Host A', id: '100' },
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CliError } from '../../errors.js';
|
||||
|
||||
// Apple Marketing Tools RSS API — public, no key required
|
||||
const CHARTS_URL = 'https://rss.marketingtools.apple.com/api/v2';
|
||||
const CHARTS_TIMEOUT_MS = 15_000;
|
||||
|
||||
cli({
|
||||
site: 'apple-podcasts',
|
||||
@@ -21,7 +22,9 @@ cli({
|
||||
const url = `${CHARTS_URL}/${country}/podcasts/top/${limit}/podcasts.json`;
|
||||
let resp: Response;
|
||||
try {
|
||||
resp = await fetch(url);
|
||||
resp = await fetch(url, {
|
||||
signal: AbortSignal.timeout(CHARTS_TIMEOUT_MS),
|
||||
});
|
||||
} catch (error: any) {
|
||||
const reason = error?.cause?.code ?? error?.message ?? 'unknown network error';
|
||||
throw new CliError(
|
||||
|
||||
+3
-17
@@ -3,7 +3,7 @@ name: hot
|
||||
description: V2EX 热门话题
|
||||
domain: www.v2ex.com
|
||||
strategy: public
|
||||
browser: true
|
||||
browser: false
|
||||
|
||||
args:
|
||||
limit:
|
||||
@@ -12,22 +12,8 @@ args:
|
||||
description: Number of topics
|
||||
|
||||
pipeline:
|
||||
- navigate: https://www.v2ex.com/
|
||||
|
||||
- evaluate: |
|
||||
(async () => {
|
||||
const response = await fetch('/api/topics/hot.json', {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
accept: 'application/json, text/plain, */*',
|
||||
'x-requested-with': 'XMLHttpRequest',
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`V2EX hot API request failed: ${response.status}`);
|
||||
}
|
||||
return await response.json();
|
||||
})()
|
||||
- fetch:
|
||||
url: https://www.v2ex.com/api/topics/hot.json
|
||||
|
||||
- map:
|
||||
rank: ${{ index + 1 }}
|
||||
|
||||
@@ -60,7 +60,10 @@ describe('plugin management E2E', () => {
|
||||
const lock = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf-8'));
|
||||
expect(lock[PLUGIN_NAME]).toBeDefined();
|
||||
expect(lock[PLUGIN_NAME].commitHash).toBeTruthy();
|
||||
expect(lock[PLUGIN_NAME].source).toContain('opencli-plugin-hot-digest');
|
||||
expect(lock[PLUGIN_NAME].source).toMatchObject({
|
||||
kind: 'git',
|
||||
});
|
||||
expect(lock[PLUGIN_NAME].source.url).toContain('opencli-plugin-hot-digest');
|
||||
expect(lock[PLUGIN_NAME].installedAt).toBeTruthy();
|
||||
}, 60_000);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ function isExpectedChineseSiteRestriction(code: number, stderr: string): boolean
|
||||
|
||||
function isExpectedApplePodcastsRestriction(code: number, stderr: string): boolean {
|
||||
if (code === 0) return false;
|
||||
return /Error \[FETCH_ERROR\]: (Charts API HTTP \d+|Unable to reach Apple Podcasts charts)/.test(stderr)
|
||||
return /(?:Error \[FETCH_ERROR\]: )?(Charts API HTTP \d+|Unable to reach Apple Podcasts charts)/.test(stderr)
|
||||
|| stderr === ''; // timeout killed the process before any output
|
||||
}
|
||||
|
||||
@@ -34,6 +34,17 @@ function isExpectedGoogleRestriction(code: number, stderr: string): boolean {
|
||||
// Keep old name as alias for existing tests
|
||||
const isExpectedXiaoyuzhouRestriction = isExpectedChineseSiteRestriction;
|
||||
|
||||
describe('public command restriction detectors', () => {
|
||||
it('treats current Apple Podcasts CliError rendering as an expected restriction', () => {
|
||||
expect(
|
||||
isExpectedApplePodcastsRestriction(
|
||||
1,
|
||||
'⚠️ Unable to reach Apple Podcasts charts for US\n→ Apple charts may be temporarily unavailable (ECONNRESET). Try again later.\n',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('public commands E2E', () => {
|
||||
// ── bloomberg (RSS-backed, browser: false) ──
|
||||
it('bloomberg main returns structured headline data', async () => {
|
||||
|
||||
Reference in New Issue
Block a user