Updated the CreateCollectionFromSuggestionCommandHandler and CreateGroupFromSuggestionCommandHandler to compare names in memory rather than using SQL queries for case-insensitive checks. This change addresses potential discrepancies with SQL's case sensitivity, particularly with special characters. Additionally, modified unit tests to use array syntax for assertions, enhancing readability and consistency across the test suite.
127 lines
4.8 KiB
C#
127 lines
4.8 KiB
C#
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Microsoft.Extensions.Options;
|
|
using TeleWave.Infrastructure.Media;
|
|
using Xunit;
|
|
|
|
namespace TeleWave.Application.Tests.Media;
|
|
|
|
/// <summary>
|
|
/// Уборка перевалочного каталога: в <c>uploads/</c> не должно оставаться файлов, на которые никто
|
|
/// не сослался — это гигабайты на диске, невидимые в админке.
|
|
/// </summary>
|
|
public class UploadsCleanupTests : IDisposable
|
|
{
|
|
private readonly string _root = Path.Combine(
|
|
Path.GetTempPath(),
|
|
"telewave-tests",
|
|
Guid.NewGuid().ToString("N")
|
|
);
|
|
|
|
private MediaPathResolver Paths() =>
|
|
new(Options.Create(new StorageOptions { RootPath = _root }));
|
|
|
|
[Fact]
|
|
public async Task DeletesPartialUpload_WhenStreamFails()
|
|
{
|
|
var paths = Paths();
|
|
paths.EnsureDirectories();
|
|
var storage = new FileSystemMediaStorage(paths);
|
|
|
|
await Assert.ThrowsAsync<IOException>(() =>
|
|
storage.SaveUploadAsync(new FailingStream(), ".mkv", CancellationToken.None)
|
|
);
|
|
|
|
Assert.Empty(Directory.GetFiles(paths.UploadsDir));
|
|
}
|
|
|
|
[Fact]
|
|
public void Sweep_DeletesStaleFilesOnly()
|
|
{
|
|
var paths = Paths();
|
|
paths.EnsureDirectories();
|
|
var stale = Path.Combine(paths.UploadsDir, "stale.mkv");
|
|
var fresh = Path.Combine(paths.UploadsDir, "fresh.mkv");
|
|
// Идущую заливку от брошенного файла отличает именно время последней записи.
|
|
WriteAged(stale, TimeSpan.FromHours(25));
|
|
WriteAged(fresh, TimeSpan.Zero);
|
|
|
|
UploadsCleaner.Sweep(paths, TimeSpan.FromHours(24), NullLogger.Instance);
|
|
|
|
Assert.False(File.Exists(stale));
|
|
Assert.True(File.Exists(fresh));
|
|
}
|
|
|
|
[Fact]
|
|
public void Sweep_SurvivesMissingDirectory()
|
|
{
|
|
// Каталога может не быть на самом первом старте — это не повод падать. И заводить его
|
|
// уборка тоже не должна: каталоги создаёт хранилище, когда в них появляется нужда.
|
|
var paths = Paths();
|
|
|
|
UploadsCleaner.Sweep(paths, TimeSpan.FromHours(24), NullLogger.Instance);
|
|
|
|
Assert.False(Directory.Exists(paths.UploadsDir));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Кладёт файл и «состаривает» его. Отметку перепроверяем: Windows дописывает время последней
|
|
/// записи при закрытии handle и может затереть выставленное сразу после записи.
|
|
/// </summary>
|
|
private static void WriteAged(string path, TimeSpan age)
|
|
{
|
|
File.WriteAllText(path, "x");
|
|
if (age == TimeSpan.Zero)
|
|
return;
|
|
|
|
var stamp = DateTime.UtcNow - age;
|
|
for (var attempt = 0; attempt < 20; attempt++)
|
|
{
|
|
File.SetLastWriteTimeUtc(path, stamp);
|
|
if ((File.GetLastWriteTimeUtc(path) - stamp).Duration() < TimeSpan.FromSeconds(1))
|
|
return;
|
|
Thread.Sleep(20);
|
|
}
|
|
|
|
Assert.Fail("Не удалось выставить файлу время последней записи.");
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
GC.SuppressFinalize(this);
|
|
if (Directory.Exists(_root))
|
|
Directory.Delete(_root, recursive: true);
|
|
}
|
|
|
|
/// <summary>Поток, обрывающийся на середине, — имитация отвалившегося клиента.</summary>
|
|
private sealed class FailingStream : Stream
|
|
{
|
|
public override bool CanRead => true;
|
|
public override bool CanSeek => false;
|
|
public override bool CanWrite => false;
|
|
public override long Length => throw new NotSupportedException();
|
|
public override long Position
|
|
{
|
|
get => throw new NotSupportedException();
|
|
set => throw new NotSupportedException();
|
|
}
|
|
|
|
public override int Read(byte[] buffer, int offset, int count) =>
|
|
throw new IOException("Соединение разорвано.");
|
|
|
|
public override ValueTask<int> ReadAsync(
|
|
Memory<byte> buffer,
|
|
CancellationToken cancellationToken = default
|
|
) => throw new IOException("Соединение разорвано.");
|
|
|
|
public override void Flush() { }
|
|
|
|
public override long Seek(long offset, SeekOrigin origin) =>
|
|
throw new NotSupportedException();
|
|
|
|
public override void SetLength(long value) => throw new NotSupportedException();
|
|
|
|
public override void Write(byte[] buffer, int offset, int count) =>
|
|
throw new NotSupportedException();
|
|
}
|
|
}
|