140 lines
5 KiB
C#
140 lines
5 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Paca.Core.Config;
|
|
using Paca.Core.Hls;
|
|
using Xunit;
|
|
|
|
namespace Paca.Tests.Core;
|
|
|
|
public class DirectDownloaderIntegrityGateTests : IDisposable
|
|
{
|
|
private readonly string _testDir;
|
|
|
|
public DirectDownloaderIntegrityGateTests()
|
|
{
|
|
_testDir = Path.Combine(Path.GetTempPath(), "paca_direct_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 Gate471_DirectDownloader_GuessExt_DetectsExtensions_FromUrlAndMediaType()
|
|
{
|
|
// Direct extension in URL
|
|
Assert.Equal(".mp4", DirectDownloader.GuessExt("", new Uri("https://paca.stream/video.mp4")));
|
|
Assert.Equal(".webm", DirectDownloader.GuessExt("", new Uri("https://paca.stream/clip.webm")));
|
|
Assert.Equal(".mkv", DirectDownloader.GuessExt("", new Uri("https://paca.stream/movie.mkv")));
|
|
Assert.Equal(".mp3", DirectDownloader.GuessExt("", new Uri("https://paca.stream/audio.mp3")));
|
|
Assert.Equal(".flac", DirectDownloader.GuessExt("", new Uri("https://paca.stream/track.flac")));
|
|
|
|
// Fallback from MIME ContentType
|
|
Assert.Equal(".mp4", DirectDownloader.GuessExt("video/mp4", new Uri("https://paca.stream/stream?id=123")));
|
|
Assert.Equal(".webm", DirectDownloader.GuessExt("video/webm", new Uri("https://paca.stream/stream?id=123")));
|
|
Assert.Equal(".m4a", DirectDownloader.GuessExt("audio/mp4", new Uri("https://paca.stream/stream?id=123")));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Gate472_DirectDownloader_VerifiesChecksum_WhenProvided_AndMatches()
|
|
{
|
|
var content = "PACA-DIRECT-DOWNLOAD-VERIFIED-DATA";
|
|
var bytes = Encoding.UTF8.GetBytes(content);
|
|
var sha256 = Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant();
|
|
|
|
var handler = new SimpleMockHttp(bytes);
|
|
using var client = new HttpClient(handler);
|
|
var config = new AppConfig();
|
|
var downloader = new DirectDownloader(client, config);
|
|
|
|
var outputBase = Path.Combine(_testDir, "test_success");
|
|
var resultPath = await downloader.DownloadAsync(
|
|
new Uri("https://paca.test/file.mp4"),
|
|
outputBase,
|
|
progress: null,
|
|
ct: CancellationToken.None,
|
|
expectedSha256: sha256
|
|
);
|
|
|
|
Assert.True(File.Exists(resultPath));
|
|
Assert.Equal(bytes.Length, new FileInfo(resultPath).Length);
|
|
Assert.False(File.Exists(outputBase + ".part"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Gate473_DirectDownloader_ThrowsInvalidDataException_WhenChecksumMismatches()
|
|
{
|
|
var content = "TAMPERED-DATA-PAYLOAD";
|
|
var bytes = Encoding.UTF8.GetBytes(content);
|
|
|
|
var handler = new SimpleMockHttp(bytes);
|
|
using var client = new HttpClient(handler);
|
|
var config = new AppConfig();
|
|
var downloader = new DirectDownloader(client, config);
|
|
|
|
var outputBase = Path.Combine(_testDir, "test_corrupt");
|
|
|
|
await Assert.ThrowsAsync<InvalidDataException>(async () =>
|
|
{
|
|
await downloader.DownloadAsync(
|
|
new Uri("https://paca.test/file.mp4"),
|
|
outputBase,
|
|
progress: null,
|
|
ct: CancellationToken.None,
|
|
expectedSha256: "0000000000000000000000000000000000000000000000000000000000000000"
|
|
);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Gate474_DirectDownloader_AtomicPartCommit_CleansPartFile_UponCompletion()
|
|
{
|
|
var content = "PACA-ATOMIC-COMMIT-STREAM";
|
|
var bytes = Encoding.UTF8.GetBytes(content);
|
|
|
|
var handler = new SimpleMockHttp(bytes);
|
|
using var client = new HttpClient(handler);
|
|
var config = new AppConfig();
|
|
var downloader = new DirectDownloader(client, config);
|
|
|
|
var outputBase = Path.Combine(_testDir, "atomic_output");
|
|
var final = await downloader.DownloadAsync(
|
|
new Uri("https://paca.test/video.mp4"),
|
|
outputBase
|
|
);
|
|
|
|
Assert.True(File.Exists(final));
|
|
Assert.False(File.Exists(outputBase + ".part"));
|
|
}
|
|
|
|
private sealed class SimpleMockHttp : HttpMessageHandler
|
|
{
|
|
private readonly byte[] _data;
|
|
|
|
public SimpleMockHttp(byte[] data)
|
|
{
|
|
_data = data;
|
|
}
|
|
|
|
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
|
{
|
|
var response = new HttpResponseMessage(HttpStatusCode.OK)
|
|
{
|
|
Content = new ByteArrayContent(_data)
|
|
};
|
|
response.Content.Headers.ContentType = new MediaTypeHeaderValue("video/mp4");
|
|
response.Content.Headers.ContentLength = _data.Length;
|
|
return Task.FromResult(response);
|
|
}
|
|
}
|
|
}
|