diff options
Diffstat (limited to 'tests')
33 files changed, 470 insertions, 155 deletions
diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 6b851021f..feec35307 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -4,7 +4,7 @@ <Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" /> <PropertyGroup> - <TargetFramework>net9.0</TargetFramework> + <TargetFramework>net10.0</TargetFramework> <IsPackable>false</IsPackable> </PropertyGroup> diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 015018910..6b84c4438 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -10,7 +10,6 @@ <PackageReference Include="AutoFixture.AutoMoq" /> <PackageReference Include="AutoFixture.Xunit2" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" /> - <PackageReference Include="Microsoft.Extensions.Options" /> <PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="xunit" /> <PackageReference Include="xunit.runner.visualstudio"> diff --git a/tests/Jellyfin.Extensions.Tests/AlphanumericComparatorTests.cs b/tests/Jellyfin.Extensions.Tests/AlphanumericComparatorTests.cs deleted file mode 100644 index 105e2a52a..000000000 --- a/tests/Jellyfin.Extensions.Tests/AlphanumericComparatorTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Linq; -using Xunit; - -namespace Jellyfin.Extensions.Tests -{ - public class AlphanumericComparatorTests - { - // InlineData is pre-sorted - [Theory] - [InlineData(null, "", "1", "9", "10", "a", "z")] - [InlineData("50F", "100F", "SR9", "SR100")] - [InlineData("image-1.jpg", "image-02.jpg", "image-4.jpg", "image-9.jpg", "image-10.jpg", "image-11.jpg", "image-22.jpg")] - [InlineData("Hard drive 2GB", "Hard drive 20GB")] - [InlineData("b", "e", "è", "ě", "f", "g", "k")] - [InlineData("123456789", "123456789a", "abc", "abcd")] - [InlineData("12345678912345678912345678913234567891", "123456789123456789123456789132345678912")] - [InlineData("12345678912345678912345678913234567891", "12345678912345678912345678913234567891")] - [InlineData("12345678912345678912345678913234567891", "12345678912345678912345678913234567892")] - [InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891a")] - [InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891b")] - [InlineData("a5", "a11")] - [InlineData("a05a", "a5b")] - [InlineData("a5a", "a05b")] - [InlineData("6xxx", "007asdf")] - [InlineData("00042Q", "42s")] - public void AlphanumericComparatorTest(params string?[] strings) - { - var copy = strings.Reverse().ToArray(); - Array.Sort(copy, new AlphanumericComparator()); - Assert.Equal(strings, copy); - } - } -} diff --git a/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj b/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj index fdcf7d61e..bdf6bc383 100644 --- a/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj +++ b/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj @@ -1,6 +1,6 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net9.0</TargetFramework> + <TargetFramework>net10.0</TargetFramework> </PropertyGroup> <ItemGroup> diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index 94710a095..8a2f84734 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -196,6 +196,18 @@ namespace Jellyfin.MediaEncoding.Tests.Probing } [Fact] + public void GetMediaInfo_WebM_Like_Mkv() + { + var bytes = File.ReadAllBytes("Test Data/Probing/video_web_like_mkv_with_subtitle.json"); + var internalMediaInfoResult = JsonSerializer.Deserialize<InternalMediaInfoResult>(bytes, _jsonOptions); + + MediaInfo res = _probeResultNormalizer.GetMediaInfo(internalMediaInfoResult, VideoType.VideoFile, false, "Test Data/Probing/video_metadata.mkv", MediaProtocol.File); + + Assert.Equal("mkv", res.Container); + Assert.Equal(3, res.MediaStreams.Count); + } + + [Fact] public void GetMediaInfo_ProgressiveVideoNoFieldOrder_Success() { var bytes = File.ReadAllBytes("Test Data/Probing/video_progressive_no_field_order.json"); diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_web_like_mkv_with_subtitle.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_web_like_mkv_with_subtitle.json new file mode 100644 index 000000000..4f52dd90d --- /dev/null +++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_web_like_mkv_with_subtitle.json @@ -0,0 +1,137 @@ +{ + "streams": [ + { + "index": 0, + "codec_name": "vp8", + "codec_long_name": "On2 VP8", + "profile": "1", + "codec_type": "video", + "codec_tag_string": "[0][0][0][0]", + "codec_tag": "0x0000", + "width": 540, + "height": 360, + "coded_width": 540, + "coded_height": 360, + "closed_captions": 0, + "film_grain": 0, + "has_b_frames": 0, + "sample_aspect_ratio": "1:1", + "display_aspect_ratio": "3:2", + "pix_fmt": "yuv420p", + "level": -99, + "field_order": "progressive", + "refs": 1, + "r_frame_rate": "2997/125", + "avg_frame_rate": "2997/125", + "time_base": "1/1000", + "start_pts": 0, + "start_time": "0.000000", + "disposition": { + "default": 1, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0, + "captions": 0, + "descriptions": 0, + "metadata": 0, + "dependent": 0, + "still_image": 0 + }, + "tags": { + "language": "eng" + } + }, + { + "index": 1, + "codec_name": "vorbis", + "codec_long_name": "Vorbis", + "codec_type": "audio", + "codec_tag_string": "[0][0][0][0]", + "codec_tag": "0x0000", + "sample_fmt": "fltp", + "sample_rate": "44100", + "channels": 2, + "channel_layout": "stereo", + "bits_per_sample": 0, + "r_frame_rate": "0/0", + "avg_frame_rate": "0/0", + "time_base": "1/1000", + "start_pts": 0, + "start_time": "0.000000", + "duration": "117.707000", + "bit_rate": "127998", + "disposition": { + "default": 1, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0, + "captions": 0, + "descriptions": 0, + "metadata": 0, + "dependent": 0, + "still_image": 0 + }, + "tags": { + "language": "eng" + } + }, + { + "index": 2, + "codec_name": "subrip", + "codec_long_name": "SubRip subtitle", + "codec_type": "subtitle", + "codec_tag_string": "[0][0][0][0]", + "codec_tag": "0x0000", + "disposition": { + "default": 0, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0, + "captions": 0, + "descriptions": 0, + "metadata": 0, + "dependent": 0, + "still_image": 0 + }, + "tags": { + "language": "eng" + } + } + ], + "format": { + "filename": "sample.mkv", + "nb_streams": 3, + "nb_programs": 0, + "format_name": "matroska,webm", + "format_long_name": "Matroska / WebM", + "start_time": "0.000000", + "duration": "117.700914", + "size": "8566268", + "bit_rate": "582239", + "probe_score": 100 + } +} diff --git a/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs b/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs index e32baef55..6436d7d0e 100644 --- a/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs +++ b/tests/Jellyfin.Model.Tests/Dlna/LegacyStreamInfo.cs @@ -134,8 +134,6 @@ public class LegacyStreamInfo : StreamInfo { list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture))); } - - list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture))); } else { diff --git a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs index f4c0d9fe8..c1a3a4544 100644 --- a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs +++ b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs @@ -108,6 +108,49 @@ namespace Jellyfin.Model.Tests.Entities IsExternal = true }); + // Test LocalizedLanguage is used when set (fixes zh-CN display issue #15935) + data.Add( + "Chinese (Simplified) - SRT", + new MediaStream + { + Type = MediaStreamType.Subtitle, + Title = null, + Language = "zh-CN", + LocalizedLanguage = "Chinese (Simplified)", + IsForced = false, + IsDefault = false, + Codec = "SRT" + }); + + // Test LocalizedLanguage for audio streams + data.Add( + "Japanese - AAC - Stereo", + new MediaStream + { + Type = MediaStreamType.Audio, + Title = null, + Language = "jpn", + LocalizedLanguage = "Japanese", + IsForced = false, + IsDefault = false, + Codec = "AAC", + ChannelLayout = "stereo" + }); + + // Test fallback to Language when LocalizedLanguage is null + data.Add( + "Eng - ASS", + new MediaStream + { + Type = MediaStreamType.Subtitle, + Title = null, + Language = "eng", + LocalizedLanguage = null, + IsForced = false, + IsDefault = false, + Codec = "ASS" + }); + return data; } diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json index 68ce3ea4a..643ff2638 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidPixel.json @@ -152,7 +152,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -169,7 +168,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -185,7 +183,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json index 3d3968268..44f63f384 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-AndroidTVExoPlayer.json @@ -130,7 +130,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -146,7 +145,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json index 5d1f5f162..f1fc9e0db 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome-NoHLS.json @@ -127,7 +127,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -144,7 +143,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -161,7 +159,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -178,7 +175,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -195,7 +191,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -212,7 +207,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -229,7 +223,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -246,7 +239,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -263,7 +255,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -281,7 +272,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -298,7 +288,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json index e2f75b569..7e37a6236 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Chrome.json @@ -107,7 +107,6 @@ "Protocol": "hls", "MaxAudioChannels": "2", "MinSegments": "2", - "BreakOnNonKeyFrames": true, "EnableAudioVbrEncoding": true }, { @@ -182,8 +181,7 @@ "Context": "Streaming", "Protocol": "hls", "MaxAudioChannels": "2", - "MinSegments": "2", - "BreakOnNonKeyFrames": true + "MinSegments": "2" }, { "Container": "ts", @@ -193,8 +191,7 @@ "Context": "Streaming", "Protocol": "hls", "MaxAudioChannels": "2", - "MinSegments": "2", - "BreakOnNonKeyFrames": true + "MinSegments": "2" } ], "ContainerProfiles": [], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json index 21ae7e5cb..4380d80ef 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Firefox.json @@ -95,7 +95,6 @@ "TranscodingProfiles": [ { "AudioCodec": "aac", - "BreakOnNonKeyFrames": true, "Container": "mp4", "Context": "Streaming", "EnableAudioVbrEncoding": true, @@ -170,7 +169,6 @@ }, { "AudioCodec": "aac,mp2,opus,flac", - "BreakOnNonKeyFrames": true, "Container": "mp4", "Context": "Streaming", "MaxAudioChannels": "2", @@ -181,7 +179,6 @@ }, { "AudioCodec": "aac,mp3,mp2", - "BreakOnNonKeyFrames": true, "Container": "ts", "Context": "Streaming", "MaxAudioChannels": "2", diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json index da9a1a4ad..cca1c16ee 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-JellyfinMediaPlayer.json @@ -30,7 +30,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -48,7 +47,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -62,7 +60,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json index 82b73fb0f..b7cd170b9 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-LowBandwidth.json @@ -30,7 +30,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -48,7 +47,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -62,7 +60,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json index 37b923558..b823ac4b8 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlus.json @@ -49,7 +49,6 @@ "MaxAudioChannels": " 2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -66,7 +65,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -83,7 +81,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -100,7 +97,6 @@ "MaxAudioChannels": " 2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -118,7 +114,6 @@ "MaxAudioChannels": " 2", "MinSegments": 1, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -135,7 +130,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json index 542bf6370..708ff73c4 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-RokuSSPlusNext.json @@ -49,7 +49,6 @@ "MaxAudioChannels": " 2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -66,7 +65,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -83,7 +81,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -100,7 +97,6 @@ "MaxAudioChannels": " 2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -118,7 +114,6 @@ "MaxAudioChannels": " 2", "MinSegments": 1, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -135,7 +130,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json index f61d0e36b..10382fa82 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-SafariNext.json @@ -114,7 +114,6 @@ "Protocol": "hls", "MaxAudioChannels": "6", "MinSegments": "2", - "BreakOnNonKeyFrames": true, "EnableAudioVbrEncoding": true }, { @@ -173,8 +172,7 @@ "Context": "Streaming", "Protocol": "hls", "MaxAudioChannels": "2", - "MinSegments": "2", - "BreakOnNonKeyFrames": true + "MinSegments": "2" }, { "Container": "ts", @@ -184,8 +182,7 @@ "Context": "Streaming", "Protocol": "hls", "MaxAudioChannels": "2", - "MinSegments": "2", - "BreakOnNonKeyFrames": true + "MinSegments": "2" } ], "ContainerProfiles": [], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json index 9d43d2166..3625b099c 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json @@ -165,7 +165,6 @@ "MaxAudioChannels": "2", "MinSegments": 1, "SegmentLength": 0, - "BreakOnNonKeyFrames": true, "$type": "TranscodingProfile" }, { @@ -182,7 +181,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -199,7 +197,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -216,7 +213,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -233,7 +229,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -250,7 +245,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -267,7 +261,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -284,7 +277,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -301,7 +293,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -319,7 +310,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", @@ -346,7 +336,6 @@ "MaxAudioChannels": "2", "MinSegments": 1, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", @@ -373,7 +362,6 @@ "MaxAudioChannels": "2", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", @@ -399,7 +387,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json index 3859ef994..deee650b2 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json @@ -165,7 +165,6 @@ "MaxAudioChannels": "6", "MinSegments": 1, "SegmentLength": 0, - "BreakOnNonKeyFrames": true, "$type": "TranscodingProfile" }, { @@ -182,7 +181,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -199,7 +197,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -216,7 +213,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -233,7 +229,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -250,7 +245,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -267,7 +261,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -284,7 +277,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -301,7 +293,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -319,7 +310,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", @@ -346,7 +336,6 @@ "MaxAudioChannels": "6", "MinSegments": 1, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", @@ -373,7 +362,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", @@ -399,7 +387,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "Conditions": [ { "Condition": "LessThanEqual", diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json index 9fc1ae6bb..38de51b04 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-TranscodeMedia.json @@ -16,7 +16,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -28,7 +27,6 @@ "Protocol": "hls", "MaxAudioChannels": "2", "MinSegments": "2", - "BreakOnNonKeyFrames": true, "$type": "TranscodingProfile" }, { @@ -40,7 +38,6 @@ "Protocol": "hls", "MaxAudioChannels": "2", "MinSegments": "2", - "BreakOnNonKeyFrames": true, "$type": "TranscodingProfile" }, { @@ -64,7 +61,6 @@ "EnableSubtitlesInManifest": false, "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-WebOS-23.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-WebOS-23.json index 094b0723b..3ff11a684 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-WebOS-23.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-WebOS-23.json @@ -135,7 +135,6 @@ "Protocol": "hls", "MaxAudioChannels": "6", "MinSegments": "1", - "BreakOnNonKeyFrames": false, "EnableAudioVbrEncoding": true }, { @@ -210,8 +209,7 @@ "Context": "Streaming", "Protocol": "hls", "MaxAudioChannels": "6", - "MinSegments": "1", - "BreakOnNonKeyFrames": false + "MinSegments": "1" } ], "ContainerProfiles": [], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json index 256c8dc2f..838a1f920 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse.json @@ -52,7 +52,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -70,7 +69,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -88,7 +86,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json index 256c8dc2f..838a1f920 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Yatse2.json @@ -52,7 +52,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -70,7 +69,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" }, { @@ -88,7 +86,6 @@ "MaxAudioChannels": "6", "MinSegments": 0, "SegmentLength": 0, - "BreakOnNonKeyFrames": false, "$type": "TranscodingProfile" } ], diff --git a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs index 38208476f..871604514 100644 --- a/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs +++ b/tests/Jellyfin.Networking.Tests/NetworkParseTests.cs @@ -113,7 +113,7 @@ namespace Jellyfin.Networking.Tests public void IPv4SubnetMaskMatchesValidIPAddress(string netMask, string ipAddress) { var ipa = IPAddress.Parse(ipAddress); - Assert.True(NetworkUtils.TryParseToSubnet(netMask, out var subnet) && subnet.Contains(IPAddress.Parse(ipAddress))); + Assert.True(NetworkUtils.TryParseToSubnet(netMask, out var subnet) && subnet.Subnet.Contains(IPAddress.Parse(ipAddress))); } /// <summary> @@ -131,7 +131,7 @@ namespace Jellyfin.Networking.Tests public void IPv4SubnetMaskDoesNotMatchInvalidIPAddress(string netMask, string ipAddress) { var ipa = IPAddress.Parse(ipAddress); - Assert.False(NetworkUtils.TryParseToSubnet(netMask, out var subnet) && subnet.Contains(IPAddress.Parse(ipAddress))); + Assert.False(NetworkUtils.TryParseToSubnet(netMask, out var subnet) && subnet.Subnet.Contains(IPAddress.Parse(ipAddress))); } /// <summary> @@ -147,7 +147,7 @@ namespace Jellyfin.Networking.Tests [InlineData("2001:db8:abcd:0012::0/128", "2001:0DB8:ABCD:0012:0000:0000:0000:0000")] public void IPv6SubnetMaskMatchesValidIPAddress(string netMask, string ipAddress) { - Assert.True(NetworkUtils.TryParseToSubnet(netMask, out var subnet) && subnet.Contains(IPAddress.Parse(ipAddress))); + Assert.True(NetworkUtils.TryParseToSubnet(netMask, out var subnet) && subnet.Subnet.Contains(IPAddress.Parse(ipAddress))); } [Theory] @@ -158,7 +158,7 @@ namespace Jellyfin.Networking.Tests [InlineData("2001:db8:abcd:0012::0/128", "2001:0DB8:ABCD:0012:0000:0000:0000:0001")] public void IPv6SubnetMaskDoesNotMatchInvalidIPAddress(string netMask, string ipAddress) { - Assert.False(NetworkUtils.TryParseToSubnet(netMask, out var subnet) && subnet.Contains(IPAddress.Parse(ipAddress))); + Assert.False(NetworkUtils.TryParseToSubnet(netMask, out var subnet) && subnet.Subnet.Contains(IPAddress.Parse(ipAddress))); } [Theory] diff --git a/tests/Jellyfin.Server.Implementations.Tests/Cryptography/CryptographyProviderTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Cryptography/CryptographyProviderTests.cs new file mode 100644 index 000000000..052bdf740 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Cryptography/CryptographyProviderTests.cs @@ -0,0 +1,102 @@ +using System; +using Emby.Server.Implementations.Cryptography; +using MediaBrowser.Model.Cryptography; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Cryptography; + +public class CryptographyProviderTests +{ + private readonly CryptographyProvider _sut = new(); + + [Fact] + public void CreatePasswordHash_WithPassword_ReturnsHashWithIterations() + { + var hash = _sut.CreatePasswordHash("testpassword"); + + Assert.Equal("PBKDF2-SHA512", hash.Id); + Assert.True(hash.Parameters.ContainsKey("iterations")); + Assert.NotEmpty(hash.Salt.ToArray()); + Assert.NotEmpty(hash.Hash.ToArray()); + } + + [Fact] + public void Verify_WithValidPassword_ReturnsTrue() + { + const string password = "testpassword"; + var hash = _sut.CreatePasswordHash(password); + + Assert.True(_sut.Verify(hash, password)); + } + + [Fact] + public void Verify_WithWrongPassword_ReturnsFalse() + { + var hash = _sut.CreatePasswordHash("correctpassword"); + + Assert.False(_sut.Verify(hash, "wrongpassword")); + } + + [Fact] + public void Verify_PBKDF2_MissingIterations_ThrowsFormatException() + { + var hash = PasswordHash.Parse("$PBKDF2$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"); + + var exception = Assert.Throws<FormatException>(() => _sut.Verify(hash, "password")); + Assert.Contains("missing required 'iterations' parameter", exception.Message, StringComparison.Ordinal); + } + + [Fact] + public void Verify_PBKDF2SHA512_MissingIterations_ThrowsFormatException() + { + var hash = PasswordHash.Parse("$PBKDF2-SHA512$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"); + + var exception = Assert.Throws<FormatException>(() => _sut.Verify(hash, "password")); + Assert.Contains("missing required 'iterations' parameter", exception.Message, StringComparison.Ordinal); + } + + [Fact] + public void Verify_PBKDF2_InvalidIterationsFormat_ThrowsFormatException() + { + var hash = PasswordHash.Parse("$PBKDF2$iterations=abc$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"); + + var exception = Assert.Throws<FormatException>(() => _sut.Verify(hash, "password")); + Assert.Contains("invalid 'iterations' parameter", exception.Message, StringComparison.Ordinal); + } + + [Fact] + public void Verify_PBKDF2SHA512_InvalidIterationsFormat_ThrowsFormatException() + { + var hash = PasswordHash.Parse("$PBKDF2-SHA512$iterations=notanumber$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"); + + var exception = Assert.Throws<FormatException>(() => _sut.Verify(hash, "password")); + Assert.Contains("invalid 'iterations' parameter", exception.Message, StringComparison.Ordinal); + } + + [Fact] + public void Verify_UnsupportedHashId_ThrowsNotSupportedException() + { + var hash = PasswordHash.Parse("$UNKNOWN$69F420$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D"); + + Assert.Throws<NotSupportedException>(() => _sut.Verify(hash, "password")); + } + + [Fact] + public void GenerateSalt_ReturnsNonEmptyArray() + { + var salt = _sut.GenerateSalt(); + + Assert.NotEmpty(salt); + } + + [Theory] + [InlineData(16)] + [InlineData(32)] + [InlineData(64)] + public void GenerateSalt_WithLength_ReturnsArrayOfSpecifiedLength(int length) + { + var salt = _sut.GenerateSalt(length); + + Assert.Equal(length, salt.Length); + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Item/BaseItemRepositoryTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Item/BaseItemRepositoryTests.cs new file mode 100644 index 000000000..c450cbb0e --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Item/BaseItemRepositoryTests.cs @@ -0,0 +1,72 @@ +using System; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Server.Implementations.Item; +using MediaBrowser.Controller; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Item; + +public class BaseItemRepositoryTests +{ + [Fact] + public void DeserializeBaseItem_WithUnknownType_ReturnsNull() + { + // Arrange + var entity = new BaseItemEntity + { + Id = Guid.NewGuid(), + Type = "NonExistent.Plugin.CustomItemType" + }; + + // Act + var result = BaseItemRepository.DeserializeBaseItem(entity, NullLogger.Instance, null, false); + + // Assert + Assert.Null(result); + } + + [Fact] + public void DeserializeBaseItem_WithUnknownType_LogsWarning() + { + // Arrange + var entity = new BaseItemEntity + { + Id = Guid.NewGuid(), + Type = "NonExistent.Plugin.CustomItemType" + }; + var loggerMock = new Mock<ILogger>(); + + // Act + BaseItemRepository.DeserializeBaseItem(entity, loggerMock.Object, null, false); + + // Assert + loggerMock.Verify( + x => x.Log( + LogLevel.Warning, + It.IsAny<EventId>(), + It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("unknown type", StringComparison.OrdinalIgnoreCase)), + It.IsAny<Exception?>(), + It.IsAny<Func<It.IsAnyType, Exception?, string>>()), + Times.Once); + } + + [Fact] + public void DeserializeBaseItem_WithKnownType_ReturnsItem() + { + // Arrange + var entity = new BaseItemEntity + { + Id = Guid.NewGuid(), + Type = "MediaBrowser.Controller.Entities.Movies.Movie" + }; + + // Act + var result = BaseItemRepository.DeserializeBaseItem(entity, NullLogger.Instance, null, false); + + // Assert + Assert.NotNull(result); + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/DotIgnoreIgnoreRuleTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/DotIgnoreIgnoreRuleTest.cs index d677c9f09..a7bbef7ed 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/DotIgnoreIgnoreRuleTest.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/DotIgnoreIgnoreRuleTest.cs @@ -1,30 +1,81 @@ +using Emby.Server.Implementations.Library; using Xunit; namespace Jellyfin.Server.Implementations.Tests.Library; public class DotIgnoreIgnoreRuleTest { - [Fact] - public void Test() + private static readonly string[] _rule1 = ["SPs"]; + private static readonly string[] _rule2 = ["SPs", "!thebestshot.mkv"]; + private static readonly string[] _rule3 = ["*.txt", @"{\colortbl;\red255\green255\blue255;}", "videos/", @"\invalid\escape\sequence", "*.mkv"]; + private static readonly string[] _rule4 = [@"{\colortbl;\red255\green255\blue255;}", @"\invalid\escape\sequence"]; + + public static TheoryData<string[], string, bool, bool> CheckIgnoreRulesTestData => + new() + { + // Basic pattern matching + { _rule1, "f:/cd/sps/ffffff.mkv", false, true }, + { _rule1, "cd/sps/ffffff.mkv", false, true }, + { _rule1, "/cd/sps/ffffff.mkv", false, true }, + + // Negate pattern + { _rule2, "f:/cd/sps/ffffff.mkv", false, true }, + { _rule2, "cd/sps/ffffff.mkv", false, true }, + { _rule2, "/cd/sps/ffffff.mkv", false, true }, + { _rule2, "f:/cd/sps/thebestshot.mkv", false, false }, + { _rule2, "cd/sps/thebestshot.mkv", false, false }, + { _rule2, "/cd/sps/thebestshot.mkv", false, false }, + + // Mixed valid and invalid patterns - skips invalid, applies valid + { _rule3, "test.txt", false, true }, + { _rule3, "videos/movie.mp4", false, true }, + { _rule3, "movie.mkv", false, true }, + { _rule3, "test.mp3", false, false }, + + // Only invalid patterns - falls back to ignore all + { _rule4, "any-file.txt", false, true }, + { _rule4, "any/path/to/file.mkv", false, true }, + }; + + public static TheoryData<string[], string, bool, bool> WindowsPathNormalizationTestData => + new() + { + // Windows paths with backslashes - should match when normalizePath is true + { _rule1, @"C:\cd\sps\ffffff.mkv", false, true }, + { _rule1, @"D:\media\sps\movie.mkv", false, true }, + { _rule1, @"\\server\share\sps\file.mkv", false, true }, + + // Negate pattern with Windows paths + { _rule2, @"C:\cd\sps\ffffff.mkv", false, true }, + { _rule2, @"C:\cd\sps\thebestshot.mkv", false, false }, + + // Directory matching with Windows paths + { _rule3, @"C:\videos\movie.mp4", false, true }, + { _rule3, @"D:\documents\test.txt", false, true }, + { _rule3, @"E:\music\song.mp3", false, false }, + }; + + [Theory] + [MemberData(nameof(CheckIgnoreRulesTestData))] + public void CheckIgnoreRules_ReturnsExpectedResult(string[] rules, string path, bool isDirectory, bool expectedIgnored) + { + Assert.Equal(expectedIgnored, DotIgnoreIgnoreRule.CheckIgnoreRules(path, rules, isDirectory)); + } + + [Theory] + [MemberData(nameof(WindowsPathNormalizationTestData))] + public void CheckIgnoreRules_WithWindowsPaths_NormalizesBackslashes(string[] rules, string path, bool isDirectory, bool expectedIgnored) { - var ignore = new Ignore.Ignore(); - ignore.Add("SPs"); - Assert.True(ignore.IsIgnored("f:/cd/sps/ffffff.mkv")); - Assert.True(ignore.IsIgnored("cd/sps/ffffff.mkv")); - Assert.True(ignore.IsIgnored("/cd/sps/ffffff.mkv")); + // With normalizePath=true, backslashes should be converted to forward slashes + Assert.Equal(expectedIgnored, DotIgnoreIgnoreRule.CheckIgnoreRules(path, rules, isDirectory, normalizePath: true)); } - [Fact] - public void TestNegatePattern() + [Theory] + [InlineData(@"C:\cd\sps\ffffff.mkv")] + [InlineData(@"D:\media\sps\movie.mkv")] + public void CheckIgnoreRules_WithWindowsPaths_WithoutNormalization_DoesNotMatch(string path) { - var ignore = new Ignore.Ignore(); - ignore.Add("SPs"); - ignore.Add("!thebestshot.mkv"); - Assert.True(ignore.IsIgnored("f:/cd/sps/ffffff.mkv")); - Assert.True(ignore.IsIgnored("cd/sps/ffffff.mkv")); - Assert.True(ignore.IsIgnored("/cd/sps/ffffff.mkv")); - Assert.True(!ignore.IsIgnored("f:/cd/sps/thebestshot.mkv")); - Assert.True(!ignore.IsIgnored("cd/sps/thebestshot.mkv")); - Assert.True(!ignore.IsIgnored("/cd/sps/thebestshot.mkv")); + // Without normalization, Windows paths with backslashes won't match patterns expecting forward slashes + Assert.False(DotIgnoreIgnoreRule.CheckIgnoreRules(path, _rule1, isDirectory: false, normalizePath: false)); } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs index 07061cfc7..4cb6cb960 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs @@ -19,7 +19,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library [InlineData("/media/movies/#recycle", true)] [InlineData("thumbs.db", true)] [InlineData(@"C:\media\movies\movie.avi", false)] - [InlineData("/media/.hiddendir/file.mp4", false)] + [InlineData("/media/.hiddendir/file.mp4", true)] [InlineData("/media/dir/.hiddenfile.mp4", true)] [InlineData("/media/dir/._macjunk.mp4", true)] [InlineData("/volume1/video/Series/@eaDir", true)] @@ -32,7 +32,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library [InlineData("/media/music/Foo B.A.R", false)] [InlineData("/media/music/Foo B.A.R.", false)] [InlineData("/movies/.zfs/snapshot/AutoM-2023-09", true)] - public void PathIgnored(string path, bool expected) + public void PathIgnored(string path, bool expected) { Assert.Equal(expected, IgnorePatterns.ShouldIgnore(path)); } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs index 6d6bba4fc..e60522bf7 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs @@ -204,6 +204,25 @@ namespace Jellyfin.Server.Implementations.Tests.Localization } [Theory] + [InlineData("TV-MA", "DE", 17, 1)] // US-only rating, DE country code + [InlineData("PG-13", "FR", 13, 0)] // US-only rating, FR country code + [InlineData("R", "JP", 17, 0)] // US-only rating, JP country code + public async Task GetRatingScore_FallbackPrioritizesUS_Success(string rating, string countryCode, int expectedScore, int? expectedSubScore) + { + var localizationManager = Setup(new ServerConfiguration() + { + MetadataCountryCode = countryCode + }); + await localizationManager.LoadAll(); + + var score = localizationManager.GetRatingScore(rating); + + Assert.NotNull(score); + Assert.Equal(expectedScore, score.Score); + Assert.Equal(expectedSubScore, score.SubScore); + } + + [Theory] [InlineData("Default", "Default")] [InlineData("HeaderLiveTV", "Live TV")] public void GetLocalizedString_Valid_Success(string key, string expected) diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj index 8228c0df7..7b0e23788 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -5,7 +5,6 @@ <PackageReference Include="AutoFixture.AutoMoq" /> <PackageReference Include="AutoFixture.Xunit2" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" /> - <PackageReference Include="Microsoft.Extensions.Options" /> <PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="xunit" /> <PackageReference Include="xunit.runner.visualstudio"> diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index 5fea805ae..21596e0ed 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -5,7 +5,6 @@ <PackageReference Include="AutoFixture.AutoMoq" /> <PackageReference Include="AutoFixture.Xunit2" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" /> - <PackageReference Include="Microsoft.Extensions.Options" /> <PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="xunit" /> <PackageReference Include="xunit.runner.visualstudio"> diff --git a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs index 123266d29..14f4c33b6 100644 --- a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs +++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs @@ -11,7 +11,6 @@ using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager; -using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork; namespace Jellyfin.Server.Tests { @@ -87,7 +86,7 @@ namespace Jellyfin.Server.Tests // Need this here as ::1 and 127.0.0.1 are in them by default. options.KnownProxies.Clear(); - options.KnownNetworks.Clear(); + options.KnownIPNetworks.Clear(); ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList, options); @@ -97,10 +96,10 @@ namespace Jellyfin.Server.Tests Assert.True(options.KnownProxies.Contains(item)); } - Assert.Equal(knownNetworks.Length, options.KnownNetworks.Count); + Assert.Equal(knownNetworks.Length, options.KnownIPNetworks.Count); foreach (var item in knownNetworks) { - Assert.NotNull(options.KnownNetworks.FirstOrDefault(x => x.Prefix.Equals(item.Prefix) && x.PrefixLength == item.PrefixLength)); + Assert.NotEqual(default, options.KnownIPNetworks.FirstOrDefault(x => x.BaseAddress.Equals(item.BaseAddress) && x.PrefixLength == item.PrefixLength)); } } |
