using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using Paca.Mobile.Core; using Xunit; namespace Paca.Tests.Mobile; /// /// Gate 167 ~ 170: PacaDrop 다중 미디어 배치 번들링 & 자동 스트리밍 언패커 기계적 검증 /// public class MobilePacaDropBatchBundleGateTests { [Fact] public void Gate167_BatchBundleManifest_Integrity() { var file1Data = Encoding.UTF8.GetBytes("Media Content 1 - 4K Video Stream"); var file2Data = Encoding.UTF8.GetBytes("Media Content 2 - 128k Audio Track"); var entries = new List { new("video1.mp4", file1Data.Length, Convert.ToHexString(SHA256.HashData(file1Data))), new("audio1.mp3", file2Data.Length, Convert.ToHexString(SHA256.HashData(file2Data))) }; var manifest = new BatchBundleManifest("bundle-001", entries, DateTimeOffset.UtcNow); Assert.Equal("bundle-001", manifest.BundleId); Assert.Equal(2, manifest.Entries.Count); Assert.Equal(file1Data.Length + file2Data.Length, manifest.TotalBytes); Assert.True(manifest.VerifyEntry("video1.mp4", file1Data)); Assert.False(manifest.VerifyEntry("video1.mp4", Encoding.UTF8.GetBytes("Corrupted"))); } [Fact] public async Task Gate168_BatchBundle_Pack_And_Unpack_Streaming() { var file1Data = Encoding.UTF8.GetBytes("Payload for Track 1"); var file2Data = Encoding.UTF8.GetBytes("Payload for Track 2"); using var bundleStream = new MemoryStream(); // 1. 번들 생성 (PC 측 동작 시뮬레이션) await PacaBatchBundlePacker.PackAsync(bundleStream, new Dictionary { ["intro.mp4"] = file1Data, ["outro.mp3"] = file2Data }); bundleStream.Position = 0; // 2. 모바일 언패커로 추출 (모바일 수신 시뮬레이션) var tempExtractDir = Path.Combine(Path.GetTempPath(), "Paca_Test_Bundle_" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(tempExtractDir); try { var extractedFiles = await PacaBatchBundleExtractor.ExtractAsync(bundleStream, tempExtractDir); Assert.Equal(2, extractedFiles.Count); Assert.True(File.Exists(Path.Combine(tempExtractDir, "intro.mp4"))); Assert.True(File.Exists(Path.Combine(tempExtractDir, "outro.mp3"))); var read1 = await File.ReadAllBytesAsync(Path.Combine(tempExtractDir, "intro.mp4")); var read2 = await File.ReadAllBytesAsync(Path.Combine(tempExtractDir, "outro.mp3")); Assert.Equal(file1Data, read1); Assert.Equal(file2Data, read2); } finally { if (Directory.Exists(tempExtractDir)) { Directory.Delete(tempExtractDir, recursive: true); } } } [Fact] public async Task Gate169_BatchBundle_CorruptedEntry_FailsSafely() { using var garbageStream = new MemoryStream(Encoding.UTF8.GetBytes("Not A Valid Paca Bundle Data")); var tempExtractDir = Path.Combine(Path.GetTempPath(), "Paca_Corrupt_" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(tempExtractDir); try { await Assert.ThrowsAnyAsync(async () => { await PacaBatchBundleExtractor.ExtractAsync(garbageStream, tempExtractDir); }); } finally { if (Directory.Exists(tempExtractDir)) Directory.Delete(tempExtractDir, recursive: true); } } [Fact] public void Gate170_AutoIngestToLibraryManager() { var manager = new OfflineLibraryManager(); var extractedEntries = new List { new("item-1", "video.mp4", 50_000_000, "/temp/video.mp4", true), new("item-2", "song.mp3", 5_000_000, "/temp/song.mp3", true) }; foreach (var entry in extractedEntries) { manager.RegisterMedia(new OfflineMediaItem( entry.Id, entry.FileName, entry.SizeBytes, entry.FilePath, DateTimeOffset.UtcNow, entry.IsChecksumVerified )); } Assert.Equal(55_000_000L, manager.TotalPacaBytes); Assert.Equal(2, manager.GetItems(MediaFilter.All, MediaSortOrder.NewestFirst).Count); } }