using PLib.Domain.Videos; using Shouldly; namespace PLib.Tests.Domain; public sealed class VideoItemTests { private static VideoItem CreateIndexedItem() { var item = new VideoItem(@"C:\videos\clip.mp4", "clip", 1_000, DateTimeOffset.UnixEpoch); item.ApplyTechnicalInfo(new VideoTechnicalInfo(TimeSpan.FromMinutes(3), 1920, 1080, "h264")); item.AttachThumbnail(@"C:\cache\clip.jpg"); item.AttachPreview(@"C:\cache\clip.strip.jpg", 12); item.ApplyPerceptualHash(0xDEADBEEF); return item; } [Fact] public void An_item_is_indexed_once_it_has_a_duration_and_a_poster_frame() { var item = new VideoItem(@"C:\videos\clip.mp4", "clip", 1_000, DateTimeOffset.UnixEpoch); item.IsIndexed.ShouldBeFalse(); item.ApplyTechnicalInfo(new VideoTechnicalInfo(TimeSpan.FromMinutes(1), 1280, 720, "h264")); item.IsIndexed.ShouldBeFalse(); item.AttachThumbnail(@"C:\cache\clip.jpg"); // Neither the animated preview nor the hash is part of this: both belong to later // passes, and the grid is usable long before either exists. item.IsIndexed.ShouldBeTrue(); item.NeedsAnimatedPreview.ShouldBeTrue(); item.NeedsPerceptualHash.ShouldBeTrue(); item.AttachPreview(@"C:\cache\clip.strip.jpg", 12); item.NeedsAnimatedPreview.ShouldBeFalse(); item.ApplyPerceptualHash(1); item.NeedsPerceptualHash.ShouldBeFalse(); } [Fact] public void The_later_passes_cannot_be_asked_for_before_the_duration_is_known() { var item = new VideoItem(@"C:\videos\clip.mp4", "clip", 1_000, DateTimeOffset.UnixEpoch); // Both sample frames across the running time, so there is nothing to sample yet. item.NeedsAnimatedPreview.ShouldBeFalse(); item.NeedsPerceptualHash.ShouldBeFalse(); } [Fact] public void A_preview_of_a_single_frame_is_not_an_animation() { var item = new VideoItem(@"C:\videos\clip.mp4", "clip", 1_000, DateTimeOffset.UnixEpoch); Should.Throw(() => item.AttachPreview(@"C:\cache\clip.jpg", 1)); } [Fact] public void Refreshing_an_unchanged_file_keeps_everything_that_was_derived_from_it() { var item = CreateIndexedItem(); item.RefreshFileFacts(1_000, DateTimeOffset.UnixEpoch); item.IsIndexed.ShouldBeTrue(); item.ThumbnailPath.ShouldNotBeNull(); item.PreviewPath.ShouldNotBeNull(); } [Fact] public void Refreshing_a_changed_file_invalidates_the_metadata_and_the_thumbnail() { var item = CreateIndexedItem(); item.RefreshFileFacts(2_000, DateTimeOffset.UnixEpoch.AddDays(1)); item.SizeInBytes.ShouldBe(2_000); item.Duration.ShouldBeNull(); item.ThumbnailPath.ShouldBeNull(); item.PreviewPath.ShouldBeNull(); item.PreviewFrameCount.ShouldBe(0); // The hash describes the picture, so a different file means a different hash. item.PerceptualHash.ShouldBeNull(); item.IsIndexed.ShouldBeFalse(); } [Theory] [InlineData("", "title")] [InlineData(" ", "title")] [InlineData(@"C:\videos\clip.mp4", "")] public void An_item_cannot_be_created_without_a_path_and_a_title(string path, string title) => Should.Throw(() => new VideoItem(path, title, 1, DateTimeOffset.UnixEpoch)); [Fact] public void An_item_cannot_have_a_negative_size() => Should.Throw( () => new VideoItem(@"C:\videos\clip.mp4", "clip", -1, DateTimeOffset.UnixEpoch)); }