test(core/browser/platform): 26 extreme RED/GREEN TDD cycles and full regression hardening (3,235 PASS)
Some checks failed
release / test (push) Failing after 14m5s
release / windows (push) Has been cancelled
release / android (push) Has been cancelled
release / linux (push) Has been cancelled
release / macos (push) Has been cancelled

This commit is contained in:
Yun Chan 2026-09-18 23:32:32 +09:00
parent bcd242691f
commit 8677853822
100 changed files with 4814 additions and 333 deletions

View file

@ -0,0 +1,100 @@
using System;
using System.IO;
using System.Security.Cryptography;
using Paca.Core.Config;
using Paca.Core.Dash;
using Paca.Core.Hls;
using Paca.Core.Models;
using Xunit;
namespace Paca.Tests.Core;
/// <summary>
/// Cycle 2: 극한 RED 시나리오 - HLS AES-128 암호화 IV/키 경계값 패딩, 블록 크기 손상 예외 정밀화, DASH 매니페스트 퍼징 검증
/// </summary>
public class HlsAndDashResilienceExtremeRedTests
{
[Fact]
public void Cycle2_Gate1_ResolveIv_ShortHex_PadsTo16BytesBigEndian()
{
// RFC 8216: IV 속성은 128비트(16바이트) 빅엔디언 정수 16진수 표현
// 0x1 과 같이 축약된 hex 가 주어졌을 때 1바이트가 아닌 16바이트로 좌측 0 패딩되어야 함
var keyInfo = new KeyInfo(new Uri("https://example.com/key.bin"), "0x1");
var iv = HlsDownloader.ResolveIv(keyInfo, 100);
Assert.Equal(16, iv.Length);
for (int i = 0; i < 15; i++)
{
Assert.Equal(0, iv[i]);
}
Assert.Equal(1, iv[15]);
// 3바이트 hex (6글자)
var keyHex6 = new KeyInfo(new Uri("https://example.com/key.bin"), "0xABCDEF");
var iv6 = HlsDownloader.ResolveIv(keyHex6, 100);
Assert.Equal(16, iv6.Length);
Assert.Equal(0xAB, iv6[13]);
Assert.Equal(0xCD, iv6[14]);
Assert.Equal(0xEF, iv6[15]);
// 잘못된 비-16진수("0xZZZZ") 주입 시 크래시 없이 시퀀스 번호(100)로 안전 폴백
var malformedKey = new KeyInfo(new Uri("https://example.com/key.bin"), "0xZZZZ");
var ivFallback = HlsDownloader.ResolveIv(malformedKey, 100);
Assert.Equal(16, ivFallback.Length);
Assert.Equal(100, ivFallback[15]);
}
[Fact]
public void Cycle2_Gate2_DecryptSegment_InvalidKeyOrCorruptedBlock_ThrowsInvalidDataException()
{
var config = new AppConfig();
var downloader = new HlsDownloader(new System.Net.Http.HttpClient(), config);
var keyUri = new Uri("https://example.com/aes.key");
var seg = new Segment(new Uri("https://example.com/seg1.ts"), 0, 1, new KeyInfo(keyUri, "0x1"));
// 1. 비정상 키 길이 (16바이트가 아닌 10바이트 등) -> InvalidDataException 발생 확인
downloader.SetPreloadedKeyForTesting(keyUri, new byte[10]);
var exKey = Assert.Throws<InvalidDataException>(() => downloader.DecryptSegmentForTesting(new byte[16], seg));
Assert.Contains("16바이트", exKey.Message);
// 2. 비정상 데이터 블록 크기 (AES CBC는 16의 배수여야 함, 15바이트 주입) -> InvalidDataException 발생 확인
downloader.SetPreloadedKeyForTesting(keyUri, new byte[16]);
var exData = Assert.Throws<InvalidDataException>(() => downloader.DecryptSegmentForTesting(new byte[15], seg));
Assert.Contains("블록 크기", exData.Message);
}
[Fact]
public void Cycle2_Gate3_DashParser_TimescaleZeroOrNegative_DefaultsToOne()
{
var mpdXml = """
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" mediaPresentationDuration="PT10S">
<Period>
<AdaptationSet contentType="video">
<Representation id="1" bandwidth="2000000">
<SegmentTemplate timescale="0" duration="2" media="chunk_$Number$.m4s" startNumber="1" />
</Representation>
</AdaptationSet>
</Period>
</MPD>
""";
var manifest = DashParser.Parse(mpdXml, new Uri("https://cdn.example.com/manifest.mpd"));
Assert.NotNull(manifest);
Assert.NotNull(manifest.Video);
// timescale="0"이어도 0으로 나누기 예외 없이 1로 폴백되어 5개 세그먼트 생성
Assert.NotEmpty(manifest.Video.Segments);
}
[Theory]
[InlineData("P1D", 86400.0)]
[InlineData("P1DT2H", 93600.0)]
[InlineData("PT1H30M", 5400.0)]
[InlineData("PT1.5S", 1.5)]
[InlineData("", 0.0)]
[InlineData("INVALID_DURATION", 0.0)]
public void Cycle2_Gate4_DashParser_Iso8601Duration_SupportsDaysAndFractions(string isoDuration, double expectedSec)
{
double actual = DashParser.ParseDuration(isoDuration);
Assert.Equal(expectedSec, actual, precision: 2);
}
}