test(core/browser/platform): 26 extreme RED/GREEN TDD cycles and full regression hardening (3,235 PASS)
This commit is contained in:
parent
bcd242691f
commit
8677853822
100 changed files with 4814 additions and 333 deletions
|
|
@ -0,0 +1,155 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Paca.Core.Hls;
|
||||
using Paca.Core.Platform;
|
||||
using Xunit;
|
||||
|
||||
namespace Paca.Tests.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Cycle 4: 극한 RED 시나리오 - 카오스 멀티스레딩 동시성 무결성 및 경로 순회(Path Traversal) / 파일 락 방어선 검증
|
||||
/// </summary>
|
||||
public class ConcurrencyAndCacheIntegrityExtremeRedTests
|
||||
{
|
||||
[Fact]
|
||||
public void Cycle4_Gate1_VideoDeduplicator_ConcurrentStress_MustNotThrowOrCorrupt()
|
||||
{
|
||||
var deduplicator = new VideoDeduplicator(maxHammingThreshold: 5);
|
||||
const int TaskCount = 32;
|
||||
const int IterationsPerTask = 200;
|
||||
|
||||
Parallel.For(0, TaskCount, taskId =>
|
||||
{
|
||||
for (int i = 0; i < IterationsPerTask; i++)
|
||||
{
|
||||
var filePath = $"C:/media/video_{taskId}_{i}.mp4";
|
||||
ulong hash = (ulong)(taskId * 10000 + i);
|
||||
|
||||
// 동시 등록
|
||||
deduplicator.RegisterVideo(filePath, hash);
|
||||
|
||||
// 동시 검색
|
||||
var match = deduplicator.FindDuplicate(hash);
|
||||
Assert.NotNull(match);
|
||||
Assert.Equal(0, match.HammingDistance);
|
||||
}
|
||||
});
|
||||
|
||||
Assert.Equal(TaskCount * IterationsPerTask, deduplicator.RegisteredCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cycle4_Gate2_MultiSourceSegmentAccelerator_ConcurrentStress_MustNotThrow()
|
||||
{
|
||||
var accelerator = new MultiSourceSegmentAccelerator();
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
accelerator.AddMirror($"https://cdn{i}.example.com/hls/");
|
||||
}
|
||||
|
||||
const int TaskCount = 32;
|
||||
const int IterationsPerTask = 200;
|
||||
|
||||
Parallel.For(0, TaskCount, taskId =>
|
||||
{
|
||||
for (int i = 0; i < IterationsPerTask; i++)
|
||||
{
|
||||
var targetMirror = $"https://cdn{i % 5}.example.com/hls/";
|
||||
if (i % 3 == 0)
|
||||
{
|
||||
accelerator.RecordFailure(targetMirror);
|
||||
}
|
||||
else if (i % 3 == 1)
|
||||
{
|
||||
accelerator.RecordLatency(targetMirror, TimeSpan.FromMilliseconds(20 + i));
|
||||
}
|
||||
else
|
||||
{
|
||||
var mirror = accelerator.SelectBestMirror(i);
|
||||
Assert.NotEmpty(mirror);
|
||||
var fastest = accelerator.GetFastestMirror();
|
||||
Assert.NotNull(fastest);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cycle4_Gate3_PredictiveChunkPrefetcher_ConcurrentStress_MustNotThrow()
|
||||
{
|
||||
var prefetcher = new PredictiveChunkPrefetcher(initialWindowSize: 3, minWindowSize: 1, maxWindowSize: 10, maxSamples: 10);
|
||||
const int TaskCount = 32;
|
||||
const int IterationsPerTask = 200;
|
||||
|
||||
Parallel.For(0, TaskCount, taskId =>
|
||||
{
|
||||
for (int i = 0; i < IterationsPerTask; i++)
|
||||
{
|
||||
long bytes = 1_000_000 + (taskId * 50_000);
|
||||
var duration = TimeSpan.FromMilliseconds(200 + (i % 100));
|
||||
|
||||
prefetcher.RecordChunkDownload(bytes, duration);
|
||||
var window = prefetcher.CalculateOptimalPrefetchWindow();
|
||||
Assert.InRange(window, 1, 10);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cycle4_Gate4_PartialChunkIntegrityCache_PathTraversalDefense_ReconcileAndClean()
|
||||
{
|
||||
var tempRoot = Path.Combine(Path.GetTempPath(), "paca_traversal_" + Guid.NewGuid().ToString("N"));
|
||||
var partsDir = Path.Combine(tempRoot, "parts");
|
||||
Directory.CreateDirectory(partsDir);
|
||||
|
||||
// 상위 디렉터리에 피해를 줄 수 있는 카나리아 파일 생성
|
||||
var canaryFile = Path.Combine(tempRoot, "canary_sensitive_data.txt");
|
||||
File.WriteAllText(canaryFile, "CRITICAL DATA DO NOT DELETE");
|
||||
|
||||
try
|
||||
{
|
||||
var cache = new PartialChunkIntegrityCache();
|
||||
|
||||
// 악의적인 path traversal 청크 이름 주입 ("../../canary_sensitive_data.txt")
|
||||
var report = cache.ReconcileAndClean(
|
||||
partsDir,
|
||||
totalSegments: 1,
|
||||
getChunkName: _ => "../../canary_sensitive_data.txt"
|
||||
);
|
||||
|
||||
// parts 디렉터리 외부의 카나리아 파일이 삭제되지 않고 온전히 보존되어야 함
|
||||
Assert.True(File.Exists(canaryFile), "상위 디렉터리의 카나리아 파일이 Path Traversal 로 인해 삭제되었습니다!");
|
||||
Assert.Equal("CRITICAL DATA DO NOT DELETE", File.ReadAllText(canaryFile));
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { if (Directory.Exists(tempRoot)) Directory.Delete(tempRoot, true); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cycle4_Gate5_PartialChunkIntegrityCache_VerifyChunkFile_LockedFileDefense()
|
||||
{
|
||||
var tempFile = Path.Combine(Path.GetTempPath(), "paca_locked_" + Guid.NewGuid().ToString("N") + ".seg");
|
||||
File.WriteAllBytes(tempFile, new byte[1024]);
|
||||
|
||||
try
|
||||
{
|
||||
var cache = new PartialChunkIntegrityCache();
|
||||
|
||||
// 백신 또는 외부 프로세스가 독점 락(FileShare.None)을 잡고 있는 상황 시뮬레이션
|
||||
using var lockedStream = new FileStream(tempFile, FileMode.Open, FileAccess.Read, FileShare.None);
|
||||
|
||||
// unhandled IOException 크래시 없이 Corrupt 또는 Partial 로 안전하게 처리되어야 함
|
||||
var state = cache.VerifyChunkFile(tempFile, expectedBytes: 1024, expectedSha256: "dummy_hash");
|
||||
Assert.True(state == ChunkIntegrityState.Corrupt || state == ChunkIntegrityState.Partial);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { if (File.Exists(tempFile)) File.Delete(tempFile); } catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue