diff options
33 files changed, 253 insertions, 126 deletions
diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml index 95e0d8c58..7838b3b02 100644 --- a/.ci/azure-pipelines-test.yml +++ b/.ci/azure-pipelines-test.yml @@ -94,5 +94,5 @@ jobs: displayName: 'Publish OpenAPI Artifact' condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) inputs: - targetPath: "tests/Jellyfin.Api.Tests/bin/Release/net5.0/openapi.json" + targetPath: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net5.0/openapi.json" artifactName: 'OpenAPI Spec' diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs index d4af72b62..8eaf12ba9 100644 --- a/Emby.Dlna/Profiles/DefaultProfile.cs +++ b/Emby.Dlna/Profiles/DefaultProfile.cs @@ -1,5 +1,7 @@ #pragma warning disable CS1591 +using System; +using System.Globalization; using System.Linq; using MediaBrowser.Model.Dlna; @@ -10,6 +12,7 @@ namespace Emby.Dlna.Profiles { public DefaultProfile() { + Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); Name = "Generic Device"; ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*"; diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs index bd7553a91..4eef3ebc5 100644 --- a/Emby.Naming/Video/CleanStringParser.cs +++ b/Emby.Naming/Video/CleanStringParser.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; namespace Emby.Naming.Video @@ -16,8 +17,14 @@ namespace Emby.Naming.Video /// <param name="expressions">List of regex to parse name and year from.</param> /// <param name="newName">Parsing result string.</param> /// <returns>True if parsing was successful.</returns> - public static bool TryClean(string name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName) + public static bool TryClean([NotNullWhen(true)] string? name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName) { + if (string.IsNullOrEmpty(name)) + { + newName = ReadOnlySpan<char>.Empty; + return false; + } + var len = expressions.Count; for (int i = 0; i < len; i++) { @@ -33,12 +40,6 @@ namespace Emby.Naming.Video private static bool TryClean(string name, Regex expression, out ReadOnlySpan<char> newName) { - if (string.IsNullOrEmpty(name)) - { - newName = ReadOnlySpan<char>.Empty; - return false; - } - var match = expression.Match(name); int index = match.Index; if (match.Success && index != 0) diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 09a030d2d..d71d60954 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -221,20 +221,22 @@ namespace Emby.Naming.Video string testFilename = Path.GetFileNameWithoutExtension(testFilePath); if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) { - if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName)) + // Remove the folder name before cleaning as we don't care about cleaning that part + if (folderName.Length <= testFilename.Length) { - testFilename = cleanName.ToString(); + testFilename = testFilename.Substring(folderName.Length).Trim(); } - if (folderName.Length <= testFilename.Length) + if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName)) { - testFilename = testFilename.Substring(folderName.Length).Trim(); + testFilename = cleanName.Trim().ToString(); } + // The CleanStringParser should have removed common keywords etc. return string.IsNullOrEmpty(testFilename) - || testFilename[0] == '-' - || testFilename[0] == '_' - || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)); + || testFilename[0] == '-' + || testFilename[0] == '_' + || Regex.IsMatch(testFilename, @"^\[([^]]*)\]"); } return false; diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 619d1520e..79a6da8f7 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Emby.Naming.Common; @@ -146,7 +147,7 @@ namespace Emby.Naming.Video /// <param name="name">Raw name.</param> /// <param name="newName">Clean name.</param> /// <returns>True if cleaning of name was successful.</returns> - public bool TryCleanString(string name, out ReadOnlySpan<char> newName) + public bool TryCleanString([NotNullWhen(true)] string? name, out ReadOnlySpan<char> newName) { return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName); } diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 7dcc925c2..57d0c26b9 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -59,11 +59,18 @@ namespace Emby.Server.Implementations.Library /// <param name="newPath">The result of the sub path replacement</param> /// <returns>The path after replacing the sub path.</returns> /// <exception cref="ArgumentNullException"><paramref name="path" />, <paramref name="newSubPath" /> or <paramref name="newSubPath" /> is empty.</exception> - public static bool TryReplaceSubPath(this string path, string subPath, string newSubPath, [NotNullWhen(true)] out string? newPath) + public static bool TryReplaceSubPath( + [NotNullWhen(true)] this string? path, + [NotNullWhen(true)] string? subPath, + [NotNullWhen(true)] string? newSubPath, + [NotNullWhen(true)] out string? newPath) { newPath = null; - if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(subPath) || string.IsNullOrEmpty(newSubPath) || subPath.Length > path.Length) + if (string.IsNullOrEmpty(path) + || string.IsNullOrEmpty(subPath) + || string.IsNullOrEmpty(newSubPath) + || subPath.Length > path.Length) { return false; } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 40b934d32..78a82118e 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -91,8 +91,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); + _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); await JsonSerializer.SerializeAsync(_logFileStream, mediaSource, _jsonOptions, cancellationToken).ConfigureAwait(false); await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 08832bae1..b16ccc561 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -193,8 +193,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { var resolved = false; - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)) + using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read)) { while (true) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 233c3d83a..eeb2426f4 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -136,8 +136,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath); using var message = response; await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.None); + await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read); await StreamHelper.CopyToAsync( stream, fileStream, diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index d20a02cf5..e2306aa27 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -508,17 +508,15 @@ namespace Jellyfin.Api.Helpers private static void ApplyDeviceProfileSettings(StreamState state, IDlnaManager dlnaManager, IDeviceManager deviceManager, HttpRequest request, string? deviceProfileId, bool? @static) { - var headers = request.Headers; - if (!string.IsNullOrWhiteSpace(deviceProfileId)) { state.DeviceProfile = dlnaManager.GetProfile(deviceProfileId); - } - else if (!string.IsNullOrWhiteSpace(deviceProfileId)) - { - var caps = deviceManager.GetCapabilities(deviceProfileId); - state.DeviceProfile = caps == null ? dlnaManager.GetProfile(headers) : caps.DeviceProfile; + if (state.DeviceProfile == null) + { + var caps = deviceManager.GetCapabilities(deviceProfileId); + state.DeviceProfile = caps == null ? dlnaManager.GetProfile(request.Headers) : caps.DeviceProfile; + } } var profile = state.DeviceProfile; diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 7cd9024b0..240d132b1 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -553,8 +553,7 @@ namespace Jellyfin.Api.Helpers $"{logFilePrefix}{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{state.Request.MediaSourceId}_{Guid.NewGuid().ToString()[..8]}.log"); // FFmpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); + Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(request.Path + Environment.NewLine + Environment.NewLine + JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false); diff --git a/Jellyfin.Server/Filters/WebsocketModelFilter.cs b/Jellyfin.Server/Filters/WebsocketModelFilter.cs index 248802857..38afb201d 100644 --- a/Jellyfin.Server/Filters/WebsocketModelFilter.cs +++ b/Jellyfin.Server/Filters/WebsocketModelFilter.cs @@ -25,6 +25,8 @@ namespace Jellyfin.Server.Filters context.SchemaGenerator.GenerateSchema(typeof(GeneralCommandType), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<object>), context.SchemaRepository); + + context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository); } } } diff --git a/Jellyfin.Server/Properties/AssemblyInfo.cs b/Jellyfin.Server/Properties/AssemblyInfo.cs index 7abf298b1..fe2d5c5f9 100644 --- a/Jellyfin.Server/Properties/AssemblyInfo.cs +++ b/Jellyfin.Server/Properties/AssemblyInfo.cs @@ -21,4 +21,4 @@ using System.Runtime.InteropServices; // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] -[assembly: InternalsVisibleTo("Jellyfin.Api.Tests")] +[assembly: InternalsVisibleTo("Jellyfin.Server.Tests")] diff --git a/Jellyfin.sln b/Jellyfin.sln index 02ac1c7e9..7b81f4346 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -76,6 +76,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "tests\Jellyfin.Server.Tests\Jellyfin.Server.Tests.csproj", "{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -206,6 +210,10 @@ Global {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -221,6 +229,7 @@ Global {30922383-D513-4F4D-B890-A940B57FA353} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 9a3e3d5fa..2cd1ee717 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -140,7 +140,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies // ParseName is required here. // Caller provides the filename with extension stripped and NOT the parsed filename var parsedName = _libraryManager.ParseName(info.Name); - var searchResults = await _tmdbClientManager.SearchMovieAsync(parsedName.Name, parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + var searchResults = await _tmdbClientManager.SearchMovieAsync(parsedName.Name, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); if (searchResults.Count > 0) { @@ -148,6 +148,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies } } + if (string.IsNullOrEmpty(tmdbId) && !string.IsNullOrEmpty(imdbId)) + { + var movieResultFromImdbId = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + if (movieResultFromImdbId?.MovieResults.Count > 0) + { + tmdbId = movieResultFromImdbId.MovieResults[0].Id.ToString(); + } + } + if (string.IsNullOrEmpty(tmdbId)) { return new MetadataResult<Movie>(); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index 0238d0574..74c2acf47 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -43,9 +43,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) { - var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb); - - if (!string.IsNullOrEmpty(tmdbId)) + if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var tmdbId)) { var series = await _tmdbClientManager .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, searchInfo.MetadataLanguage, cancellationToken) @@ -59,9 +57,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } } - var imdbId = searchInfo.GetProviderId(MetadataProvider.Imdb); - - if (!string.IsNullOrEmpty(imdbId)) + if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out var imdbId)) { var findResult = await _tmdbClientManager .FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, searchInfo.MetadataLanguage, cancellationToken) @@ -82,9 +78,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } } - var tvdbId = searchInfo.GetProviderId(MetadataProvider.Tvdb); - - if (!string.IsNullOrEmpty(tvdbId)) + if (searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out var tvdbId)) { var findResult = await _tmdbClientManager .FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, searchInfo.MetadataLanguage, cancellationToken) @@ -171,33 +165,21 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var tmdbId = info.GetProviderId(MetadataProvider.Tmdb); - if (string.IsNullOrEmpty(tmdbId)) + if (string.IsNullOrEmpty(tmdbId) && info.TryGetProviderId(MetadataProvider.Imdb, out var imdbId)) { - var imdbId = info.GetProviderId(MetadataProvider.Imdb); - - if (!string.IsNullOrEmpty(imdbId)) + var searchResult = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + if (searchResult?.TvResults.Count > 0) { - var searchResult = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); - - if (searchResult != null) - { - tmdbId = searchResult.TvResults.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture); - } + tmdbId = searchResult.TvResults[0].Id.ToString(CultureInfo.InvariantCulture); } } - if (string.IsNullOrEmpty(tmdbId)) + if (string.IsNullOrEmpty(tmdbId) && info.TryGetProviderId(MetadataProvider.Tvdb, out var tvdbId)) { - var tvdbId = info.GetProviderId(MetadataProvider.Tvdb); - - if (!string.IsNullOrEmpty(tvdbId)) + var searchResult = await _tmdbClientManager.FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + if (searchResult?.TvResults.Count > 0) { - var searchResult = await _tmdbClientManager.FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); - - if (searchResult != null) - { - tmdbId = searchResult.TvResults.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture); - } + tmdbId = searchResult.TvResults[0].Id.ToString(CultureInfo.InvariantCulture); } } @@ -207,7 +189,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // ParseName is required here. // Caller provides the filename with extension stripped and NOT the parsed filename var parsedName = _libraryManager.ParseName(info.Name); - var searchResults = await _tmdbClientManager.SearchSeriesAsync(parsedName.Name, info.MetadataLanguage, info.Year ?? 0, cancellationToken).ConfigureAwait(false); + var searchResults = await _tmdbClientManager.SearchSeriesAsync(parsedName.Name, info.MetadataLanguage, info.Year ?? parsedName.Year ?? 0, cancellationToken).ConfigureAwait(false); if (searchResults.Count > 0) { @@ -215,32 +197,34 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } } - if (!string.IsNullOrEmpty(tmdbId)) + if (string.IsNullOrEmpty(tmdbId)) { - cancellationToken.ThrowIfCancellationRequested(); + return result; + } - var tvShow = await _tmdbClientManager - .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) - .ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); - result = new MetadataResult<Series> - { - Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode), - ResultLanguage = info.MetadataLanguage ?? tvShow.OriginalLanguage - }; + var tvShow = await _tmdbClientManager + .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) + .ConfigureAwait(false); - foreach (var person in GetPersons(tvShow)) - { - result.AddPerson(person); - } + result = new MetadataResult<Series> + { + Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode), + ResultLanguage = info.MetadataLanguage ?? tvShow.OriginalLanguage + }; - result.HasMetadata = result.Item != null; + foreach (var person in GetPersons(tvShow)) + { + result.AddPerson(person); } + result.HasMetadata = result.Item != null; + return result; } - private Series MapTvShowToSeries(TvShow seriesResult, string preferredCountryCode) + private static Series MapTvShowToSeries(TvShow seriesResult, string preferredCountryCode) { var series = new Series { @@ -29,9 +29,6 @@ <a href="https://features.jellyfin.org"> <img alt="Submit Feature Requests" src="https://img.shields.io/badge/fider-vote%20on%20features-success.svg"/> </a> -<a href="https://forum.jellyfin.org"> -<img alt="Discuss on our Forum" src="https://img.shields.io/discourse/https/forum.jellyfin.org/users.svg"/> -</a> <a href="https://matrix.to/#/+jellyfin:matrix.org"> <img alt="Chat on Matrix" src="https://img.shields.io/matrix/jellyfin:matrix.org.svg?logo=matrix"/> </a> diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64 index 2549f25ee..137e56ecf 100644 --- a/deployment/Dockerfile.fedora.amd64 +++ b/deployment/Dockerfile.fedora.amd64 @@ -13,9 +13,7 @@ RUN dnf update -y \ && dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd # Install DotNET SDK -RUN rpm --import https://packages.microsoft.com/keys/microsoft.asc \ - && curl -o /etc/yum.repos.d/microsoft-prod.repo https://packages.microsoft.com/config/fedora/$(rpm -E %fedora)/prod.repo \ - && dnf install -y dotnet-sdk-${SDK_VERSION} dotnet-runtime-${SDK_VERSION} +RUN dnf install -y dotnet-sdk-${SDK_VERSION} dotnet-runtime-${SDK_VERSION} # Create symlinks and directories RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora.amd64 /build.sh \ diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index a336d2aee..577b61d02 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -35,11 +35,8 @@ </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\Jellyfin.Server\Jellyfin.Server.csproj" /> - </ItemGroup> - - <ItemGroup> - <EmbeddedResource Include="TestPage.html" /> + <ProjectReference Include="../../Jellyfin.Api/Jellyfin.Api.csproj" /> + <ProjectReference Include="../../Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj" /> </ItemGroup> </Project> diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs index 4b363843a..a720bdade 100644 --- a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs @@ -7,18 +7,13 @@ namespace Jellyfin.Naming.Tests.Video { public sealed class CleanStringTests { - private readonly NamingOptions _namingOptions = new NamingOptions(); + private readonly VideoResolver _videoResolver = new VideoResolver(new NamingOptions()); [Theory] [InlineData("Super movie 480p.mp4", "Super movie")] [InlineData("Super movie 480p 2001.mp4", "Super movie")] [InlineData("Super movie [480p].mp4", "Super movie")] [InlineData("480 Super movie [tmdbid=12345].mp4", "480 Super movie")] - [InlineData("Super movie(2009).mp4", "Super movie(2009).mp4")] - [InlineData("Run lola run (lola rennt) (2009).mp4", "Run lola run (lola rennt) (2009).mp4")] - [InlineData(@"American.Psycho.mkv", "American.Psycho.mkv")] - [InlineData(@"American Psycho.mkv", "American Psycho.mkv")] - [InlineData(@"[rec].mkv", "[rec].mkv")] [InlineData("Crouching.Tiger.Hidden.Dragon.4k.mkv", "Crouching.Tiger.Hidden.Dragon")] [InlineData("Crouching.Tiger.Hidden.Dragon.UltraHD.mkv", "Crouching.Tiger.Hidden.Dragon")] [InlineData("Crouching.Tiger.Hidden.Dragon.UHD.mkv", "Crouching.Tiger.Hidden.Dragon")] @@ -28,19 +23,26 @@ namespace Jellyfin.Naming.Tests.Video [InlineData("Crouching.Tiger.Hidden.Dragon.BDrip.mkv", "Crouching.Tiger.Hidden.Dragon")] [InlineData("Crouching.Tiger.Hidden.Dragon.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")] [InlineData("Crouching.Tiger.Hidden.Dragon.4K.UltraHD.HDR.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")] - [InlineData(null, null)] // FIXME: [InlineData("After The Sunset - [0004].mkv", "After The Sunset")] - public void CleanStringTest(string input, string expectedName) + public void CleanStringTest_NeedsCleaning_Success(string input, string expectedName) { - if (new VideoResolver(_namingOptions).TryCleanString(input, out ReadOnlySpan<char> newName)) - { - // TODO: compare spans when XUnit supports it - Assert.Equal(expectedName, newName.ToString()); - } - else - { - Assert.Equal(expectedName, input); - } + Assert.True(_videoResolver.TryCleanString(input, out ReadOnlySpan<char> newName)); + // TODO: compare spans when XUnit supports it + Assert.Equal(expectedName, newName.ToString()); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("Super movie(2009).mp4")] + [InlineData("[rec].mkv")] + [InlineData("American.Psycho.mkv")] + [InlineData("American Psycho.mkv")] + [InlineData("Run lola run (lola rennt) (2009).mp4")] + public void CleanStringTest_DoesntNeedCleaning_False(string? input) + { + Assert.False(_videoResolver.TryCleanString(input, out ReadOnlySpan<char> newName)); + Assert.True(newName.IsEmpty); } } } diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs index bc5e6fa63..2af666759 100644 --- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs @@ -369,6 +369,44 @@ namespace Jellyfin.Naming.Tests.Video } [Fact] + public void Resolve_GivenFolderNameWithBracketsAndHyphens_GroupsBasedOnFolderName() + { + var files = new[] + { + @"/movies/John Wick - Kapitel 3 (2019) [imdbid=tt6146586]/John Wick - Kapitel 3 (2019) [imdbid=tt6146586] - Version 1.mkv", + @"/movies/John Wick - Kapitel 3 (2019) [imdbid=tt6146586]/John Wick - Kapitel 3 (2019) [imdbid=tt6146586] - Version 2.mkv" + }; + + var result = _videoListResolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + }).ToList()).ToList(); + + Assert.Single(result); + Assert.Empty(result[0].Extras); + Assert.Single(result[0].AlternateVersions); + } + + [Fact] + public void Resolve_GivenUnclosedBrackets_DoesNotGroup() + { + var files = new[] + { + @"/movies/John Wick - Chapter 3 (2019)/John Wick - Chapter 3 (2019) [Version 1].mkv", + @"/movies/John Wick - Chapter 3 (2019)/John Wick - Chapter 3 (2019) [Version 2.mkv" + }; + + var result = _videoListResolver.Resolve(files.Select(i => new FileSystemMetadata + { + IsDirectory = false, + FullName = i + }).ToList()).ToList(); + + Assert.Equal(2, result.Count); + } + + [Fact] public void TestEmptyList() { var result = _videoListResolver.Resolve(new List<FileSystemMetadata>()).ToList(); diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index a6fe90566..e5508243f 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -40,12 +40,16 @@ namespace Jellyfin.Server.Implementations.Tests.Library } [Theory] + [InlineData(null, null, null)] + [InlineData(null, "/my/path", "/another/path")] + [InlineData("/my/path", null, "/another/path")] + [InlineData("/my/path", "/another/path", null)] [InlineData("", "", "")] [InlineData("/my/path", "", "")] [InlineData("", "/another/path", "")] [InlineData("", "", "/new/subpath")] [InlineData("/home/jeff/music/jeff's band/consistently inconsistent.mp3", "/home/jeff/music/not jeff's band", "/home/not jeff")] - public void TryReplaceSubPath_InvalidInput_ReturnsFalseAndNull(string path, string subPath, string newSubPath) + public void TryReplaceSubPath_InvalidInput_ReturnsFalseAndNull(string? path, string? subPath, string? newSubPath) { Assert.False(PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result)); Assert.Null(result); diff --git a/tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs index 40933562d..87136dfc8 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs @@ -1,3 +1,4 @@ +using System.Net; using System.Net.Mime; using System.Text; using System.Text.Json; @@ -5,7 +6,7 @@ using System.Threading.Tasks; using MediaBrowser.Model.Branding; using Xunit; -namespace Jellyfin.Api.Tests +namespace Jellyfin.Server.Integration.Tests { public sealed class BrandingControllerTests : IClassFixture<JellyfinApplicationFactory> { @@ -26,7 +27,7 @@ namespace Jellyfin.Api.Tests var response = await client.GetAsync("/Branding/Configuration"); // Assert - Assert.True(response.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); var responseBody = await response.Content.ReadAsStreamAsync(); diff --git a/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs index 300b2697f..86d6326d8 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs @@ -8,7 +8,7 @@ using Jellyfin.Api.Models; using MediaBrowser.Common.Json; using Xunit; -namespace Jellyfin.Api.Tests.Controllers +namespace Jellyfin.Server.Integration.Tests.Controllers { public sealed class DashboardControllerTests : IClassFixture<JellyfinApplicationFactory> { @@ -37,9 +37,9 @@ namespace Jellyfin.Api.Tests.Controllers var response = await client.GetAsync("/web/ConfigurationPage?name=TestPlugin").ConfigureAwait(false); - Assert.True(response.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(MediaTypeNames.Text.Html, response.Content.Headers.ContentType?.MediaType); - StreamReader reader = new StreamReader(typeof(TestPlugin).Assembly.GetManifestResourceStream("Jellyfin.Api.Tests.TestPage.html")!); + StreamReader reader = new StreamReader(typeof(TestPlugin).Assembly.GetManifestResourceStream("Jellyfin.Server.Integration.Tests.TestPage.html")!); Assert.Equal(await response.Content.ReadAsStringAsync(), reader.ReadToEnd()); } @@ -60,7 +60,7 @@ namespace Jellyfin.Api.Tests.Controllers var response = await client.GetAsync("/web/ConfigurationPages").ConfigureAwait(false); - Assert.True(response.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); var res = await response.Content.ReadAsStreamAsync(); _ = await JsonSerializer.DeserializeAsync<ConfigurationPageInfo[]>(res, _jsonOpions); @@ -74,7 +74,7 @@ namespace Jellyfin.Api.Tests.Controllers var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true").ConfigureAwait(false); - Assert.True(response.IsSuccessStatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj new file mode 100644 index 000000000..49004966b --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -0,0 +1,40 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + <IsPackable>false</IsPackable> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <Nullable>enable</Nullable> + <AnalysisMode>AllEnabledByDefault</AnalysisMode> + <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="AutoFixture" Version="4.15.0" /> + <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" /> + <PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.3" /> + <PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> + <PackageReference Include="xunit" Version="2.4.1" /> + <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> + <PackageReference Include="coverlet.collector" Version="3.0.3" /> + <PackageReference Include="Moq" Version="4.16.0" /> + </ItemGroup> + + <!-- Code Analyzers --> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="../../Jellyfin.Server/Jellyfin.Server.csproj" /> + </ItemGroup> + + <ItemGroup> + <EmbeddedResource Include="TestPage.html" /> + </ItemGroup> + +</Project> diff --git a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs index 92c5495c8..d9ec81a27 100644 --- a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs @@ -4,7 +4,6 @@ using System.IO; using System.Threading; using Emby.Server.Implementations; using Emby.Server.Implementations.IO; -using Jellyfin.Server; using MediaBrowser.Common; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; @@ -15,7 +14,7 @@ using Microsoft.Extensions.Logging; using Serilog; using Serilog.Extensions.Logging; -namespace Jellyfin.Api.Tests +namespace Jellyfin.Server.Integration.Tests { /// <summary> /// Factory for bootstrapping the Jellyfin application in memory for functional end to end tests. @@ -26,9 +25,9 @@ namespace Jellyfin.Api.Tests private readonly ConcurrentBag<IDisposable> _disposableComponents = new ConcurrentBag<IDisposable>(); /// <summary> - /// Initializes a new instance of the <see cref="JellyfinApplicationFactory"/> class. + /// Initializes static members of the <see cref="JellyfinApplicationFactory"/> class. /// </summary> - public JellyfinApplicationFactory() + static JellyfinApplicationFactory() { // Perform static initialization that only needs to happen once per test-run Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger(); diff --git a/tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs index 03ab56d1f..3cbd638f9 100644 --- a/tests/Jellyfin.Api.Tests/OpenApiSpecTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Branding; using Xunit; using Xunit.Abstractions; -namespace Jellyfin.Api.Tests +namespace Jellyfin.Server.Integration.Tests { public sealed class OpenApiSpecTests : IClassFixture<JellyfinApplicationFactory> { diff --git a/tests/Jellyfin.Api.Tests/TestAppHost.cs b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs index eb4c9b305..4e5d0fcb6 100644 --- a/tests/Jellyfin.Api.Tests/TestAppHost.cs +++ b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Jellyfin.Api.Tests +namespace Jellyfin.Server.Integration.Tests { /// <summary> /// Implementation of the abstract <see cref="ApplicationHost" /> class. diff --git a/tests/Jellyfin.Api.Tests/TestPage.html b/tests/Jellyfin.Server.Integration.Tests/TestPage.html index 8037af8a6..8037af8a6 100644 --- a/tests/Jellyfin.Api.Tests/TestPage.html +++ b/tests/Jellyfin.Server.Integration.Tests/TestPage.html diff --git a/tests/Jellyfin.Api.Tests/TestPlugin.cs b/tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs index a3b4b6994..1d67ac487 100644 --- a/tests/Jellyfin.Api.Tests/TestPlugin.cs +++ b/tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs @@ -7,7 +7,7 @@ using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; -namespace Jellyfin.Api.Tests +namespace Jellyfin.Server.Integration.Tests { public class TestPlugin : BasePlugin<BasePluginConfiguration>, IHasWebPages { diff --git a/tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs b/tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs index 2d2f78a98..ac10c4784 100644 --- a/tests/Jellyfin.Api.Tests/TestPluginWithoutPages.cs +++ b/tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs @@ -6,7 +6,7 @@ using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; -namespace Jellyfin.Api.Tests +namespace Jellyfin.Server.Integration.Tests { public class TestPluginWithoutPages : BasePlugin<BasePluginConfiguration> { diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj new file mode 100644 index 000000000..65ea28e94 --- /dev/null +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -0,0 +1,39 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + <IsPackable>false</IsPackable> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <Nullable>enable</Nullable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="AutoFixture" Version="4.15.0" /> + <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" /> + <PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.3" /> + <PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> + <PackageReference Include="xunit" Version="2.4.1" /> + <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> + <PackageReference Include="coverlet.collector" Version="3.0.3" /> + <PackageReference Include="Moq" Version="4.16.0" /> + </ItemGroup> + + <!-- Code Analyzers --> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="../../Jellyfin.Server/Jellyfin.Server.csproj" /> + </ItemGroup> + + <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> + <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + +</Project> diff --git a/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs index 3984407ee..0b714e80a 100644 --- a/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs +++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; -namespace Jellyfin.Api.Tests +namespace Jellyfin.Server.Tests { public class ParseNetworkTests { |
