#include "media/tv_engine.hpp" #include "media/plugin_manager.hpp" #include #include #include #include // ============================================================================ // Simple test framework // ============================================================================ static int tests_run = 0; static int tests_passed = 0; #define TEST(name) \ do { \ ++tests_run; \ std::cout << "TEST: " << name << " ... "; \ std::cout.flush(); \ } while(0) #define PASS() \ do { \ ++tests_passed; \ std::cout << "PASS" << std::endl; \ } while(0) #define ASSERT_TRUE(expr) \ do { \ if (!(expr)) { \ std::cout << "FAIL\n Assertion failed: " << #expr \ << " at " << __FILE__ << ":" << __LINE__ << std::endl; \ return false; \ } \ } while(0) #define ASSERT_EQ(a, b) \ do { \ if ((a) != (b)) { \ std::cout << "FAIL\n Expected " << #a << " == " << #b \ << " at " << __FILE__ << ":" << __LINE__ << std::endl; \ return false; \ } \ } while(0) // ============================================================================ // Tests // ============================================================================ bool test_tv_engine_init() { TEST("TvEngine::initialize"); media::PluginManager pm; pm.initialize("", ""); media::TvEngine engine; media::TvEngineConfig config; config.max_channels = 3; config.hls_output_dir = "/tmp/media_test/hls/tv"; config.hls_segment_duration = 4; config.hls_playlist_size = 10; auto result = engine.initialize(config, &pm); ASSERT_TRUE(result.is_ok()); PASS(); return true; } bool test_tv_engine_create_channel() { TEST("TvEngine::create_channel"); media::PluginManager pm; pm.initialize("", ""); media::TvEngine engine; media::TvEngineConfig config; config.max_channels = 5; config.hls_output_dir = "/tmp/media_test/hls/tv"; engine.initialize(config, &pm); media::TvChannelConfig ch; ch.id = "tv_ch_1"; ch.tenant_id = "tenant_a"; ch.name = "Test TV Channel"; ch.channel_number = 1; ch.codec = "h264"; ch.segment_duration_seconds = 4; ch.playlist_size = 10; auto result = engine.create_channel(ch); ASSERT_TRUE(result.is_ok()); ASSERT_EQ(result.value(), std::string("tv_ch_1")); PASS(); return true; } bool test_tv_engine_duplicate_channel() { TEST("TvEngine::create_channel (duplicate)"); media::PluginManager pm; pm.initialize("", ""); media::TvEngine engine; media::TvEngineConfig config; config.max_channels = 5; config.hls_output_dir = "/tmp/media_test/hls/tv"; engine.initialize(config, &pm); media::TvChannelConfig ch; ch.id = "dup_tv_ch"; ch.tenant_id = "tenant_a"; ch.name = "Dup"; auto r1 = engine.create_channel(ch); ASSERT_TRUE(r1.is_ok()); auto r2 = engine.create_channel(ch); ASSERT_TRUE(r2.is_error()); ASSERT_EQ(static_cast(r2.error_code()), static_cast(media::ErrorCode::CONFLICT)); PASS(); return true; } bool test_tv_engine_max_channels() { TEST("TvEngine::create_channel (max channels)"); media::PluginManager pm; pm.initialize("", ""); media::TvEngine engine; media::TvEngineConfig config; config.max_channels = 2; config.hls_output_dir = "/tmp/media_test/hls/tv"; engine.initialize(config, &pm); for (int i = 0; i < 2; ++i) { media::TvChannelConfig ch; ch.id = "tv_max_" + std::to_string(i); ch.tenant_id = "tenant_a"; ch.name = "Channel " + std::to_string(i); auto r = engine.create_channel(ch); ASSERT_TRUE(r.is_ok()); } media::TvChannelConfig ch3; ch3.id = "tv_max_2"; ch3.tenant_id = "tenant_a"; ch3.name = "Channel 3"; auto r3 = engine.create_channel(ch3); ASSERT_TRUE(r3.is_error()); ASSERT_EQ(static_cast(r3.error_code()), static_cast(media::ErrorCode::CONFLICT)); PASS(); return true; } bool test_tv_engine_delete_channel() { TEST("TvEngine::delete_channel"); media::PluginManager pm; pm.initialize("", ""); media::TvEngine engine; media::TvEngineConfig config; config.max_channels = 5; config.hls_output_dir = "/tmp/media_test/hls/tv"; engine.initialize(config, &pm); media::TvChannelConfig ch; ch.id = "tv_delete_me"; ch.tenant_id = "tenant_a"; ch.name = "Delete Me"; engine.create_channel(ch); auto del_result = engine.delete_channel("tv_delete_me"); ASSERT_TRUE(del_result.is_ok()); auto status_result = engine.get_channel_status("tv_delete_me"); ASSERT_TRUE(status_result.is_error()); ASSERT_EQ(static_cast(status_result.error_code()), static_cast(media::ErrorCode::NOT_FOUND)); PASS(); return true; } bool test_tv_engine_schedule_management() { TEST("TvEngine: schedule management"); media::PluginManager pm; pm.initialize("", ""); media::TvEngine engine; media::TvEngineConfig config; config.max_channels = 5; config.hls_output_dir = "/tmp/media_test/hls/tv"; engine.initialize(config, &pm); media::TvChannelConfig ch; ch.id = "sched_channel"; ch.tenant_id = "tenant_a"; ch.name = "Schedule Test"; engine.create_channel(ch); // Build schedule entries auto now = std::chrono::system_clock::now(); std::vector entries; for (int i = 0; i < 3; ++i) { media::TvProgram prog; prog.id = "prog_" + std::to_string(i); prog.title = "Program " + std::to_string(i); prog.description = "Test program"; prog.category = "movie"; prog.duration_seconds = 3600; prog.content_path = "/data/video/movie" + std::to_string(i) + ".mp4"; media::TvScheduleEntry entry; entry.program = prog; entry.start_time = now + std::chrono::hours(i); entry.end_time = entry.start_time + std::chrono::hours(1); entry.is_live = false; entries.push_back(entry); } auto set_result = engine.set_schedule("sched_channel", entries); ASSERT_TRUE(set_result.is_ok()); // Get schedule for next 4 hours auto end_time = now + std::chrono::hours(4); auto sched_result = engine.get_schedule("sched_channel", now, end_time); ASSERT_TRUE(sched_result.is_ok()); ASSERT_EQ(sched_result.value().size(), static_cast(3)); PASS(); return true; } bool test_tv_engine_add_remove_program() { TEST("TvEngine: add/remove program"); media::PluginManager pm; pm.initialize("", ""); media::TvEngine engine; media::TvEngineConfig config; config.max_channels = 5; config.hls_output_dir = "/tmp/media_test/hls/tv"; engine.initialize(config, &pm); media::TvChannelConfig ch; ch.id = "prog_channel"; ch.tenant_id = "tenant_a"; ch.name = "Program Test"; engine.create_channel(ch); auto now = std::chrono::system_clock::now(); media::TvProgram prog; prog.id = "removable_prog"; prog.title = "Removable Program"; prog.duration_seconds = 1800; prog.content_path = "/data/video/test.mp4"; media::TvScheduleEntry entry; entry.program = prog; entry.start_time = now + std::chrono::hours(1); entry.end_time = entry.start_time + std::chrono::minutes(30); auto add_result = engine.add_program("prog_channel", entry); ASSERT_TRUE(add_result.is_ok()); // Verify it's in schedule auto sched = engine.get_schedule( "prog_channel", now, now + std::chrono::hours(3) ); ASSERT_TRUE(sched.is_ok()); ASSERT_TRUE(sched.value().size() >= 1); // Remove it auto rem_result = engine.remove_program("prog_channel", "removable_prog"); ASSERT_TRUE(rem_result.is_ok()); // Should be gone auto sched2 = engine.get_schedule( "prog_channel", now, now + std::chrono::hours(3) ); ASSERT_TRUE(sched2.is_ok()); ASSERT_EQ(sched2.value().size(), static_cast(0)); PASS(); return true; } bool test_tv_engine_epg() { TEST("TvEngine::generate_epg"); media::PluginManager pm; pm.initialize("", ""); media::TvEngine engine; media::TvEngineConfig config; config.max_channels = 5; config.hls_output_dir = "/tmp/media_test/hls/tv"; engine.initialize(config, &pm); // Create two channels with programs auto now = std::chrono::system_clock::now(); for (int c = 0; c < 2; ++c) { media::TvChannelConfig ch; ch.id = "epg_ch_" + std::to_string(c); ch.tenant_id = "tenant_a"; ch.name = "EPG Channel " + std::to_string(c); engine.create_channel(ch); for (int p = 0; p < 3; ++p) { media::TvProgram prog; prog.id = "epg_prog_" + std::to_string(c) + "_" + std::to_string(p); prog.title = "Program " + std::to_string(p); prog.duration_seconds = 3600; media::TvScheduleEntry entry; entry.program = prog; entry.start_time = now + std::chrono::hours(p); entry.end_time = entry.start_time + std::chrono::hours(1); engine.add_program("epg_ch_" + std::to_string(c), entry); } } auto epg = engine.generate_epg(24); ASSERT_TRUE(epg.size() >= 6); // 2 channels x 3 programs // XMLTV export std::string xmltv = engine.export_xmltv(24); ASSERT_TRUE(xmltv.find("") != std::string::npos); PASS(); return true; } bool test_tv_engine_viewer_count() { TEST("TvEngine::update_viewer_count"); media::PluginManager pm; pm.initialize("", ""); media::TvEngine engine; media::TvEngineConfig config; config.max_channels = 5; config.hls_output_dir = "/tmp/media_test/hls/tv"; engine.initialize(config, &pm); media::TvChannelConfig ch; ch.id = "viewer_channel"; ch.tenant_id = "tenant_a"; ch.name = "Viewer Test"; engine.create_channel(ch); engine.update_viewer_count("viewer_channel", 10); engine.update_viewer_count("viewer_channel", 5); ASSERT_EQ(engine.get_total_viewers(), 15); engine.update_viewer_count("viewer_channel", -3); ASSERT_EQ(engine.get_total_viewers(), 12); PASS(); return true; } bool test_tv_engine_list_channels_tenant_filter() { TEST("TvEngine::list_channels (tenant filter)"); media::PluginManager pm; pm.initialize("", ""); media::TvEngine engine; media::TvEngineConfig config; config.max_channels = 10; config.hls_output_dir = "/tmp/media_test/hls/tv"; engine.initialize(config, &pm); // Tenant A: 2 channels for (int i = 0; i < 2; ++i) { media::TvChannelConfig ch; ch.id = "tv_ta_" + std::to_string(i); ch.tenant_id = "tenant_a"; ch.name = "TV A " + std::to_string(i); engine.create_channel(ch); } // Tenant B: 3 channels for (int i = 0; i < 3; ++i) { media::TvChannelConfig ch; ch.id = "tv_tb_" + std::to_string(i); ch.tenant_id = "tenant_b"; ch.name = "TV B " + std::to_string(i); engine.create_channel(ch); } auto ta = engine.list_channels("tenant_a"); ASSERT_EQ(ta.size(), static_cast(2)); auto tb = engine.list_channels("tenant_b"); ASSERT_EQ(tb.size(), static_cast(3)); auto all = engine.list_channels(); ASSERT_TRUE(all.size() >= 5); PASS(); return true; } bool test_tv_channel_not_found() { TEST("TvEngine::get_channel_status (not found)"); media::PluginManager pm; pm.initialize("", ""); media::TvEngine engine; media::TvEngineConfig config; config.hls_output_dir = "/tmp/media_test/hls/tv"; engine.initialize(config, &pm); auto result = engine.get_channel_status("nonexistent_tv_channel"); ASSERT_TRUE(result.is_error()); ASSERT_EQ(static_cast(result.error_code()), static_cast(media::ErrorCode::NOT_FOUND)); PASS(); return true; } // ============================================================================ // Main // ============================================================================ int main() { std::cout << "=== TV Engine Unit Tests ===" << std::endl; std::cout << std::endl; bool all_passed = true; all_passed &= test_tv_engine_init(); all_passed &= test_tv_engine_create_channel(); all_passed &= test_tv_engine_duplicate_channel(); all_passed &= test_tv_engine_max_channels(); all_passed &= test_tv_engine_delete_channel(); all_passed &= test_tv_engine_schedule_management(); all_passed &= test_tv_engine_add_remove_program(); all_passed &= test_tv_engine_epg(); all_passed &= test_tv_engine_viewer_count(); all_passed &= test_tv_engine_list_channels_tenant_filter(); all_passed &= test_tv_channel_not_found(); std::cout << std::endl; std::cout << "=== Results: " << tests_passed << "/" << tests_run << " tests passed ===" << std::endl; return all_passed ? 0 : 1; }