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); } }