// // Copyright (c) 2013-2025 The SRS Authors // // SPDX-License-Identifier: MIT // #include using namespace std; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // External function declarations for testing extern srs_error_t srs_api_response_jsonp(ISrsHttpResponseWriter *w, string callback, string data); extern srs_error_t srs_api_response_code(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, int code); extern srs_error_t srs_api_response_code(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, srs_error_t code); // External global variables for reload state extern srs_error_t _srs_reload_err; extern SrsReloadState _srs_reload_state; extern std::string _srs_reload_id; MockBufferCacheForAac::MockBufferCacheForAac() { dump_cache_count_ = 0; last_consumer_ = NULL; last_jitter_ = SrsRtmpJitterAlgorithmOFF; } MockBufferCacheForAac::~MockBufferCacheForAac() { } srs_error_t MockBufferCacheForAac::start() { return srs_success; } void MockBufferCacheForAac::stop() { } bool MockBufferCacheForAac::alive() { return true; } srs_error_t MockBufferCacheForAac::dump_cache(ISrsLiveConsumer *consumer, SrsRtmpJitterAlgorithm jitter) { dump_cache_count_++; last_consumer_ = consumer; last_jitter_ = jitter; return srs_success; } srs_error_t MockBufferCacheForAac::update_auth(ISrsRequest *r) { return srs_success; } VOID TEST(KernelBalanceTest, RoundRobinBasicSelection) { // Test the major use scenario: round-robin selection across multiple servers // This covers the typical edge server origin selection use case SrsUniquePtr lb(new SrsLbRoundRobin()); // Setup test servers vector servers; servers.push_back("192.168.1.100:1935"); servers.push_back("192.168.1.101:1935"); servers.push_back("192.168.1.102:1935"); // Test round-robin selection - should cycle through all servers string selected1 = lb->select(servers); EXPECT_STREQ("192.168.1.100:1935", selected1.c_str()); EXPECT_EQ(0, (int)lb->current()); EXPECT_STREQ("192.168.1.100:1935", lb->selected().c_str()); string selected2 = lb->select(servers); EXPECT_STREQ("192.168.1.101:1935", selected2.c_str()); EXPECT_EQ(1, (int)lb->current()); EXPECT_STREQ("192.168.1.101:1935", lb->selected().c_str()); string selected3 = lb->select(servers); EXPECT_STREQ("192.168.1.102:1935", selected3.c_str()); EXPECT_EQ(2, (int)lb->current()); EXPECT_STREQ("192.168.1.102:1935", lb->selected().c_str()); // Test wrap-around - should go back to first server string selected4 = lb->select(servers); EXPECT_STREQ("192.168.1.100:1935", selected4.c_str()); EXPECT_EQ(0, (int)lb->current()); EXPECT_STREQ("192.168.1.100:1935", lb->selected().c_str()); // Continue cycling to verify consistent behavior string selected5 = lb->select(servers); EXPECT_STREQ("192.168.1.101:1935", selected5.c_str()); EXPECT_EQ(1, (int)lb->current()); string selected6 = lb->select(servers); EXPECT_STREQ("192.168.1.102:1935", selected6.c_str()); EXPECT_EQ(2, (int)lb->current()); } // Mock request implementation for SrsBufferCache testing MockBufferCacheRequest::MockBufferCacheRequest(std::string vhost, std::string app, std::string stream) { vhost_ = vhost; app_ = app; stream_ = stream; host_ = "127.0.0.1"; port_ = 1935; tcUrl_ = "rtmp://127.0.0.1/" + app; schema_ = "rtmp"; param_ = ""; duration_ = 0; args_ = NULL; protocol_ = "rtmp"; objectEncoding_ = 0; } MockBufferCacheRequest::~MockBufferCacheRequest() { } ISrsRequest *MockBufferCacheRequest::copy() { MockBufferCacheRequest *req = new MockBufferCacheRequest(vhost_, app_, stream_); req->host_ = host_; req->port_ = port_; req->tcUrl_ = tcUrl_; req->pageUrl_ = pageUrl_; req->swfUrl_ = swfUrl_; req->schema_ = schema_; req->param_ = param_; req->duration_ = duration_; req->protocol_ = protocol_; req->objectEncoding_ = objectEncoding_; req->ip_ = ip_; return req; } std::string MockBufferCacheRequest::get_stream_url() { if (vhost_ == "__defaultVhost__" || vhost_.empty()) { return "/" + app_ + "/" + stream_; } else { return vhost_ + "/" + app_ + "/" + stream_; } } void MockBufferCacheRequest::update_auth(ISrsRequest *req) { if (req) { pageUrl_ = req->pageUrl_; swfUrl_ = req->swfUrl_; tcUrl_ = req->tcUrl_; } } void MockBufferCacheRequest::strip() { // Mock implementation - basic string cleanup host_ = srs_strings_remove(host_, "/ \n\r\t"); vhost_ = srs_strings_remove(vhost_, "/ \n\r\t"); app_ = srs_strings_remove(app_, " \n\r\t"); stream_ = srs_strings_remove(stream_, " \n\r\t"); app_ = srs_strings_trim_end(app_, "/"); stream_ = srs_strings_trim_end(stream_, "/"); } ISrsRequest *MockBufferCacheRequest::as_http() { return copy(); } VOID TEST(SrsBufferCacheTest, ConstructorAndUpdateAuth) { srs_error_t err; // Test the major use scenario: constructor initialization and update_auth // This covers the typical HTTP streaming cache initialization use case // Create mock request SrsUniquePtr mock_request(new MockBufferCacheRequest("test.vhost", "live", "stream1")); mock_request->pageUrl_ = "http://example.com/page"; mock_request->swfUrl_ = "http://example.com/player.swf"; mock_request->tcUrl_ = "rtmp://127.0.0.1/live"; // Test constructor - should initialize all fields properly SrsUniquePtr cache(new SrsBufferCache(mock_request.get())); // Verify that the request was copied (as_http() was called in constructor) EXPECT_TRUE(cache->req_ != NULL); EXPECT_STREQ("test.vhost", cache->req_->vhost_.c_str()); EXPECT_STREQ("live", cache->req_->app_.c_str()); EXPECT_STREQ("stream1", cache->req_->stream_.c_str()); // Verify that queue and thread were created EXPECT_TRUE(cache->queue_ != NULL); EXPECT_TRUE(cache->trd_ != NULL); // Verify that fast_cache was initialized to 0 EXPECT_EQ(0, (int)cache->fast_cache_); // Verify that config and live_sources were set to global singletons EXPECT_TRUE(cache->config_ != NULL); EXPECT_TRUE(cache->live_sources_ != NULL); // Test update_auth - should update the request with new auth info SrsUniquePtr new_request(new MockBufferCacheRequest("test.vhost", "live", "stream1")); new_request->pageUrl_ = "http://example.com/new_page"; new_request->swfUrl_ = "http://example.com/new_player.swf"; new_request->tcUrl_ = "rtmp://127.0.0.1/live_new"; HELPER_EXPECT_SUCCESS(cache->update_auth(new_request.get())); // Verify that the request was updated EXPECT_TRUE(cache->req_ != NULL); EXPECT_STREQ("http://example.com/new_page", cache->req_->pageUrl_.c_str()); EXPECT_STREQ("http://example.com/new_player.swf", cache->req_->swfUrl_.c_str()); EXPECT_STREQ("rtmp://127.0.0.1/live_new", cache->req_->tcUrl_.c_str()); // Destructor will be called automatically and should clean up all resources } VOID TEST(SrsBufferCacheTest, DumpCacheWithMessages) { srs_error_t err; // Test the major use scenario: dump_cache with messages in queue // This covers the typical HTTP streaming cache dump use case // Create mock request SrsUniquePtr mock_request(new MockBufferCacheRequest("test.vhost", "live", "stream1")); // Create buffer cache SrsUniquePtr cache(new SrsBufferCache(mock_request.get())); // Set fast_cache to enable dump_cache functionality cache->fast_cache_ = 3 * SRS_UTIME_SECONDS; // Create mock media packets and enqueue them to the cache queue SrsMediaPacket *video_packet1 = new SrsMediaPacket(); video_packet1->timestamp_ = 1000; video_packet1->message_type_ = SrsFrameTypeVideo; char *video_data1 = new char[128]; memset(video_data1, 0x00, 128); video_data1[0] = 0x17; // keyframe + H.264 video_packet1->wrap(video_data1, 128); SrsMediaPacket *audio_packet1 = new SrsMediaPacket(); audio_packet1->timestamp_ = 1020; audio_packet1->message_type_ = SrsFrameTypeAudio; char *audio_data1 = new char[32]; memset(audio_data1, 0x00, 32); audio_data1[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo audio_packet1->wrap(audio_data1, 32); // Enqueue packets to cache queue - queue takes ownership HELPER_EXPECT_SUCCESS(cache->queue_->enqueue(video_packet1, NULL)); HELPER_EXPECT_SUCCESS(cache->queue_->enqueue(audio_packet1, NULL)); // Verify queue has packets EXPECT_EQ(2, cache->queue_->size()); // Create mock source and consumer SrsUniquePtr source(new MockLiveSourceForQueue()); SrsUniquePtr consumer(new MockLiveConsumerForQueue(source.get())); // Test dump_cache - should dump all packets to consumer HELPER_EXPECT_SUCCESS(cache->dump_cache(consumer.get(), SrsRtmpJitterAlgorithmFULL)); // Verify consumer received all packets EXPECT_EQ(2, consumer->enqueue_count_); EXPECT_EQ(1000, consumer->enqueued_timestamps_[0]); EXPECT_EQ(1020, consumer->enqueued_timestamps_[1]); // Verify queue still contains packets (dump_cache doesn't clear the queue) EXPECT_EQ(2, cache->queue_->size()); } VOID TEST(HttpStreamTest, TsStreamEncoderWriteAudioVideo) { srs_error_t err; // Test the major use scenario: writing audio and video data to TS stream encoder // This covers the typical HTTP-TS streaming use case // Create mock file writer for TS output SrsUniquePtr writer(new MockSrsFileWriter()); HELPER_EXPECT_SUCCESS(writer->open("test.ts")); // Create TS stream encoder SrsUniquePtr encoder(new SrsTsStreamEncoder()); // Initialize encoder with mock writer HELPER_EXPECT_SUCCESS(encoder->initialize(writer.get(), NULL)); // Prepare test video data (H.264 keyframe with AVC packet) // Format: [frame_type(4bits) + codec_id(4bits)][avc_packet_type][composition_time(3bytes)][data] char video_data[128]; memset(video_data, 0x00, sizeof(video_data)); video_data[0] = 0x17; // keyframe (1) + H.264 (7) video_data[1] = 0x01; // AVC NALU video_data[2] = 0x00; // composition time video_data[3] = 0x00; video_data[4] = 0x00; // Add some dummy NALU data video_data[5] = 0x00; video_data[6] = 0x00; video_data[7] = 0x00; video_data[8] = 0x01; // NALU start code video_data[9] = 0x65; // IDR slice // Write video data HELPER_EXPECT_SUCCESS(encoder->write_video(1000, video_data, sizeof(video_data))); // Prepare test audio data (AAC packet) // Format: [sound_format(4bits) + sound_rate(2bits) + sound_size(1bit) + sound_type(1bit)][aac_packet_type][data] char audio_data[64]; memset(audio_data, 0x00, sizeof(audio_data)); audio_data[0] = 0xAF; // AAC (10) + 44kHz (3) + 16-bit (1) + stereo (1) audio_data[1] = 0x01; // AAC raw data // Add some dummy AAC data for (int i = 2; i < 64; i++) { audio_data[i] = i; } // Write audio data HELPER_EXPECT_SUCCESS(encoder->write_audio(1020, audio_data, sizeof(audio_data))); // Write metadata (should succeed but do nothing) char metadata[32]; memset(metadata, 0x00, sizeof(metadata)); HELPER_EXPECT_SUCCESS(encoder->write_metadata(1040, metadata, sizeof(metadata))); // Verify that data was written to the mock file writer EXPECT_TRUE(writer->filesize() > 0); } VOID TEST(HttpStreamTest, TsStreamEncoderCacheAndAVSettings) { srs_error_t err; // Test the major use scenario: cache behavior and audio/video settings // This covers TS stream encoder cache handling and A/V configuration // Create mock file writer for TS output SrsUniquePtr writer(new MockSrsFileWriter()); HELPER_EXPECT_SUCCESS(writer->open("test.ts")); // Create TS stream encoder SrsUniquePtr encoder(new SrsTsStreamEncoder()); // Initialize encoder with mock writer HELPER_EXPECT_SUCCESS(encoder->initialize(writer.get(), NULL)); // Test has_cache - TS stream encoder should not have cache (uses SrsLiveSource GOP cache) EXPECT_FALSE(encoder->has_cache()); // Test dump_cache - should always succeed and do nothing for TS stream HELPER_EXPECT_SUCCESS(encoder->dump_cache(NULL, SrsRtmpJitterAlgorithmFULL)); // Test set_has_audio - configure encoder to expect audio encoder->set_has_audio(true); // Test set_has_video - configure encoder to expect video encoder->set_has_video(true); // Test set_guess_has_av - enable A/V guessing mode encoder->set_guess_has_av(true); // Verify encoder still works after configuration // Prepare test video data (H.264 keyframe) char video_data[128]; memset(video_data, 0x00, sizeof(video_data)); video_data[0] = 0x17; // keyframe + H.264 video_data[1] = 0x01; // AVC NALU video_data[5] = 0x00; video_data[6] = 0x00; video_data[7] = 0x00; video_data[8] = 0x01; // NALU start code video_data[9] = 0x65; // IDR slice // Write video data - should succeed with configured settings HELPER_EXPECT_SUCCESS(encoder->write_video(1000, video_data, sizeof(video_data))); // Prepare test audio data (AAC packet) char audio_data[64]; memset(audio_data, 0x00, sizeof(audio_data)); audio_data[0] = 0xAF; // AAC + 44kHz + 16-bit + stereo audio_data[1] = 0x01; // AAC raw data // Write audio data - should succeed with configured settings HELPER_EXPECT_SUCCESS(encoder->write_audio(1020, audio_data, sizeof(audio_data))); // Verify that data was written successfully EXPECT_TRUE(writer->filesize() > 0); } VOID TEST(SrsFlvStreamEncoderTest, InitializeSuccess) { srs_error_t err; // Test the major use scenario: initialize FLV stream encoder with file writer // This covers the typical HTTP-FLV streaming initialization use case // Create mock file writer SrsUniquePtr writer(new MockSrsFileWriter()); // Create FLV stream encoder SrsUniquePtr encoder(new SrsFlvStreamEncoder()); // Initialize encoder with file writer - should succeed HELPER_EXPECT_SUCCESS(encoder->initialize(writer.get(), NULL)); // Verify that encoder is ready to write data (internal enc_ was initialized) // We can verify this by checking that subsequent operations don't crash EXPECT_TRUE(encoder.get() != NULL); } VOID TEST(SrsFlvStreamEncoderTest, WriteAudioVideoMetadata) { srs_error_t err; // Test the major use scenario: write audio, video, and metadata to FLV stream // This covers the typical HTTP-FLV streaming workflow where encoder writes // FLV header automatically on first write, then writes media packets // Create mock file writer SrsUniquePtr writer(new MockSrsFileWriter()); // Create FLV stream encoder SrsUniquePtr encoder(new SrsFlvStreamEncoder()); // Initialize encoder with file writer HELPER_EXPECT_SUCCESS(encoder->initialize(writer.get(), NULL)); // Prepare test metadata (AMF0 encoded onMetaData) char metadata[128]; memset(metadata, 0x00, sizeof(metadata)); metadata[0] = 0x02; // AMF0 string marker metadata[1] = 0x00; metadata[2] = 0x0a; // length = 10 memcpy(metadata + 3, "onMetaData", 10); metadata[13] = 0x08; // AMF0 object marker // Write metadata - should succeed and trigger header write HELPER_EXPECT_SUCCESS(encoder->write_metadata(0, metadata, sizeof(metadata))); // Prepare test video data (H.264 keyframe) char video_data[128]; memset(video_data, 0x00, sizeof(video_data)); video_data[0] = 0x17; // keyframe + H.264 video_data[1] = 0x01; // AVC NALU video_data[5] = 0x00; video_data[6] = 0x00; video_data[7] = 0x00; video_data[8] = 0x01; // NALU start code video_data[9] = 0x65; // IDR slice // Write video data - should succeed (header already written) HELPER_EXPECT_SUCCESS(encoder->write_video(1000, video_data, sizeof(video_data))); // Prepare test audio data (AAC packet) char audio_data[64]; memset(audio_data, 0x00, sizeof(audio_data)); audio_data[0] = 0xAF; // AAC + 44kHz + 16-bit + stereo audio_data[1] = 0x01; // AAC raw data // Write audio data - should succeed (header already written) HELPER_EXPECT_SUCCESS(encoder->write_audio(1020, audio_data, sizeof(audio_data))); // Verify that data was written successfully // The file should contain FLV header + metadata tag + video tag + audio tag EXPECT_TRUE(writer->filesize() > 0); } VOID TEST(SrsFlvStreamEncoderTest, ConfigurationAndCacheMethods) { srs_error_t err; // Test the major use scenario: configure encoder settings and verify cache behavior // This covers the typical HTTP-FLV streaming encoder configuration use case // Create mock file writer SrsUniquePtr writer(new MockSrsFileWriter()); // Create FLV stream encoder SrsUniquePtr encoder(new SrsFlvStreamEncoder()); // Initialize encoder with mock writer HELPER_EXPECT_SUCCESS(encoder->initialize(writer.get(), NULL)); // Test set_drop_if_not_match - should configure the underlying transmuxer encoder->set_drop_if_not_match(true); encoder->set_drop_if_not_match(false); // Test set_has_audio - should configure audio presence encoder->set_has_audio(true); encoder->set_has_audio(false); encoder->set_has_audio(true); // Reset to true for later tests // Test set_has_video - should configure video presence encoder->set_has_video(true); encoder->set_has_video(false); encoder->set_has_video(true); // Reset to true for later tests // Test set_guess_has_av - should configure A/V guessing behavior encoder->set_guess_has_av(true); encoder->set_guess_has_av(false); encoder->set_guess_has_av(true); // Reset to true // Test has_cache - should always return false for FLV stream encoder // because FLV stream uses GOP cache from SrsLiveSource EXPECT_FALSE(encoder->has_cache()); // Test dump_cache - should always succeed and do nothing // because FLV stream ignores cache (uses SrsLiveSource cache instead) HELPER_EXPECT_SUCCESS(encoder->dump_cache(NULL, SrsRtmpJitterAlgorithmOFF)); // Verify encoder still works after configuration changes // Write a video frame to ensure encoder is functional char video_data[10]; video_data[0] = 0x17; // AVC keyframe video_data[1] = 0x01; // AVC NALU HELPER_EXPECT_SUCCESS(encoder->write_video(1000, video_data, sizeof(video_data))); // Verify that data was written successfully EXPECT_TRUE(writer->filesize() > 0); } VOID TEST(HttpStreamTest, FlvStreamEncoderWriteTagsWithGuessAV) { srs_error_t err; // Test the major use scenario: write_tags with guess_has_av enabled // This covers the typical HTTP-FLV streaming workflow where encoder // automatically detects whether stream has audio/video by analyzing packets // and writes FLV header accordingly (issue #939) // Create mock file writer SrsUniquePtr writer(new MockSrsFileWriter()); // Create FLV stream encoder SrsUniquePtr encoder(new SrsFlvStreamEncoder()); // Initialize encoder with file writer HELPER_EXPECT_SUCCESS(encoder->initialize(writer.get(), NULL)); // Enable guess_has_av mode (default is true) encoder->set_guess_has_av(true); // Create array of media packets with mixed audio and video const int count = 5; SrsMediaPacket *msgs[count]; // Create video sequence header (H.264 SPS/PPS) msgs[0] = new SrsMediaPacket(); msgs[0]->timestamp_ = 0; msgs[0]->message_type_ = SrsFrameTypeVideo; char *video_sh_data = new char[10]; video_sh_data[0] = 0x17; // keyframe + AVC video_sh_data[1] = 0x00; // AVC sequence header for (int i = 2; i < 10; i++) { video_sh_data[i] = (char)i; } msgs[0]->wrap(video_sh_data, 10); // Create video frame (non-sequence header) msgs[1] = new SrsMediaPacket(); msgs[1]->timestamp_ = 40; msgs[1]->message_type_ = SrsFrameTypeVideo; char *video_frame_data = new char[20]; video_frame_data[0] = 0x17; // keyframe + AVC video_frame_data[1] = 0x01; // AVC NALU for (int i = 2; i < 20; i++) { video_frame_data[i] = (char)(0x10 + i); } msgs[1]->wrap(video_frame_data, 20); // Create audio sequence header (AAC) msgs[2] = new SrsMediaPacket(); msgs[2]->timestamp_ = 0; msgs[2]->message_type_ = SrsFrameTypeAudio; char *audio_sh_data = new char[4]; audio_sh_data[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo audio_sh_data[1] = 0x00; // AAC sequence header audio_sh_data[2] = 0x12; audio_sh_data[3] = 0x10; msgs[2]->wrap(audio_sh_data, 4); // Create audio frame (non-sequence header) msgs[3] = new SrsMediaPacket(); msgs[3]->timestamp_ = 23; msgs[3]->message_type_ = SrsFrameTypeAudio; char *audio_frame_data = new char[128]; audio_frame_data[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo audio_frame_data[1] = 0x01; // AAC raw data for (int i = 2; i < 128; i++) { audio_frame_data[i] = (char)(0x20 + i); } msgs[3]->wrap(audio_frame_data, 128); // Create another video frame msgs[4] = new SrsMediaPacket(); msgs[4]->timestamp_ = 80; msgs[4]->message_type_ = SrsFrameTypeVideo; char *video_frame_data2 = new char[30]; video_frame_data2[0] = 0x27; // inter frame + AVC video_frame_data2[1] = 0x01; // AVC NALU for (int i = 2; i < 30; i++) { video_frame_data2[i] = (char)(0x30 + i); } msgs[4]->wrap(video_frame_data2, 30); // Write all tags at once - encoder should: // 1. Analyze packets to detect has_audio=true, has_video=true // 2. Count non-sequence-header frames (2 video frames, 1 audio frame) // 3. Write FLV header with both audio and video flags // 4. Write all the tags HELPER_EXPECT_SUCCESS(encoder->write_tags(msgs, count)); // Verify that FLV header and tags were written EXPECT_TRUE(writer->filesize() > 0); // Clean up - write_tags does not free the messages for (int i = 0; i < count; i++) { srs_freep(msgs[i]); } } VOID TEST(AppHttpStreamTest, AacStreamEncoderMajorScenario) { srs_error_t err; // Test the major use scenario: initialize encoder, write AAC audio data, and dump cache // This covers the typical AAC HTTP streaming use case // Create mock file writer and buffer cache SrsUniquePtr writer(new MockSrsFileWriter()); HELPER_EXPECT_SUCCESS(writer->open("test.aac")); MockBufferCacheForAac mock_cache; // Create AAC stream encoder SrsUniquePtr encoder(new SrsAacStreamEncoder()); // Test initialization HELPER_EXPECT_SUCCESS(encoder->initialize(writer.get(), &mock_cache)); // Test has_cache - should always return true for AAC encoder EXPECT_TRUE(encoder->has_cache()); // Create AAC sequence header (AudioSpecificConfig) // Format: [sound_format(4bits)|sound_rate(2bits)|sound_size(1bit)|sound_type(1bit)][aac_packet_type][AudioSpecificConfig] char aac_sequence_header[4]; aac_sequence_header[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo aac_sequence_header[1] = 0x00; // AAC sequence header aac_sequence_header[2] = 0x12; // AudioSpecificConfig byte 1 (AAC-LC, 44.1kHz) aac_sequence_header[3] = 0x10; // AudioSpecificConfig byte 2 (stereo) // Write AAC sequence header HELPER_EXPECT_SUCCESS(encoder->write_audio(0, aac_sequence_header, 4)); // Create AAC raw audio frame // Format: [sound_format(4bits)|sound_rate(2bits)|sound_size(1bit)|sound_type(1bit)][aac_packet_type][raw_aac_frame_data] char aac_raw_frame[10]; aac_raw_frame[0] = 0xAF; // AAC, 44kHz, 16-bit, stereo aac_raw_frame[1] = 0x01; // AAC raw frame data // Fill with dummy AAC frame data for (int i = 2; i < 10; i++) { aac_raw_frame[i] = 0xCB; } // Write AAC raw audio frame HELPER_EXPECT_SUCCESS(encoder->write_audio(1000, aac_raw_frame, 10)); // Verify that ADTS header (7 bytes) + AAC frame data (8 bytes) were written // Total: 7 + 8 = 15 bytes EXPECT_EQ(15, writer->filesize()); // Test write_video - should be ignored for AAC encoder char dummy_video[10]; memset(dummy_video, 0x00, 10); HELPER_EXPECT_SUCCESS(encoder->write_video(2000, dummy_video, 10)); // File size should not change after writing video EXPECT_EQ(15, writer->filesize()); // Test write_metadata - should be ignored for AAC encoder char dummy_metadata[10]; memset(dummy_metadata, 0x00, 10); HELPER_EXPECT_SUCCESS(encoder->write_metadata(3000, dummy_metadata, 10)); // File size should not change after writing metadata EXPECT_EQ(15, writer->filesize()); // Test dump_cache - should delegate to buffer cache MockLiveSourceForQueue mock_source; SrsUniquePtr consumer(new MockLiveConsumerForQueue(&mock_source)); HELPER_EXPECT_SUCCESS(encoder->dump_cache(consumer.get(), SrsRtmpJitterAlgorithmFULL)); // Verify that dump_cache was called on the buffer cache EXPECT_EQ(1, mock_cache.dump_cache_count_); EXPECT_EQ(consumer.get(), mock_cache.last_consumer_); EXPECT_EQ(SrsRtmpJitterAlgorithmFULL, mock_cache.last_jitter_); } VOID TEST(BufferWriterTest, WriteToHttpResponse) { srs_error_t err; // Test the major use scenario: writing data to HTTP response through SrsBufferWriter // This covers the typical HTTP streaming use case where media data is written directly to HTTP response // Create mock HTTP response writer MockResponseWriter mock_writer; // Set content length to allow writing data char test_data[] = "Hello, SRS!"; char buf1[] = "First"; char buf2[] = "Second"; int total_size = (sizeof(test_data) - 1) + (sizeof(buf1) - 1) + (sizeof(buf2) - 1); mock_writer.header()->set_content_length(total_size); // Create SrsBufferWriter with the mock writer SrsUniquePtr buffer_writer(new SrsBufferWriter(&mock_writer)); // Test is_open - should always return true EXPECT_TRUE(buffer_writer->is_open()); // Test tellg - should always return 0 EXPECT_EQ(0, buffer_writer->tellg()); // Test open - should always succeed HELPER_EXPECT_SUCCESS(buffer_writer->open("dummy_file")); // Test write - write some data ssize_t nwrite = 0; HELPER_EXPECT_SUCCESS(buffer_writer->write(test_data, sizeof(test_data) - 1, &nwrite)); EXPECT_EQ((ssize_t)(sizeof(test_data) - 1), nwrite); // Verify data was written to the mock writer string written_data = string(mock_writer.io.out_buffer.bytes(), mock_writer.io.out_buffer.length()); EXPECT_TRUE(written_data.find("Hello, SRS!") != string::npos); // Test writev - write multiple buffers iovec iov[2]; iov[0].iov_base = buf1; iov[0].iov_len = sizeof(buf1) - 1; iov[1].iov_base = buf2; iov[1].iov_len = sizeof(buf2) - 1; ssize_t nwrite_v = 0; HELPER_EXPECT_SUCCESS(buffer_writer->writev(iov, 2, &nwrite_v)); EXPECT_EQ((ssize_t)(sizeof(buf1) - 1 + sizeof(buf2) - 1), nwrite_v); // Test close - should do nothing but not crash buffer_writer->close(); } // Old mock implementations for backward compatibility MockHttpxConn::MockHttpxConn() { enable_stat_ = false; } MockHttpxConn::~MockHttpxConn() { } void MockHttpxConn::set_enable_stat(bool v) { enable_stat_ = v; } srs_error_t MockHttpxConn::on_start() { return srs_success; } srs_error_t MockHttpxConn::on_http_message(ISrsHttpMessage *r, ISrsHttpResponseWriter *w) { return srs_success; } srs_error_t MockHttpxConn::on_message_done(ISrsHttpMessage *r, ISrsHttpResponseWriter *w) { return srs_success; } srs_error_t MockHttpxConn::on_conn_done(srs_error_t r0) { return r0; } MockHttpConn::MockHttpConn() { handler_ = new MockHttpxConn(); remote_ip_ = "127.0.0.1"; } MockHttpConn::~MockHttpConn() { srs_freep(handler_); } std::string MockHttpConn::remote_ip() { return remote_ip_; } const SrsContextId &MockHttpConn::get_id() { static SrsContextId id; return id; } std::string MockHttpConn::desc() { return "MockHttpConn"; } void MockHttpConn::expire() { } ISrsHttpConnOwner *MockHttpConn::handler() { return handler_; } MockHttpMessage::MockHttpMessage() : SrsHttpMessage() { mock_conn_ = new MockHttpConn(); set_connection(mock_conn_); } MockHttpMessage::~MockHttpMessage() { srs_freep(mock_conn_); } std::string MockHttpMessage::path() { return "/live/stream.flv"; } // New mock implementations for SrsLiveStream testing MockHttpxConnForLiveStream::MockHttpxConnForLiveStream() : SrsHttpxConn(NULL, NULL, NULL, "127.0.0.1", 1935, "", "") { enable_stat_called_ = false; } MockHttpxConnForLiveStream::~MockHttpxConnForLiveStream() { } void MockHttpxConnForLiveStream::set_enable_stat(bool v) { enable_stat_called_ = true; SrsHttpxConn::set_enable_stat(v); } MockHttpConnForLiveStream::MockHttpConnForLiveStream() : SrsHttpConn(NULL, NULL, NULL, "127.0.0.1", 1935) { mock_handler_ = new MockHttpxConnForLiveStream(); } MockHttpConnForLiveStream::~MockHttpConnForLiveStream() { srs_freep(mock_handler_); } ISrsHttpConnOwner *MockHttpConnForLiveStream::handler() { return mock_handler_; } MockHttpMessageForLiveStream::MockHttpMessageForLiveStream() : SrsHttpMessage() { mock_conn_ = new MockHttpConnForLiveStream(); } MockHttpMessageForLiveStream::~MockHttpMessageForLiveStream() { srs_freep(mock_conn_); } ISrsConnection *MockHttpMessageForLiveStream::connection() { return mock_conn_; } std::string MockHttpMessageForLiveStream::path() { return "/live/stream.flv"; } VOID TEST(SrsLiveStreamTest, ServeHttpWithDisabledEntry) { srs_error_t err; // Test the major use scenario: serve_http_impl with entry_->enabled = false // This covers the case where stream is disabled and should return error after // security check and HTTP hooks // Create mock request SrsUniquePtr mock_request(new MockBufferCacheRequest("test.vhost", "live", "stream1")); // Create mock buffer cache SrsUniquePtr mock_cache(new MockBufferCacheForAac()); // Create SrsLiveStream SrsUniquePtr live_stream(new SrsLiveStream(mock_request.get(), mock_cache.get())); // Create and set mock dependencies MockStatisticForLiveStream mock_stat; MockSecurity mock_security; // Replace dependencies with mocks live_stream->stat_ = &mock_stat; srs_freep(live_stream->security_); live_stream->security_ = &mock_security; // Create mock HTTP message and response writer SrsUniquePtr mock_message(new MockHttpMessageForLiveStream()); MockResponseWriter mock_writer; // Set up entry with enabled = false - this is the key test condition live_stream->entry_ = new SrsHttpMuxEntry(); live_stream->entry_->enabled = false; live_stream->entry_->pattern = "/live/stream.flv"; // Call serve_http - should add viewer, call serve_http_impl, then remove viewer err = live_stream->serve_http(&mock_writer, mock_message.get()); // Verify that error was returned due to disabled entry EXPECT_TRUE(err != srs_success); EXPECT_EQ(ERROR_RTMP_STREAM_NOT_FOUND, srs_error_code(err)); srs_freep(err); // Verify that stat->on_client was called EXPECT_EQ(1, mock_stat.on_client_count_); // Verify that security->check was called EXPECT_EQ(1, mock_security.check_count_); // Verify that viewers list is empty after serve_http returns // This confirms that the viewer was added before serve_http_impl and removed after EXPECT_EQ(0, (int)live_stream->viewers_.size()); // Clean up - set dependencies back to NULL before destruction live_stream->stat_ = NULL; live_stream->security_ = NULL; srs_freep(live_stream->entry_); } // Mock ISrsStatistic implementation for SrsLiveStream testing MockStatisticForLiveStream::MockStatisticForLiveStream() { on_client_count_ = 0; on_client_error_ = srs_success; } MockStatisticForLiveStream::~MockStatisticForLiveStream() { } void MockStatisticForLiveStream::on_disconnect(std::string id, srs_error_t err) { } srs_error_t MockStatisticForLiveStream::on_client(std::string id, ISrsRequest *req, ISrsExpire *conn, SrsRtmpConnType type) { on_client_count_++; return srs_error_copy(on_client_error_); } srs_error_t MockStatisticForLiveStream::on_video_info(ISrsRequest *req, SrsVideoCodecId vcodec, int avc_profile, int avc_level, int width, int height) { return srs_success; } srs_error_t MockStatisticForLiveStream::on_audio_info(ISrsRequest *req, SrsAudioCodecId acodec, SrsAudioSampleRate asample_rate, SrsAudioChannels asound_type, SrsAacObjectType aac_object) { return srs_success; } void MockStatisticForLiveStream::on_stream_publish(ISrsRequest *req, std::string publisher_id) { } void MockStatisticForLiveStream::on_stream_close(ISrsRequest *req) { } void MockStatisticForLiveStream::kbps_add_delta(std::string id, ISrsKbpsDelta *delta) { } void MockStatisticForLiveStream::kbps_sample() { } srs_error_t MockStatisticForLiveStream::on_video_frames(ISrsRequest *req, int nb_frames) { return srs_success; } std::string MockStatisticForLiveStream::server_id() { return "mock_server_id"; } std::string MockStatisticForLiveStream::service_id() { return "mock_service_id"; } std::string MockStatisticForLiveStream::service_pid() { return "mock_pid"; } SrsStatisticVhost *MockStatisticForLiveStream::find_vhost_by_id(std::string vid) { return NULL; } SrsStatisticStream *MockStatisticForLiveStream::find_stream(std::string sid) { return NULL; } SrsStatisticStream *MockStatisticForLiveStream::find_stream_by_url(std::string url) { return NULL; } SrsStatisticClient *MockStatisticForLiveStream::find_client(std::string client_id) { return NULL; } srs_error_t MockStatisticForLiveStream::dumps_vhosts(SrsJsonArray *arr) { return srs_success; } srs_error_t MockStatisticForLiveStream::dumps_streams(SrsJsonArray *arr, int start, int count) { return srs_success; } srs_error_t MockStatisticForLiveStream::dumps_clients(SrsJsonArray *arr, int start, int count) { return srs_success; } srs_error_t MockStatisticForLiveStream::dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs) { send_bytes = 0; recv_bytes = 0; nstreams = 0; nclients = 0; total_nclients = 0; nerrs = 0; return srs_success; } // Mock config implementation for SrsLiveStream hooks testing MockAppConfigForLiveStreamHooks::MockAppConfigForLiveStreamHooks() { http_hooks_enabled_ = false; on_play_directive_ = NULL; on_stop_directive_ = NULL; } MockAppConfigForLiveStreamHooks::~MockAppConfigForLiveStreamHooks() { srs_freep(on_play_directive_); srs_freep(on_stop_directive_); } bool MockAppConfigForLiveStreamHooks::get_vhost_http_hooks_enabled(std::string vhost) { return http_hooks_enabled_; } SrsConfDirective *MockAppConfigForLiveStreamHooks::get_vhost_on_play(std::string vhost) { return on_play_directive_; } SrsConfDirective *MockAppConfigForLiveStreamHooks::get_vhost_on_stop(std::string vhost) { return on_stop_directive_; } // Mock HTTP hooks implementation for SrsLiveStream testing MockHttpHooksForLiveStream::MockHttpHooksForLiveStream() { on_play_count_ = 0; on_play_error_ = srs_success; on_stop_count_ = 0; } MockHttpHooksForLiveStream::~MockHttpHooksForLiveStream() { srs_freep(on_play_error_); } srs_error_t MockHttpHooksForLiveStream::on_connect(std::string url, ISrsRequest *req) { return srs_success; } void MockHttpHooksForLiveStream::on_close(std::string url, ISrsRequest *req, int64_t send_bytes, int64_t recv_bytes) { } srs_error_t MockHttpHooksForLiveStream::on_publish(std::string url, ISrsRequest *req) { return srs_success; } void MockHttpHooksForLiveStream::on_unpublish(std::string url, ISrsRequest *req) { } srs_error_t MockHttpHooksForLiveStream::on_play(std::string url, ISrsRequest *req) { on_play_count_++; on_play_calls_.push_back(std::make_pair(url, req)); return srs_error_copy(on_play_error_); } void MockHttpHooksForLiveStream::on_stop(std::string url, ISrsRequest *req) { on_stop_count_++; on_stop_calls_.push_back(std::make_pair(url, req)); } srs_error_t MockHttpHooksForLiveStream::on_dvr(SrsContextId cid, std::string url, ISrsRequest *req, std::string file) { return srs_success; } srs_error_t MockHttpHooksForLiveStream::on_hls(SrsContextId cid, std::string url, ISrsRequest *req, std::string file, std::string ts_url, std::string m3u8, std::string m3u8_url, int sn, srs_utime_t duration) { return srs_success; } srs_error_t MockHttpHooksForLiveStream::on_hls_notify(SrsContextId cid, std::string url, ISrsRequest *req, std::string ts_url, int nb_notify) { return srs_success; } srs_error_t MockHttpHooksForLiveStream::discover_co_workers(std::string url, std::string &host, int &port) { return srs_success; } srs_error_t MockHttpHooksForLiveStream::on_forward_backend(std::string url, ISrsRequest *req, std::vector &rtmp_urls) { return srs_success; } void MockHttpHooksForLiveStream::reset() { on_play_calls_.clear(); on_play_count_ = 0; srs_freep(on_play_error_); on_play_error_ = srs_success; on_stop_calls_.clear(); on_stop_count_ = 0; } VOID TEST(SrsLiveStreamTest, HttpHooksOnPlayAndStop) { srs_error_t err = srs_success; // Test the major use scenario: http_hooks_on_play and http_hooks_on_stop with multiple hook URLs // This covers the typical HTTP-FLV/HLS streaming hook notification use case // Create mock request SrsUniquePtr mock_request(new MockBufferCacheRequest("test.vhost", "live", "stream1")); // Create mock buffer cache SrsUniquePtr mock_cache(new MockBufferCacheForAac()); // Create SrsLiveStream SrsUniquePtr live_stream(new SrsLiveStream(mock_request.get(), mock_cache.get())); // Create mock config and hooks MockAppConfigForLiveStreamHooks *mock_config = new MockAppConfigForLiveStreamHooks(); MockHttpHooksForLiveStream *mock_hooks = new MockHttpHooksForLiveStream(); // Inject mock dependencies live_stream->config_ = mock_config; live_stream->hooks_ = mock_hooks; // Enable HTTP hooks mock_config->http_hooks_enabled_ = true; // Setup on_play hook URLs SrsConfDirective *on_play_directive = new SrsConfDirective(); on_play_directive->args_.push_back("http://localhost:8080/api/on_play"); on_play_directive->args_.push_back("http://localhost:8080/api/on_play2"); mock_config->on_play_directive_ = on_play_directive; // Setup on_stop hook URLs SrsConfDirective *on_stop_directive = new SrsConfDirective(); on_stop_directive->args_.push_back("http://localhost:8080/api/on_stop"); on_stop_directive->args_.push_back("http://localhost:8080/api/on_stop2"); mock_config->on_stop_directive_ = on_stop_directive; // Create mock HTTP message SrsUniquePtr mock_http_msg(new MockHttpMessage()); // Test http_hooks_on_play - should call hooks for all URLs HELPER_EXPECT_SUCCESS(live_stream->http_hooks_on_play(mock_http_msg.get())); // Verify on_play was called twice (once for each URL) EXPECT_EQ(2, mock_hooks->on_play_count_); EXPECT_EQ(2, (int)mock_hooks->on_play_calls_.size()); EXPECT_STREQ("http://localhost:8080/api/on_play", mock_hooks->on_play_calls_[0].first.c_str()); EXPECT_STREQ("http://localhost:8080/api/on_play2", mock_hooks->on_play_calls_[1].first.c_str()); // Test http_hooks_on_stop - should call hooks for all URLs live_stream->http_hooks_on_stop(mock_http_msg.get()); // Verify on_stop was called twice (once for each URL) EXPECT_EQ(2, mock_hooks->on_stop_count_); EXPECT_EQ(2, (int)mock_hooks->on_stop_calls_.size()); EXPECT_STREQ("http://localhost:8080/api/on_stop", mock_hooks->on_stop_calls_[0].first.c_str()); EXPECT_STREQ("http://localhost:8080/api/on_stop2", mock_hooks->on_stop_calls_[1].first.c_str()); // Clean up - set to NULL to avoid double free (mocks will be freed automatically) live_stream->config_ = NULL; live_stream->hooks_ = NULL; srs_freep(mock_config); srs_freep(mock_hooks); } VOID TEST(SrsLiveEntryTest, FormatDetection) { // Test the major use scenario: SrsLiveEntry format detection based on file extension // This covers the typical HTTP streaming entry creation use case where the mount path // determines the stream format (FLV, TS, AAC, MP3) // Test TS format detection SrsUniquePtr ts_entry(new SrsLiveEntry("/live/stream.ts")); EXPECT_TRUE(ts_entry->is_ts()); EXPECT_FALSE(ts_entry->is_aac()); EXPECT_FALSE(ts_entry->is_mp3()); EXPECT_FALSE(ts_entry->is_flv()); // Test AAC format detection SrsUniquePtr aac_entry(new SrsLiveEntry("/live/stream.aac")); EXPECT_TRUE(aac_entry->is_aac()); EXPECT_FALSE(aac_entry->is_ts()); EXPECT_FALSE(aac_entry->is_mp3()); EXPECT_FALSE(aac_entry->is_flv()); // Test MP3 format detection SrsUniquePtr mp3_entry(new SrsLiveEntry("/live/stream.mp3")); EXPECT_TRUE(mp3_entry->is_mp3()); EXPECT_FALSE(mp3_entry->is_ts()); EXPECT_FALSE(mp3_entry->is_aac()); EXPECT_FALSE(mp3_entry->is_flv()); // Test FLV format detection (for completeness) SrsUniquePtr flv_entry(new SrsLiveEntry("/live/stream.flv")); EXPECT_TRUE(flv_entry->is_flv()); EXPECT_FALSE(flv_entry->is_ts()); EXPECT_FALSE(flv_entry->is_aac()); EXPECT_FALSE(flv_entry->is_mp3()); } VOID TEST(SrsHttpStreamServerTest, InitializeFlvEntry) { srs_error_t err = srs_success; // Test the major use scenario: initialize_flv_entry creates template handler // when HTTP remux is enabled for a vhost // This covers the typical HTTP-FLV live streaming initialization use case // Create mock config MockAppConfigForHttpStreamServer *mock_config = new MockAppConfigForHttpStreamServer(); mock_config->http_remux_enabled_ = true; mock_config->http_remux_mount_ = "[vhost]/[app]/[stream].flv"; // Use a nested scope to control server lifetime { // Create SrsHttpStreamServer SrsUniquePtr server(new SrsHttpStreamServer()); // Replace config with mock server->config_ = mock_config; // Test initialize_flv_entry with enabled HTTP remux std::string vhost = "test.vhost"; HELPER_EXPECT_SUCCESS(server->initialize_flv_entry(vhost)); // Verify template handler was created EXPECT_EQ(1, (int)server->templateHandlers_.size()); EXPECT_TRUE(server->templateHandlers_.find(vhost) != server->templateHandlers_.end()); SrsLiveEntry *entry = server->templateHandlers_[vhost]; EXPECT_TRUE(entry != NULL); EXPECT_STREQ("[vhost]/[app]/[stream].flv", entry->mount_.c_str()); // Verify it's a template entry (no stream/cache/req created yet) EXPECT_TRUE(entry->stream_ == NULL); EXPECT_TRUE(entry->cache_ == NULL); EXPECT_TRUE(entry->req_ == NULL); EXPECT_FALSE(entry->disposing_); // Test initialize_flv_entry with disabled HTTP remux mock_config->http_remux_enabled_ = false; std::string vhost2 = "disabled.vhost"; HELPER_EXPECT_SUCCESS(server->initialize_flv_entry(vhost2)); // Verify no template handler was created for disabled vhost EXPECT_EQ(1, (int)server->templateHandlers_.size()); EXPECT_TRUE(server->templateHandlers_.find(vhost2) == server->templateHandlers_.end()); // Server destructor will be called here when exiting scope } // Now we can safely free the mock config srs_freep(mock_config); } MockAsyncCallWorker::MockAsyncCallWorker() { execute_count_ = 0; } MockAsyncCallWorker::~MockAsyncCallWorker() { // Free all tasks for (size_t i = 0; i < tasks_.size(); i++) { srs_freep(tasks_[i]); } tasks_.clear(); } srs_error_t MockAsyncCallWorker::execute(ISrsAsyncCallTask *t) { execute_count_++; tasks_.push_back(t); return srs_success; } srs_error_t MockAsyncCallWorker::start() { return srs_success; } void MockAsyncCallWorker::stop() { } MockAppConfigForHttpStreamServer::MockAppConfigForHttpStreamServer() { http_remux_enabled_ = true; http_remux_mount_ = "[vhost]/[app]/[stream].flv"; vhost_directive_ = NULL; } MockAppConfigForHttpStreamServer::~MockAppConfigForHttpStreamServer() { srs_freep(vhost_directive_); } bool MockAppConfigForHttpStreamServer::get_vhost_http_remux_enabled(std::string vhost) { return http_remux_enabled_; } std::string MockAppConfigForHttpStreamServer::get_vhost_http_remux_mount(std::string vhost) { return http_remux_mount_; } SrsConfDirective *MockAppConfigForHttpStreamServer::get_vhost(std::string vhost, bool try_default_vhost) { return vhost_directive_; } bool MockAppConfigForHttpStreamServer::get_vhost_enabled(SrsConfDirective *conf) { return conf != NULL; } MockHttpMessageForDynamicMatch::MockHttpMessageForDynamicMatch() : SrsHttpMessage() { path_ = "/live/stream1.flv"; ext_ = ".flv"; host_ = "test.vhost"; mock_conn_ = new MockHttpConn(); set_connection(mock_conn_); // Initialize the URL properly so to_request() can parse it SrsHttpHeader header; header.set("Host", host_); set_basic(HTTP_REQUEST, HTTP_GET, HTTP_STATUS_OK, 0); set_header(&header, true); set_url(path_, false); } MockHttpMessageForDynamicMatch::~MockHttpMessageForDynamicMatch() { srs_freep(mock_conn_); } std::string MockHttpMessageForDynamicMatch::path() { return path_; } std::string MockHttpMessageForDynamicMatch::ext() { return ext_; } std::string MockHttpMessageForDynamicMatch::host() { return host_; } VOID TEST(SrsHttpStreamServerTest, HttpMountAndUnmount) { srs_error_t err = srs_success; // Test the major use scenario: http_mount creates stream entry from template, // and http_unmount marks it for disposal // This covers the typical HTTP-FLV live streaming mount/unmount use case // Create mock config MockAppConfigForHttpStreamServer *mock_config = new MockAppConfigForHttpStreamServer(); // Use a nested scope to control server lifetime { // Create SrsHttpStreamServer SrsUniquePtr server(new SrsHttpStreamServer()); // Replace the async worker created in constructor with our mock MockAsyncCallWorker *mock_async = new MockAsyncCallWorker(); srs_freep(server->async_); server->async_ = mock_async; server->config_ = mock_config; // Setup template handler for vhost std::string vhost = "test.vhost"; std::string mount = "[vhost]/[app]/[stream].flv"; SrsLiveEntry *tmpl = new SrsLiveEntry(mount); server->templateHandlers_[vhost] = tmpl; // Create mock request for stream SrsUniquePtr mock_request(new MockBufferCacheRequest(vhost, "live", "stream1")); // Test http_mount - should create stream entry from template HELPER_EXPECT_SUCCESS(server->http_mount(mock_request.get())); // Verify stream entry was created std::string sid = mock_request->get_stream_url(); EXPECT_TRUE(server->streamHandlers_.find(sid) != server->streamHandlers_.end()); SrsLiveEntry *entry = server->streamHandlers_[sid]; EXPECT_TRUE(entry != NULL); EXPECT_FALSE(entry->disposing_); EXPECT_TRUE(entry->stream_ != NULL); EXPECT_TRUE(entry->cache_ != NULL); EXPECT_TRUE(entry->req_ != NULL); // Verify mount path was correctly generated std::string expected_mount = "test.vhost/live/stream1.flv"; EXPECT_STREQ(expected_mount.c_str(), entry->mount_.c_str()); // Test http_mount again with same request - should reuse existing entry HELPER_EXPECT_SUCCESS(server->http_mount(mock_request.get())); EXPECT_EQ(1, (int)server->streamHandlers_.size()); // Test http_unmount - should mark entry as disposing and schedule async destroy server->http_unmount(mock_request.get()); // Verify entry is marked as disposing EXPECT_TRUE(entry->disposing_); // Verify async task was scheduled EXPECT_EQ(1, mock_async->execute_count_); EXPECT_EQ(1, (int)mock_async->tasks_.size()); // Test http_mount after unmount - should fail with ERROR_STREAM_DISPOSING err = server->http_mount(mock_request.get()); EXPECT_TRUE(err != srs_success); EXPECT_EQ(ERROR_STREAM_DISPOSING, srs_error_code(err)); srs_freep(err); // Server destructor will be called here when exiting scope // It will call async_->stop() and free async_, so we don't need to do it manually } // Now we can safely free the mock config srs_freep(mock_config); } VOID TEST(SrsGoApiSummariesTest, ServeHttpSuccess) { srs_error_t err; // Test the major use scenario: serve_http returns JSON response with server info and summaries // This covers the typical HTTP API /api/v1/summaries request use case // Create SrsGoApiSummaries handler SrsUniquePtr handler(new SrsGoApiSummaries()); // Create mock HTTP response writer and message MockResponseWriter mock_writer; SrsUniquePtr mock_message(new MockHttpMessageForApiResponse()); // Call serve_http - should return success and write JSON response HELPER_EXPECT_SUCCESS(handler->serve_http(&mock_writer, mock_message.get())); // Verify that response was written string response = string(mock_writer.io.out_buffer.bytes(), mock_writer.io.out_buffer.length()); EXPECT_TRUE(response.length() > 0); // The response includes HTTP headers, we need to extract just the JSON body // Find the start of JSON (after the double CRLF that separates headers from body) size_t json_start = response.find("\r\n\r\n"); ASSERT_TRUE(json_start != string::npos); string json_body = response.substr(json_start + 4); // Parse the JSON response SrsJsonAny *json = SrsJsonAny::loads(json_body); ASSERT_TRUE(json != NULL); ASSERT_TRUE(json->is_object()); SrsUniquePtr obj((SrsJsonObject *)json); // Verify "code" field is ERROR_SUCCESS SrsJsonAny *code_any = obj->get_property("code"); ASSERT_TRUE(code_any != NULL); ASSERT_TRUE(code_any->is_integer()); EXPECT_EQ(ERROR_SUCCESS, code_any->to_integer()); // Verify "server" field exists and is a string SrsJsonAny *server_any = obj->get_property("server"); ASSERT_TRUE(server_any != NULL); ASSERT_TRUE(server_any->is_string()); // Verify "service" field exists and is a string SrsJsonAny *service_any = obj->get_property("service"); ASSERT_TRUE(service_any != NULL); ASSERT_TRUE(service_any->is_string()); // Verify "pid" field exists and is a string SrsJsonAny *pid_any = obj->get_property("pid"); ASSERT_TRUE(pid_any != NULL); ASSERT_TRUE(pid_any->is_string()); // Verify "data" object exists (from srs_api_dump_summaries) SrsJsonAny *data_any = obj->get_property("data"); ASSERT_TRUE(data_any != NULL); ASSERT_TRUE(data_any->is_object()); } VOID TEST(SrsGoApiAuthorsTest, ServeHttpSuccess) { srs_error_t err; // Test the major use scenario: serve_http returns JSON response with license and contributors // This covers the typical HTTP API /api/v1/authors request use case // Create SrsGoApiAuthors handler SrsUniquePtr handler(new SrsGoApiAuthors()); // Create mock HTTP response writer and message MockResponseWriter mock_writer; SrsUniquePtr mock_message(new MockHttpMessageForApiResponse()); // Call serve_http - should return success and write JSON response HELPER_EXPECT_SUCCESS(handler->serve_http(&mock_writer, mock_message.get())); // Verify that response was written string response = string(mock_writer.io.out_buffer.bytes(), mock_writer.io.out_buffer.length()); EXPECT_TRUE(response.length() > 0); // The response includes HTTP headers, we need to extract just the JSON body // Find the start of JSON (after the double CRLF that separates headers from body) size_t json_start = response.find("\r\n\r\n"); ASSERT_TRUE(json_start != string::npos); string json_body = response.substr(json_start + 4); // Parse the JSON response SrsJsonAny *json = SrsJsonAny::loads(json_body); ASSERT_TRUE(json != NULL); ASSERT_TRUE(json->is_object()); SrsUniquePtr obj((SrsJsonObject *)json); // Verify "code" field is ERROR_SUCCESS SrsJsonAny *code_any = obj->get_property("code"); ASSERT_TRUE(code_any != NULL); ASSERT_TRUE(code_any->is_integer()); EXPECT_EQ(ERROR_SUCCESS, code_any->to_integer()); // Verify "server" field exists and is a string SrsJsonAny *server_any = obj->get_property("server"); ASSERT_TRUE(server_any != NULL); ASSERT_TRUE(server_any->is_string()); // Verify "service" field exists and is a string SrsJsonAny *service_any = obj->get_property("service"); ASSERT_TRUE(service_any != NULL); ASSERT_TRUE(service_any->is_string()); // Verify "pid" field exists and is a string SrsJsonAny *pid_any = obj->get_property("pid"); ASSERT_TRUE(pid_any != NULL); ASSERT_TRUE(pid_any->is_string()); // Verify "data" object exists SrsJsonAny *data_any = obj->get_property("data"); ASSERT_TRUE(data_any != NULL); ASSERT_TRUE(data_any->is_object()); SrsJsonObject *data = (SrsJsonObject *)data_any; // Verify "license" field exists and contains "MIT" SrsJsonAny *license_any = data->get_property("license"); ASSERT_TRUE(license_any != NULL); ASSERT_TRUE(license_any->is_string()); EXPECT_STREQ("MIT", license_any->to_str().c_str()); // Verify "contributors" field exists and contains the contributors URL SrsJsonAny *contributors_any = data->get_property("contributors"); ASSERT_TRUE(contributors_any != NULL); ASSERT_TRUE(contributors_any->is_string()); string contributors_url = contributors_any->to_str(); EXPECT_TRUE(contributors_url.find("github.com/ossrs/srs") != string::npos); EXPECT_TRUE(contributors_url.find("AUTHORS.md") != string::npos); } VOID TEST(SrsGoApiFeaturesTest, ServeHttpSuccess) { srs_error_t err; // Test the major use scenario: serve_http returns JSON response with build info and feature flags // This covers the typical HTTP API /api/v1/features request use case // Create SrsGoApiFeatures handler SrsUniquePtr handler(new SrsGoApiFeatures()); // Create mock HTTP response writer and message MockResponseWriter mock_writer; SrsUniquePtr mock_message(new MockHttpMessageForApiResponse()); // Call serve_http - should return success and write JSON response HELPER_EXPECT_SUCCESS(handler->serve_http(&mock_writer, mock_message.get())); // Verify that response was written string response = string(mock_writer.io.out_buffer.bytes(), mock_writer.io.out_buffer.length()); EXPECT_TRUE(response.length() > 0); // The response includes HTTP headers, we need to extract just the JSON body // Find the start of JSON (after the double CRLF that separates headers from body) size_t json_start = response.find("\r\n\r\n"); ASSERT_TRUE(json_start != string::npos); string json_body = response.substr(json_start + 4); // Parse the JSON response SrsJsonAny *json = SrsJsonAny::loads(json_body); ASSERT_TRUE(json != NULL); ASSERT_TRUE(json->is_object()); SrsUniquePtr obj((SrsJsonObject *)json); // Verify "code" field is ERROR_SUCCESS SrsJsonAny *code_any = obj->get_property("code"); ASSERT_TRUE(code_any != NULL); ASSERT_TRUE(code_any->is_integer()); EXPECT_EQ(ERROR_SUCCESS, code_any->to_integer()); // Verify "server" field exists and is a string SrsJsonAny *server_any = obj->get_property("server"); ASSERT_TRUE(server_any != NULL); ASSERT_TRUE(server_any->is_string()); // Verify "service" field exists and is a string SrsJsonAny *service_any = obj->get_property("service"); ASSERT_TRUE(service_any != NULL); ASSERT_TRUE(service_any->is_string()); // Verify "pid" field exists and is a string SrsJsonAny *pid_any = obj->get_property("pid"); ASSERT_TRUE(pid_any != NULL); ASSERT_TRUE(pid_any->is_string()); // Verify "data" object exists SrsJsonAny *data_any = obj->get_property("data"); ASSERT_TRUE(data_any != NULL); ASSERT_TRUE(data_any->is_object()); SrsJsonObject *data = (SrsJsonObject *)data_any; // Verify build info fields exist in data SrsJsonAny *options_any = data->get_property("options"); ASSERT_TRUE(options_any != NULL); ASSERT_TRUE(options_any->is_string()); SrsJsonAny *options2_any = data->get_property("options2"); ASSERT_TRUE(options2_any != NULL); ASSERT_TRUE(options2_any->is_string()); SrsJsonAny *build_any = data->get_property("build"); ASSERT_TRUE(build_any != NULL); ASSERT_TRUE(build_any->is_string()); SrsJsonAny *build2_any = data->get_property("build2"); ASSERT_TRUE(build2_any != NULL); ASSERT_TRUE(build2_any->is_string()); // Verify "features" object exists in data SrsJsonAny *features_any = data->get_property("features"); ASSERT_TRUE(features_any != NULL); ASSERT_TRUE(features_any->is_object()); SrsJsonObject *features = (SrsJsonObject *)features_any; // Verify key feature flags exist and are boolean SrsJsonAny *ssl_any = features->get_property("ssl"); ASSERT_TRUE(ssl_any != NULL); ASSERT_TRUE(ssl_any->is_boolean()); EXPECT_TRUE(ssl_any->to_boolean()); SrsJsonAny *hls_any = features->get_property("hls"); ASSERT_TRUE(hls_any != NULL); ASSERT_TRUE(hls_any->is_boolean()); EXPECT_TRUE(hls_any->to_boolean()); SrsJsonAny *hds_any = features->get_property("hds"); ASSERT_TRUE(hds_any != NULL); ASSERT_TRUE(hds_any->is_boolean()); SrsJsonAny *callback_any = features->get_property("callback"); ASSERT_TRUE(callback_any != NULL); ASSERT_TRUE(callback_any->is_boolean()); EXPECT_TRUE(callback_any->to_boolean()); SrsJsonAny *api_any = features->get_property("api"); ASSERT_TRUE(api_any != NULL); ASSERT_TRUE(api_any->is_boolean()); EXPECT_TRUE(api_any->to_boolean()); SrsJsonAny *httpd_any = features->get_property("httpd"); ASSERT_TRUE(httpd_any != NULL); ASSERT_TRUE(httpd_any->is_boolean()); EXPECT_TRUE(httpd_any->to_boolean()); SrsJsonAny *dvr_any = features->get_property("dvr"); ASSERT_TRUE(dvr_any != NULL); ASSERT_TRUE(dvr_any->is_boolean()); EXPECT_TRUE(dvr_any->to_boolean()); SrsJsonAny *transcode_any = features->get_property("transcode"); ASSERT_TRUE(transcode_any != NULL); ASSERT_TRUE(transcode_any->is_boolean()); EXPECT_TRUE(transcode_any->to_boolean()); SrsJsonAny *ingest_any = features->get_property("ingest"); ASSERT_TRUE(ingest_any != NULL); ASSERT_TRUE(ingest_any->is_boolean()); EXPECT_TRUE(ingest_any->to_boolean()); SrsJsonAny *stat_any = features->get_property("stat"); ASSERT_TRUE(stat_any != NULL); ASSERT_TRUE(stat_any->is_boolean()); EXPECT_TRUE(stat_any->to_boolean()); SrsJsonAny *caster_any = features->get_property("caster"); ASSERT_TRUE(caster_any != NULL); ASSERT_TRUE(caster_any->is_boolean()); EXPECT_TRUE(caster_any->to_boolean()); // Verify performance feature flags exist and are boolean SrsJsonAny *complex_send_any = features->get_property("complex_send"); ASSERT_TRUE(complex_send_any != NULL); ASSERT_TRUE(complex_send_any->is_boolean()); SrsJsonAny *tcp_nodelay_any = features->get_property("tcp_nodelay"); ASSERT_TRUE(tcp_nodelay_any != NULL); ASSERT_TRUE(tcp_nodelay_any->is_boolean()); SrsJsonAny *so_sendbuf_any = features->get_property("so_sendbuf"); ASSERT_TRUE(so_sendbuf_any != NULL); ASSERT_TRUE(so_sendbuf_any->is_boolean()); SrsJsonAny *mr_any = features->get_property("mr"); ASSERT_TRUE(mr_any != NULL); ASSERT_TRUE(mr_any->is_boolean()); } VOID TEST(SrsHttpStreamServerTest, DynamicMatchHttpFlv) { srs_error_t err = srs_success; // Test the major use scenario: dynamic_match for HTTP-FLV stream request // This covers the typical edge server HTTP-FLV dynamic matching use case // where a request comes in for a stream that hasn't been mounted yet // Create mock config with vhost support MockAppConfigForHttpStreamServer *mock_config = new MockAppConfigForHttpStreamServer(); mock_config->http_remux_enabled_ = true; mock_config->http_remux_mount_ = "[vhost]/[app]/[stream].flv"; // Create vhost directive SrsConfDirective *vhost_directive = new SrsConfDirective(); vhost_directive->name_ = "vhost"; vhost_directive->args_.push_back("test.vhost"); mock_config->vhost_directive_ = vhost_directive; // Use a nested scope to control server lifetime { // Create SrsHttpStreamServer SrsUniquePtr server(new SrsHttpStreamServer()); // Replace the async worker created in constructor with our mock MockAsyncCallWorker *mock_async = new MockAsyncCallWorker(); srs_freep(server->async_); server->async_ = mock_async; server->config_ = mock_config; // Setup template handler for vhost std::string vhost = "test.vhost"; std::string mount = "[vhost]/[app]/[stream].flv"; SrsLiveEntry *tmpl = new SrsLiveEntry(mount); server->templateHandlers_[vhost] = tmpl; // Create mock HTTP message for HTTP-FLV request SrsUniquePtr mock_request(new MockHttpMessageForDynamicMatch()); mock_request->path_ = "/live/stream1.flv"; mock_request->ext_ = ".flv"; mock_request->host_ = "test.vhost"; // Test dynamic_match - should create stream entry from template ISrsHttpHandler *handler = NULL; HELPER_EXPECT_SUCCESS(server->dynamic_match(mock_request.get(), &handler)); // Verify handler was set EXPECT_TRUE(handler != NULL); // Verify stream entry was created in streamHandlers_ EXPECT_EQ(1, (int)server->streamHandlers_.size()); // Verify the stream entry has correct properties std::map::iterator it = server->streamHandlers_.begin(); EXPECT_TRUE(it != server->streamHandlers_.end()); SrsLiveEntry *entry = it->second; EXPECT_TRUE(entry != NULL); EXPECT_TRUE(entry->stream_ != NULL); EXPECT_TRUE(entry->cache_ != NULL); EXPECT_TRUE(entry->req_ != NULL); EXPECT_FALSE(entry->disposing_); // Verify mount path was correctly generated std::string expected_mount = "test.vhost/live/stream1.flv"; EXPECT_STREQ(expected_mount.c_str(), entry->mount_.c_str()); // Test dynamic_match again with same request - should reuse existing handler ISrsHttpHandler *handler2 = NULL; HELPER_EXPECT_SUCCESS(server->dynamic_match(mock_request.get(), &handler2)); EXPECT_TRUE(handler2 != NULL); EXPECT_EQ(handler, handler2); EXPECT_EQ(1, (int)server->streamHandlers_.size()); // Server destructor will be called here when exiting scope } // Now we can safely free the mock config srs_freep(mock_config); } MockLiveStreamForDestroy::MockLiveStreamForDestroy() { alive_ = true; expired_ = false; } MockLiveStreamForDestroy::~MockLiveStreamForDestroy() { } srs_error_t MockLiveStreamForDestroy::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r) { return srs_success; } srs_error_t MockLiveStreamForDestroy::update_auth(ISrsRequest *r) { return srs_success; } bool MockLiveStreamForDestroy::alive() { return alive_; } void MockLiveStreamForDestroy::expire() { expired_ = true; alive_ = false; } MockBufferCacheForDestroy::MockBufferCacheForDestroy() { alive_ = true; stopped_ = false; } MockBufferCacheForDestroy::~MockBufferCacheForDestroy() { } srs_error_t MockBufferCacheForDestroy::start() { return srs_success; } void MockBufferCacheForDestroy::stop() { stopped_ = true; alive_ = false; } bool MockBufferCacheForDestroy::alive() { return alive_; } srs_error_t MockBufferCacheForDestroy::dump_cache(ISrsLiveConsumer *consumer, SrsRtmpJitterAlgorithm jitter) { return srs_success; } srs_error_t MockBufferCacheForDestroy::update_auth(ISrsRequest *r) { return srs_success; } MockBufferEncoderForStreamingSend::MockBufferEncoderForStreamingSend() { write_audio_count_ = 0; write_video_count_ = 0; write_metadata_count_ = 0; write_error_ = srs_success; } MockBufferEncoderForStreamingSend::~MockBufferEncoderForStreamingSend() { srs_freep(write_error_); } srs_error_t MockBufferEncoderForStreamingSend::initialize(SrsFileWriter *w, ISrsBufferCache *c) { return srs_success; } srs_error_t MockBufferEncoderForStreamingSend::write_audio(int64_t timestamp, char *data, int size) { write_audio_count_++; audio_timestamps_.push_back(timestamp); return srs_error_copy(write_error_); } srs_error_t MockBufferEncoderForStreamingSend::write_video(int64_t timestamp, char *data, int size) { write_video_count_++; video_timestamps_.push_back(timestamp); return srs_error_copy(write_error_); } srs_error_t MockBufferEncoderForStreamingSend::write_metadata(int64_t timestamp, char *data, int size) { write_metadata_count_++; metadata_timestamps_.push_back(timestamp); return srs_error_copy(write_error_); } bool MockBufferEncoderForStreamingSend::has_cache() { return false; } srs_error_t MockBufferEncoderForStreamingSend::dump_cache(ISrsLiveConsumer *consumer, SrsRtmpJitterAlgorithm jitter) { return srs_success; } void MockBufferEncoderForStreamingSend::reset() { write_audio_count_ = 0; write_video_count_ = 0; write_metadata_count_ = 0; audio_timestamps_.clear(); video_timestamps_.clear(); metadata_timestamps_.clear(); srs_freep(write_error_); write_error_ = srs_success; } VOID TEST(SrsLiveStreamTest, StreamingSendMessagesWithMixedPackets) { srs_error_t err; // Test the major use scenario: streaming_send_messages with mixed audio, video, and metadata packets // This covers the typical HTTP streaming workflow where encoder writes different packet types // Create mock request and buffer cache SrsUniquePtr mock_request(new MockBufferCacheRequest("test.vhost", "live", "stream1")); SrsUniquePtr mock_cache(new MockBufferCacheForAac()); // Create SrsLiveStream SrsUniquePtr live_stream(new SrsLiveStream(mock_request.get(), mock_cache.get())); // Create mock encoder MockBufferEncoderForStreamingSend mock_encoder; // Create array of media packets with mixed types const int count = 5; SrsMediaPacket *msgs[count]; // Create video packet msgs[0] = new SrsMediaPacket(); msgs[0]->timestamp_ = 1000; msgs[0]->message_type_ = SrsFrameTypeVideo; char *video_data = new char[10]; memset(video_data, 0x17, 10); msgs[0]->wrap(video_data, 10); // Create audio packet msgs[1] = new SrsMediaPacket(); msgs[1]->timestamp_ = 1020; msgs[1]->message_type_ = SrsFrameTypeAudio; char *audio_data = new char[8]; memset(audio_data, 0xAF, 8); msgs[1]->wrap(audio_data, 8); // Create metadata packet msgs[2] = new SrsMediaPacket(); msgs[2]->timestamp_ = 1040; msgs[2]->message_type_ = SrsFrameTypeScript; char *metadata_data = new char[12]; memset(metadata_data, 0x02, 12); msgs[2]->wrap(metadata_data, 12); // Create another video packet msgs[3] = new SrsMediaPacket(); msgs[3]->timestamp_ = 1060; msgs[3]->message_type_ = SrsFrameTypeVideo; char *video_data2 = new char[15]; memset(video_data2, 0x27, 15); msgs[3]->wrap(video_data2, 15); // Create another audio packet msgs[4] = new SrsMediaPacket(); msgs[4]->timestamp_ = 1080; msgs[4]->message_type_ = SrsFrameTypeAudio; char *audio_data2 = new char[9]; memset(audio_data2, 0xAF, 9); msgs[4]->wrap(audio_data2, 9); // Test streaming_send_messages - should call encoder methods for each packet type HELPER_EXPECT_SUCCESS(live_stream->streaming_send_messages(&mock_encoder, msgs, count)); // Verify encoder methods were called with correct counts EXPECT_EQ(2, mock_encoder.write_video_count_); EXPECT_EQ(2, mock_encoder.write_audio_count_); EXPECT_EQ(1, mock_encoder.write_metadata_count_); // Verify timestamps were passed correctly EXPECT_EQ(2, (int)mock_encoder.video_timestamps_.size()); EXPECT_EQ(1000, mock_encoder.video_timestamps_[0]); EXPECT_EQ(1060, mock_encoder.video_timestamps_[1]); EXPECT_EQ(2, (int)mock_encoder.audio_timestamps_.size()); EXPECT_EQ(1020, mock_encoder.audio_timestamps_[0]); EXPECT_EQ(1080, mock_encoder.audio_timestamps_[1]); EXPECT_EQ(1, (int)mock_encoder.metadata_timestamps_.size()); EXPECT_EQ(1040, mock_encoder.metadata_timestamps_[0]); // Clean up - free the messages for (int i = 0; i < count; i++) { srs_freep(msgs[i]); } } VOID TEST(SrsLiveStreamTest, DoServeHttpFlvWithDisabledEntry) { srs_error_t err; // Test the major use scenario: do_serve_http with FLV encoder and entry_->enabled = false // This covers the typical HTTP-FLV streaming initialization and immediate exit scenario // Create mock request SrsUniquePtr mock_request(new MockBufferCacheRequest("test.vhost", "live", "stream1")); // Create mock buffer cache SrsUniquePtr mock_cache(new MockBufferCacheForAac()); // Create SrsLiveStream SrsUniquePtr live_stream(new SrsLiveStream(mock_request.get(), mock_cache.get())); // Create and set mock entry with enabled = false to exit the while loop immediately live_stream->entry_ = new SrsHttpMuxEntry(); live_stream->entry_->enabled = false; live_stream->entry_->pattern = "/live/stream.flv"; // Create mock HTTP message and response writer SrsUniquePtr mock_message(new MockHttpMessageForLiveStream()); MockResponseWriter mock_writer; // Create mock live source and consumer SrsUniquePtr mock_source(new MockLiveSourceForQueue()); SrsUniquePtr mock_consumer(new MockLiveConsumerForQueue(mock_source.get())); // Call do_serve_http - should initialize FLV encoder, write header, and exit immediately err = live_stream->do_serve_http(mock_source.get(), mock_consumer.get(), &mock_writer, mock_message.get()); // Verify that the method returns ERROR_HTTP_STREAM_EOF // The while loop should exit immediately because entry_->enabled is false, // and then return ERROR_HTTP_STREAM_EOF to disconnect the client EXPECT_TRUE(err != srs_success); EXPECT_EQ(ERROR_HTTP_STREAM_EOF, srs_error_code(err)); srs_freep(err); // Verify that HTTP header was written (status 200 OK) // Check through the internal writer object EXPECT_TRUE(mock_writer.w->writer_->header_wrote()); // Clean up - set entry_ back to NULL before destruction srs_freep(live_stream->entry_); } VOID TEST(SrsLiveStreamTest, AliveAndExpireWithViewers) { // Test the major use scenario: alive() and expire() with multiple viewers // This covers the typical HTTP-FLV/HLS streaming viewer management use case // where multiple viewers are watching the same stream and need to be expired // Create mock request SrsUniquePtr mock_request(new MockBufferCacheRequest("test.vhost", "live", "stream1")); // Create mock buffer cache SrsUniquePtr mock_cache(new MockBufferCacheForAac()); // Create SrsLiveStream SrsUniquePtr live_stream(new SrsLiveStream(mock_request.get(), mock_cache.get())); // Test alive() with no viewers - should return false EXPECT_FALSE(live_stream->alive()); // Create mock viewers (HTTP connections) SrsUniquePtr viewer1(new MockHttpConn()); SrsUniquePtr viewer2(new MockHttpConn()); SrsUniquePtr viewer3(new MockHttpConn()); // Add viewers to the stream live_stream->viewers_.push_back(viewer1.get()); live_stream->viewers_.push_back(viewer2.get()); live_stream->viewers_.push_back(viewer3.get()); // Test alive() with viewers - should return true EXPECT_TRUE(live_stream->alive()); EXPECT_EQ(3, (int)live_stream->viewers_.size()); // Test expire() - should call expire() on all viewers live_stream->expire(); // Note: We cannot directly verify that expire() was called on each viewer // because MockHttpConn::expire() doesn't track calls. However, the test // verifies that expire() doesn't crash and completes successfully. // Clean up - remove viewers before destruction to avoid assertion failure live_stream->viewers_.clear(); // Verify alive() returns false after clearing viewers EXPECT_FALSE(live_stream->alive()); } VOID TEST(HttpStreamDestroyTest, DestroyStreamSuccess) { srs_error_t err = srs_success; // Test the major use scenario: successfully destroy an HTTP stream entry // This covers the typical cleanup path when a stream is being disposed // Create real SrsHttpServeMux SrsUniquePtr mux(new SrsHttpServeMux()); HELPER_EXPECT_SUCCESS(mux->initialize()); std::map streamHandlers; // Create a live entry with mock stream and cache std::string sid = "test_stream_id"; std::string mount = "/live/stream.flv"; SrsLiveEntry *entry = new SrsLiveEntry(mount); entry->disposing_ = true; // Create mock stream and cache that will stop immediately MockLiveStreamForDestroy *mock_stream = new MockLiveStreamForDestroy(); MockBufferCacheForDestroy *mock_cache = new MockBufferCacheForDestroy(); // Create mock request MockBufferCacheRequest *mock_req = new MockBufferCacheRequest(); entry->stream_ = mock_stream; entry->cache_ = mock_cache; entry->req_ = mock_req; // Add entry to handlers map streamHandlers[sid] = entry; // Register the handler with mux so unhandle can work HELPER_EXPECT_SUCCESS(mux->handle(mount, mock_stream)); // Create the destroy task SrsUniquePtr destroy_task( new SrsHttpStreamDestroy(mux.get(), &streamHandlers, sid)); // Verify initial state EXPECT_EQ(1, (int)streamHandlers.size()); EXPECT_TRUE(mock_stream->alive_); EXPECT_TRUE(mock_cache->alive_); EXPECT_FALSE(mock_stream->expired_); EXPECT_FALSE(mock_cache->stopped_); // Execute the destroy task - this will free mock_stream and mock_cache HELPER_EXPECT_SUCCESS(destroy_task->call()); // After call(), the stream and cache objects are freed by SrsUniquePtr // We can only verify that the entry was removed from handlers EXPECT_EQ(0, (int)streamHandlers.size()); } VOID TEST(SrsBufferCacheTest, StopAndAlive) { // Test the major use scenario: stop() and alive() methods with fast_cache enabled // This covers the typical HTTP streaming cache lifecycle management use case // Create mock request SrsUniquePtr mock_request(new MockBufferCacheRequest("test.vhost", "live", "stream1")); // Create buffer cache SrsUniquePtr cache(new SrsBufferCache(mock_request.get())); // Test alive() when fast_cache is disabled (default is 0) EXPECT_FALSE(cache->alive()); // Enable fast_cache to test the actual functionality cache->fast_cache_ = 3 * SRS_UTIME_SECONDS; // Replace the real coroutine with a mock coroutine MockCoroutineForRtmpConn *mock_trd = new MockCoroutineForRtmpConn(); srs_freep(cache->trd_); cache->trd_ = mock_trd; // Test alive() when thread is healthy (pull returns success) mock_trd->pull_error_ = srs_success; EXPECT_TRUE(cache->alive()); EXPECT_EQ(1, mock_trd->pull_count_); // Test alive() when thread has error (pull returns error) mock_trd->pull_error_ = srs_error_new(ERROR_SYSTEM_STREAM_BUSY, "mock error"); EXPECT_FALSE(cache->alive()); EXPECT_EQ(2, mock_trd->pull_count_); // Test stop() - should call trd_->stop() cache->stop(); // Note: We can't directly verify stop() was called on mock_trd since MockCoroutineForRtmpConn // doesn't track stop() calls, but we verify it doesn't crash // Test stop() when fast_cache is disabled - should return early without calling trd_->stop() cache->fast_cache_ = 0; cache->stop(); // Should not crash even though fast_cache is 0 } VOID TEST(SrsBufferCacheTest, CycleWithThreadPullError) { srs_error_t err; // Test the major use scenario: cycle() method with thread pull error // This covers the typical HTTP streaming cache cycle execution where the thread // is interrupted or encounters an error, causing the cycle to exit // Create mock request SrsUniquePtr mock_request(new MockBufferCacheRequest("test.vhost", "live", "stream1")); // Create buffer cache SrsUniquePtr cache(new SrsBufferCache(mock_request.get())); // Enable fast_cache to allow cycle to run cache->fast_cache_ = 3 * SRS_UTIME_SECONDS; // Replace the real coroutine with a mock coroutine that will return error on pull MockCoroutineForRtmpConn *mock_trd = new MockCoroutineForRtmpConn(); srs_freep(cache->trd_); cache->trd_ = mock_trd; // Set pull_error to make trd_->pull() return error immediately // This will cause cycle() to exit the while loop and return the error mock_trd->pull_error_ = srs_error_new(ERROR_SYSTEM_STREAM_BUSY, "mock thread interrupted"); // Call cycle() - should create live source, create consumer, dump consumer, // then enter the while loop and exit immediately when trd_->pull() returns error err = cache->cycle(); // Verify that cycle() returned an error (wrapped with "buffer cache" context) EXPECT_TRUE(err != srs_success); EXPECT_EQ(ERROR_SYSTEM_STREAM_BUSY, srs_error_code(err)); srs_freep(err); // Verify that pull() was called at least once EXPECT_TRUE(mock_trd->pull_count_ > 0); } VOID TEST(AppHttpStreamTest, Mp3StreamEncoderMajorScenario) { srs_error_t err; // Test the major use scenario: initialize encoder, write MP3 audio data, ignore video/metadata // This covers the typical MP3 HTTP streaming use case // Create mock file writer and buffer cache SrsUniquePtr writer(new MockSrsFileWriter()); HELPER_EXPECT_SUCCESS(writer->open("test.mp3")); MockBufferCacheForAac mock_cache; // Create MP3 stream encoder SrsUniquePtr encoder(new SrsMp3StreamEncoder()); // Test initialization - should initialize transmuxer and write MP3 header HELPER_EXPECT_SUCCESS(encoder->initialize(writer.get(), &mock_cache)); // Verify MP3 header was written (ID3v2 header should be present) EXPECT_TRUE(writer->filesize() > 0); // Test has_cache - should always return true for MP3 encoder EXPECT_TRUE(encoder->has_cache()); // Test write_audio - should write MP3 audio data // FLV audio tag format: first byte is sound format (MP3=2 in upper 4 bits) // 0x2f = 0010 1111 = MP3 codec (2) + 44kHz (3) + 16-bit (1) + stereo (1) char audio_data[] = {0x2f, 0x00, (char)0xff, (char)0xfb, (char)0x90, 0x00}; // FLV audio tag + MP3 frame HELPER_EXPECT_SUCCESS(encoder->write_audio(1000, audio_data, sizeof(audio_data))); // Verify audio data was written int64_t size_after_audio = writer->filesize(); EXPECT_TRUE(size_after_audio > 0); // Test write_video - should be ignored (MP3 is audio-only) char video_data[] = {0x17, 0x00, 0x00, 0x00, 0x00}; int64_t size_before_video = writer->filesize(); HELPER_EXPECT_SUCCESS(encoder->write_video(2000, video_data, sizeof(video_data))); // Verify video data was NOT written (size should remain the same) EXPECT_EQ(size_before_video, writer->filesize()); // Test write_metadata - should be ignored (MP3 doesn't use FLV metadata) char metadata_data[] = {0x02, 0x00, 0x0a, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61}; int64_t size_before_metadata = writer->filesize(); HELPER_EXPECT_SUCCESS(encoder->write_metadata(3000, metadata_data, sizeof(metadata_data))); // Verify metadata was NOT written (size should remain the same) EXPECT_EQ(size_before_metadata, writer->filesize()); // Test dump_cache - should delegate to buffer cache HELPER_EXPECT_SUCCESS(encoder->dump_cache(NULL, SrsRtmpJitterAlgorithmOFF)); // Verify dump_cache was called on the mock cache EXPECT_EQ(1, mock_cache.dump_cache_count_); EXPECT_EQ(SrsRtmpJitterAlgorithmOFF, mock_cache.last_jitter_); } VOID TEST(HttpApiTest, JsonpResponse) { srs_error_t err; // Create mock response writer MockResponseWriter w; // Test JSONP response with callback and data string callback = "myCallback"; string data = "{\"code\":0,\"message\":\"success\"}"; HELPER_EXPECT_SUCCESS(srs_api_response_jsonp(&w, callback, data)); // Verify the response contains callback(data) format string response = HELPER_BUFFER2STR(&w.io.out_buffer); EXPECT_TRUE(response.find("myCallback({\"code\":0,\"message\":\"success\"})") != string::npos); } MockHttpMessageForApiResponse::MockHttpMessageForApiResponse() { mock_conn_ = new MockHttpConn(); set_connection(mock_conn_); is_jsonp_ = false; callback_ = ""; path_ = "/api/test"; } MockHttpMessageForApiResponse::~MockHttpMessageForApiResponse() { srs_freep(mock_conn_); } bool MockHttpMessageForApiResponse::is_jsonp() { return is_jsonp_; } std::string MockHttpMessageForApiResponse::query_get(std::string key) { if (key == "callback") { return callback_; } std::map::iterator it = query_params_.find(key); if (it != query_params_.end()) { return it->second; } return ""; } std::string MockHttpMessageForApiResponse::path() { return path_; } VOID TEST(HttpApiTest, GoApiV1ServeHttp) { srs_error_t err; // Test the major use scenario: SrsGoApiV1::serve_http returns JSON with server info and API URLs // This covers the typical HTTP API v1 root endpoint use case // Create SrsGoApiV1 instance SrsUniquePtr api(new SrsGoApiV1()); // Create mock statistic MockStatisticForLiveStream mock_stat; // Replace stat_ with mock api->stat_ = &mock_stat; // Create mock HTTP message and response writer SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); MockResponseWriter mock_writer; // Call serve_http - should return JSON with server info and API URLs HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); // Verify response was written string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); EXPECT_TRUE(response.length() > 0); // Verify response contains expected JSON fields EXPECT_TRUE(response.find("\"code\":0") != string::npos); EXPECT_TRUE(response.find("\"server\":\"mock_server_id\"") != string::npos); EXPECT_TRUE(response.find("\"service\":\"mock_service_id\"") != string::npos); EXPECT_TRUE(response.find("\"pid\":\"mock_pid\"") != string::npos); // Verify response contains API URLs EXPECT_TRUE(response.find("\"urls\"") != string::npos); EXPECT_TRUE(response.find("\"versions\"") != string::npos); EXPECT_TRUE(response.find("\"summaries\"") != string::npos); EXPECT_TRUE(response.find("\"rusages\"") != string::npos); EXPECT_TRUE(response.find("\"vhosts\"") != string::npos); EXPECT_TRUE(response.find("\"streams\"") != string::npos); EXPECT_TRUE(response.find("\"clients\"") != string::npos); EXPECT_TRUE(response.find("\"raw\"") != string::npos); EXPECT_TRUE(response.find("\"clusters\"") != string::npos); // Verify response contains test URLs EXPECT_TRUE(response.find("\"tests\"") != string::npos); EXPECT_TRUE(response.find("\"requests\"") != string::npos); EXPECT_TRUE(response.find("\"errors\"") != string::npos); EXPECT_TRUE(response.find("\"redirects\"") != string::npos); // Clean up - set stat_ back to NULL before destruction api->stat_ = NULL; } VOID TEST(SrsGoApiVhostsTest, ServeHttpGetAllVhosts) { srs_error_t err; // Test the major use scenario: GET request to /api/v1/vhosts/ returns all vhosts // This covers the typical HTTP API request to list all vhosts // Create SrsGoApiVhosts handler SrsUniquePtr handler(new SrsGoApiVhosts()); // Create mock HTTP mux entry with pattern SrsHttpMuxEntry entry; entry.pattern = "/api/v1/vhosts/"; handler->entry_ = &entry; // Create mock HTTP response writer and message MockResponseWriter mock_writer; SrsUniquePtr mock_message(new MockHttpMessageForApiResponse()); // Set the URL to match the pattern (no vhost_id, so list all vhosts) HELPER_EXPECT_SUCCESS(mock_message->set_url("http://127.0.0.1/api/v1/vhosts/", false)); // Call serve_http - should return success and write JSON response with all vhosts HELPER_EXPECT_SUCCESS(handler->serve_http(&mock_writer, mock_message.get())); // Verify that response was written string response = string(mock_writer.io.out_buffer.bytes(), mock_writer.io.out_buffer.length()); EXPECT_TRUE(response.length() > 0); // Extract JSON body from HTTP response (after headers) size_t json_start = response.find("\r\n\r\n"); ASSERT_TRUE(json_start != string::npos); string json_body = response.substr(json_start + 4); // Parse the JSON response SrsJsonAny *json = SrsJsonAny::loads(json_body); ASSERT_TRUE(json != NULL); ASSERT_TRUE(json->is_object()); SrsUniquePtr obj((SrsJsonObject *)json); // Verify "code" field is ERROR_SUCCESS SrsJsonAny *code_any = obj->get_property("code"); ASSERT_TRUE(code_any != NULL); ASSERT_TRUE(code_any->is_integer()); EXPECT_EQ(ERROR_SUCCESS, code_any->to_integer()); // Verify "server" field exists and is a string SrsJsonAny *server_any = obj->get_property("server"); ASSERT_TRUE(server_any != NULL); ASSERT_TRUE(server_any->is_string()); // Verify "service" field exists and is a string SrsJsonAny *service_any = obj->get_property("service"); ASSERT_TRUE(service_any != NULL); ASSERT_TRUE(service_any->is_string()); // Verify "pid" field exists and is a string SrsJsonAny *pid_any = obj->get_property("pid"); ASSERT_TRUE(pid_any != NULL); ASSERT_TRUE(pid_any->is_string()); // Verify "vhosts" array exists (for listing all vhosts) SrsJsonAny *vhosts_any = obj->get_property("vhosts"); ASSERT_TRUE(vhosts_any != NULL); ASSERT_TRUE(vhosts_any->is_array()); } VOID TEST(HttpApiTest, ApiResponseCodeWithJsonAndJsonp) { srs_error_t err; // Test the major use scenario: srs_api_response_code handles both JSON and JSONP responses // This covers the typical HTTP API response workflow where the function automatically // detects whether the request is JSONP (has callback parameter) and responds accordingly // Test 1: JSON response with integer code (no JSONP) { MockResponseWriter mock_writer; SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->is_jsonp_ = false; HELPER_EXPECT_SUCCESS(srs_api_response_code(&mock_writer, mock_msg.get(), 0)); // Verify JSON response format: {"code":0} string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); EXPECT_TRUE(response.find("{\"code\":0}") != string::npos); EXPECT_TRUE(response.find("callback") == string::npos); // No callback wrapper } // Test 2: JSONP response with integer code (has callback parameter) { MockResponseWriter mock_writer; SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->is_jsonp_ = true; mock_msg->callback_ = "myCallback"; HELPER_EXPECT_SUCCESS(srs_api_response_code(&mock_writer, mock_msg.get(), 0)); // Verify JSONP response format: myCallback({"code":0}) string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); EXPECT_TRUE(response.find("myCallback({\"code\":0})") != string::npos); } // Test 3: JSON response with srs_error_t code (no JSONP) { MockResponseWriter mock_writer; SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->is_jsonp_ = false; srs_error_t test_err = srs_error_new(ERROR_RTMP_STREAM_NOT_FOUND, "stream not found"); HELPER_EXPECT_SUCCESS(srs_api_response_code(&mock_writer, mock_msg.get(), test_err)); // Note: srs_api_response_code frees the error internally, so we don't need to free it // Verify JSON response contains the error code string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); char expected[128]; snprintf(expected, sizeof(expected), "{\"code\":%d}", ERROR_RTMP_STREAM_NOT_FOUND); EXPECT_TRUE(response.find(expected) != string::npos); } // Test 4: JSONP response with srs_error_t code (has callback parameter) { MockResponseWriter mock_writer; SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->is_jsonp_ = true; mock_msg->callback_ = "errorCallback"; srs_error_t test_err = srs_error_new(ERROR_RTMP_STREAM_NOT_FOUND, "stream not found"); HELPER_EXPECT_SUCCESS(srs_api_response_code(&mock_writer, mock_msg.get(), test_err)); // Note: srs_api_response_code frees the error internally, so we don't need to free it // Verify JSONP response format: errorCallback({"code":ERROR_CODE}) string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); char expected[128]; snprintf(expected, sizeof(expected), "errorCallback({\"code\":%d})", ERROR_RTMP_STREAM_NOT_FOUND); EXPECT_TRUE(response.find(expected) != string::npos); } // Test 5: JSON response with success code (srs_success as srs_error_t) { MockResponseWriter mock_writer; SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->is_jsonp_ = false; srs_error_t success_err = srs_success; HELPER_EXPECT_SUCCESS(srs_api_response_code(&mock_writer, mock_msg.get(), success_err)); // Verify JSON response format: {"code":0} string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); EXPECT_TRUE(response.find("{\"code\":0}") != string::npos); } // Test the major use scenario: srs_api_response_code with both JSON and JSONP responses // This covers the typical HTTP API response workflow where the function automatically // detects whether the request is JSONP (has callback parameter) and responds accordingly // Test 1: JSON response with success code - most common use case { MockResponseWriterForJsonp w; SrsUniquePtr msg(new MockHttpMessage()); // Call srs_api_response_code with success code HELPER_EXPECT_SUCCESS(srs_api_response_code(&w, msg.get(), ERROR_SUCCESS)); // Verify JSON response was written with correct code string response = HELPER_BUFFER2STR(&w.io.out_buffer); EXPECT_TRUE(response.find("{\"code\":0}") != string::npos); // Verify content-type is application/json EXPECT_STREQ("application/json", w.header()->content_type().c_str()); } // Test 2: JSON response with error code { MockResponseWriterForJsonp w; SrsUniquePtr msg(new MockHttpMessage()); // Call srs_api_response_code with error code HELPER_EXPECT_SUCCESS(srs_api_response_code(&w, msg.get(), ERROR_RTMP_STREAM_NOT_FOUND)); // Verify error code was written in JSON response string response = HELPER_BUFFER2STR(&w.io.out_buffer); char expected[64]; snprintf(expected, sizeof(expected), "{\"code\":%d}", ERROR_RTMP_STREAM_NOT_FOUND); EXPECT_TRUE(response.find(expected) != string::npos); // Verify content-type is application/json EXPECT_STREQ("application/json", w.header()->content_type().c_str()); } // Test 3: Error code response with automatic error cleanup (srs_error_t overload) { MockResponseWriterForJsonp w; SrsUniquePtr msg(new MockHttpMessage()); // Create an error object - srs_api_response_code will free it automatically srs_error_t error_code = srs_error_new(ERROR_RTMP_STREAM_NOT_FOUND, "stream not found"); // Call srs_api_response_code with error - it should free the error object HELPER_EXPECT_SUCCESS(srs_api_response_code(&w, msg.get(), error_code)); // Verify error code was written in JSON response string response = HELPER_BUFFER2STR(&w.io.out_buffer); char expected[64]; snprintf(expected, sizeof(expected), "{\"code\":%d}", ERROR_RTMP_STREAM_NOT_FOUND); EXPECT_TRUE(response.find(expected) != string::npos); // Verify content-type is application/json EXPECT_STREQ("application/json", w.header()->content_type().c_str()); // Note: error_code was freed by srs_api_response_code, so we don't free it here } // Test 4: JSONP response (with callback parameter) { MockResponseWriterForJsonp w; SrsUniquePtr msg(new MockHttpMessage()); // Set up JSONP request by adding callback query parameter msg->set_url("/api/v1/test?callback=myCallback", false); // Debug: Check if is_jsonp() works bool is_jsonp = msg->is_jsonp(); string callback = msg->query_get("callback"); // Call srs_api_response_code with success code HELPER_EXPECT_SUCCESS(srs_api_response_code(&w, msg.get(), ERROR_SUCCESS)); // Verify response was written string response = HELPER_BUFFER2STR(&w.io.out_buffer); // If is_jsonp() returns true, expect JSONP format, otherwise expect JSON format if (is_jsonp && !callback.empty()) { // JSONP format: callback({"code":0}) EXPECT_TRUE(response.find("myCallback({\"code\":0})") != string::npos); EXPECT_STREQ("text/javascript", w.header()->content_type().c_str()); } else { // JSON format: {"code":0} EXPECT_TRUE(response.find("{\"code\":0}") != string::npos); EXPECT_STREQ("application/json", w.header()->content_type().c_str()); } } } VOID TEST(HttpApiTest, GoApiApiServeHttp) { srs_error_t err; // Test the major use scenario: SrsGoApiApi::serve_http returns API metadata // This covers the typical /api endpoint that provides server information and API version MockResponseWriter mock_writer; SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->is_jsonp_ = false; SrsUniquePtr api(new SrsGoApiApi()); HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); // Verify JSON response contains expected fields string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); // Check for required fields in response EXPECT_TRUE(response.find("\"code\":0") != string::npos); EXPECT_TRUE(response.find("\"server\"") != string::npos); EXPECT_TRUE(response.find("\"service\"") != string::npos); EXPECT_TRUE(response.find("\"pid\"") != string::npos); EXPECT_TRUE(response.find("\"urls\"") != string::npos); EXPECT_TRUE(response.find("\"v1\"") != string::npos); EXPECT_TRUE(response.find("the api version 1.0") != string::npos); } VOID TEST(HttpApiTest, GoApiVersionServeHttp) { srs_error_t err; // Test the major use scenario: SrsGoApiVersion::serve_http returns version information // This covers the typical /api/v1/version endpoint that provides SRS version details MockResponseWriter mock_writer; SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->is_jsonp_ = false; SrsUniquePtr api(new SrsGoApiVersion()); HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); // Verify JSON response contains expected fields string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); // Check for required fields in response EXPECT_TRUE(response.find("\"code\":0") != string::npos); EXPECT_TRUE(response.find("\"server\"") != string::npos); EXPECT_TRUE(response.find("\"service\"") != string::npos); EXPECT_TRUE(response.find("\"pid\"") != string::npos); EXPECT_TRUE(response.find("\"data\"") != string::npos); EXPECT_TRUE(response.find("\"major\"") != string::npos); EXPECT_TRUE(response.find("\"minor\"") != string::npos); EXPECT_TRUE(response.find("\"revision\"") != string::npos); EXPECT_TRUE(response.find("\"version\"") != string::npos); } VOID TEST(HttpApiTest, GoApiRusagesServeHttp) { srs_error_t err; // Test the major use scenario: SrsGoApiRusages::serve_http returns system resource usage information // This covers the typical /api/v1/rusages endpoint that provides system rusage statistics MockResponseWriter mock_writer; SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->is_jsonp_ = false; SrsUniquePtr api(new SrsGoApiRusages()); HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); // Verify JSON response contains expected fields string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); // Check for required fields in response EXPECT_TRUE(response.find("\"code\":0") != string::npos); EXPECT_TRUE(response.find("\"server\"") != string::npos); EXPECT_TRUE(response.find("\"service\"") != string::npos); EXPECT_TRUE(response.find("\"pid\"") != string::npos); EXPECT_TRUE(response.find("\"data\"") != string::npos); // Check for rusage-specific fields EXPECT_TRUE(response.find("\"ok\"") != string::npos); EXPECT_TRUE(response.find("\"sample_time\"") != string::npos); EXPECT_TRUE(response.find("\"ru_utime\"") != string::npos); EXPECT_TRUE(response.find("\"ru_stime\"") != string::npos); EXPECT_TRUE(response.find("\"ru_maxrss\"") != string::npos); EXPECT_TRUE(response.find("\"ru_ixrss\"") != string::npos); EXPECT_TRUE(response.find("\"ru_idrss\"") != string::npos); EXPECT_TRUE(response.find("\"ru_isrss\"") != string::npos); EXPECT_TRUE(response.find("\"ru_minflt\"") != string::npos); EXPECT_TRUE(response.find("\"ru_majflt\"") != string::npos); EXPECT_TRUE(response.find("\"ru_nswap\"") != string::npos); EXPECT_TRUE(response.find("\"ru_inblock\"") != string::npos); EXPECT_TRUE(response.find("\"ru_oublock\"") != string::npos); EXPECT_TRUE(response.find("\"ru_msgsnd\"") != string::npos); EXPECT_TRUE(response.find("\"ru_msgrcv\"") != string::npos); EXPECT_TRUE(response.find("\"ru_nsignals\"") != string::npos); EXPECT_TRUE(response.find("\"ru_nvcsw\"") != string::npos); EXPECT_TRUE(response.find("\"ru_nivcsw\"") != string::npos); } VOID TEST(HttpApiTest, GoApiSelfProcStatsServeHttp) { srs_error_t err; // Test the major use scenario: SrsGoApiSelfProcStats::serve_http returns process statistics // This covers the typical /api/v1/self_proc_stats endpoint that provides /proc/self/stat information MockResponseWriter mock_writer; SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->is_jsonp_ = false; SrsUniquePtr api(new SrsGoApiSelfProcStats()); HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); // Verify JSON response contains expected fields string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); // Check for required fields in response EXPECT_TRUE(response.find("\"code\":0") != string::npos); EXPECT_TRUE(response.find("\"server\"") != string::npos); EXPECT_TRUE(response.find("\"service\"") != string::npos); EXPECT_TRUE(response.find("\"pid\"") != string::npos); EXPECT_TRUE(response.find("\"data\"") != string::npos); // Check for self proc stat-specific fields EXPECT_TRUE(response.find("\"ok\"") != string::npos); EXPECT_TRUE(response.find("\"sample_time\"") != string::npos); EXPECT_TRUE(response.find("\"percent\"") != string::npos); EXPECT_TRUE(response.find("\"comm\"") != string::npos); EXPECT_TRUE(response.find("\"state\"") != string::npos); EXPECT_TRUE(response.find("\"ppid\"") != string::npos); EXPECT_TRUE(response.find("\"pgrp\"") != string::npos); EXPECT_TRUE(response.find("\"session\"") != string::npos); EXPECT_TRUE(response.find("\"tty_nr\"") != string::npos); EXPECT_TRUE(response.find("\"tpgid\"") != string::npos); EXPECT_TRUE(response.find("\"flags\"") != string::npos); EXPECT_TRUE(response.find("\"minflt\"") != string::npos); EXPECT_TRUE(response.find("\"cminflt\"") != string::npos); EXPECT_TRUE(response.find("\"majflt\"") != string::npos); EXPECT_TRUE(response.find("\"cmajflt\"") != string::npos); EXPECT_TRUE(response.find("\"utime\"") != string::npos); EXPECT_TRUE(response.find("\"stime\"") != string::npos); EXPECT_TRUE(response.find("\"cutime\"") != string::npos); EXPECT_TRUE(response.find("\"cstime\"") != string::npos); EXPECT_TRUE(response.find("\"priority\"") != string::npos); EXPECT_TRUE(response.find("\"nice\"") != string::npos); EXPECT_TRUE(response.find("\"num_threads\"") != string::npos); EXPECT_TRUE(response.find("\"itrealvalue\"") != string::npos); EXPECT_TRUE(response.find("\"starttime\"") != string::npos); EXPECT_TRUE(response.find("\"vsize\"") != string::npos); EXPECT_TRUE(response.find("\"rss\"") != string::npos); EXPECT_TRUE(response.find("\"rsslim\"") != string::npos); EXPECT_TRUE(response.find("\"startcode\"") != string::npos); EXPECT_TRUE(response.find("\"endcode\"") != string::npos); EXPECT_TRUE(response.find("\"startstack\"") != string::npos); EXPECT_TRUE(response.find("\"kstkesp\"") != string::npos); EXPECT_TRUE(response.find("\"kstkeip\"") != string::npos); EXPECT_TRUE(response.find("\"signal\"") != string::npos); EXPECT_TRUE(response.find("\"blocked\"") != string::npos); EXPECT_TRUE(response.find("\"sigignore\"") != string::npos); EXPECT_TRUE(response.find("\"sigcatch\"") != string::npos); EXPECT_TRUE(response.find("\"wchan\"") != string::npos); EXPECT_TRUE(response.find("\"nswap\"") != string::npos); EXPECT_TRUE(response.find("\"cnswap\"") != string::npos); EXPECT_TRUE(response.find("\"exit_signal\"") != string::npos); EXPECT_TRUE(response.find("\"processor\"") != string::npos); EXPECT_TRUE(response.find("\"rt_priority\"") != string::npos); EXPECT_TRUE(response.find("\"policy\"") != string::npos); EXPECT_TRUE(response.find("\"delayacct_blkio_ticks\"") != string::npos); EXPECT_TRUE(response.find("\"guest_time\"") != string::npos); EXPECT_TRUE(response.find("\"cguest_time\"") != string::npos); } VOID TEST(HttpApiTest, GoApiSystemProcStatsServeHttp) { srs_error_t err; // Test the major use scenario: SrsGoApiSystemProcStats::serve_http returns system CPU statistics // This covers the typical /api/v1/system_proc_stats endpoint that provides /proc/stat information MockResponseWriter mock_writer; SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->is_jsonp_ = false; SrsUniquePtr api(new SrsGoApiSystemProcStats()); HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); // Verify JSON response contains expected fields string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); // Check for required fields in response EXPECT_TRUE(response.find("\"code\":0") != string::npos); EXPECT_TRUE(response.find("\"server\"") != string::npos); EXPECT_TRUE(response.find("\"service\"") != string::npos); EXPECT_TRUE(response.find("\"pid\"") != string::npos); EXPECT_TRUE(response.find("\"data\"") != string::npos); // Check for system proc stat-specific fields EXPECT_TRUE(response.find("\"ok\"") != string::npos); EXPECT_TRUE(response.find("\"sample_time\"") != string::npos); EXPECT_TRUE(response.find("\"percent\"") != string::npos); EXPECT_TRUE(response.find("\"user\"") != string::npos); EXPECT_TRUE(response.find("\"nice\"") != string::npos); EXPECT_TRUE(response.find("\"sys\"") != string::npos); EXPECT_TRUE(response.find("\"idle\"") != string::npos); EXPECT_TRUE(response.find("\"iowait\"") != string::npos); EXPECT_TRUE(response.find("\"irq\"") != string::npos); EXPECT_TRUE(response.find("\"softirq\"") != string::npos); EXPECT_TRUE(response.find("\"steal\"") != string::npos); EXPECT_TRUE(response.find("\"guest\"") != string::npos); } VOID TEST(HttpApiTest, GoApiMemInfosServeHttp) { srs_error_t err; // Test the major use scenario: SrsGoApiMemInfos::serve_http returns memory information // This covers the typical /api/v1/meminfos endpoint that provides /proc/meminfo statistics MockResponseWriter mock_writer; SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->is_jsonp_ = false; SrsUniquePtr api(new SrsGoApiMemInfos()); HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); // Verify JSON response contains expected fields string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); // Check for required fields in response EXPECT_TRUE(response.find("\"code\":0") != string::npos); EXPECT_TRUE(response.find("\"server\"") != string::npos); EXPECT_TRUE(response.find("\"service\"") != string::npos); EXPECT_TRUE(response.find("\"pid\"") != string::npos); EXPECT_TRUE(response.find("\"data\"") != string::npos); // Check for memory info-specific fields EXPECT_TRUE(response.find("\"ok\"") != string::npos); EXPECT_TRUE(response.find("\"sample_time\"") != string::npos); EXPECT_TRUE(response.find("\"percent_ram\"") != string::npos); EXPECT_TRUE(response.find("\"percent_swap\"") != string::npos); EXPECT_TRUE(response.find("\"MemActive\"") != string::npos); EXPECT_TRUE(response.find("\"RealInUse\"") != string::npos); EXPECT_TRUE(response.find("\"NotInUse\"") != string::npos); EXPECT_TRUE(response.find("\"MemTotal\"") != string::npos); EXPECT_TRUE(response.find("\"MemFree\"") != string::npos); EXPECT_TRUE(response.find("\"Buffers\"") != string::npos); EXPECT_TRUE(response.find("\"Cached\"") != string::npos); EXPECT_TRUE(response.find("\"SwapTotal\"") != string::npos); EXPECT_TRUE(response.find("\"SwapFree\"") != string::npos); } VOID TEST(HttpApiTest, GoApiRootServeHttp) { srs_error_t err; // Test the major use scenario: SrsGoApiRoot::serve_http returns API root information // This covers the typical / endpoint that provides server identification and available API URLs MockResponseWriter mock_writer; SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->is_jsonp_ = false; SrsUniquePtr api(new SrsGoApiRoot()); HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); // Verify JSON response contains expected fields string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); // Verify response contains code field EXPECT_TRUE(response.find("\"code\":0") != string::npos); // Verify response contains server identification fields EXPECT_TRUE(response.find("\"server\"") != string::npos); EXPECT_TRUE(response.find("\"service\"") != string::npos); EXPECT_TRUE(response.find("\"pid\"") != string::npos); // Verify response contains urls object EXPECT_TRUE(response.find("\"urls\"") != string::npos); EXPECT_TRUE(response.find("\"api\"") != string::npos); // Verify response contains rtc URLs EXPECT_TRUE(response.find("\"rtc\"") != string::npos); EXPECT_TRUE(response.find("\"v1\"") != string::npos); EXPECT_TRUE(response.find("\"play\"") != string::npos); EXPECT_TRUE(response.find("\"publish\"") != string::npos); EXPECT_TRUE(response.find("\"nack\"") != string::npos); } VOID TEST(HttpApiTest, GoApiRequestsServeHttp) { srs_error_t err; // Test the major use scenario: SrsGoApiRequests::serve_http returns request information // This covers the typical /api/v1/requests endpoint that echoes back request details // including URI, path, method, headers, and server information MockResponseWriter mock_writer; SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->is_jsonp_ = false; SrsUniquePtr api(new SrsGoApiRequests()); HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); // Verify JSON response contains expected fields string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); // Check for required server info fields EXPECT_TRUE(response.find("\"code\":0") != string::npos); EXPECT_TRUE(response.find("\"server\"") != string::npos); EXPECT_TRUE(response.find("\"service\"") != string::npos); EXPECT_TRUE(response.find("\"pid\"") != string::npos); EXPECT_TRUE(response.find("\"data\"") != string::npos); // Check for request-specific fields in data object EXPECT_TRUE(response.find("\"uri\"") != string::npos); EXPECT_TRUE(response.find("\"path\"") != string::npos); EXPECT_TRUE(response.find("\"METHOD\"") != string::npos); EXPECT_TRUE(response.find("\"headers\"") != string::npos); // Check for server information fields EXPECT_TRUE(response.find("\"sigature\"") != string::npos); EXPECT_TRUE(response.find("\"version\"") != string::npos); EXPECT_TRUE(response.find("\"link\"") != string::npos); EXPECT_TRUE(response.find("\"time\"") != string::npos); } VOID TEST(HttpApiTest, GoApiVhostsServeHttpWithVhostId) { srs_error_t err; // Test the major use scenario: SrsGoApiVhosts::serve_http returns vhost list // This covers the typical /api/v1/vhosts endpoint that provides vhost statistics // Create mock response writer and HTTP message MockResponseWriter mock_writer; SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->is_jsonp_ = false; // Set URL for parse_rest_id to work correctly HELPER_EXPECT_SUCCESS(mock_msg->set_url("http://127.0.0.1/api/v1/vhosts/", false)); // Create mock statistic MockStatisticForLiveStream mock_stat; // Create API handler and inject mock statistic SrsUniquePtr api(new SrsGoApiVhosts()); api->stat_ = &mock_stat; // Setup entry pattern for REST ID parsing api->entry_ = new SrsHttpMuxEntry(); api->entry_->pattern = "/api/v1/vhosts/"; // Test the major use scenario: no vhost ID provided - should list all vhosts HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); // Verify JSON response contains expected fields string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); // Check for required fields in response EXPECT_TRUE(response.find("\"code\":0") != string::npos); EXPECT_TRUE(response.find("\"server\"") != string::npos); EXPECT_TRUE(response.find("\"service\"") != string::npos); EXPECT_TRUE(response.find("\"pid\"") != string::npos); EXPECT_TRUE(response.find("\"vhosts\"") != string::npos); // Clean up api->stat_ = NULL; srs_freep(api->entry_); } VOID TEST(HttpApiTest, StreamsApiGetSpecificStream) { srs_error_t err; // Create mock HTTP message for GET request with stream ID SrsUniquePtr mock_msg(new MockHttpMessageForApiResponse()); mock_msg->_method = SRS_CONSTS_HTTP_GET; mock_msg->path_ = "/api/v1/streams/test_stream_id_123"; HELPER_EXPECT_SUCCESS(mock_msg->set_url("http://127.0.0.1/api/v1/streams/test_stream_id_123", false)); // Create mock response writer MockResponseWriter mock_writer; // Create mock statistic with a test stream MockStatisticForLiveStream mock_stat; // Create a real stream object to return from find_stream SrsStatisticStream test_stream; test_stream.id_ = "test_stream_id_123"; test_stream.stream_ = "livestream"; test_stream.app_ = "live"; test_stream.url_ = "live/livestream"; test_stream.tcUrl_ = "rtmp://localhost/live"; // Create a vhost for the stream SrsStatisticVhost test_vhost; test_vhost.id_ = "test_vhost_id"; test_stream.vhost_ = &test_vhost; // Mock find_stream to return our test stream // Note: We need to extend MockStatisticForLiveStream to support find_stream // For now, we'll create a custom mock inline class MockStatisticForStreamsApi : public MockStatisticForLiveStream { public: SrsStatisticStream *stream_to_return_; MockStatisticForStreamsApi() : stream_to_return_(NULL) {} virtual SrsStatisticStream *find_stream(std::string sid) { if (sid == "test_stream_id_123" && stream_to_return_) { return stream_to_return_; } return NULL; } }; MockStatisticForStreamsApi mock_stat_with_stream; mock_stat_with_stream.stream_to_return_ = &test_stream; // Create API handler and inject mock statistic SrsUniquePtr api(new SrsGoApiStreams()); api->stat_ = &mock_stat_with_stream; // Setup entry pattern for REST ID parsing api->entry_ = new SrsHttpMuxEntry(); api->entry_->pattern = "/api/v1/streams/"; // Test the major use scenario: specific stream ID provided - should return stream details HELPER_EXPECT_SUCCESS(api->serve_http(&mock_writer, mock_msg.get())); // Verify JSON response contains expected fields string response = HELPER_BUFFER2STR(&mock_writer.io.out_buffer); // Check for required fields in response EXPECT_TRUE(response.find("\"code\":0") != string::npos); EXPECT_TRUE(response.find("\"server\"") != string::npos); EXPECT_TRUE(response.find("\"service\"") != string::npos); EXPECT_TRUE(response.find("\"pid\"") != string::npos); EXPECT_TRUE(response.find("\"stream\"") != string::npos); // Check for stream-specific fields from dumps() method EXPECT_TRUE(response.find("\"id\":\"test_stream_id_123\"") != string::npos); EXPECT_TRUE(response.find("\"name\":\"livestream\"") != string::npos); EXPECT_TRUE(response.find("\"app\":\"live\"") != string::npos); // Clean up api->stat_ = NULL; srs_freep(api->entry_); } VOID TEST(HTTPApiTest, ClientsApiGetAllClients) { srs_error_t err; // Test the major use scenario: GET /api/v1/clients to list all clients // This covers the typical HTTP API usage for querying client list // Create mock statistic with find_client and dumps_clients support class MockStatisticForClientsApi : public MockStatisticForLiveStream { public: SrsStatisticClient *client_to_return_; srs_error_t dumps_clients_error_; int dumps_clients_count_; MockStatisticForClientsApi() : client_to_return_(NULL), dumps_clients_error_(srs_success), dumps_clients_count_(0) {} virtual ~MockStatisticForClientsApi() {} virtual SrsStatisticClient *find_client(std::string client_id) { if (client_id == "test_client_123" && client_to_return_) { return client_to_return_; } return NULL; } virtual srs_error_t dumps_clients(SrsJsonArray *arr, int start, int count) { dumps_clients_count_++; if (dumps_clients_error_ != srs_success) { return srs_error_copy(dumps_clients_error_); } // Add mock client data SrsJsonObject *client = SrsJsonAny::object(); client->set("id", SrsJsonAny::str("test_client_123")); client->set("vhost", SrsJsonAny::str("__defaultVhost__")); client->set("stream", SrsJsonAny::str("livestream")); client->set("ip", SrsJsonAny::str("127.0.0.1")); arr->append(client); return srs_success; } }; // Create mock statistic SrsUniquePtr mock_stat(new MockStatisticForClientsApi()); // Create SrsGoApiClients instance SrsUniquePtr api(new SrsGoApiClients()); api->stat_ = mock_stat.get(); // Create mock entry with pattern api->entry_ = new SrsHttpMuxEntry(); api->entry_->pattern = "/api/v1/clients/"; // Create mock HTTP request for GET /api/v1/clients?start=0&count=10 SrsUniquePtr req(new SrsHttpMessage()); HELPER_EXPECT_SUCCESS(req->set_url("http://127.0.0.1/api/v1/clients?start=0&count=10", false)); // Create mock HTTP response writer MockResponseWriter w; // Call serve_http HELPER_EXPECT_SUCCESS(api->serve_http(&w, req.get())); // Verify dumps_clients was called EXPECT_EQ(1, mock_stat->dumps_clients_count_); // Verify response contains expected JSON structure string response = HELPER_BUFFER2STR(&w.io.out_buffer); EXPECT_TRUE(response.find("\"code\":0") != string::npos); EXPECT_TRUE(response.find("\"server\":\"mock_server_id\"") != string::npos); EXPECT_TRUE(response.find("\"service\":\"mock_service_id\"") != string::npos); EXPECT_TRUE(response.find("\"pid\":\"mock_pid\"") != string::npos); EXPECT_TRUE(response.find("\"clients\"") != string::npos); EXPECT_TRUE(response.find("\"id\":\"test_client_123\"") != string::npos); EXPECT_TRUE(response.find("\"stream\":\"livestream\"") != string::npos); // Clean up api->stat_ = NULL; srs_freep(api->entry_); } VOID TEST(HTTPApiTest, ClientsApiGetSpecificClient) { srs_error_t err; // Test the major use scenario: GET /api/v1/clients/{client_id} to get specific client info // This covers the typical HTTP API usage for querying a specific client and calling client->dumps() // Create mock statistic with find_client support class MockStatisticForClientApi : public MockStatisticForLiveStream { public: SrsStatisticClient *client_to_return_; MockStatisticForClientApi() : client_to_return_(NULL) {} virtual ~MockStatisticForClientApi() {} virtual SrsStatisticClient *find_client(std::string client_id) { if (client_id == "test_client_456" && client_to_return_) { return client_to_return_; } return NULL; } }; // Create mock statistic SrsUniquePtr mock_stat(new MockStatisticForClientApi()); // Create a real SrsStatisticClient with all required dependencies SrsUniquePtr test_client(new SrsStatisticClient()); test_client->id_ = "test_client_456"; test_client->type_ = SrsRtmpConnPlay; // Create mock request for the client - SrsStatisticClient destructor will free this MockBufferCacheRequest *mock_req = new MockBufferCacheRequest("__defaultVhost__", "live", "livestream"); test_client->req_ = mock_req; // Create mock vhost and stream for the client SrsStatisticVhost test_vhost; test_vhost.id_ = "__defaultVhost__"; SrsStatisticStream test_stream; test_stream.id_ = "livestream"; test_stream.vhost_ = &test_vhost; test_client->stream_ = &test_stream; // Set the client to return mock_stat->client_to_return_ = test_client.get(); // Create SrsGoApiClients instance SrsUniquePtr api(new SrsGoApiClients()); api->stat_ = mock_stat.get(); // Create mock entry with pattern api->entry_ = new SrsHttpMuxEntry(); api->entry_->pattern = "/api/v1/clients/"; // Create mock HTTP request for GET /api/v1/clients/test_client_456 SrsUniquePtr req(new SrsHttpMessage()); HELPER_EXPECT_SUCCESS(req->set_url("http://127.0.0.1/api/v1/clients/test_client_456", false)); // Create mock HTTP response writer MockResponseWriter w; // Call serve_http - this should call client->dumps() HELPER_EXPECT_SUCCESS(api->serve_http(&w, req.get())); // Verify response contains expected JSON structure with client data string response = HELPER_BUFFER2STR(&w.io.out_buffer); EXPECT_TRUE(response.find("\"code\":0") != string::npos); EXPECT_TRUE(response.find("\"server\":\"mock_server_id\"") != string::npos); EXPECT_TRUE(response.find("\"service\":\"mock_service_id\"") != string::npos); EXPECT_TRUE(response.find("\"pid\":\"mock_pid\"") != string::npos); EXPECT_TRUE(response.find("\"client\"") != string::npos); EXPECT_TRUE(response.find("\"id\":\"test_client_456\"") != string::npos); EXPECT_TRUE(response.find("\"vhost\":\"__defaultVhost__\"") != string::npos); EXPECT_TRUE(response.find("\"stream\":\"livestream\"") != string::npos); EXPECT_TRUE(response.find("\"type\":\"rtmp-play\"") != string::npos); // Clean up api->stat_ = NULL; srs_freep(api->entry_); } VOID TEST(SrsGoApiClustersTest, ServeHttpSuccess) { srs_error_t err; // Test the major use scenario: serve_http returns JSON response with query parameters and origin cluster info // This covers the typical HTTP API /api/v1/clusters request use case for origin cluster discovery // Create SrsGoApiClusters handler SrsUniquePtr handler(new SrsGoApiClusters()); // Create mock HTTP response writer MockResponseWriter mock_writer; // Create mock HTTP message with query parameters class MockHttpMessageForClusters : public SrsHttpMessage { public: MockHttpConn *mock_conn_; std::map query_params_; MockHttpMessageForClusters() { mock_conn_ = new MockHttpConn(); set_connection(mock_conn_); // Set query parameters for the test query_params_["ip"] = "192.168.1.100"; query_params_["vhost"] = "test.vhost"; query_params_["app"] = "live"; query_params_["stream"] = "livestream"; query_params_["coworker"] = "127.0.0.1:1935"; } virtual ~MockHttpMessageForClusters() { srs_freep(mock_conn_); } virtual std::string query_get(std::string key) { std::map::iterator it = query_params_.find(key); if (it != query_params_.end()) { return it->second; } return ""; } virtual std::string path() { return "/api/v1/clusters"; } virtual bool is_jsonp() { return false; } }; SrsUniquePtr mock_message(new MockHttpMessageForClusters()); // Call serve_http - should return success and write JSON response HELPER_EXPECT_SUCCESS(handler->serve_http(&mock_writer, mock_message.get())); // Verify that response was written string response = string(mock_writer.io.out_buffer.bytes(), mock_writer.io.out_buffer.length()); EXPECT_TRUE(response.length() > 0); // The response includes HTTP headers, we need to extract just the JSON body // Find the start of JSON (after the double CRLF that separates headers from body) size_t json_start = response.find("\r\n\r\n"); ASSERT_TRUE(json_start != string::npos); string json_body = response.substr(json_start + 4); // Parse the JSON response SrsJsonAny *json = SrsJsonAny::loads(json_body); ASSERT_TRUE(json != NULL); ASSERT_TRUE(json->is_object()); SrsUniquePtr obj((SrsJsonObject *)json); // Verify "code" field is ERROR_SUCCESS SrsJsonAny *code_any = obj->get_property("code"); ASSERT_TRUE(code_any != NULL); ASSERT_TRUE(code_any->is_integer()); EXPECT_EQ(ERROR_SUCCESS, code_any->to_integer()); // Verify "data" object exists SrsJsonAny *data_any = obj->get_property("data"); ASSERT_TRUE(data_any != NULL); ASSERT_TRUE(data_any->is_object()); SrsJsonObject *data = (SrsJsonObject *)data_any; // Verify "query" object exists and contains the query parameters SrsJsonAny *query_any = data->get_property("query"); ASSERT_TRUE(query_any != NULL); ASSERT_TRUE(query_any->is_object()); SrsJsonObject *query = (SrsJsonObject *)query_any; // Verify query parameters are correctly echoed back SrsJsonAny *ip_any = query->get_property("ip"); ASSERT_TRUE(ip_any != NULL); ASSERT_TRUE(ip_any->is_string()); EXPECT_STREQ("192.168.1.100", ip_any->to_str().c_str()); SrsJsonAny *vhost_any = query->get_property("vhost"); ASSERT_TRUE(vhost_any != NULL); ASSERT_TRUE(vhost_any->is_string()); EXPECT_STREQ("test.vhost", vhost_any->to_str().c_str()); SrsJsonAny *app_any = query->get_property("app"); ASSERT_TRUE(app_any != NULL); ASSERT_TRUE(app_any->is_string()); EXPECT_STREQ("live", app_any->to_str().c_str()); SrsJsonAny *stream_any = query->get_property("stream"); ASSERT_TRUE(stream_any != NULL); ASSERT_TRUE(stream_any->is_string()); EXPECT_STREQ("livestream", stream_any->to_str().c_str()); // Verify "origin" field exists (from SrsCoWorkers::dumps) // Note: origin will be null if the stream is not published, which is expected in this test SrsJsonAny *origin_any = data->get_property("origin"); ASSERT_TRUE(origin_any != NULL); // origin can be null or object depending on whether stream is published } MockSignalHandler::MockSignalHandler() { signal_received_ = 0; signal_count_ = 0; } MockSignalHandler::~MockSignalHandler() { } void MockSignalHandler::on_signal(int signo) { signal_received_ = signo; signal_count_++; } void MockSignalHandler::reset() { signal_received_ = 0; signal_count_ = 0; } MockAppConfigForRawApi::MockAppConfigForRawApi() { raw_api_ = false; allow_reload_ = false; allow_query_ = false; allow_update_ = false; raw_to_json_error_ = srs_success; } MockAppConfigForRawApi::~MockAppConfigForRawApi() { } bool MockAppConfigForRawApi::get_raw_api() { return raw_api_; } bool MockAppConfigForRawApi::get_raw_api_allow_reload() { return allow_reload_; } bool MockAppConfigForRawApi::get_raw_api_allow_query() { return allow_query_; } bool MockAppConfigForRawApi::get_raw_api_allow_update() { return allow_update_; } srs_error_t MockAppConfigForRawApi::raw_to_json(SrsJsonObject *obj) { if (raw_to_json_error_ != srs_success) { return srs_error_copy(raw_to_json_error_); } // Add some test data to the object obj->set("raw_api", SrsJsonAny::boolean(raw_api_)); obj->set("allow_reload", SrsJsonAny::boolean(allow_reload_)); obj->set("allow_query", SrsJsonAny::boolean(allow_query_)); obj->set("allow_update", SrsJsonAny::boolean(allow_update_)); return srs_success; } VOID TEST(HttpApiTest, GoApiRawServeHttp) { srs_error_t err; // Test the major use scenario: HTTP RAW API with reload functionality // This covers the typical RAW API use cases: query config, reload, and reload-fetch // Create mock signal handler MockSignalHandler mock_handler; // Create mock config MockAppConfigForRawApi mock_config; mock_config.raw_api_ = true; mock_config.allow_reload_ = true; mock_config.allow_query_ = true; mock_config.allow_update_ = true; // Create SrsGoApiRaw instance SrsUniquePtr api(new SrsGoApiRaw(&mock_handler)); // Inject mock config before calling assemble() api->config_ = &mock_config; api->assemble(); // Test 1: rpc=raw - query the raw api config { MockResponseWriter w; SrsUniquePtr r(new MockHttpMessageForApiResponse()); r->query_params_["rpc"] = "raw"; HELPER_EXPECT_SUCCESS(api->serve_http(&w, r.get())); // Verify response contains raw api config string response = HELPER_BUFFER2STR(&w.io.out_buffer); EXPECT_TRUE(response.find("\"code\":0") != string::npos); EXPECT_TRUE(response.find("\"raw_api\":true") != string::npos); EXPECT_TRUE(response.find("\"allow_reload\":true") != string::npos); } // Test 2: rpc=reload - trigger reload signal { MockResponseWriter w; SrsUniquePtr r(new MockHttpMessageForApiResponse()); r->query_params_["rpc"] = "reload"; mock_handler.reset(); HELPER_EXPECT_SUCCESS(api->serve_http(&w, r.get())); // Verify reload signal was sent EXPECT_EQ(SRS_SIGNAL_RELOAD, mock_handler.signal_received_); EXPECT_EQ(1, mock_handler.signal_count_); // Verify response indicates success string response = HELPER_BUFFER2STR(&w.io.out_buffer); EXPECT_TRUE(response.find("\"code\":0") != string::npos); } // Test 3: rpc=reload-fetch - query reload status { MockResponseWriter w; SrsUniquePtr r(new MockHttpMessageForApiResponse()); r->query_params_["rpc"] = "reload-fetch"; HELPER_EXPECT_SUCCESS(api->serve_http(&w, r.get())); // Verify response contains reload status string response = HELPER_BUFFER2STR(&w.io.out_buffer); EXPECT_TRUE(response.find("\"code\":0") != string::npos); EXPECT_TRUE(response.find("\"data\"") != string::npos); EXPECT_TRUE(response.find("\"err\"") != string::npos); EXPECT_TRUE(response.find("\"msg\"") != string::npos); EXPECT_TRUE(response.find("\"state\"") != string::npos); EXPECT_TRUE(response.find("\"rid\"") != string::npos); } // Unsubscribe before cleanup to avoid double unsubscribe in destructor mock_config.unsubscribe(api.get()); } VOID TEST(SrsGoApiMetricsTest, ServeHttpSuccess) { srs_error_t err = srs_success; // Create mock statistic with test data MockStatisticForResampleKbps mock_stat; // Create mock config MockAppConfig mock_config; // Create SrsGoApiMetrics instance SrsUniquePtr api(new SrsGoApiMetrics()); api->stat_ = &mock_stat; api->config_ = &mock_config; api->enabled_ = true; api->label_ = "test_label"; api->tag_ = "test_tag"; // Create mock HTTP request and response MockResponseWriter w; SrsUniquePtr r(new MockHttpMessageForApiResponse()); // Call serve_http HELPER_EXPECT_SUCCESS(api->serve_http(&w, r.get())); // Verify response string response = HELPER_BUFFER2STR(&w.io.out_buffer); // Verify response contains Prometheus metrics format EXPECT_TRUE(response.find("# HELP srs_build_info") != string::npos); EXPECT_TRUE(response.find("# TYPE srs_build_info gauge") != string::npos); EXPECT_TRUE(response.find("srs_build_info{") != string::npos); // Verify server/service info is included EXPECT_TRUE(response.find("server=\"mock_server_id\"") != string::npos); EXPECT_TRUE(response.find("service=\"mock_service_id\"") != string::npos); EXPECT_TRUE(response.find("pid=\"mock_pid\"") != string::npos); // Verify label and tag are included EXPECT_TRUE(response.find("label=\"test_label\"") != string::npos); EXPECT_TRUE(response.find("tag=\"test_tag\"") != string::npos); // Verify CPU metric EXPECT_TRUE(response.find("# HELP srs_cpu_percent") != string::npos); EXPECT_TRUE(response.find("# TYPE srs_cpu_percent gauge") != string::npos); EXPECT_TRUE(response.find("srs_cpu_percent") != string::npos); // Verify memory metric EXPECT_TRUE(response.find("# HELP srs_memory") != string::npos); EXPECT_TRUE(response.find("# TYPE srs_memory gauge") != string::npos); EXPECT_TRUE(response.find("srs_memory") != string::npos); // Verify send/receive bytes metrics EXPECT_TRUE(response.find("# HELP srs_send_bytes_total") != string::npos); EXPECT_TRUE(response.find("# TYPE srs_send_bytes_total counter") != string::npos); EXPECT_TRUE(response.find("srs_send_bytes_total") != string::npos); EXPECT_TRUE(response.find("# HELP srs_receive_bytes_total") != string::npos); EXPECT_TRUE(response.find("# TYPE srs_receive_bytes_total counter") != string::npos); EXPECT_TRUE(response.find("srs_receive_bytes_total") != string::npos); // Verify streams metric EXPECT_TRUE(response.find("# HELP srs_streams") != string::npos); EXPECT_TRUE(response.find("# TYPE srs_streams gauge") != string::npos); EXPECT_TRUE(response.find("srs_streams") != string::npos); // Verify clients metrics EXPECT_TRUE(response.find("# HELP srs_clients") != string::npos); EXPECT_TRUE(response.find("# TYPE srs_clients gauge") != string::npos); EXPECT_TRUE(response.find("srs_clients") != string::npos); EXPECT_TRUE(response.find("# HELP srs_clients_total") != string::npos); EXPECT_TRUE(response.find("# TYPE srs_clients_total counter") != string::npos); EXPECT_TRUE(response.find("srs_clients_total") != string::npos); // Verify errors metric EXPECT_TRUE(response.find("# HELP srs_clients_errs_total") != string::npos); EXPECT_TRUE(response.find("# TYPE srs_clients_errs_total counter") != string::npos); EXPECT_TRUE(response.find("srs_clients_errs_total") != string::npos); // Clean up api->stat_ = NULL; api->config_ = NULL; }