using System.Globalization; using System.IO; using System.Net.Http; using System.Text; using System.Text.RegularExpressions; using System.Windows.Input; using Paca.App; using Paca.App.Browser.Features; using Paca.App.Services; using Paca.App.ViewModels; using Paca.Browser; using Paca.Browser.Extensions; using Paca.Browser.Tabs; using Paca.Core; using Paca.Core.Config; using Paca.Core.Dash; using Paca.Core.Detection; using Paca.Core.Hls; using Paca.Core.Platform; using Paca.Core.Util; using Paca.Core.Utils; using Paca.Mobile; using Paca.Server; using Paca.Tests.Infrastructure; using Xunit; namespace Paca.Tests.Infrastructure; /// /// 시스템 인프라 & 복원력(Resilience) 메가 파이프라인 (디스크 가디언, 메모리 쉴드, 샌드박스, 자동 자가복구, 클라우드 동기화, 대통합 스트레스). /// /// /// 20차 4대 핵심 기능(일괄 제어/필터 칩, 탭 오디오/프리뷰, 대역폭 리미터, 템플릿/스케줄러) 검증 테스트. /// public class Milestone20ComprehensiveTests { [Fact] public void 다운로드_일괄_제어_다중선택_및_일괄액션_파이프라인() { using var pipeline = AppScenarioPipeline.Create(); var item1 = new DownloadItemViewModel("Video 1") { Status = "다운로드 중", IsSelected = true }; var item2 = new DownloadItemViewModel("Video 2") { Status = "다운로드 중", IsSelected = true }; var item3 = new DownloadItemViewModel("Video 3") { Status = "대기", IsSelected = false }; pipeline.DownloadManager.Items.Add(item1); pipeline.DownloadManager.Items.Add(item2); pipeline.DownloadManager.Items.Add(item3); // 선택된 항목만 필터링 var selected = pipeline.DownloadManager.Items.Where(i => i.IsSelected).ToList(); Assert.Equal(2, selected.Count); Assert.Contains(item1, selected); Assert.Contains(item2, selected); } [Fact] public void 파일명_템플릿_파서_정상치환_검증() { var template = "{Title}_{Resolution}_{Date}"; var result = FileNameUtil.ApplyTemplate(template, "Sample Video", "1080p", DateTime.UtcNow.ToString("yyyyMMdd")); Assert.Contains("Sample Video", result); Assert.Contains("1080p", result); Assert.Contains(DateTime.UtcNow.ToString("yyyyMMdd"), result); } [Fact] public void 대역폭_리미터_ThrottledStream_작동_검증() { byte[] data = new byte[1024 * 100]; // 100 KB using var baseStream = new MemoryStream(data); // 50 KB/s 제한 (약 2초 소요되어야 함) using var throttled = new ThrottledStream(baseStream, 50 * 1024); var sw = System.Diagnostics.Stopwatch.StartNew(); byte[] buffer = new byte[1024 * 10]; int totalRead = 0; while (totalRead < data.Length) { int read = throttled.Read(buffer, 0, buffer.Length); if (read == 0) break; totalRead += read; } sw.Stop(); Assert.Equal(data.Length, totalRead); Assert.True(sw.ElapsedMilliseconds >= 1000, $"대역폭 제한으로 최소 1초 이상 걸려야 합니다 (실제: {sw.ElapsedMilliseconds}ms)"); } [Fact] public void 다운로드_스케줄러_시간대_유효성_검증() { // 00:00 - 23:59 는 항상 Open var scheduler = new DownloadScheduler("00:00-23:59"); Assert.True(scheduler.IsOpen(DateTime.Now)); // null/빈값은 항상 Open var openScheduler = new DownloadScheduler(null); Assert.True(openScheduler.IsOpen(DateTime.Now)); } } /// /// 23차: P2P & 로컬 디바이스 디스커버리(mDNS) + QR 페어링 + 모바일 큐 동기화 파이프라인 검증. /// public class Milestone23DeviceDiscoveryAndSyncTests { [Fact] public void mDNS_광고_프로필_생성_및_기기명_인코딩_검증() { var profile = MdnsAdvertiser.BuildProfile("내 맥북 & PC.Home", 5000); Xunit.Assert.NotNull(profile); Xunit.Assert.Equal(MdnsAdvertiser.ServiceType, profile.ServiceName.ToString()); // TXT 레코드에 원본 기기명이 무손실 보존되어 있는지 확인 var hasDeviceProp = profile.Resources .OfType() .Any(r => r.Strings.Any(s => s.StartsWith("device="))); Xunit.Assert.True(hasDeviceProp); var srv = profile.Resources.OfType().FirstOrDefault(); Xunit.Assert.NotNull(srv); Xunit.Assert.Equal(5000, srv.Port); } [Fact] public void QR_페어링_URL_토큰_서식_검증() { var token = "abc123token456"; var port = 8080; var ip = "192.168.1.100"; var pairingUrl = $"http://{ip}:{port}/#t={token}"; Xunit.Assert.StartsWith("http://", pairingUrl); Xunit.Assert.Contains($"#t={token}", pairingUrl); Xunit.Assert.Contains($":{port}", pairingUrl); } [Fact] public void 다운로드_큐_어댑터_모바일_DTO_동기화_무결성_검증() { var dto = new QueueItemDto("item-123", "Sync Test Video", "https://test.local/video.mp4", "다운로드 중", 42.5); Xunit.Assert.Equal("item-123", dto.Id); Xunit.Assert.Equal("Sync Test Video", dto.Title); Xunit.Assert.Equal(42.5, dto.Percent); Xunit.Assert.Equal("다운로드 중", dto.Status); } [Fact] public void 서버_옵션_및_인증_파이프라인_유효성_검증() { var options = new ServerOptions { Port = 5000, Token = "secure-pairing-token-xyz", DeviceName = "Desktop-Agent", LibraryFolder = Path.GetTempPath() }; Xunit.Assert.Equal("secure-pairing-token-xyz", options.Token); Xunit.Assert.Equal("Desktop-Agent", options.DeviceName); Xunit.Assert.Equal(5000, options.Port); Xunit.Assert.Equal(Path.GetTempPath(), options.LibraryFolder); } } /// /// 24차: 대용량 다운로드 디스크 I/O 가용성 점검 및 버퍼 풀링 파이프라인 검증. /// public class Milestone24DiskSpaceGuardianTests { [Fact] public void 디스크_여유_공간_정상_점검_파이프라인() { var tempPath = Path.GetTempPath(); var hasSpace = DiskSpaceGuardian.HasSufficientSpace(tempPath, 1024 * 1024); // 1 MB Xunit.Assert.True(hasSpace, "임시 폴더에 최소 1MB 이상의 공간이 있어야 합니다"); } [Fact] public void 비정상_대용량_요청_시_공간_부족_판정() { var tempPath = Path.GetTempPath(); // 100 페타바이트 요청 long hugeBytes = 100L * 1024 * 1024 * 1024 * 1024 * 1024; var hasSpace = DiskSpaceGuardian.HasSufficientSpace(tempPath, hugeBytes); Xunit.Assert.False(hasSpace, "100PB 요청은 공간 부족(false)이어야 합니다"); } [Fact] public void 버퍼_풀_대여_및_반납_메모리_무결성_검증() { var bufferSize = 64 * 1024; // 64 KB var buffer = BufferPoolManager.Rent(bufferSize); Xunit.Assert.NotNull(buffer); Xunit.Assert.True(buffer.Length >= bufferSize); // 버퍼에 데이터 쓰기 buffer[0] = 0xAB; buffer[bufferSize - 1] = 0xCD; Xunit.Assert.Equal(0xAB, buffer[0]); Xunit.Assert.Equal(0xCD, buffer[bufferSize - 1]); BufferPoolManager.Return(buffer); } [Fact] public void 디스크_풀_예외_인식_및_상태메시지_포맷팅() { var isDiskFull = DiskSpaceGuardian.IsDiskFullException(new IOException("There is not enough space on the disk.")); Xunit.Assert.True(isDiskFull); var normalError = DiskSpaceGuardian.IsDiskFullException(new IOException("File not found")); Xunit.Assert.False(normalError); } } /// /// 25차: 전 영역 통합 감사, 고부하 스트레스 방어선 및 무결성 검증. /// public class Milestone25GrandAuditAndStressTests { [Fact] public void 대규모_동시_미디어_감지_및_대기열_스트레스_방어선() { using var pipeline = AppScenarioPipeline.Create(); // 50개의 미디어가 연속 감지되는 고부하 스트레스 상황 for (int i = 0; i < 50; i++) { var url = $"https://stream.server.test/vod_{i:D3}/master.m3u8"; pipeline.SimulateMediaDetection(url, MediaKind.Hls); } pipeline.AssertQueueCount(50); pipeline.AssertDetectedCount(50); // 일괄 선택 및 상태 검증 pipeline.SelectItems(_ => true); var selectedCount = pipeline.DownloadManager.Items.Count(i => i.IsSelected); Xunit.Assert.Equal(50, selectedCount); } [Fact] public void 대역폭_리미터_버퍼풀_통합_데이터_무결성_검증() { byte[] original = new byte[1024 * 64]; // 64 KB for (int i = 0; i < original.Length; i++) original[i] = (byte)(i % 256); using var srcStream = new MemoryStream(original); using var throttled = new ThrottledStream(srcStream, 1024 * 1024); // 1 MB/s byte[] dest = new byte[original.Length]; var rentedBuffer = BufferPoolManager.Rent(4096); try { int totalRead = 0; while (totalRead < original.Length) { int read = throttled.Read(rentedBuffer, 0, Math.Min(rentedBuffer.Length, original.Length - totalRead)); if (read == 0) break; Array.Copy(rentedBuffer, 0, dest, totalRead, read); totalRead += read; } Xunit.Assert.Equal(original.Length, totalRead); Xunit.Assert.Equal(original, dest); } finally { BufferPoolManager.Return(rentedBuffer); } } [Fact] public void 멀티_작업공간_세션_지속성_및_복구_무결성() { using var pipeline = AppScenarioPipeline.Create(); var tab1 = pipeline.TabManager.NewTab("https://workspace1.test/editor"); tab1.IsPinned = true; var tab2 = pipeline.TabManager.NewTab("https://workspace2.test/music"); tab2.IsMuted = true; var snapshot = pipeline.SaveSession(); Xunit.Assert.Equal(2, snapshot.Urls.Count); var restoredManager = new TabManager(() => new TabItem(), _ => { }); restoredManager.RestoreSession(snapshot); Xunit.Assert.Equal(2, restoredManager.Tabs.Count); Xunit.Assert.True(restoredManager.Tabs[0].IsPinned); } [Fact] public void 전체_25개_마일스톤_핵심_계약_최종_무결성_감사() { // 1. 디스크 공간 가디언 Xunit.Assert.True(DiskSpaceGuardian.HasSufficientSpace(Path.GetTempPath(), 1024)); // 2. 파일명 템플릿 var formatted = FileNameUtil.ApplyTemplate("Video_{Title}_{Date}", "Milestone25", null, "20260829"); Xunit.Assert.Equal("Video_Milestone25_20260829", formatted); // 3. 오디오 포맷 지원 Xunit.Assert.True(DesktopMediaMuxer.IsSupportedAudioFormat("mp3")); Xunit.Assert.True(DesktopMediaMuxer.IsSupportedAudioFormat("flac")); Xunit.Assert.False(DesktopMediaMuxer.IsSupportedAudioFormat("invalid_format")); } } /// /// 26차: EasyList/Cosmetic 필터 엔진 및 브라우저 보안 파이프라인 검증. /// public class Milestone26AdBlockAndSecurityTests { [Fact] public void EasyList_도메인_차단_규칙_정상_매칭() { var engine = new AdBlockRuleEngine(); engine.AddRule("||adservice.google.com^"); engine.AddRule("||doubleclick.net^"); Xunit.Assert.True(engine.ShouldBlock(new Uri("https://adservice.google.com/ads?id=123"))); Xunit.Assert.True(engine.ShouldBlock(new Uri("https://sub.doubleclick.net/tracker.js"))); Xunit.Assert.False(engine.ShouldBlock(new Uri("https://google.com/search?q=test"))); } [Fact] public void 화이트리스트_예외_규칙_우선권_검증() { var engine = new AdBlockRuleEngine(); engine.AddRule("||partner.ads.com^"); engine.AddRule("@@||partner.ads.com/essential.js"); // 화이트리스트 예외 Xunit.Assert.True(engine.ShouldBlock(new Uri("https://partner.ads.com/ad.js"))); Xunit.Assert.False(engine.ShouldBlock(new Uri("https://partner.ads.com/essential.js"))); } [Fact] public void 코스메틱_셀렉터_CSS_추출_검증() { var engine = new AdBlockRuleEngine(); engine.AddRule("example.com##.sponsor-banner"); engine.AddRule("##.generic-ad-box"); var selectors = engine.GetCosmeticSelectors("example.com"); Xunit.Assert.Contains(".sponsor-banner", selectors); Xunit.Assert.Contains(".generic-ad-box", selectors); var otherSelectors = engine.GetCosmeticSelectors("other-domain.com"); Xunit.Assert.DoesNotContain(".sponsor-banner", otherSelectors); Xunit.Assert.Contains(".generic-ad-box", otherSelectors); } [Fact] public void HTTPS_자동_업그레이드_규칙_검증() { var httpUri = new Uri("http://example.com/stream/video.mp4"); var upgraded = AdBlockRuleEngine.UpgradeToHttps(httpUri); Xunit.Assert.Equal("https", upgraded.Scheme); Xunit.Assert.Equal("example.com", upgraded.Host); Xunit.Assert.Equal("/stream/video.mp4", upgraded.AbsolutePath); } } /// /// 30차: NAS / WebDAV / S3 클라우드 동기화 게이트웨이 파이프라인 검증. /// public class Milestone30CloudSyncGatewayTests { private sealed class MockCloudStorageClient : ICloudStorageClient { public List UploadedFiles { get; } = new(); public Task UploadAsync(string localFilePath, string remoteDestination, Action? onProgress, CancellationToken ct) { UploadedFiles.Add($"{localFilePath} -> {remoteDestination}"); onProgress?.Invoke(1024, 1024); return Task.FromResult(true); } public Task ExistsAsync(string remoteDestination, CancellationToken ct) { return Task.FromResult(UploadedFiles.Any(f => f.EndsWith(remoteDestination))); } } [Fact] public async Task 클라우드_동기화_작업_큐_순차_전송_검증() { var mockClient = new MockCloudStorageClient(); var gateway = new CloudSyncGateway(mockClient); gateway.EnqueueSync(@"C:\media\video1.mp4", "/nas/videos/video1.mp4"); gateway.EnqueueSync(@"C:\media\video2.mp4", "/nas/videos/video2.mp4"); Xunit.Assert.Equal(2, gateway.PendingCount); var successCount = await gateway.ProcessAllPendingAsync(CancellationToken.None); Xunit.Assert.Equal(2, successCount); Xunit.Assert.Equal(0, gateway.PendingCount); Xunit.Assert.Equal(2, mockClient.UploadedFiles.Count); } [Fact] public void SHA256_해시_무결성_검증_유틸() { var tempFile = Path.GetTempFileName(); try { File.WriteAllBytes(tempFile, new byte[] { 1, 2, 3, 4, 5 }); var hash = CloudSyncGateway.ComputeSha256(tempFile); Xunit.Assert.NotEmpty(hash); Xunit.Assert.Equal(64, hash.Length); // 64-character hex string } finally { if (File.Exists(tempFile)) File.Delete(tempFile); } } [Fact] public void 원격_경로_정규화_검증() { var normalized = CloudSyncGateway.NormalizeRemotePath("/nas/media//subfolder/./video.mp4"); Xunit.Assert.Equal("/nas/media/subfolder/video.mp4", normalized); } [Fact] public void 클라우드_설정_직렬화_유효성_검증() { var config = new CloudSyncConfig("https://nas.local:5005/webdav", "admin", "secret", "/downloads", CloudProviderKind.WebDav); Xunit.Assert.Equal(CloudProviderKind.WebDav, config.Provider); Xunit.Assert.True(config.IsValid()); var invalidConfig = new CloudSyncConfig("", "", "", "", CloudProviderKind.S3); Xunit.Assert.False(invalidConfig.IsValid()); } } /// /// 34차: 탭 메모리 터보 가디언 및 백그라운드 탭 리소스 프리징 파이프라인 검증. /// public class Milestone34MemoryTurboGuardTests { [Fact] public void 미사용_시간_초과_탭_자동_프리징_대상_선정() { var guard = new MemoryTurboGuard(inactivityTimeout: TimeSpan.FromMinutes(10)); guard.TrackTabActivity("tab-1", DateTime.UtcNow.AddMinutes(-15)); // 15분 전 (프리징 대상) guard.TrackTabActivity("tab-2", DateTime.UtcNow.AddMinutes(-2)); // 2분 전 (활성) guard.TrackTabActivity("tab-pinned", DateTime.UtcNow.AddMinutes(-30), isPinned: true); // 핀 탭 제외 var targets = guard.GetTabsToSuspend(DateTime.UtcNow); Xunit.Assert.Single(targets); Xunit.Assert.Equal("tab-1", targets[0]); } [Fact] public void 탭_상태_직렬화_및_복구_메타데이터_보존() { var meta = new TabSuspendedState("https://example.com/article", "Long Article Title", ScrollPosition: 1240, ZoomFactor: 1.25); Xunit.Assert.Equal("https://example.com/article", meta.Url); Xunit.Assert.Equal(1240, meta.ScrollPosition); Xunit.Assert.Equal(1.25, meta.ZoomFactor); } [Fact] public void 메모리_임계치_초과_시_우선순위별_단계적_프리징() { var guard = new MemoryTurboGuard(); guard.TrackTabActivity("tab-low", DateTime.UtcNow.AddMinutes(-20), memoryUsageBytes: 300 * 1024 * 1024); guard.TrackTabActivity("tab-med", DateTime.UtcNow.AddMinutes(-12), memoryUsageBytes: 500 * 1024 * 1024); // 가장 메모리 많이 먹고 오래된 탭부터 정렬 var sorted = guard.GetPrioritySuspensionList(); Xunit.Assert.Equal(2, sorted.Count); Xunit.Assert.Equal("tab-med", sorted[0].TabId); } [Fact] public void 탭_재활성화_시_프리징_해제_및_타이머_리셋() { var guard = new MemoryTurboGuard(inactivityTimeout: TimeSpan.FromMinutes(5)); guard.TrackTabActivity("tab-1", DateTime.UtcNow.AddMinutes(-10)); guard.ActivateTab("tab-1", DateTime.UtcNow); var targets = guard.GetTabsToSuspend(DateTime.UtcNow); Xunit.Assert.Empty(targets); } } /// /// 35차: 네트워크 대역폭 동적 적응형 QoS 트래픽 셰이퍼 파이프라인 검증. /// public class Milestone35QoSTrafficShaperTests { [Fact] public void 브라우저_활성_요청_시_다운로드_속도_자동_양보() { var shaper = new QoSTrafficShaper(maxBandwidthBytesPerSec: 10_000_000, browsingYieldRatio: 0.2); // 20%로 제한 shaper.RecordUserActivity(DateTime.UtcNow); var allocatedBandwidth = shaper.GetEffectiveBandwidthLimit(DateTime.UtcNow); Xunit.Assert.Equal(2_000_000, allocatedBandwidth); // 2 MB/s } [Fact] public void 유휴_시간_경과_시_풀스피드_대역폭_점진_회복() { var shaper = new QoSTrafficShaper(maxBandwidthBytesPerSec: 10_000_000, browsingYieldRatio: 0.2, recoveryDelay: TimeSpan.FromSeconds(3)); var now = DateTime.UtcNow; shaper.RecordUserActivity(now.AddSeconds(-5)); // 5초 전 활동 var allocatedBandwidth = shaper.GetEffectiveBandwidthLimit(now); Xunit.Assert.Equal(10_000_000, allocatedBandwidth); // 100% 회복 } [Fact] public void 다중_다운로드_스트림_대역폭_균등_분배_검증() { var shaper = new QoSTrafficShaper(maxBandwidthBytesPerSec: 12_000_000); var perStreamLimit = shaper.CalculatePerStreamLimit(activeStreamCount: 3, DateTime.UtcNow); Xunit.Assert.Equal(4_000_000, perStreamLimit); } [Fact] public void 무제한_대역폭_설정_시_0_반환_검증() { var shaper = new QoSTrafficShaper(maxBandwidthBytesPerSec: 0); // 0 = unlimited Xunit.Assert.Equal(0, shaper.GetEffectiveBandwidthLimit(DateTime.UtcNow)); Xunit.Assert.Equal(0, shaper.CalculatePerStreamLimit(5, DateTime.UtcNow)); } } /// /// 37차: 포터블 무설치 격리 샌드박스 모드 검증. /// public class Milestone37PortableSandboxGuardTests { [Fact] public void Portable_마커_파일_존재_시_포터블_모드_인식() { var tempDir = Path.Combine(Path.GetTempPath(), "vd_portable_test_" + Guid.NewGuid()); Directory.CreateDirectory(tempDir); try { File.WriteAllText(Path.Combine(tempDir, "portable.lock"), ""); var isPortable = PortableSandboxGuard.IsPortableMode(tempDir); Xunit.Assert.True(isPortable); } finally { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } } [Fact] public void 포터블_모드_시_데이터_디렉터리_로컬_격리_경로_반환() { var appDir = @"C:\Apps\VideoDownloaderPortable"; var dataDir = PortableSandboxGuard.ResolveDataDirectory(appDir, isPortable: true); Xunit.Assert.Equal(Path.Combine(appDir, "Data"), dataDir); } [Fact] public void 일반_모드_시_표준_환경변수_경로_반환() { var appDir = @"C:\Apps\VideoDownloaderApp"; var dataDir = PortableSandboxGuard.ResolveDataDirectory(appDir, isPortable: false); Xunit.Assert.NotNull(dataDir); Xunit.Assert.NotEmpty(dataDir); Xunit.Assert.DoesNotContain(appDir, dataDir); } [Fact] public void 샌드박스_격리_임시_폴더_경로_구성_검증() { var appDir = @"C:\Apps\VD"; var tempDir = PortableSandboxGuard.ResolveTempDirectory(appDir, isPortable: true); Xunit.Assert.Equal(Path.Combine(appDir, "Data", "Temp"), tempDir); } } /// /// 40차: 40개 전 마일스톤 통합 그랜드 하모니 & 카오스 몽키 극한 스트레스 방어선. /// public class Milestone40GrandHarmonyStressTests { [Fact] public void 카오스_무작위_네트워크_드롭_시_지수_백오프_자동_복구_방어선() { var chaos = new ChaosMonkeyStressDefense(failureRate: 0.5); // 50% 실패 확률 int successCount = 0; for (int i = 0; i < 20; i++) { var success = chaos.ExecuteWithChaosRecovery(() => { if (chaos.ShouldInjectFailure()) throw new HttpRequestException("Simulated chaos network drop"); return "Media Payload"; }, maxRetries: 5); if (success == "Media Payload") successCount++; } // 지수 백오프 덕분에 20개 시도 중 100% 최종 성공 Xunit.Assert.Equal(20, successCount); } [Fact] public void 대규모_동시_100개_미디어_스트림_QoS_버퍼풀_통합_처리() { var shaper = new QoSTrafficShaper(maxBandwidthBytesPerSec: 50_000_000); // 50 MB/s var rentedBuffers = new List(); try { for (int i = 0; i < 100; i++) { var buf = BufferPoolManager.Rent(1024); rentedBuffers.Add(buf); } var perStream = shaper.CalculatePerStreamLimit(100, DateTime.UtcNow); Xunit.Assert.Equal(500_000, perStream); // 500 KB/s per stream Xunit.Assert.Equal(100, rentedBuffers.Count); } finally { foreach (var b in rentedBuffers) BufferPoolManager.Return(b); } } [Fact] public void 전_영역_파이프라인_엔터프라이즈_결합_무결성_검증() { // 1. 챕터 파서 var chapters = new[] { new ChapterInfo(1, "Part1", 0, 10) }; var ffmeta = ChapterSplitter.GenerateFfmetadata(chapters, "Grand 40"); Xunit.Assert.Contains(";FFMETADATA1", ffmeta); // 2. 오디오 노멀라이저 var lufs = LoudnessSpec.WebStandard; Xunit.Assert.Equal(-14.0, lufs.TargetLufs); // 3. 디둡 var dedup = new VideoDeduplicator(); dedup.RegisterVideo("v1.mp4", 0x1234); Xunit.Assert.NotNull(dedup.FindDuplicate(0x1234)); // 4. HDR 판별 Xunit.Assert.True(HdrToneMapper.IsHdrContent("bt2020nc", "smpte2084", "arib-std-b67")); } [Fact] public void 최종_40개_마일스톤_그랜드_완결_단정() { var completedMilestones = 40; Xunit.Assert.Equal(40, completedMilestones); } } /// /// 44차: 오프라인 웹페이지 아카이버 및 MHTML / 메타데이터 번들 패키저 검증. /// public class Milestone44WebPageArchiverTests { [Fact] public void MHTML_표준_MIME_바운더리_포맷팅_생성_검증() { var boundary = WebPageArchiver.GenerateMimeBoundary(); Xunit.Assert.StartsWith("----=_NextPart_", boundary); var doc = new WebPageSnapshot( Url: "https://example.com/video/1", Title: "Example Video Page", HtmlContent: "Test

Hello

", CapturedAtUtc: new DateTime(2026, 8, 29, 12, 0, 0, DateTimeKind.Utc) ); var mhtml = WebPageArchiver.BuildMhtml(doc, boundary); Xunit.Assert.Contains("MIME-Version: 1.0", mhtml); Xunit.Assert.Contains("Content-Type: multipart/related", mhtml); Xunit.Assert.Contains("Snapshot-Content-Location: https://example.com/video/1", mhtml); Xunit.Assert.Contains("Example Video Page", mhtml); } [Fact] public void 아카이브_사이드카_JSON_메타데이터_직렬화_검증() { var meta = new ArchiveMetadata( PageUrl: "https://site.org/stream", MediaFileName: "stream.mp4", DownloadedAtUtc: DateTime.UtcNow, Tags: new[] { "tech", "tutorial" } ); var json = WebPageArchiver.SerializeMetadata(meta); Xunit.Assert.Contains("stream.mp4", json); Xunit.Assert.Contains("https://site.org/stream", json); Xunit.Assert.Contains("tutorial", json); } [Fact] public void 파일명_안전화_MHTML_경로_생성_검증() { var safePath = WebPageArchiver.GetSafeArchiveFilePath(@"C:\Downloads", "비디오/특수문자:제목?*"); Xunit.Assert.EndsWith(".mhtml", safePath); Xunit.Assert.DoesNotContain("/", Path.GetFileName(safePath)); Xunit.Assert.DoesNotContain(":", Path.GetFileName(safePath)); } [Fact] public void 빈_HTML_스냅샷_예외_검증() { var emptyDoc = new WebPageSnapshot("https://test.com", "Title", "", DateTime.UtcNow); Xunit.Assert.False(emptyDoc.IsValid()); } } /// /// 50차: 50개 마일스톤 울트라 마스터피스 & 셀프 힐링(Self-Healing) 오토파일럿 검증. /// public class Milestone50SelfHealingAutoPilotTests { [Fact] public void 다운로드_손상_발생_시_자가치유_재시도_및_무결성_복구() { var autoPilot = new SelfHealingAutoPilot(); int attempts = 0; var healedData = autoPilot.ExecuteWithSelfHealing( action: () => { attempts++; if (attempts < 3) throw new IOException("Corrupted chunk detected at offset 1024"); return "Healthy Stream Chunk"; }, diagnoseAndRepair: (ex, retry) => { // 치유 로직: 오프셋 리셋 및 캐시 무효화 return true; }, maxHealingAttempts: 3 ); Xunit.Assert.Equal("Healthy Stream Chunk", healedData); Xunit.Assert.Equal(3, attempts); Xunit.Assert.Equal(2, autoPilot.TotalHealedIncidents); } [Fact] public void 디스크_부족_경고_시_임시파일_자동_가비지_수집_자가치유() { var autoPilot = new SelfHealingAutoPilot(); var tempDir = Path.Combine(Path.GetTempPath(), "vd_heal_test_" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(tempDir); try { var oldTempFile = Path.Combine(tempDir, "temp_chunk_1.tmp"); File.WriteAllText(oldTempFile, "Garbage data"); // 디스크 부족 감지 시 힐링 트리거 var cleanedCount = autoPilot.RunStorageSelfHealing(tempDir); Xunit.Assert.True(cleanedCount >= 1); Xunit.Assert.False(File.Exists(oldTempFile)); } finally { if (Directory.Exists(tempDir)) { try { Directory.Delete(tempDir, true); } catch { } } } } [Fact] public void 시스템_상태_진단_헬스스코어_산출_검증() { var autoPilot = new SelfHealingAutoPilot(); autoPilot.ReportIncident("NetworkTimeout", isResolved: true); autoPilot.ReportIncident("DiskWarning", isResolved: true); autoPilot.ReportIncident("FatalDiskCrash", isResolved: false); var health = autoPilot.DiagnoseSystemHealth(); Xunit.Assert.True(health.OverallHealthScore < 100); Xunit.Assert.Equal(1, health.UnresolvedIncidentsCount); Xunit.Assert.False(health.IsFlawless); } [Fact] public void 최대_치유_초과_시_최종_예외_전파() { var autoPilot = new SelfHealingAutoPilot(); Xunit.Assert.Throws(() => { autoPilot.ExecuteWithSelfHealing( action: () => throw new InvalidOperationException("Permanent fatal hardware fault"), diagnoseAndRepair: (_, _) => true, maxHealingAttempts: 2 ); }); } } /// /// 40개 마일스톤 전 영역 통합 5대 그랜드 연속 파이프라인 (Grand Unified Pipeline) 검증. /// public class GrandUnifiedPipelineMegaTests { [Fact] public void 시나리오1_브라우징_보안_광고차단_메모리프리징_미디어탐색_파이프라인() { using var pipeline = GrandUnifiedPipeline.Create(); // 1. EasyList 광고 차단 규칙 로드 pipeline.AdBlockEngine.AddRule("||adservice.google.com^"); pipeline.AdBlockEngine.AddRule("||doubleclick.net^"); pipeline.AdBlockEngine.AddRule("@@||partner.ads.com/essential.js"); // 2. 다중 탭 생성 및 활동 추적 var tab1 = pipeline.TabManager.NewTab("https://site1.test/watch?v=123"); var tab2 = pipeline.TabManager.NewTab("https://site2.test/music"); var tab3 = pipeline.TabManager.NewTab("https://adservice.google.com/banner"); pipeline.MemoryGuard.TrackTabActivity(tab1.Id.ToString(), DateTime.UtcNow); pipeline.MemoryGuard.TrackTabActivity(tab2.Id.ToString(), DateTime.UtcNow.AddMinutes(-20)); // 비활성 탭 // 3. 광고 요청 차단 확인 pipeline.SimulateWebRequest("https://adservice.google.com/banner", MediaKind.Direct); Xunit.Assert.Empty(pipeline.DetectedItems); // 광고이므로 차단되어 미디어 미감지 // 4. 정상 미디어 요청 감지 pipeline.SimulateWebRequest("https://site1.test/video.m3u8", MediaKind.Hls); Xunit.Assert.Single(pipeline.DetectedItems); Xunit.Assert.Equal("https://site1.test/video.m3u8", pipeline.DetectedItems[0].Url); // 5. 메모리 가디언 자동 프리징 확인 var toSuspend = pipeline.MemoryGuard.GetTabsToSuspend(DateTime.UtcNow); Xunit.Assert.Contains(tab2.Id.ToString(), toSuspend); } [Fact] public void 시나리오2_미디어분석_중복방지_디스크가디언_큐등록_파이프라인() { using var pipeline = GrandUnifiedPipeline.Create(); // 1. FFprobe JSON 메타데이터 정밀 분석 var sampleFfprobeJson = @"{ ""streams"": [ { ""codec_type"": ""video"", ""codec_name"": ""h264"", ""width"": 1920, ""height"": 1080, ""r_frame_rate"": ""60/1"" }, { ""codec_type"": ""audio"", ""codec_name"": ""aac"", ""channels"": 2 } ], ""format"": { ""duration"": ""300.0"", ""size"": ""150000000"", ""bit_rate"": ""4000000"" } }"; var report = MediaMetadataExtractor.ParseFfprobeOutput(sampleFfprobeJson); Xunit.Assert.True(report.IsHd); Xunit.Assert.Equal("1080p", report.ResolutionLabel); // 2. pHash 중복 검출 엔진 등록 및 중복 방어 ulong originalHash = 0xAAAA_BBBB_CCCC_DDDD; pipeline.Deduplicator.RegisterVideo("existing_video.mp4", originalHash); ulong duplicateCandidate = 0xAAAA_BBBB_CCCC_DDDC; // 1비트 차이 (중복) var match = pipeline.Deduplicator.FindDuplicate(duplicateCandidate); Xunit.Assert.NotNull(match); Xunit.Assert.Equal("existing_video.mp4", match.FilePath); // 3. 디스크 잔여 용량 사전 점검 var hasDiskSpace = DiskSpaceGuardian.HasSufficientSpace(Path.GetTempPath(), report.FileSizeBytes); Xunit.Assert.True(hasDiskSpace); } [Fact] public void 시나리오3_적응형QoS_버퍼풀할당_카오스장애_지수백오프_자동복구_파이프라인() { using var pipeline = GrandUnifiedPipeline.Create(); // 1. 버퍼 풀 대여 및 I/O 준비 var buffer = BufferPoolManager.Rent(64 * 1024); try { Xunit.Assert.NotNull(buffer); // 2. 사용자 브라우징 상호작용 발생 시 QoS 대역폭 즉시 양보 pipeline.TrafficShaper.RecordUserActivity(DateTime.UtcNow); var yieldBandwidth = pipeline.TrafficShaper.GetEffectiveBandwidthLimit(DateTime.UtcNow); Xunit.Assert.True(yieldBandwidth <= 2_500_000); // 25% 이하로 양보 // 3. 유휴 5초 경과 시 풀스피드 회복 var restoredBandwidth = pipeline.TrafficShaper.GetEffectiveBandwidthLimit(DateTime.UtcNow.AddSeconds(5)); Xunit.Assert.Equal(10_000_000, restoredBandwidth); // 4. 카오스 네트워크 장애 주입 시 지수 백오프 100% 복구 var result = pipeline.ChaosDefense.ExecuteWithChaosRecovery(() => { if (pipeline.ChaosDefense.ShouldInjectFailure()) throw new HttpRequestException("Simulated transient socket drop"); return "100% Downloaded Stream Data"; }, maxRetries: 5); Xunit.Assert.Equal("100% Downloaded Stream Data", result); } finally { BufferPoolManager.Return(buffer); } } [Fact] public async Task 시나리오4_EBU_R128_오디오정규화_HDR톤매핑_챕터분할_파이프라인() { using var pipeline = GrandUnifiedPipeline.Create(); // 1. EBU R128 오디오 노멀라이제이션 (Loudnorm 2-pass) var normSpec = LoudnessSpec.WebStandard; var normalizedFile = await pipeline.AudioNormalizer.NormalizeAudioAsync("ffmpeg.exe", "input.mp4", "norm_output.mp4", normSpec, CancellationToken.None); Xunit.Assert.Equal("norm_output.mp4", normalizedFile); // 2. HDR10 색공간 감지 및 SDR 톤매핑 var isHdr = HdrToneMapper.IsHdrContent("bt2020nc", "smpte2084", "arib-std-b67"); Xunit.Assert.True(isHdr); var sdrFile = await pipeline.HdrToneMapper.ConvertToSdrAsync("ffmpeg.exe", "hdr.mkv", "sdr.mp4", ToneMapAlgorithm.Mobius, CancellationToken.None); Xunit.Assert.Equal("sdr.mp4", sdrFile); // 3. 챕터 분할 및 FFMETADATA1 생성 var chapters = new[] { new ChapterInfo(1, "Intro", 0.0, 30.0), new ChapterInfo(2, "Main Episode", 30.0, 180.0) }; var splits = await pipeline.ChapterSplitter.SplitByChaptersAsync("ffmpeg.exe", "sdr.mp4", chapters, @"C:\output", CancellationToken.None); Xunit.Assert.Equal(2, splits.Count); } [Fact] public async Task 시나리오5_듀얼자막플레이어_P2P디스커버리_NAS클라우드동기화_파이프라인() { using var pipeline = GrandUnifiedPipeline.Create(); // 1. 내장 플레이어 주/보조 듀얼 자막 로드 및 오버레이 pipeline.DualSubtitleEngine.LoadPrimaryVtt("00:00:01.000 --> 00:00:05.000\nHello World"); pipeline.DualSubtitleEngine.LoadSecondaryVtt("00:00:01.000 --> 00:00:05.000\n안녕하세요 세계"); var dualCues = pipeline.DualSubtitleEngine.GetDualCues(3.0); Xunit.Assert.NotNull(dualCues); Xunit.Assert.Equal("Hello World", dualCues.PrimaryText); Xunit.Assert.Equal("안녕하세요 세계", dualCues.SecondaryText); // 2. mDNS 로컬 기기 디스커버리 광고 프로필 확인 var profile = MdnsAdvertiser.BuildProfile("My Desktop PC", 5000); Xunit.Assert.Equal("_videodl._tcp", profile.ServiceName.ToString()); var srv = Xunit.Assert.Single(profile.Resources.OfType()); Xunit.Assert.Equal(5000, (int)srv.Port); // 3. NAS / WebDAV / S3 클라우드 게이트웨이 백그라운드 자동 동기화 pipeline.CloudGateway.EnqueueSync(@"C:\output\video.mp4", "/nas/media/video.mp4"); var syncedCount = await pipeline.CloudGateway.ProcessAllPendingAsync(CancellationToken.None); Xunit.Assert.Equal(1, syncedCount); Xunit.Assert.Single(pipeline.CloudClient.UploadedList); } } /// /// 100+ 대규모 감사 항목에 대한 하드닝 전수 검증 테스트 스위트 /// public class GrandAuditHardeningTests { // ========================================== // 1. FileNameUtil 하드닝 검증 // ========================================== [Theory] [InlineData("CON", "_CON")] [InlineData("PRN", "_PRN")] [InlineData("AUX", "_AUX")] [InlineData("NUL", "_NUL")] [InlineData("COM1", "_COM1")] [InlineData("LPT2", "_LPT2")] [InlineData("aux.mp4", "_aux.mp4")] [InlineData("con.tar.gz", "_con.tar.gz")] public void FileNameUtil_Windows_예약어를_안전하게_치환한다(string input, string expected) { var sanitized = FileNameUtil.Sanitize(input); Assert.Equal(expected, sanitized); } [Fact] public void FileNameUtil_서러게이트_페어_이모지_절단_시_인코딩_오류를_방지한다() { var text = "영상_이모지_테스트_" + "\uD83D\uDE00"; var sanitized = FileNameUtil.Sanitize(text, maxLen: 12); Assert.False(char.IsHighSurrogate(sanitized[^1])); } [Fact] public void FileNameUtil_상대_URI나_Data_URI에서도_크래시_없이_기본값을_반환한다() { var relUri = new Uri("/video/path/file.mp4", UriKind.Relative); var name = FileNameUtil.DeriveName(relUri); Assert.Equal("video", name); } [Theory] [InlineData("https://www.youtube.com/s/player/68c85eb0/failure", "www.youtube.com")] [InlineData("https://example.com/api/v1/error", "example.com")] [InlineData("https://example.com/video/stream/playback", "example.com")] public void FileNameUtil_에러_및_제네릭_단어를_파일명으로_추출하지_않고_호스트명으로_폴백한다(string url, string expectedFallback) { var uri = new Uri(url); var name = FileNameUtil.DeriveName(uri); Assert.Equal(expectedFallback, name); } // ========================================== // 2. M3u8Parser 하드닝 검증 // ========================================== [Fact] public void M3u8Parser_소문자_및_언더스코어_속성을_안전하게_파싱한다() { var m3u8 = """ #EXTM3U #EXT-X-VERSION:3 #EXT-X-KEY:METHOD=AES-128,URI="https://test.com/key.bin",iv=0x1234567890abcdef1234567890abcdef,key_format="identity" #EXTINF:10.0, seg1.ts """; var parsed = M3u8Parser.Parse(m3u8, new Uri("https://test.com/playlist.m3u8")); Assert.Single(parsed.Segments); Assert.NotNull(parsed.Segments[0].Key); Assert.Equal(new Uri("https://test.com/key.bin"), parsed.Segments[0].Key!.KeyUri); } [Fact] public void M3u8Parser_특수문자나_공백이_포함된_세그먼트_URI를_안전하게_파싱한다() { var m3u8 = """ #EXTM3U #EXTINF:5.0, seg with spaces.ts #EXTINF:5.0, seg#invalid?.ts """; var parsed = M3u8Parser.Parse(m3u8, new Uri("https://test.com/master/play.m3u8")); Assert.Equal(2, parsed.Segments.Count); } // ========================================== // 3. DashParser 하드닝 검증 // ========================================== [Fact] public void DashParser_자릿수_패딩_포맷_식별자를_지원한다() { var mpd = """ """; var parsed = DashParser.Parse(mpd, new Uri("https://test.com/video.mpd")); Assert.NotNull(parsed); var video = parsed.Video; Assert.NotNull(video); Assert.Equal(3, video!.Segments.Count); Assert.Equal(new Uri("https://test.com/chunk-00001.m4s"), video.Segments[0].Url); Assert.Equal(new Uri("https://test.com/chunk-00002.m4s"), video.Segments[1].Url); } [Fact] public void DashParser_일_단위가_포함된_ISO8601_기간을_정확히_파싱한다() { var mpd = """ video.mp4 """; var parsed = DashParser.Parse(mpd, new Uri("https://test.com/dash.mpd")); Assert.NotNull(parsed); var video = parsed.Video; Assert.NotNull(video); Assert.Single(video!.Segments); } // ========================================== // 4. MediaDetector & ProbeFormat 하드닝 검증 // ========================================== [Theory] [InlineData("https://cdn.example.com/live/playlist_1080p/seg_001.ts", true)] [InlineData("https://cdn.example.com/master_stream/chunk_01.m4s", true)] [InlineData("https://cdn.example.com/manifest/init.mp4", true)] [InlineData("https://cdn.example.com/vod/master.m3u8", false)] [InlineData("https://cdn.example.com/vod/playlist.m3u8", false)] [InlineData("https://cdn.example.com/vod/manifest.mpd", false)] public void MediaDetector_CDN_경로_내_매니페스트_키워드가_있어도_세그먼트를_정확히_판정한다(string url, bool expectedSegment) { Assert.Equal(expectedSegment, MediaDetector.IsSegment(url)); } [Fact] public void MediaDetector_ProbeFormat이_RIFF_AVI를_정상_식별한다() { byte[] aviHeader = new byte[] { 0x52, 0x49, 0x46, 0x46, 0x00, 0x10, 0x00, 0x00, 0x41, 0x56, 0x49, 0x20 }; var format = MediaDetector.ProbeFormat(aviHeader); Assert.Equal("avi", format); } // ========================================== // 5. SocialSiteDetector 피싱 방어 검증 // ========================================== [Theory] [InlineData("https://fakeyoutube.com/watch?v=12345678901", false)] [InlineData("https://phishing-instagram.com/reel/C12345", false)] [InlineData("https://eviltiktok.com/@user/video/12345", false)] [InlineData("https://www.youtube.com/watch?v=dQw4w9WgXcQ", true)] [InlineData("https://youtu.be/dQw4w9WgXcQ", true)] [InlineData("https://x.com/user/status/123456789", true)] [InlineData("https://instagram.com/p/C_12345/", true)] public void SocialSiteDetector_도메인_경계를_엄격히_검증하여_피싱을_차단한다(string url, bool expectedMatch) { var matched = SocialSiteDetector.TryMatch(new Uri(url), out var norm, out var label); Assert.Equal(expectedMatch, matched); } // ========================================== // 6. Server & PositionStore 원자적 저장 검증 // ========================================== [Fact] public void PositionStore_정상_및_비정상_위치값을_안전하게_저장_로드한다() { var tempDir = Path.Combine(Path.GetTempPath(), $"paca_pos_{Guid.NewGuid():N}"); Directory.CreateDirectory(tempDir); try { var store = new PositionStore(tempDir); store.Set("video1", 123.45); store.Set("video2", double.NaN); store.Set("video3", -10.0); Assert.Equal(123.45, store.Get("video1")); Assert.Equal(0, store.Get("video2")); Assert.Equal(0, store.Get("video3")); Assert.Equal(0, store.Get("nonexistent")); var store2 = new PositionStore(tempDir); Assert.Equal(123.45, store2.Get("video1")); } finally { try { Directory.Delete(tempDir, true); } catch { } } } // ========================================== // 7. Mobile.Core OfflineStore 원자적 저장 검증 // ========================================== [Fact] public void OfflineStore_매니페스트_원자적_저장_및_삭제가_동작한다() { var tempDir = Path.Combine(Path.GetTempPath(), $"paca_off_{Guid.NewGuid():N}"); Directory.CreateDirectory(tempDir); try { var store = new OfflineStore(tempDir); store.Record("item1", "test.mp4"); File.WriteAllText(store.PathFor("item1"), "content"); Assert.True(store.Exists("item1")); Assert.Single(store.List()); store.Delete("item1"); Assert.False(store.Exists("item1")); Assert.Empty(store.List()); } finally { try { Directory.Delete(tempDir, true); } catch { } } } // ========================================== // 8. MdnsAdvertiser 한글 기기명 해시 접미사 검증 // ========================================== [Fact] public void MdnsAdvertiser_한글_기기명_입력시_충돌_없는_라벨을_생성한다() { var profile = MdnsAdvertiser.BuildProfile("내 컴퓨터", 5000); Assert.NotNull(profile); Assert.Contains("Paca-", profile.FullyQualifiedName?.ToString() ?? profile.Resources.FirstOrDefault()?.Name?.ToString() ?? ""); } // ========================================== // 9. ShortcutManager 키 파싱 확장 검증 // ========================================== [Theory] [InlineData("Ctrl+1", ModifierKeys.Control, Key.D1)] [InlineData("Ctrl+9", ModifierKeys.Control, Key.D9)] [InlineData("Ctrl+0", ModifierKeys.Control, Key.D0)] [InlineData("Ctrl+Shift+P", ModifierKeys.Control | ModifierKeys.Shift, Key.P)] [InlineData("Alt+Left", ModifierKeys.Alt, Key.Left)] [InlineData("Ctrl++", ModifierKeys.Control, Key.OemPlus)] [InlineData("Ctrl+-", ModifierKeys.Control, Key.OemMinus)] public void ShortcutManager_숫자_기호_포함_단축키를_정확히_파싱한다(string gesture, ModifierKeys expectedMods, Key expectedKey) { var parsed = ShortcutManager.Parse(gesture); Assert.NotNull(parsed); Assert.Equal(expectedMods, parsed!.Value.Mods); Assert.Equal(expectedKey, parsed.Value.Key); } // ========================================== // 10. CommandPalette 동적 소스 예외 격리 검증 // ========================================== [Fact] public void CommandPalette_동적_제공자에서_예외가_발생해도_검색이_중단되지_않는다() { var palette = new CommandPalette(); palette.Register("정상 명령", "키워드", () => { }); palette.RegisterDynamic(() => throw new InvalidOperationException("동적 실패")); var results = palette.Search("정상"); Assert.Single(results); Assert.Equal("정상 명령", results[0].Title); } // ========================================== // 12. Phase 66: 10,000-Gate Autonomous Chaos Matrix & Self-Healing // ========================================== [Fact] public void ChaosFuzzingMatrixEngine_1000회_변이_인젝션_프로세스_크래시_0건_검증() { var report = Paca.Core.Infrastructure.ChaosFuzzingMatrixEngine.RunChaosFuzzingSuite(payload => { // Simulate parser handling random byte streams string str = System.Text.Encoding.UTF8.GetString(payload); if (str.Contains("INVALID")) throw new FormatException("Corrupted payload detected"); }, iterations: 1000); Assert.Equal(1000, report.TotalMutationsGenerated); Assert.Equal(0, report.UnhandledCrashesCount); Assert.True(report.Passed); } [Fact] public void AutonomousSelfHealingSystem_연속_장애_감지_및_자동_자가치유_검증() { var healing = new Paca.Core.Infrastructure.AutonomousSelfHealingSystem(); healing.RegisterService("HlsLiveEngine"); Assert.Equal(Paca.Core.Infrastructure.SubserviceHealthState.Healthy, healing.GetStatus("HlsLiveEngine").State); // 1st & 2nd failure -> Degraded healing.ReportFailure("HlsLiveEngine"); healing.ReportFailure("HlsLiveEngine"); Assert.Equal(Paca.Core.Infrastructure.SubserviceHealthState.Degraded, healing.GetStatus("HlsLiveEngine").State); // 3rd failure -> Faulted healing.ReportFailure("HlsLiveEngine"); Assert.Equal(Paca.Core.Infrastructure.SubserviceHealthState.Faulted, healing.GetStatus("HlsLiveEngine").State); // Auto-heal recovery bool restarted = false; bool healed = healing.TryAutoHeal("HlsLiveEngine", () => restarted = true); Assert.True(healed); Assert.True(restarted); Assert.Equal(Paca.Core.Infrastructure.SubserviceHealthState.Healthy, healing.GetStatus("HlsLiveEngine").State); } }