feat(ads/seo/release): v1.2.0 with live Google Ads, isolated ad profile, ads.txt, and SEO/AEO/GEO optimization

This commit is contained in:
Yun Chan 2026-09-14 00:39:44 +09:00
parent a88aafa1e5
commit 0e568f1f0a
975 changed files with 130593 additions and 16783 deletions

View file

@ -0,0 +1,169 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Paca.Core.Config;
using Paca.Core.Hls;
using Xunit;
namespace Paca.Tests.Core;
public class PartialChunkIntegrityCacheGateTests : IDisposable
{
private readonly string _testDir;
public PartialChunkIntegrityCacheGateTests()
{
_testDir = Path.Combine(Path.GetTempPath(), "paca_chunk_test_" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(_testDir);
}
public void Dispose()
{
try { if (Directory.Exists(_testDir)) Directory.Delete(_testDir, recursive: true); }
catch { }
}
[Fact]
public void Gate457_PartialChunkIntegrityCache_ComputesSha256_Correctly()
{
var cache = new PartialChunkIntegrityCache();
var bytes = Encoding.UTF8.GetBytes("PACA-STREAM-CHUNK-INTEGRITY-2026");
var hash = cache.ComputeSha256(bytes);
Assert.NotNull(hash);
Assert.Equal(64, hash.Length);
Assert.Matches("^[0-9a-f]{64}$", hash);
}
[Fact]
public void Gate458_PartialChunkIntegrityCache_VerifiesIntactChunk_Correctly()
{
var cache = new PartialChunkIntegrityCache();
var chunkPath = Path.Combine(_testDir, "000001.seg");
var testData = Encoding.UTF8.GetBytes("VALID-CHUNK-DATA-123456789");
File.WriteAllBytes(chunkPath, testData);
var expectedHash = cache.ComputeSha256(testData);
var status = cache.VerifyChunkFile(chunkPath, expectedBytes: testData.Length, expectedSha256: expectedHash);
Assert.Equal(ChunkIntegrityState.Verified, status);
}
[Fact]
public void Gate459_PartialChunkIntegrityCache_DetectsZeroByteOrTruncatedChunk_AsCorrupt()
{
var cache = new PartialChunkIntegrityCache();
// 1. Zero-byte chunk
var zeroPath = Path.Combine(_testDir, "zero.seg");
File.WriteAllBytes(zeroPath, Array.Empty<byte>());
var statusZero = cache.VerifyChunkFile(zeroPath, expectedBytes: 100);
Assert.Equal(ChunkIntegrityState.Corrupt, statusZero);
// 2. Truncated chunk (file is 10 bytes, expected 50)
var truncPath = Path.Combine(_testDir, "trunc.seg");
File.WriteAllBytes(truncPath, new byte[10]);
var statusTrunc = cache.VerifyChunkFile(truncPath, expectedBytes: 50);
Assert.Equal(ChunkIntegrityState.Corrupt, statusTrunc);
// 3. Missing chunk
var missingPath = Path.Combine(_testDir, "nonexistent.seg");
var statusMissing = cache.VerifyChunkFile(missingPath);
Assert.Equal(ChunkIntegrityState.Missing, statusMissing);
}
[Fact]
public void Gate460_PartialChunkIntegrityCache_DetectsSha256Mismatch_AsCorrupt()
{
var cache = new PartialChunkIntegrityCache();
var chunkPath = Path.Combine(_testDir, "tampered.seg");
File.WriteAllBytes(chunkPath, Encoding.UTF8.GetBytes("Original Content"));
var status = cache.VerifyChunkFile(chunkPath, expectedSha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
Assert.Equal(ChunkIntegrityState.Corrupt, status);
}
[Fact]
public void Gate461_PartialChunkIntegrityCache_PersistsAndRestores_LedgerJson()
{
var cache = new PartialChunkIntegrityCache();
var chunkName = "000000.seg";
var chunkPath = Path.Combine(_testDir, chunkName);
var data = Encoding.UTF8.GetBytes("CHUNK-CONTENT-0");
File.WriteAllBytes(chunkPath, data);
var hash = cache.ComputeSha256(data);
cache.RecordChunk(_testDir, index: 0, chunkName: chunkName, bytes: data.Length, sha256: hash);
cache.SaveLedger(_testDir);
// New cache instance reading same directory
var cache2 = new PartialChunkIntegrityCache();
cache2.LoadLedger(_testDir);
var entry = cache2.GetChunk(_testDir, 0);
Assert.NotNull(entry);
Assert.Equal(0, entry!.Index);
Assert.Equal(chunkName, entry.ChunkName);
Assert.Equal(data.Length, entry.ActualBytes);
Assert.Equal(hash, entry.Sha256Hash);
Assert.Equal(ChunkIntegrityState.Verified, entry.State);
}
[Fact]
public void Gate462_PartialChunkIntegrityCache_ReconcileAndClean_PurgesCorrupted_AndReturnsMissingIndices()
{
var cache = new PartialChunkIntegrityCache();
// Prepare 4 segments in _testDir:
// seg 0: Valid, 20 bytes
var seg0Path = Path.Combine(_testDir, "000000.seg");
var seg0Data = Encoding.UTF8.GetBytes("Valid 20-byte seg 0!");
File.WriteAllBytes(seg0Path, seg0Data);
// seg 1: Corrupted (0 bytes)
var seg1Path = Path.Combine(_testDir, "000001.seg");
File.WriteAllBytes(seg1Path, Array.Empty<byte>());
// seg 2: Missing (file doesn't exist)
var seg2Path = Path.Combine(_testDir, "000002.seg");
// seg 3: Valid, 15 bytes
var seg3Path = Path.Combine(_testDir, "000003.seg");
var seg3Data = Encoding.UTF8.GetBytes("Valid seg 3 data");
File.WriteAllBytes(seg3Path, seg3Data);
// Reconcile 4 total chunks
var report = cache.ReconcileAndClean(_testDir, totalSegments: 4, index => $"{index:000000}.seg");
Assert.Equal(4, report.TotalChunks);
Assert.Equal(2, report.VerifiedCount);
Assert.Equal(1, report.CorruptedOrTruncatedCount);
Assert.Equal(1, report.MissingCount);
Assert.False(report.IsFullyComplete);
// Corrupted 0-byte file must have been purged from disk
Assert.False(File.Exists(seg1Path));
// Valid files must remain
Assert.True(File.Exists(seg0Path));
Assert.True(File.Exists(seg3Path));
// Segments needed for download are index 1 and 2
Assert.Equal(new[] { 1, 2 }, report.SegmentsToDownload);
}
[Fact]
public void Gate463_HlsDownloader_IntegratesWithPartialChunkIntegrityCache_ExposesCache()
{
var config = new AppConfig();
var cache = new PartialChunkIntegrityCache();
using var http = new System.Net.Http.HttpClient();
var downloader = new HlsDownloader(http, config, Paca.Core.Platform.DesktopMediaMuxer.Default, null, cache);
Assert.Same(cache, downloader.ChunkCache);
}
}