This PR adds separate audio and video frame counting to the HTTP API
(`/api/v1/streams/`) for better stream observability. The API now
reports three frame fields:
- `frames` - Total frames (video + audio)
- `video_frames` - Video frames/packets only
- `audio_frames` - Audio frames/packets only
This enhancement provides better visibility into stream composition and
helps detect issues with CBR/VBR streams, audio/video sync problems, and
codec-specific behavior.
**Before:**
```json
{
"streams": [
{
"frames": 0, // video frames.
}
]
}
```
**After:**
```json
{
"streams": [
{
"frames": 6912, // video frames.
"audio_frames": 5678, // audio frames.
"video_frames": 1234, // video frames.
}
]
}
```
Frame Counting Strategy
- All protocols report frames every N frames to balance accuracy and
performance
- Frames are counted at the protocol-specific message/packet level:
- RTMP: Counts RTMP messages (video/audio)
- WebRTC: Counts RTP packets (video/audio)
- SRT: Counts MPEG-TS messages (H.264/HEVC/AAC)
3539 lines
130 KiB
C++
3539 lines
130 KiB
C++
//
|
|
// Copyright (c) 2013-2025 The SRS Authors
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
#include <srs_utest_ai16.hpp>
|
|
|
|
using namespace std;
|
|
|
|
#include <srs_app_config.hpp>
|
|
#include <srs_app_http_api.hpp>
|
|
#include <srs_app_http_hooks.hpp>
|
|
#include <srs_app_http_stream.hpp>
|
|
#include <srs_app_rtmp_source.hpp>
|
|
#include <srs_kernel_balance.hpp>
|
|
#include <srs_kernel_consts.hpp>
|
|
#include <srs_kernel_error.hpp>
|
|
#include <srs_kernel_packet.hpp>
|
|
#include <srs_kernel_utility.hpp>
|
|
#include <srs_protocol_json.hpp>
|
|
#include <srs_utest_ai14.hpp>
|
|
#include <srs_utest_ai15.hpp>
|
|
#include <srs_utest_manual_http.hpp>
|
|
#include <srs_utest_manual_kernel.hpp>
|
|
#include <vector>
|
|
|
|
// 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;
|
|
|
|
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<SrsLbRoundRobin> lb(new SrsLbRoundRobin());
|
|
|
|
// Setup test servers
|
|
vector<string> 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());
|
|
}
|
|
|
|
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<MockRequest> mock_request(new MockRequest("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<SrsBufferCache> 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<MockRequest> new_request(new MockRequest("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<MockRequest> mock_request(new MockRequest("test.vhost", "live", "stream1"));
|
|
|
|
// Create buffer cache
|
|
SrsUniquePtr<SrsBufferCache> 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<MockLiveSourceForQueue> source(new MockLiveSourceForQueue());
|
|
SrsUniquePtr<MockLiveConsumerForQueue> 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<MockSrsFileWriter> writer(new MockSrsFileWriter());
|
|
HELPER_EXPECT_SUCCESS(writer->open("test.ts"));
|
|
|
|
// Create TS stream encoder
|
|
SrsUniquePtr<SrsTsStreamEncoder> 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<MockSrsFileWriter> writer(new MockSrsFileWriter());
|
|
HELPER_EXPECT_SUCCESS(writer->open("test.ts"));
|
|
|
|
// Create TS stream encoder
|
|
SrsUniquePtr<SrsTsStreamEncoder> 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<MockSrsFileWriter> writer(new MockSrsFileWriter());
|
|
|
|
// Create FLV stream encoder
|
|
SrsUniquePtr<SrsFlvStreamEncoder> 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<MockSrsFileWriter> writer(new MockSrsFileWriter());
|
|
|
|
// Create FLV stream encoder
|
|
SrsUniquePtr<SrsFlvStreamEncoder> 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<MockSrsFileWriter> writer(new MockSrsFileWriter());
|
|
|
|
// Create FLV stream encoder
|
|
SrsUniquePtr<SrsFlvStreamEncoder> 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<MockSrsFileWriter> writer(new MockSrsFileWriter());
|
|
|
|
// Create FLV stream encoder
|
|
SrsUniquePtr<SrsFlvStreamEncoder> 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<MockSrsFileWriter> writer(new MockSrsFileWriter());
|
|
HELPER_EXPECT_SUCCESS(writer->open("test.aac"));
|
|
|
|
MockBufferCache mock_cache;
|
|
|
|
// Create AAC stream encoder
|
|
SrsUniquePtr<SrsAacStreamEncoder> 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<MockLiveConsumerForQueue> 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<SrsBufferWriter> 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();
|
|
}
|
|
|
|
// 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<MockRequest> mock_request(new MockRequest("test.vhost", "live", "stream1"));
|
|
|
|
// Create mock buffer cache
|
|
SrsUniquePtr<MockBufferCache> mock_cache(new MockBufferCache());
|
|
|
|
// Create SrsLiveStream
|
|
SrsUniquePtr<SrsLiveStream> 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<MockHttpMessageForLiveStream> 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;
|
|
}
|
|
|
|
srs_error_t MockStatisticForLiveStream::on_audio_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<std::string> &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<MockRequest> mock_request(new MockRequest("test.vhost", "live", "stream1"));
|
|
|
|
// Create mock buffer cache
|
|
SrsUniquePtr<MockBufferCache> mock_cache(new MockBufferCache());
|
|
|
|
// Create SrsLiveStream
|
|
SrsUniquePtr<SrsLiveStream> 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<MockHttpMessage> 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<SrsLiveEntry> 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<SrsLiveEntry> 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<SrsLiveEntry> 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<SrsLiveEntry> 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<SrsHttpStreamServer> 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<SrsHttpStreamServer> 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<MockRequest> mock_request(new MockRequest(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<SrsGoApiSummaries> handler(new SrsGoApiSummaries());
|
|
|
|
// Create mock HTTP response writer and message
|
|
MockResponseWriter mock_writer;
|
|
SrsUniquePtr<MockHttpMessageForApiResponse> 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<SrsJsonObject> 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<SrsGoApiAuthors> handler(new SrsGoApiAuthors());
|
|
|
|
// Create mock HTTP response writer and message
|
|
MockResponseWriter mock_writer;
|
|
SrsUniquePtr<MockHttpMessageForApiResponse> 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<SrsJsonObject> 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<SrsGoApiFeatures> handler(new SrsGoApiFeatures());
|
|
|
|
// Create mock HTTP response writer and message
|
|
MockResponseWriter mock_writer;
|
|
SrsUniquePtr<MockHttpMessageForApiResponse> 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<SrsJsonObject> 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<SrsHttpStreamServer> 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<MockHttpMessageForDynamicMatch> 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<std::string, SrsLiveEntry *>::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<MockRequest> mock_request(new MockRequest("test.vhost", "live", "stream1"));
|
|
SrsUniquePtr<MockBufferCache> mock_cache(new MockBufferCache());
|
|
|
|
// Create SrsLiveStream
|
|
SrsUniquePtr<SrsLiveStream> 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<MockRequest> mock_request(new MockRequest("test.vhost", "live", "stream1"));
|
|
|
|
// Create mock buffer cache
|
|
SrsUniquePtr<MockBufferCache> mock_cache(new MockBufferCache());
|
|
|
|
// Create SrsLiveStream
|
|
SrsUniquePtr<SrsLiveStream> 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<MockHttpMessageForLiveStream> mock_message(new MockHttpMessageForLiveStream());
|
|
MockResponseWriter mock_writer;
|
|
|
|
// Create mock live source and consumer
|
|
SrsUniquePtr<MockLiveSourceForQueue> mock_source(new MockLiveSourceForQueue());
|
|
SrsUniquePtr<MockLiveConsumerForQueue> 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<MockRequest> mock_request(new MockRequest("test.vhost", "live", "stream1"));
|
|
|
|
// Create mock buffer cache
|
|
SrsUniquePtr<MockBufferCache> mock_cache(new MockBufferCache());
|
|
|
|
// Create SrsLiveStream
|
|
SrsUniquePtr<SrsLiveStream> 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<MockHttpConn> viewer1(new MockHttpConn());
|
|
SrsUniquePtr<MockHttpConn> viewer2(new MockHttpConn());
|
|
SrsUniquePtr<MockHttpConn> 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<SrsHttpServeMux> mux(new SrsHttpServeMux());
|
|
HELPER_EXPECT_SUCCESS(mux->initialize());
|
|
|
|
std::map<std::string, SrsLiveEntry *> 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
|
|
MockRequest *mock_req = new MockRequest();
|
|
|
|
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<SrsHttpStreamDestroy> 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<MockRequest> mock_request(new MockRequest("test.vhost", "live", "stream1"));
|
|
|
|
// Create buffer cache
|
|
SrsUniquePtr<SrsBufferCache> 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<MockRequest> mock_request(new MockRequest("test.vhost", "live", "stream1"));
|
|
|
|
// Create buffer cache
|
|
SrsUniquePtr<SrsBufferCache> 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<MockSrsFileWriter> writer(new MockSrsFileWriter());
|
|
HELPER_EXPECT_SUCCESS(writer->open("test.mp3"));
|
|
|
|
MockBufferCache mock_cache;
|
|
|
|
// Create MP3 stream encoder
|
|
SrsUniquePtr<SrsMp3StreamEncoder> 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<std::string, std::string>::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<SrsGoApiV1> 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<MockHttpMessageForApiResponse> 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<SrsGoApiVhosts> 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<MockHttpMessageForApiResponse> 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<SrsJsonObject> 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<MockHttpMessageForApiResponse> 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<MockHttpMessageForApiResponse> 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<MockHttpMessageForApiResponse> 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<MockHttpMessageForApiResponse> 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<MockHttpMessageForApiResponse> 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<MockHttpMessage> 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<MockHttpMessage> 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<MockHttpMessage> 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<MockHttpMessage> 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<MockHttpMessageForApiResponse> mock_msg(new MockHttpMessageForApiResponse());
|
|
mock_msg->is_jsonp_ = false;
|
|
|
|
SrsUniquePtr<SrsGoApiApi> 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<MockHttpMessageForApiResponse> mock_msg(new MockHttpMessageForApiResponse());
|
|
mock_msg->is_jsonp_ = false;
|
|
|
|
SrsUniquePtr<SrsGoApiVersion> 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<MockHttpMessageForApiResponse> mock_msg(new MockHttpMessageForApiResponse());
|
|
mock_msg->is_jsonp_ = false;
|
|
|
|
SrsUniquePtr<SrsGoApiRusages> 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<MockHttpMessageForApiResponse> mock_msg(new MockHttpMessageForApiResponse());
|
|
mock_msg->is_jsonp_ = false;
|
|
|
|
SrsUniquePtr<SrsGoApiSelfProcStats> 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<MockHttpMessageForApiResponse> mock_msg(new MockHttpMessageForApiResponse());
|
|
mock_msg->is_jsonp_ = false;
|
|
|
|
SrsUniquePtr<SrsGoApiSystemProcStats> 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<MockHttpMessageForApiResponse> mock_msg(new MockHttpMessageForApiResponse());
|
|
mock_msg->is_jsonp_ = false;
|
|
|
|
SrsUniquePtr<SrsGoApiMemInfos> 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<MockHttpMessageForApiResponse> mock_msg(new MockHttpMessageForApiResponse());
|
|
mock_msg->is_jsonp_ = false;
|
|
|
|
SrsUniquePtr<SrsGoApiRoot> 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<MockHttpMessageForApiResponse> mock_msg(new MockHttpMessageForApiResponse());
|
|
mock_msg->is_jsonp_ = false;
|
|
|
|
SrsUniquePtr<SrsGoApiRequests> 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<MockHttpMessageForApiResponse> 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<SrsGoApiVhosts> 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<MockHttpMessageForApiResponse> 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<SrsGoApiStreams> 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<MockStatisticForClientsApi> mock_stat(new MockStatisticForClientsApi());
|
|
|
|
// Create SrsGoApiClients instance
|
|
SrsUniquePtr<SrsGoApiClients> 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<SrsHttpMessage> 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<MockStatisticForClientApi> mock_stat(new MockStatisticForClientApi());
|
|
|
|
// Create a real SrsStatisticClient with all required dependencies
|
|
SrsUniquePtr<SrsStatisticClient> 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
|
|
MockRequest *mock_req = new MockRequest("__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<SrsGoApiClients> 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<SrsHttpMessage> 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<SrsGoApiClusters> 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<std::string, std::string> 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<std::string, std::string>::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<MockHttpMessageForClusters> 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<SrsJsonObject> 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<SrsGoApiRaw> 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<MockHttpMessageForApiResponse> 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<MockHttpMessageForApiResponse> 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<MockHttpMessageForApiResponse> 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<SrsGoApiMetrics> 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<MockHttpMessageForApiResponse> 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;
|
|
}
|