import { describe, it, expect, vi, beforeEach } from 'vitest'; import { XtreamClient } from '../parsers/xtream'; // Mock fetch globally global.fetch = vi.fn(); describe('XtreamClient', () => { let client: XtreamClient; const mockServerUrl = 'http://example.com'; const mockUsername = 'testuser'; const mockPassword = 'testpass'; beforeEach(() => { client = new XtreamClient(mockServerUrl, mockUsername, mockPassword); vi.clearAllMocks(); }); describe('constructor', () => { it('should remove trailing slash from server URL', () => { const clientWithSlash = new XtreamClient( 'http://example.com/', 'user', 'pass' ); expect(clientWithSlash['baseUrl']).toBe('http://example.com'); }); }); describe('authenticate', () => { it('should authenticate successfully with valid credentials', async () => { const mockAuthResponse = { user_info: { username: 'testuser', password: 'testpass', status: 'Active', exp_date: '1735689600', is_trial: '0', active_cons: '1', max_connections: '2', }, server_info: { url: 'example.com', port: '80', https_port: '443', server_protocol: 'http', rtmp_port: '1935', timezone: 'UTC', }, }; (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockAuthResponse, }); const result = await client.authenticate(); expect(result).toEqual(mockAuthResponse); expect(global.fetch).toHaveBeenCalledWith( `${mockServerUrl}/player_api.php?username=${mockUsername}&password=${mockPassword}` ); }); it('should throw error when authentication fails', async () => { (global.fetch as any).mockResolvedValueOnce({ ok: false, statusText: 'Unauthorized', }); await expect(client.authenticate()).rejects.toThrow( 'Authentication failed: Unauthorized' ); }); it('should throw error when user status is not Active', async () => { const mockAuthResponse = { user_info: { username: 'testuser', password: 'testpass', status: 'Expired', exp_date: '1609459200', is_trial: '0', active_cons: '0', max_connections: '1', }, server_info: { url: 'example.com', port: '80', https_port: '443', server_protocol: 'http', rtmp_port: '1935', timezone: 'UTC', }, }; (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockAuthResponse, }); await expect(client.authenticate()).rejects.toThrow( 'Xtream account is not active. Status: Expired' ); }); it('should throw error on network failure', async () => { (global.fetch as any).mockRejectedValueOnce(new Error('Network error')); await expect(client.authenticate()).rejects.toThrow( 'Xtream authentication failed' ); }); }); describe('getCategories', () => { it('should fetch categories successfully', async () => { const mockCategories = [ { category_id: '1', category_name: 'Movies', parent_id: 0, }, { category_id: '2', category_name: 'Sports', parent_id: 0, }, ]; (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockCategories, }); const result = await client.getCategories(); expect(result).toEqual(mockCategories); expect(global.fetch).toHaveBeenCalledWith( `${mockServerUrl}/player_api.php?username=${mockUsername}&password=${mockPassword}&action=get_live_categories` ); }); it('should throw error when fetching categories fails', async () => { (global.fetch as any).mockResolvedValueOnce({ ok: false, statusText: 'Internal Server Error', }); await expect(client.getCategories()).rejects.toThrow( 'Failed to fetch categories: Internal Server Error' ); }); }); describe('getLiveStreams', () => { it('should fetch all live streams without category filter', async () => { const mockStreams = [ { num: 1, name: 'HBO', stream_type: 'live', stream_id: 100, stream_icon: 'http://example.com/hbo.png', epg_channel_id: 'hbo', category_id: '1', }, { num: 2, name: 'ESPN', stream_type: 'live', stream_id: 101, category_id: '2', }, ]; (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockStreams, }); const result = await client.getLiveStreams(); expect(result).toEqual(mockStreams); expect(global.fetch).toHaveBeenCalledWith( `${mockServerUrl}/player_api.php?username=${mockUsername}&password=${mockPassword}&action=get_live_streams` ); }); it('should fetch live streams with category filter', async () => { const mockStreams = [ { num: 1, name: 'HBO', stream_type: 'live', stream_id: 100, category_id: '1', }, ]; (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockStreams, }); const result = await client.getLiveStreams('1'); expect(result).toEqual(mockStreams); expect(global.fetch).toHaveBeenCalledWith( `${mockServerUrl}/player_api.php?username=${mockUsername}&password=${mockPassword}&action=get_live_streams&category_id=1` ); }); it('should throw error when fetching streams fails', async () => { (global.fetch as any).mockResolvedValueOnce({ ok: false, statusText: 'Not Found', }); await expect(client.getLiveStreams()).rejects.toThrow( 'Failed to fetch streams: Not Found' ); }); }); describe('getChannels', () => { it('should convert Xtream streams to Channel format', async () => { const mockCategories = [ { category_id: '1', category_name: 'Movies', parent_id: 0 }, { category_id: '2', category_name: 'Sports', parent_id: 0 }, ]; const mockStreams = [ { num: 1, name: 'HBO', stream_type: 'live', stream_id: 100, stream_icon: 'http://example.com/hbo.png', epg_channel_id: 'hbo', category_id: '1', }, { num: 2, name: 'ESPN', stream_type: 'live', stream_id: 101, category_id: '2', }, ]; (global.fetch as any) .mockResolvedValueOnce({ ok: true, json: async () => mockCategories, }) .mockResolvedValueOnce({ ok: true, json: async () => mockStreams, }); const result = await client.getChannels('test-provider'); expect(result).toHaveLength(2); // Check first channel (HBO) expect(result[0].id).toBe('test-provider-100'); expect(result[0].name).toBe('HBO'); expect(result[0].streamUrl).toBe( `${mockServerUrl}/live/${mockUsername}/${mockPassword}/100.m3u8` ); expect(result[0].logoUrl).toBe('http://example.com/hbo.png'); expect(result[0].category).toBe('Movies'); expect(result[0].tvgId).toBe('hbo'); expect(result[0].providerId).toBe('test-provider'); // Check second channel (ESPN) expect(result[1].id).toBe('test-provider-101'); expect(result[1].name).toBe('ESPN'); expect(result[1].category).toBe('Sports'); expect(result[1].providerId).toBe('test-provider'); }); it('should throw error when getChannels fails', async () => { (global.fetch as any).mockRejectedValueOnce(new Error('Network error')); await expect(client.getChannels('test-provider')).rejects.toThrow( 'Failed to get Xtream channels' ); }); }); describe('testConnection', () => { it('should return true for successful connection', async () => { const mockAuthResponse = { user_info: { username: 'testuser', status: 'Active', exp_date: '1735689600', password: 'testpass', is_trial: '0', active_cons: '1', max_connections: '2', }, server_info: { url: 'example.com', port: '80', https_port: '443', server_protocol: 'http', rtmp_port: '1935', timezone: 'UTC', }, }; (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockAuthResponse, }); const result = await client.testConnection(); expect(result).toBe(true); }); it('should return false for failed connection', async () => { (global.fetch as any).mockRejectedValueOnce(new Error('Network error')); const result = await client.testConnection(); expect(result).toBe(false); }); }); describe('getServerInfo', () => { it('should return server info after authentication', async () => { const mockAuthResponse = { user_info: { username: 'testuser', status: 'Active', exp_date: '1735689600', password: 'testpass', is_trial: '0', active_cons: '1', max_connections: '2', }, server_info: { url: 'example.com', port: '80', https_port: '443', server_protocol: 'http', rtmp_port: '1935', timezone: 'UTC', }, }; (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockAuthResponse, }); await client.authenticate(); const serverInfo = client.getServerInfo(); expect(serverInfo).toEqual(mockAuthResponse.server_info); }); it('should throw error when not authenticated', () => { expect(() => client.getServerInfo()).toThrow( 'Not authenticated. Call authenticate() first.' ); }); }); describe('getUserInfo', () => { it('should return user info after authentication', async () => { const mockAuthResponse = { user_info: { username: 'testuser', status: 'Active', exp_date: '1735689600', password: 'testpass', is_trial: '0', active_cons: '1', max_connections: '2', }, server_info: { url: 'example.com', port: '80', https_port: '443', server_protocol: 'http', rtmp_port: '1935', timezone: 'UTC', }, }; (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockAuthResponse, }); await client.authenticate(); const userInfo = client.getUserInfo(); expect(userInfo).toEqual(mockAuthResponse.user_info); }); it('should throw error when not authenticated', () => { expect(() => client.getUserInfo()).toThrow( 'Not authenticated. Call authenticate() first.' ); }); }); });