using Microsoft.Extensions.Options;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Storage;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Media;
using TeleWave.Infrastructure.Media;
using Xunit;
namespace TeleWave.Application.Tests.Storage;
///
/// Отчёт по диску. Сегменты программ и заставок лежат в одном каталоге, и разделить их можно
/// только по базе — это и проверяется в первую очередь.
///
public class StorageStatsTests : IDisposable
{
private readonly string _root = Path.Combine(
Path.GetTempPath(),
"telewave-storage-" + Guid.NewGuid().ToString("N")
);
private FileSystemStorageInspector NewInspector()
{
var options = Options.Create(new StorageOptions { RootPath = _root });
return new FileSystemStorageInspector(new MediaPathResolver(options), options);
}
private void Write(string relativePath, int bytes)
{
var path = Path.Combine(_root, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
File.WriteAllBytes(path, new byte[bytes]);
}
private static readonly IReadOnlySet Empty = new HashSet();
private static long BytesOf(StorageUsage usage, StorageArea area) =>
usage.Areas.Single(a => a.Area == area).Bytes;
[Fact]
public async Task Inspect_SplitsProgramAndBumperSegments()
{
var program = Guid.NewGuid();
var bumper = Guid.NewGuid();
Write($"assets/{program:N}/seg00000.ts", 300);
Write($"assets/{bumper:N}/seg00000.ts", 100);
Write("images/poster.png", 50);
Write("uploads/half-uploaded.mkv", 20);
Write("originals/movie.mkv", 10);
var usage = await NewInspector()
.InspectAsync(new HashSet { bumper }, refresh: false, CancellationToken.None);
Assert.Equal(300, BytesOf(usage, StorageArea.Assets));
Assert.Equal(100, BytesOf(usage, StorageArea.BumperAssets));
Assert.Equal(50, BytesOf(usage, StorageArea.Images));
Assert.Equal(20, BytesOf(usage, StorageArea.Uploads));
Assert.Equal(10, BytesOf(usage, StorageArea.Originals));
Assert.Equal(480, usage.StorageBytes);
Assert.Equal(5, usage.StorageFiles);
Assert.Equal(_root, usage.RootPath);
}
[Fact]
public async Task Inspect_CountsStrayFilesAsOther()
{
Write("забытая-папка/dump.sql", 70);
Write("readme.txt", 30);
var usage = await NewInspector()
.InspectAsync(Empty, refresh: false, CancellationToken.None);
// «Прочее» обычно ноль — и именно поэтому его надо показывать, когда оно не ноль.
Assert.Equal(100, BytesOf(usage, StorageArea.Other));
}
[Fact]
public async Task Inspect_ReusesCache_UntilRefreshAsked()
{
Write("images/a.png", 10);
var inspector = NewInspector();
var first = await inspector.InspectAsync(Empty, refresh: false, CancellationToken.None);
Write("images/b.png", 40);
var cached = await inspector.InspectAsync(Empty, refresh: false, CancellationToken.None);
var fresh = await inspector.InspectAsync(Empty, refresh: true, CancellationToken.None);
// Обход дорогой: без явной просьбы отдаём прежнюю цифру вместе с её временем.
Assert.Equal(first.ComputedAt, cached.ComputedAt);
Assert.Equal(10, BytesOf(cached, StorageArea.Images));
Assert.Equal(50, BytesOf(fresh, StorageArea.Images));
}
[Fact]
public async Task Handler_PassesGeneratedAssetsToInspector()
{
var fixture = new TestDb();
var generated = MediaAsset.RegisterGenerated("Заставка");
var uploaded = MediaAsset.Register("film.mkv", ".mkv", MediaSource.Upload);
await using (var seed = fixture.New())
{
seed.MediaAssets.AddRange(generated, uploaded);
await seed.SaveChangesAsync(CancellationToken.None);
}
var inspector = new CapturingInspector();
await using var db = fixture.New();
var result = await new GetStorageStatsQueryHandler(db, inspector).Handle(
new GetStorageStatsQuery(Refresh: true),
CancellationToken.None
);
Assert.True(inspector.Refresh);
Assert.Equal([generated.Id], inspector.GeneratedAssetIds);
Assert.Equal("/media", result.RootPath);
}
private sealed class CapturingInspector : IStorageInspector
{
public IReadOnlySet GeneratedAssetIds { get; private set; } = new HashSet();
public bool Refresh { get; private set; }
public Task InspectAsync(
IReadOnlySet generatedAssetIds,
bool refresh,
CancellationToken cancellationToken
)
{
GeneratedAssetIds = generatedAssetIds;
Refresh = refresh;
return Task.FromResult(
new StorageUsage("/media", 0, 0, 0, 0, 0, [], DateTimeOffset.UnixEpoch)
);
}
}
public void Dispose()
{
if (Directory.Exists(_root))
Directory.Delete(_root, recursive: true);
GC.SuppressFinalize(this);
}
}