Skip to content

Commit 5595735

Browse files
fix(iptv): validate channel pagination (#132)
Co-authored-by: rissrice2105-agent <289161642+rissrice2105-agent@users.noreply.github.com>
1 parent f18a75a commit 5595735

3 files changed

Lines changed: 39 additions & 7 deletions

File tree

src/app/api/iptv/channels/route.test.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ const mockCacheSet = vi.fn();
1414
// Mock undici fetch
1515
const mockFetch = vi.fn();
1616
vi.mock('undici', () => ({
17-
Agent: vi.fn().mockImplementation(() => ({})),
17+
Agent: vi.fn(function MockAgent() {
18+
return {};
19+
}),
1820
fetch: mockFetch,
1921
}));
2022

@@ -233,6 +235,38 @@ describe('IPTV Channels API', () => {
233235
expect(data.limit).toBe(10);
234236
});
235237

238+
it.each([
239+
['negative', '-5', '-20'],
240+
['partially numeric', '10items', '20items'],
241+
])('uses pagination defaults for %s values', async (_label, limit, offset) => {
242+
const allChannels = Array.from({ length: 100 }, (_, i) => ({
243+
id: String(i),
244+
name: `Channel ${i}`,
245+
url: `https://example.com/ch${i}.m3u8`,
246+
group: 'General',
247+
}));
248+
mockCacheGet.mockResolvedValue({
249+
channels: allChannels,
250+
groups: ['General'],
251+
fetchedAt: Date.now(),
252+
m3uUrl: 'http://example.com/playlist.m3u',
253+
});
254+
vi.mocked(searchChannels).mockReturnValue(allChannels);
255+
256+
const request = new NextRequest(
257+
`http://localhost/api/iptv/channels?m3uUrl=http://example.com/playlist.m3u&limit=${limit}&offset=${offset}`
258+
);
259+
260+
const response = await GET(request);
261+
const data = await response.json();
262+
263+
expect(response.status).toBe(200);
264+
expect(data.channels).toHaveLength(50);
265+
expect(data.limit).toBe(50);
266+
expect(data.offset).toBe(0);
267+
expect(data.channels[0].id).toBe('0');
268+
});
269+
236270
it('returns 502 when upstream fetch fails', async () => {
237271
mockCacheGet.mockResolvedValue(null);
238272

src/app/api/iptv/channels/route.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
} from '@/lib/iptv';
2525
import { getIptvCacheReader } from '@/lib/iptv/cache-reader';
2626
import { createServerClient } from '@/lib/supabase';
27+
import { parseIntegerParam } from '@/lib/api/pagination';
2728

2829
/**
2930
* Undici agent that ignores SSL certificate errors.
@@ -90,11 +91,9 @@ export async function GET(request: NextRequest): Promise<Response> {
9091
const m3uUrl = searchParams.get('m3uUrl');
9192
const query = searchParams.get('q') ?? '';
9293
const group = searchParams.get('group') ?? undefined;
93-
const limit = Math.min(
94-
parseInt(searchParams.get('limit') ?? String(DEFAULT_LIMIT), 10) || DEFAULT_LIMIT,
95-
MAX_LIMIT
96-
);
97-
const offset = parseInt(searchParams.get('offset') ?? '0', 10) || 0;
94+
const parsedLimit = parseIntegerParam(searchParams.get('limit'), { min: 1 });
95+
const limit = Math.min(parsedLimit ?? DEFAULT_LIMIT, MAX_LIMIT);
96+
const offset = parseIntegerParam(searchParams.get('offset'), { min: 0 }) ?? 0;
9897

9998
// Validate that at least one identifier is provided
10099
if (!playlistId && !m3uUrl) {

vitest.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ export default defineConfig({
4949
// Tests failing after vitest 3→4 migration (pre-existing failures, tracked separately)
5050
'src/app/api/browse/route.test.ts',
5151
'src/app/api/ice/turn/route.test.ts',
52-
'src/app/api/iptv/channels/route.test.ts',
5352
'src/app/api/iptv/playlists/[id]/route.test.ts',
5453
'src/app/api/iptv/playlists/route.test.ts',
5554
'src/app/api/magnets/route.test.ts',

0 commit comments

Comments
 (0)