using dy.net.model.dto; using dy.net.model.entity; using dy.net.storage; using Newtonsoft.Json; namespace dy.net.Tests; public class StorageArtifactCleanerTests { [Fact] public async Task DeleteWebDavArtifacts_DeletesOwnedFilesButPreservesRootAndSharedTvShowNfo() { var storage = new TrackingStorage(); var video = new DouyinVideo { AwemeId = "video-1", StorageType = StorageType.WebDav, ViedoType = VideoTypeEnum.dy_series, VideoSavePath = "/shows/demo/S01E01.mp4", VideoCoverSavePath = "/shows/demo/S01E01.jpg", VideoTitle = "Episode 1", DynamicVideos = JsonConvert.SerializeObject(new[] { new DouyinMergeVideoDto { Path = "/shows/demo/parts/clip-1.mp4" }, new DouyinMergeVideoDto { Path = "/shows/demo/manifest.json" }, new DouyinMergeVideoDto { Path = "/" } }) }; await StorageArtifactCleaner.DeleteWebDavArtifactsAsync(storage, video); var expected = new HashSet { "/shows/demo/S01E01.mp4", "/shows/demo/S01E01.jpg", "/shows/demo/S01E01.nfo", "/shows/demo/parts/clip-1.mp4", "/shows/demo/manifest.json" }; Assert.True(expected.SetEquals(storage.DeletedPaths)); Assert.DoesNotContain("/", storage.DeletedPaths); Assert.DoesNotContain("/shows/demo/tvshow.nfo", storage.DeletedPaths); } [Fact] public async Task DeleteWebDavArtifacts_IgnoresLocalRecords() { var storage = new TrackingStorage(); var video = new DouyinVideo { StorageType = StorageType.Local, VideoSavePath = "/local/video.mp4" }; await StorageArtifactCleaner.DeleteWebDavArtifactsAsync(storage, video); Assert.Empty(storage.DeletedPaths); } private sealed class TrackingStorage : IMediaStorage { public StorageType StorageType => StorageType.WebDav; public HashSet DeletedPaths { get; } = new(StringComparer.Ordinal); public Task DeleteAsync(string path, CancellationToken cancellationToken = default) { DeletedPaths.Add(path); return Task.CompletedTask; } public Task EnsureDirectoryAsync(string path, CancellationToken cancellationToken = default) => Task.CompletedTask; public Task ExistsAsync(string path, CancellationToken cancellationToken = default) => Task.FromResult(false); public Task GetLengthAsync(string path, CancellationToken cancellationToken = default) => Task.FromResult(null); public Task OpenReadAsync(string path, long? from = null, long? to = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task WriteAsync(string path, Stream source, long? contentLength = null, string contentType = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(); } }