Files
TeleWave/backend/tests/TeleWave.Integration.Tests/ManualInboxStorageTests.cs
T
Leonid Pershin f22aee4685
ci / build-backend (push) Successful in 2m50s
ci / build-frontend (push) Successful in 1m7s
ci / tests (push) Successful in 3m1s
ci / sonar (push) Successful in 6m15s
Enhance CleanupManualLeftoversAsync to remove subtitles from nested folders and empty directories
Updated the CleanupManualLeftoversAsync method to improve its functionality by allowing it to delete subtitle files located in nested directories and remove empty directories after the last episode is processed. Introduced a CompanionMatcher class to accurately identify companion files based on naming conventions. Added integration tests to verify the new cleanup behavior, ensuring that only relevant files are removed while preserving necessary content.
2026-07-27 08:57:11 +03:00

224 lines
8.4 KiB
C#

using Microsoft.Extensions.Options;
using TeleWave.Domain.Media;
using TeleWave.Infrastructure.Media;
using Xunit;
namespace TeleWave.Integration.Tests;
/// <summary>
/// Файловая часть ручного разбора: перечисление, перенос и подчистка спутников. Работает с реальной
/// ФС во временном каталоге — код удаляет файлы, и проверять его на моках бессмысленно.
/// </summary>
public sealed class ManualInboxStorageTests : IDisposable
{
private readonly string _root = Path.Combine(
Path.GetTempPath(),
"telewave-tests-" + Guid.NewGuid().ToString("N")[..8]
);
private readonly FileSystemMediaStorage _storage;
private readonly MediaPathResolver _paths;
public ManualInboxStorageTests()
{
_paths = new MediaPathResolver(Options.Create(new StorageOptions { RootPath = _root }));
_paths.EnsureDirectories();
_storage = new FileSystemMediaStorage(_paths);
}
[Fact]
public void ListManualInbox_WalksSubdirectories_AndReturnsRelativePaths()
{
Write("Сериал/s01e01.mkv");
Write("Сериал/s01e02.mkv");
Write("одиночный.mp4");
var files = _storage.ListManualInbox(int.MaxValue);
Assert.Equal(3, files.Count);
// Разделитель нормализован: путь уезжает в URL и возвращается строкой запроса.
Assert.Contains(files, f => f.RelativePath == "Сериал/s01e01.mkv");
Assert.Contains(files, f => f.RelativePath == "одиночный.mp4");
Assert.All(files, f => Assert.True(f.SizeBytes > 0));
}
[Fact]
public async Task PromoteToOriginal_MovesFileOutOfManual()
{
Write("Сериал/s01e01.mkv");
var assetId = Guid.NewGuid();
await _storage.PromoteToOriginalAsync(
MediaSource.ManualInbox,
"Сериал/s01e01.mkv",
assetId,
".mkv",
default
);
Assert.False(File.Exists(Path.Combine(_paths.ManualDir, "Сериал", "s01e01.mkv")));
Assert.True(File.Exists(_paths.OriginalPath(assetId, ".mkv")));
}
[Fact]
public async Task Cleanup_RemovesSiblings_ButKeepsOtherVideos()
{
Write("Сериал/s01e01.mkv");
Write("Сериал/s01e01.srt");
Write("Сериал/s01e01.ru.srt");
Write("Сериал/s01e01.nfo");
Write("Сериал/s01e01.Extended.mkv");
Write("Сериал/s01e02.mkv");
Write("Сериал/s01e02.srt");
// Так делает импорт: сначала файл уходит в originals/, потом подчищаются спутники.
await _storage.PromoteToOriginalAsync(
MediaSource.ManualInbox,
"Сериал/s01e01.mkv",
Guid.NewGuid(),
".mkv",
default
);
await _storage.CleanupManualLeftoversAsync("Сериал/s01e01.mkv", default);
var left = Directory
.EnumerateFiles(Path.Combine(_paths.ManualDir, "Сериал"))
.Select(Path.GetFileName)
.ToList();
// Субтитры и nfo забранной серии ушли…
Assert.DoesNotContain("s01e01.srt", left);
Assert.DoesNotContain("s01e01.ru.srt", left);
Assert.DoesNotContain("s01e01.nfo", left);
// …а другой материал и спутники соседних серий остались.
Assert.Contains("s01e01.Extended.mkv", left);
Assert.Contains("s01e02.mkv", left);
Assert.Contains("s01e02.srt", left);
}
[Fact]
public async Task Cleanup_RemovesEmptiedFolder_ButNotManualRoot()
{
Write("Сериал/один.mkv");
Write("Сериал/один.srt");
Write("в-корне.mkv");
await _storage.PromoteToOriginalAsync(
MediaSource.ManualInbox,
"Сериал/один.mkv",
Guid.NewGuid(),
".mkv",
default
);
await _storage.CleanupManualLeftoversAsync("Сериал/один.mkv", default);
Assert.False(Directory.Exists(Path.Combine(_paths.ManualDir, "Сериал")));
// Файл в корне забираем — сам корень manual/ остаться обязан.
await _storage.PromoteToOriginalAsync(
MediaSource.ManualInbox,
"в-корне.mkv",
Guid.NewGuid(),
".mkv",
default
);
await _storage.CleanupManualLeftoversAsync("в-корне.mkv", default);
Assert.True(Directory.Exists(_paths.ManualDir));
}
[Fact]
public async Task Cleanup_RemovesSubtitlesFromNestedFolders()
{
// Раскладка из жизни: субтитры лежат не рядом с серией, а в подпапках по языкам, и названы
// иначе — совпадает только номер серии.
Write("Season 16/S16E21.mkv");
Write("Season 16/subs_ru/S16E21 - The Father, the Son.srt");
Write("Season 16/subs_en/S16E21 - The Father, the Son.srt");
Write("Season 16/subs_ru/S16E20 - Home Away From Homer.srt");
Write("Season 16/extras/S16E21 удалённые сцены.mkv");
await _storage.PromoteToOriginalAsync(
MediaSource.ManualInbox,
"Season 16/S16E21.mkv",
Guid.NewGuid(),
".mkv",
default
);
await _storage.CleanupManualLeftoversAsync("Season 16/S16E21.mkv", default);
var season = Path.Combine(_paths.ManualDir, "Season 16");
Assert.False(
File.Exists(Path.Combine(season, "subs_ru", "S16E21 - The Father, the Son.srt"))
);
Assert.False(
File.Exists(Path.Combine(season, "subs_en", "S16E21 - The Father, the Son.srt"))
);
// Субтитры соседней серии остаются: её ещё не забирали.
Assert.True(
File.Exists(Path.Combine(season, "subs_ru", "S16E20 - Home Away From Homer.srt"))
);
// Видео не трогаем нигде, даже с тем же номером серии, — это другой материал.
Assert.True(File.Exists(Path.Combine(season, "extras", "S16E21 удалённые сцены.mkv")));
}
[Fact]
public async Task Cleanup_RemovesEmptiedNestedFolders_AfterLastEpisode()
{
Write("Season 16/S16E01.mkv");
Write("Season 16/S16E02.mkv");
Write("Season 16/subs_ru/S16E01 - Treehouse.srt");
Write("Season 16/subs_ru/S16E02 - All's Fair.srt");
foreach (var episode in new[] { "S16E01", "S16E02" })
{
await _storage.PromoteToOriginalAsync(
MediaSource.ManualInbox,
$"Season 16/{episode}.mkv",
Guid.NewGuid(),
".mkv",
default
);
await _storage.CleanupManualLeftoversAsync($"Season 16/{episode}.mkv", default);
}
// Опустевшие подпапки уходят вместе с каталогом сезона — корень manual/ остаётся.
Assert.False(Directory.Exists(Path.Combine(_paths.ManualDir, "Season 16")));
Assert.True(Directory.Exists(_paths.ManualDir));
}
[Fact]
public async Task Cleanup_KeepsFolderThatStillHasFiles()
{
Write("Сериал/s01e01.mkv");
Write("Сериал/s01e02.mkv");
await _storage.PromoteToOriginalAsync(
MediaSource.ManualInbox,
"Сериал/s01e01.mkv",
Guid.NewGuid(),
".mkv",
default
);
await _storage.CleanupManualLeftoversAsync("Сериал/s01e01.mkv", default);
Assert.True(File.Exists(Path.Combine(_paths.ManualDir, "Сериал", "s01e02.mkv")));
}
private void Write(string relativePath)
{
var full = Path.Combine(
_paths.ManualDir,
relativePath.Replace('/', Path.DirectorySeparatorChar)
);
Directory.CreateDirectory(Path.GetDirectoryName(full)!);
File.WriteAllText(full, "x");
}
public void Dispose()
{
if (Directory.Exists(_root))
Directory.Delete(_root, recursive: true);
}
}