Files
GmRelayBot/tests/GmRelay.Bot.Tests/Web/LocalPortfolioCoverStorageTests.cs
T

173 lines
5.7 KiB
C#

using GmRelay.Web.Services.Portfolio.Covers;
namespace GmRelay.Bot.Tests.Web;
public sealed class LocalPortfolioCoverStorageTests : IDisposable
{
private readonly string _storagePath;
private readonly LocalPortfolioCoverStorage _storage;
public LocalPortfolioCoverStorageTests()
{
_storagePath = Path.Combine(
Path.GetTempPath(),
"gmrelay-portfolio-covers-tests-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(_storagePath);
_storage = new LocalPortfolioCoverStorage(new PortfolioCoverStorageOptions
{
StoragePath = _storagePath
});
}
public void Dispose()
{
if (Directory.Exists(_storagePath))
{
Directory.Delete(_storagePath, recursive: true);
}
GC.SuppressFinalize(this);
}
[Fact]
public async Task SaveAsync_ShouldPersistPngWithRandomProviderNeutralKey()
{
await using var stream = new MemoryStream(
[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00]);
var result = await _storage.SaveAsync(stream, "image/png");
Assert.EndsWith(".png", result.StorageKey, StringComparison.Ordinal);
Assert.StartsWith("/portfolio-covers/", _storage.GetPublicPath(result.StorageKey), StringComparison.Ordinal);
Assert.True(File.Exists(Path.Combine(_storagePath, result.StorageKey)));
}
[Theory]
[InlineData("image/jpeg")]
[InlineData("image/png")]
[InlineData("image/webp")]
public async Task SaveAsync_ShouldRejectMismatchedSignature(string contentType)
{
await using var stream = new MemoryStream([0x00, 0x01, 0x02, 0x03]);
await Assert.ThrowsAsync<InvalidOperationException>(
() => _storage.SaveAsync(stream, contentType));
}
[Fact]
public async Task SaveAsync_ShouldPersistJpegWithCorrectSignature()
{
await using var stream = new MemoryStream(
[0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46]);
var result = await _storage.SaveAsync(stream, "image/jpeg");
Assert.EndsWith(".jpg", result.StorageKey, StringComparison.Ordinal);
Assert.Equal("image/jpeg", result.ContentType);
Assert.True(File.Exists(Path.Combine(_storagePath, result.StorageKey)));
}
[Fact]
public async Task SaveAsync_ShouldPersistWebpWithRiffWebpSignature()
{
await using var stream = new MemoryStream(
[0x52, 0x49, 0x46, 0x46, 0x1A, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50, 0x38]);
var result = await _storage.SaveAsync(stream, "image/webp");
Assert.EndsWith(".webp", result.StorageKey, StringComparison.Ordinal);
Assert.Equal("image/webp", result.ContentType);
Assert.True(File.Exists(Path.Combine(_storagePath, result.StorageKey)));
}
[Fact]
public async Task SaveAsync_ShouldRejectStreamLargerThanMaxBytes()
{
var oversized = new byte[LocalPortfolioCoverStorage.MaxBytes + 1];
await using var stream = new MemoryStream(oversized);
await Assert.ThrowsAsync<InvalidOperationException>(
() => _storage.SaveAsync(stream, "image/png"));
}
[Fact]
public async Task SaveAsync_ShouldRejectUnknownContentType()
{
await using var stream = new MemoryStream([0x89, 0x50, 0x4E, 0x47]);
await Assert.ThrowsAsync<InvalidOperationException>(
() => _storage.SaveAsync(stream, "application/octet-stream"));
}
[Fact]
public async Task DeleteIfExistsAsync_ShouldRemoveExistingKey()
{
await using var stream = new MemoryStream(
[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
var result = await _storage.SaveAsync(stream, "image/png");
var path = Path.Combine(_storagePath, result.StorageKey);
Assert.True(File.Exists(path));
await _storage.DeleteIfExistsAsync(result.StorageKey);
Assert.False(File.Exists(path));
}
[Fact]
public async Task DeleteIfExistsAsync_ShouldBeNoOpForMissingKey()
{
var key = Guid.NewGuid().ToString("N") + ".png";
await _storage.DeleteIfExistsAsync(key);
}
[Fact]
public async Task DeleteIfExistsAsync_ShouldRejectPathTraversal()
{
await Assert.ThrowsAsync<InvalidOperationException>(
() => _storage.DeleteIfExistsAsync("../escape.png"));
}
[Fact]
public async Task DeleteIfExistsAsync_ShouldRejectKeyWithInvalidExtension()
{
await Assert.ThrowsAsync<InvalidOperationException>(
() => _storage.DeleteIfExistsAsync(Guid.NewGuid().ToString("N") + ".gif"));
}
[Fact]
public void GetPublicPath_ShouldEscapeSpecialCharacters()
{
var key = "0123456789abcdef0123456789abcdef" + ".png";
var path = _storage.GetPublicPath(key);
Assert.Equal("/portfolio-covers/" + Uri.EscapeDataString(key), path);
}
[Fact]
public async Task SaveAsync_ShouldGenerateUniqueKeys()
{
await using var stream1 = new MemoryStream(
[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
await using var stream2 = new MemoryStream(
[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
var first = await _storage.SaveAsync(stream1, "image/png");
var second = await _storage.SaveAsync(stream2, "image/png");
Assert.NotEqual(first.StorageKey, second.StorageKey);
}
[Fact]
public async Task SaveAsync_ShouldNotLeaveTempFileBehind()
{
await using var stream = new MemoryStream(
[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
await _storage.SaveAsync(stream, "image/png");
var tempFiles = Directory.GetFiles(_storagePath, "*.tmp");
Assert.Empty(tempFiles);
}
}