import {describe, it, expect, beforeEach, afterEach} from 'bun:test'; import {RtmpPush} from '../src/RtmpPush'; describe('RtmpPush', () => { let rtmpPush: RtmpPush; const mockMediaSourceUri = 'https://example.com/test-video.ts'; const mockRtmpIngestUri = 'rtmp://ingest.example.com:80/ingest'; beforeEach(() => { rtmpPush = new RtmpPush(mockMediaSourceUri, mockRtmpIngestUri); }); afterEach(() => { // Clean up any running processes if (rtmpPush.isRunning()) { rtmpPush.stop(); } }); describe('Constructor', () => { it('should create instance with correct properties', () => { expect(rtmpPush).toBeInstanceOf(RtmpPush); expect(rtmpPush.mediaSourceUri).toBe(mockMediaSourceUri); expect(rtmpPush.rtmpIngestUri).toBe(mockRtmpIngestUri); }); it('should handle empty strings', () => { const emptyRtmpPush = new RtmpPush('', ''); expect(emptyRtmpPush.mediaSourceUri).toBe(''); expect(emptyRtmpPush.rtmpIngestUri).toBe(''); }); it('should handle special characters in URIs', () => { const specialMediaUri = 'https://example.com/video with spaces & special chars!.ts'; const specialRtmpUri = 'rtmp://ingest.example.com:80/ingest/path with spaces'; const specialRtmpPush = new RtmpPush(specialMediaUri, specialRtmpUri); expect(specialRtmpPush.mediaSourceUri).toBe(specialMediaUri); expect(specialRtmpPush.rtmpIngestUri).toBe(specialRtmpUri); }); it('should handle very long URIs', () => { const longMediaUri = 'https://example.com/' + 'a'.repeat(1000) + '.ts'; const longRtmpUri = 'rtmp://ingest.example.com:80/ingest/' + 'b'.repeat(1000); const longRtmpPush = new RtmpPush(longMediaUri, longRtmpUri); expect(longRtmpPush.mediaSourceUri).toBe(longMediaUri); expect(longRtmpPush.rtmpIngestUri).toBe(longRtmpUri); }); }); describe('Getters', () => { it('should return correct mediaSourceUri', () => { expect(rtmpPush.mediaSourceUri).toBe(mockMediaSourceUri); }); it('should return correct rtmpIngestUri', () => { expect(rtmpPush.rtmpIngestUri).toBe(mockRtmpIngestUri); }); it('should return immutable values', () => { const originalMediaUri = rtmpPush.mediaSourceUri; const originalRtmpUri = rtmpPush.rtmpIngestUri; // These should not change the internal state expect(rtmpPush.mediaSourceUri).toBe(originalMediaUri); expect(rtmpPush.rtmpIngestUri).toBe(originalRtmpUri); }); }); describe('isRunning() method', () => { it('should return false when no process is started', () => { expect(rtmpPush.isRunning()).toBe(false); }); it('should return false for new instances', () => { const newRtmpPush = new RtmpPush('https://example.com/video.ts', 'rtmp://example.com/ingest'); expect(newRtmpPush.isRunning()).toBe(false); }); }); describe('stop() method', () => { it('should handle stopping when no process is running', () => { expect(rtmpPush.isRunning()).toBe(false); const result = rtmpPush.stop(); expect(result).toBe(rtmpPush); expect(rtmpPush.isRunning()).toBe(false); }); it('should be chainable', () => { const result = rtmpPush.stop(); expect(result).toBe(rtmpPush); }); it('should handle multiple stop calls', () => { rtmpPush.stop(); const result = rtmpPush.stop(); expect(result).toBe(rtmpPush); expect(rtmpPush.isRunning()).toBe(false); }); }); describe('URI Construction Logic', () => { it('should construct valid RTMP URIs', () => { const streamKey = 'test-stream-key'; const capabilities = ['h264', 'aac', 'stereo']; const expectedUri = `${mockRtmpIngestUri}/${streamKey};capabilities=${capabilities.join(',')};tags=`; // The URI should be properly formatted expect(expectedUri).toContain(streamKey); expect(expectedUri).toContain(capabilities.join(',')); expect(expectedUri).toContain(';capabilities='); expect(expectedUri).toContain(';tags='); }); it('should handle various stream key formats', () => { const testCases = ['simple-key', 'key_with_underscores', 'key-with-dashes', 'key123', 'key!@#$%^&*()', 'key with spaces', 'key.with.dots']; testCases.forEach(streamKey => { const capabilities = ['h264']; const expectedIngestUri = `${mockRtmpIngestUri}/${streamKey};capabilities=${capabilities.join(',')};tags=`; expect(expectedIngestUri).toContain(streamKey); expect(expectedIngestUri).toContain(';capabilities=h264;tags='); }); }); it('should handle empty capabilities array', () => { const streamKey = 'test-stream'; const capabilities: string[] = []; const expectedIngestUri = `${mockRtmpIngestUri}/${streamKey};capabilities=;tags=`; expect(expectedIngestUri).toContain(streamKey); expect(expectedIngestUri).toContain(';capabilities=;tags='); }); it('should handle single capability', () => { const streamKey = 'test-stream'; const capabilities = ['h264']; const expectedIngestUri = `${mockRtmpIngestUri}/${streamKey};capabilities=h264;tags=`; expect(expectedIngestUri).toContain(streamKey); expect(expectedIngestUri).toContain(';capabilities=h264;tags='); }); it('should handle multiple capabilities', () => { const streamKey = 'test-stream'; const capabilities = ['h264', 'aac', 'stereo', '1080p']; const expectedIngestUri = `${mockRtmpIngestUri}/${streamKey};capabilities=${capabilities.join(',')};tags=`; expect(expectedIngestUri).toContain(streamKey); expect(expectedIngestUri).toContain(';capabilities=h264,aac,stereo,1080p;tags='); }); it('should handle special characters in stream key', () => { const streamKey = 'test-stream-with-special-chars!@#$%^&*()'; const capabilities = ['h264']; const expectedIngestUri = `${mockRtmpIngestUri}/${streamKey};capabilities=${capabilities.join(',')};tags=`; expect(expectedIngestUri).toContain(streamKey); expect(expectedIngestUri).toContain(';capabilities=h264;tags='); }); it('should handle special characters in capabilities', () => { const streamKey = 'test-stream'; const capabilities = ['h.264', 'aac-lc', 'stereo_audio']; const expectedIngestUri = `${mockRtmpIngestUri}/${streamKey};capabilities=${capabilities.join(',')};tags=`; expect(expectedIngestUri).toContain(streamKey); expect(expectedIngestUri).toContain(';capabilities=h.264,aac-lc,stereo_audio;tags='); }); it('should handle very long stream keys', () => { const streamKey = 'a'.repeat(1000); const capabilities = ['h264']; const expectedIngestUri = `${mockRtmpIngestUri}/${streamKey};capabilities=${capabilities.join(',')};tags=`; expect(expectedIngestUri).toContain(streamKey); expect(expectedIngestUri).toContain(';capabilities=h264;tags='); }); it('should handle very long capability lists', () => { const streamKey = 'test-stream'; const capabilities = Array.from({length: 100}, (_, i) => `capability-${i}`); const expectedIngestUri = `${mockRtmpIngestUri}/${streamKey};capabilities=${capabilities.join(',')};tags=`; expect(expectedIngestUri).toContain(streamKey); expect(expectedIngestUri).toContain(';capabilities='); expect(expectedIngestUri).toContain(';tags='); expect(expectedIngestUri).toContain('capability-0'); expect(expectedIngestUri).toContain('capability-99'); }); }); describe('Method Chaining', () => { it('should support method chaining', () => { const result = rtmpPush.stop(); expect(result).toBe(rtmpPush); // Should be able to chain multiple calls const chainedResult = rtmpPush.stop().stop().stop(); expect(chainedResult).toBe(rtmpPush); }); }); describe('Instance Independence', () => { it('should maintain separate state for different instances', () => { const rtmpPush1 = new RtmpPush('https://example1.com/video.ts', 'rtmp://example1.com/ingest'); const rtmpPush2 = new RtmpPush('https://example2.com/video.ts', 'rtmp://example2.com/ingest'); expect(rtmpPush1.mediaSourceUri).toBe('https://example1.com/video.ts'); expect(rtmpPush2.mediaSourceUri).toBe('https://example2.com/video.ts'); expect(rtmpPush1.rtmpIngestUri).toBe('rtmp://example1.com/ingest'); expect(rtmpPush2.rtmpIngestUri).toBe('rtmp://example2.com/ingest'); expect(rtmpPush1.isRunning()).toBe(false); expect(rtmpPush2.isRunning()).toBe(false); }); }); describe('start() method basic functionality', () => { it('should be chainable', () => { const streamKey = 'test-stream'; const capabilities = ['h264']; try { const result = rtmpPush.start(streamKey, capabilities); expect(result).toBe(rtmpPush); } catch (error) { // If ffmpeg is not available, that's expected expect(error).toBeInstanceOf(Error); } }); it('should handle invalid parameters gracefully', () => { try { rtmpPush.start('', []); // If it doesn't throw, it should at least not be running expect(rtmpPush.isRunning()).toBe(false); } catch (error) { // Expected to fail expect(error).toBeInstanceOf(Error); } }); }); });