diff --git a/src/clis/xiaoyuzhou/podcast-episodes.ts b/src/clis/xiaoyuzhou/podcast-episodes.ts index 64b4972..3ec1898 100644 --- a/src/clis/xiaoyuzhou/podcast-episodes.ts +++ b/src/clis/xiaoyuzhou/podcast-episodes.ts @@ -19,7 +19,11 @@ cli({ const podcast = pageProps.podcast; if (!podcast) throw new CliError('NOT_FOUND', 'Podcast not found', 'Please check the ID'); const allEpisodes = podcast.episodes ?? []; - const limit = Math.max(1, Math.min(Number(args.limit) ?? 15, allEpisodes.length)); + const requestedLimit = Number(args.limit); + if (!Number.isInteger(requestedLimit) || requestedLimit < 1) { + throw new CliError('INVALID_ARGUMENT', 'limit must be a positive integer', 'Example: --limit 5'); + } + const limit = Math.min(requestedLimit, allEpisodes.length); const episodes = allEpisodes.slice(0, limit); return episodes.map((ep: any) => ({ eid: ep.eid, diff --git a/tests/e2e/public-commands.test.ts b/tests/e2e/public-commands.test.ts index 7909319..5148687 100644 --- a/tests/e2e/public-commands.test.ts +++ b/tests/e2e/public-commands.test.ts @@ -6,6 +6,11 @@ import { describe, it, expect } from 'vitest'; import { runCli, parseJsonOutput } from './helpers.js'; +function isExpectedXiaoyuzhouRestriction(code: number, stderr: string): boolean { + if (code === 0) return false; + return /Error \[FETCH_ERROR\]: HTTP (403|429|451|503)\b/.test(stderr); +} + describe('public commands E2E', () => { // ── hackernews ── it('hackernews top returns structured data', async () => { @@ -56,8 +61,12 @@ describe('public commands E2E', () => { // ── xiaoyuzhou (Chinese site — may return empty on overseas CI runners) ── it('xiaoyuzhou podcast returns podcast profile', async () => { - const { stdout, code } = await runCli(['xiaoyuzhou', 'podcast', '6013f9f58e2f7ee375cf4216', '-f', 'json']); - if (code !== 0) { console.warn('xiaoyuzhou podcast skipped (geo-blocked?)'); return; } + const { stdout, stderr, code } = await runCli(['xiaoyuzhou', 'podcast', '6013f9f58e2f7ee375cf4216', '-f', 'json']); + if (isExpectedXiaoyuzhouRestriction(code, stderr)) { + console.warn(`xiaoyuzhou podcast skipped: ${stderr.trim()}`); + return; + } + expect(code).toBe(0); const data = parseJsonOutput(stdout); expect(Array.isArray(data)).toBe(true); expect(data.length).toBe(1); @@ -67,8 +76,12 @@ describe('public commands E2E', () => { }, 30_000); it('xiaoyuzhou podcast-episodes returns episode list', async () => { - const { stdout, code } = await runCli(['xiaoyuzhou', 'podcast-episodes', '6013f9f58e2f7ee375cf4216', '-f', 'json']); - if (code !== 0) { console.warn('xiaoyuzhou podcast-episodes skipped (geo-blocked?)'); return; } + const { stdout, stderr, code } = await runCli(['xiaoyuzhou', 'podcast-episodes', '6013f9f58e2f7ee375cf4216', '-f', 'json']); + if (isExpectedXiaoyuzhouRestriction(code, stderr)) { + console.warn(`xiaoyuzhou podcast-episodes skipped: ${stderr.trim()}`); + return; + } + expect(code).toBe(0); const data = parseJsonOutput(stdout); expect(Array.isArray(data)).toBe(true); expect(data.length).toBeGreaterThanOrEqual(1); @@ -78,8 +91,12 @@ describe('public commands E2E', () => { }, 30_000); it('xiaoyuzhou episode returns episode detail', async () => { - const { stdout, code } = await runCli(['xiaoyuzhou', 'episode', '69b3b675772ac2295bfc01d0', '-f', 'json']); - if (code !== 0) { console.warn('xiaoyuzhou episode skipped (geo-blocked?)'); return; } + const { stdout, stderr, code } = await runCli(['xiaoyuzhou', 'episode', '69b3b675772ac2295bfc01d0', '-f', 'json']); + if (isExpectedXiaoyuzhouRestriction(code, stderr)) { + console.warn(`xiaoyuzhou episode skipped: ${stderr.trim()}`); + return; + } + expect(code).toBe(0); const data = parseJsonOutput(stdout); expect(Array.isArray(data)).toBe(true); expect(data.length).toBe(1); @@ -88,4 +105,14 @@ describe('public commands E2E', () => { expect(data[0]).toHaveProperty('plays'); expect(data[0]).toHaveProperty('comments'); }, 30_000); + + it('xiaoyuzhou podcast-episodes rejects invalid limit', async () => { + const { stderr, code } = await runCli(['xiaoyuzhou', 'podcast-episodes', '6013f9f58e2f7ee375cf4216', '--limit', 'abc', '-f', 'json']); + if (isExpectedXiaoyuzhouRestriction(code, stderr)) { + console.warn(`xiaoyuzhou invalid-limit skipped: ${stderr.trim()}`); + return; + } + expect(code).not.toBe(0); + expect(stderr).toMatch(/limit must be a positive integer|Argument "limit" must be a valid number/); + }, 30_000); });