diff options
| author | cvium <clausvium@gmail.com> | 2023-02-12 22:59:48 +0100 |
|---|---|---|
| committer | cvium <clausvium@gmail.com> | 2023-02-12 22:59:48 +0100 |
| commit | 52e2776d8e9390db35fc1d0c3899ed3dccad5282 (patch) | |
| tree | d59cf8572ba62ad03169833045f64b30960482c5 | |
| parent | c9aef96dba26d29df3db9e3a1ce243be23bea772 (diff) | |
| parent | 318f11e79331e4786c44734ce496eb6485201c2b (diff) | |
Merge branch 'master' into simplify_authz
# Conflicts:
# Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs
60 files changed, 694 insertions, 351 deletions
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ef9ab45a1..41d59e243 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,11 +27,11 @@ jobs: dotnet-version: '7.0.x' - name: Initialize CodeQL - uses: github/codeql-action/init@8775e868027fa230df8586bdf502bbd9b618a477 # v2 + uses: github/codeql-action/init@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2 with: languages: ${{ matrix.language }} queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@8775e868027fa230df8586bdf502bbd9b618a477 # v2 + uses: github/codeql-action/autobuild@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@8775e868027fa230df8586bdf502bbd9b618a477 # v2 + uses: github/codeql-action/analyze@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5884c7376..20ea7a4ab 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -232,3 +232,4 @@ - [Matthew Jones](https://github.com/matthew-jones-uk) - [Jakob Kukla](https://github.com/jakobkukla) - [Utku Özdemir](https://github.com/utkuozdemir) + - [JPUC1143](https://github.com/Jpuc1143/) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index bc703fe90..602d2a853 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -4477,6 +4477,24 @@ namespace Emby.Server.Implementations.Data } } + if (query.IncludeInheritedTags.Length > 0) + { + var paramName = "@IncludeInheritedTags"; + if (statement is null) + { + int index = 0; + string includedTags = string.Join(',', query.IncludeInheritedTags.Select(_ => paramName + index++)); + whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in (" + includedTags + ")) is not null)"); + } + else + { + for (int index = 0; index < query.IncludeInheritedTags.Length; index++) + { + statement.TryBind(paramName + index, GetCleanValue(query.IncludeInheritedTags[index])); + } + } + } + if (query.SeriesStatuses.Length > 0) { var statuses = new List<string>(); @@ -5440,6 +5458,9 @@ AND Type = @InternalPersonType)"); list.AddRange(inheritedTags.Select(i => (6, i))); + // Remove all invalid values. + list.RemoveAll(i => string.IsNullOrEmpty(i.Item2)); + return list; } diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index e874990da..066afb956 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -137,32 +137,33 @@ namespace Emby.Server.Implementations.LiveTv.Listings private static ProgramInfo GetProgramInfo(XmlTvProgram program, ListingsProviderInfo info) { - string episodeTitle = program.Episode?.Title; + string episodeTitle = program.Episode.Title; + var programCategories = program.Categories.Where(c => !string.IsNullOrWhiteSpace(c)).ToList(); var programInfo = new ProgramInfo { ChannelId = program.ChannelId, EndDate = program.EndDate.UtcDateTime, - EpisodeNumber = program.Episode?.Episode, + EpisodeNumber = program.Episode.Episode, EpisodeTitle = episodeTitle, - Genres = program.Categories, + Genres = programCategories, StartDate = program.StartDate.UtcDateTime, Name = program.Title, Overview = program.Description, ProductionYear = program.CopyrightDate?.Year, - SeasonNumber = program.Episode?.Series, - IsSeries = program.Episode is not null, + SeasonNumber = program.Episode.Series, + IsSeries = program.Episode.Series is not null, IsRepeat = program.IsPreviouslyShown && !program.IsNew, IsPremiere = program.Premiere is not null, - IsKids = program.Categories.Any(c => info.KidsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)), - IsMovie = program.Categories.Any(c => info.MovieCategories.Contains(c, StringComparison.OrdinalIgnoreCase)), - IsNews = program.Categories.Any(c => info.NewsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)), - IsSports = program.Categories.Any(c => info.SportsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)), + IsKids = programCategories.Any(c => info.KidsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)), + IsMovie = programCategories.Any(c => info.MovieCategories.Contains(c, StringComparison.OrdinalIgnoreCase)), + IsNews = programCategories.Any(c => info.NewsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)), + IsSports = programCategories.Any(c => info.SportsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)), ImageUrl = string.IsNullOrEmpty(program.Icon?.Source) ? null : program.Icon.Source, HasImage = !string.IsNullOrEmpty(program.Icon?.Source), OfficialRating = string.IsNullOrEmpty(program.Rating?.Value) ? null : program.Rating.Value, CommunityRating = program.StarRating, - SeriesId = program.Episode is null ? null : program.Title?.GetMD5().ToString("N", CultureInfo.InvariantCulture) + SeriesId = program.Episode.Episode is null ? null : program.Title?.GetMD5().ToString("N", CultureInfo.InvariantCulture) }; if (string.IsNullOrWhiteSpace(program.ProgramId)) @@ -243,7 +244,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { Id = c.Id, Name = c.DisplayName, - ImageUrl = string.IsNullOrEmpty(c.Icon.Source) ? null : c.Icon.Source, + ImageUrl = string.IsNullOrEmpty(c.Icon?.Source) ? null : c.Icon.Source, Number = string.IsNullOrWhiteSpace(c.Number) ? c.Id : c.Number }).ToList(); } diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index f2212f4dc..7c23254a1 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -123,41 +123,64 @@ namespace Emby.Server.Implementations.Plugins continue; } + var assemblyLoadContext = new PluginLoadContext(plugin.Path); + _assemblyLoadContexts.Add(assemblyLoadContext); + + var assemblies = new List<Assembly>(plugin.DllFiles.Count); + var loadedAll = true; + foreach (var file in plugin.DllFiles) { - Assembly assembly; try { - var assemblyLoadContext = new PluginLoadContext(file); - _assemblyLoadContexts.Add(assemblyLoadContext); - - assembly = assemblyLoadContext.LoadFromAssemblyPath(file); - - // Load all required types to verify that the plugin will load - assembly.GetTypes(); + assemblies.Add(assemblyLoadContext.LoadFromAssemblyPath(file)); } catch (FileLoadException ex) { - _logger.LogError(ex, "Failed to load assembly {Path}. Disabling plugin.", file); + _logger.LogError(ex, "Failed to load assembly {Path}. Disabling plugin", file); ChangePluginState(plugin, PluginStatus.Malfunctioned); - continue; + loadedAll = false; + break; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + _logger.LogError(ex, "Failed to load assembly {Path}. Unknown exception was thrown. Disabling plugin", file); + ChangePluginState(plugin, PluginStatus.Malfunctioned); + loadedAll = false; + break; + } + } + + if (!loadedAll) + { + continue; + } + + foreach (var assembly in assemblies) + { + try + { + // Load all required types to verify that the plugin will load + assembly.GetTypes(); } catch (SystemException ex) when (ex is TypeLoadException or ReflectionTypeLoadException) // Undocumented exception { - _logger.LogError(ex, "Failed to load assembly {Path}. This error occurs when a plugin references an incompatible version of one of the shared libraries. Disabling plugin.", file); + _logger.LogError(ex, "Failed to load assembly {Path}. This error occurs when a plugin references an incompatible version of one of the shared libraries. Disabling plugin", assembly.Location); ChangePluginState(plugin, PluginStatus.NotSupported); - continue; + break; } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types { - _logger.LogError(ex, "Failed to load assembly {Path}. Unknown exception was thrown. Disabling plugin.", file); + _logger.LogError(ex, "Failed to load assembly {Path}. Unknown exception was thrown. Disabling plugin", assembly.Location); ChangePluginState(plugin, PluginStatus.Malfunctioned); - continue; + break; } - _logger.LogInformation("Loaded assembly {Assembly} from {Path}", assembly.FullName, file); + _logger.LogInformation("Loaded assembly {Assembly} from {Path}", assembly.FullName, assembly.Location); yield return assembly; } } diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index b2adb6a2d..aecdf00dc 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -99,12 +99,17 @@ public class ImageController : BaseJellyfinApiController [FromRoute, Required] ImageType imageType, [FromQuery] int? index = null) { + var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } + if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, userId, true)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image."); } - var user = _userManager.GetUserById(userId); var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false); await using (memoryStream.ConfigureAwait(false)) { @@ -148,12 +153,17 @@ public class ImageController : BaseJellyfinApiController [FromRoute, Required] ImageType imageType, [FromRoute] int index) { + var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } + if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, userId, true)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image."); } - var user = _userManager.GetUserById(userId); var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false); await using (memoryStream.ConfigureAwait(false)) { diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 97922d5db..99366e80c 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -240,7 +241,7 @@ public class ItemsController : BaseJellyfinApiController var isApiKey = User.GetIsApiKey(); // if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method var user = !isApiKey && userId.HasValue && !userId.Value.Equals(default) - ? _userManager.GetUserById(userId.Value) + ? _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException() : null; // beyond this point, we're either using an api key or we have a valid user @@ -814,6 +815,11 @@ public class ItemsController : BaseJellyfinApiController [FromQuery] bool excludeActiveSessions = false) { var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } + var parentIdGuid = parentId ?? Guid.Empty; var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(User) diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index e1ad87412..e8b68c7c3 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -283,6 +283,11 @@ public class LibraryController : BaseJellyfinApiController userId, inheritFromParent); + if (themeSongs.Result is NotFoundObjectResult || themeVideos.Result is NotFoundObjectResult) + { + return NotFound(); + } + return new AllThemeMediaResult { ThemeSongsResult = themeSongs?.Value, @@ -452,6 +457,10 @@ public class LibraryController : BaseJellyfinApiController if (user is not null) { parent = TranslateParentItem(parent, user); + if (parent is null) + { + break; + } } baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user)); @@ -672,6 +681,11 @@ public class LibraryController : BaseJellyfinApiController : _libraryManager.GetUserRootFolder()) : _libraryManager.GetItemById(itemId); + if (item is null) + { + return NotFound(); + } + if (item is Episode || (item is IItemByName && item is not MusicArtist)) { return new QueryResult<BaseItemDto>(); diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index c1f5d74cd..3425c8589 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -1210,7 +1210,7 @@ public class LiveTvController : BaseJellyfinApiController private async Task AssertUserCanManageLiveTv() { - var user = _userManager.GetUserById(User.GetUserId()); + var user = _userManager.GetUserById(User.GetUserId()) ?? throw new ResourceNotFoundException(); var session = await _sessionManager.LogSessionActivity( User.GetClient(), User.GetVersion(), diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index da1a6e832..3db1d89c1 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -157,6 +157,11 @@ public class MusicGenresController : BaseJellyfinApiController item = _libraryManager.GetMusicGenre(genreName); } + if (item is null) + { + return NotFound(); + } + if (userId.HasValue && !userId.Value.Equals(default)) { var user = _userManager.GetUserById(userId.Value); diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs index 11f3ddbb0..8ad553bcb 100644 --- a/Jellyfin.Api/Controllers/PlaystateController.cs +++ b/Jellyfin.Api/Controllers/PlaystateController.cs @@ -76,6 +76,11 @@ public class PlaystateController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed) { var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } + var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); var item = _libraryManager.GetItemById(itemId); @@ -88,6 +93,11 @@ public class PlaystateController : BaseJellyfinApiController foreach (var additionalUserInfo in session.AdditionalUsers) { var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId); + if (additionalUser is null) + { + return NotFound(); + } + UpdatePlayedStatus(additionalUser, item, true, datePlayed); } @@ -108,6 +118,11 @@ public class PlaystateController : BaseJellyfinApiController public async Task<ActionResult<UserItemDataDto>> MarkUnplayedItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) { var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } + var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); var item = _libraryManager.GetItemById(itemId); @@ -120,6 +135,11 @@ public class PlaystateController : BaseJellyfinApiController foreach (var additionalUserInfo in session.AdditionalUsers) { var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId); + if (additionalUser is null) + { + return NotFound(); + } + UpdatePlayedStatus(additionalUser, item, false, null); } diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index bae8e0a49..e93456de6 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -75,6 +75,10 @@ public class SessionController : BaseJellyfinApiController result = result.Where(i => i.SupportsRemoteControl); var user = _userManager.GetUserById(controllableByUserId.Value); + if (user is null) + { + return NotFound(); + } if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) { diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index d9ea96f2d..b0973b8a1 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -147,6 +147,11 @@ public class UserController : BaseJellyfinApiController public async Task<ActionResult> DeleteUser([FromRoute, Required] Guid userId) { var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } + await _sessionManager.RevokeUserTokens(user.Id, null).ConfigureAwait(false); await _userManager.DeleteUserAsync(userId).ConfigureAwait(false); return NoContent(); @@ -281,8 +286,8 @@ public class UserController : BaseJellyfinApiController { var success = await _userManager.AuthenticateUser( user.Username, - request.CurrentPw, - request.CurrentPw, + request.CurrentPw ?? string.Empty, + request.CurrentPw ?? string.Empty, HttpContext.GetNormalizedRemoteIp().ToString(), false).ConfigureAwait(false); @@ -292,7 +297,7 @@ public class UserController : BaseJellyfinApiController } } - await _userManager.ChangePassword(user, request.NewPw).ConfigureAwait(false); + await _userManager.ChangePassword(user, request.NewPw ?? string.Empty).ConfigureAwait(false); var currentToken = User.GetToken(); @@ -338,7 +343,7 @@ public class UserController : BaseJellyfinApiController } else { - await _userManager.ChangeEasyPassword(user, request.NewPw, request.NewPassword).ConfigureAwait(false); + await _userManager.ChangeEasyPassword(user, request.NewPw ?? string.Empty, request.NewPassword ?? string.Empty).ConfigureAwait(false); } return NoContent(); @@ -362,13 +367,17 @@ public class UserController : BaseJellyfinApiController [FromRoute, Required] Guid userId, [FromBody, Required] UserDto updateUser) { + var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } + if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true)) { return StatusCode(StatusCodes.Status403Forbidden, "User update not allowed."); } - var user = _userManager.GetUserById(userId); - if (!string.Equals(user.Username, updateUser.Name, StringComparison.Ordinal)) { await _userManager.RenameUser(user, updateUser.Name).ConfigureAwait(false); @@ -398,6 +407,10 @@ public class UserController : BaseJellyfinApiController [FromBody, Required] UserPolicy newPolicy) { var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } // If removing admin access if (!newPolicy.IsAdministrator && user.HasPermission(PermissionKind.IsAdministrator)) diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index 93312b817..1233995b4 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -78,10 +78,18 @@ public class UserLibraryController : BaseJellyfinApiController public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) { var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } var item = itemId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); + if (item is null) + { + return NotFound(); + } await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false); @@ -101,6 +109,11 @@ public class UserLibraryController : BaseJellyfinApiController public ActionResult<BaseItemDto> GetRootFolder([FromRoute, Required] Guid userId) { var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } + var item = _libraryManager.GetUserRootFolder(); var dtoOptions = new DtoOptions().AddClientFields(User); return _dtoService.GetBaseItemDto(item, dtoOptions, user); @@ -118,10 +131,18 @@ public class UserLibraryController : BaseJellyfinApiController public async Task<ActionResult<QueryResult<BaseItemDto>>> GetIntros([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) { var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } var item = itemId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); + if (item is null) + { + return NotFound(); + } var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false); var dtoOptions = new DtoOptions().AddClientFields(User); @@ -199,10 +220,18 @@ public class UserLibraryController : BaseJellyfinApiController public ActionResult<IEnumerable<BaseItemDto>> GetLocalTrailers([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) { var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } var item = itemId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); + if (item is null) + { + return NotFound(); + } var dtoOptions = new DtoOptions().AddClientFields(User); @@ -229,10 +258,18 @@ public class UserLibraryController : BaseJellyfinApiController public ActionResult<IEnumerable<BaseItemDto>> GetSpecialFeatures([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) { var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } var item = itemId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); + if (item is null) + { + return NotFound(); + } var dtoOptions = new DtoOptions().AddClientFields(User); @@ -274,6 +311,10 @@ public class UserLibraryController : BaseJellyfinApiController [FromQuery] bool groupItems = true) { var user = _userManager.GetUserById(userId); + if (user is null) + { + return NotFound(); + } if (!isPlayed.HasValue) { diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 929999196..3a61367f7 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -155,7 +155,12 @@ public class VideosController : BaseJellyfinApiController if (video.LinkedAlternateVersions.Length == 0) { - video = (Video)_libraryManager.GetItemById(video.PrimaryVersionId); + video = (Video?)_libraryManager.GetItemById(video.PrimaryVersionId); + } + + if (video is null) + { + return NotFound(); } foreach (var link in video.GetLinkedAlternateVersions()) diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index df37d96c6..5910d8073 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -200,7 +200,7 @@ public class MediaInfoHelper options.SubtitleStreamIndex = subtitleStreamIndex; } - var user = _userManager.GetUserById(userId); + var user = _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException(); if (!enableDirectPlay) { diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index 3ce2b834d..0b7a4fa1a 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -81,6 +81,11 @@ public static class RequestHelpers } var user = userManager.GetUserById(userId); + if (user is null) + { + throw new ResourceNotFoundException(); + } + return user.EnableUserPreferenceAccess; } @@ -98,7 +103,7 @@ public static class RequestHelpers if (session is null) { - throw new ArgumentException("Session not found."); + throw new ResourceNotFoundException("Session not found."); } return session; diff --git a/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs b/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs index 0503c5d57..6b6d9682b 100644 --- a/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs +++ b/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs @@ -1,4 +1,6 @@ -namespace Jellyfin.Api.Models.UserDtos; +using System.ComponentModel.DataAnnotations; + +namespace Jellyfin.Api.Models.UserDtos; /// <summary> /// The create user by name request body. @@ -8,7 +10,8 @@ public class CreateUserByName /// <summary> /// Gets or sets the username. /// </summary> - public string? Name { get; set; } + [Required] + required public string Name { get; set; } /// <summary> /// Gets or sets the password. diff --git a/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs b/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs index ebe9297ea..a0631fd07 100644 --- a/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs +++ b/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs @@ -11,5 +11,5 @@ public class ForgotPasswordDto /// Gets or sets the entered username to have its password reset. /// </summary> [Required] - public string? EnteredUsername { get; set; } + required public string EnteredUsername { get; set; } } diff --git a/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs b/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs index 2949efe29..79b8a5d63 100644 --- a/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs +++ b/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs @@ -11,5 +11,5 @@ public class ForgotPasswordPinDto /// Gets or sets the entered pin to have the password reset. /// </summary> [Required] - public string? Pin { get; set; } + required public string Pin { get; set; } } diff --git a/Jellyfin.Data/Enums/PreferenceKind.cs b/Jellyfin.Data/Enums/PreferenceKind.cs index a54d789af..d2b412e45 100644 --- a/Jellyfin.Data/Enums/PreferenceKind.cs +++ b/Jellyfin.Data/Enums/PreferenceKind.cs @@ -63,6 +63,11 @@ namespace Jellyfin.Data.Enums /// <summary> /// A list of ordered views. /// </summary> - OrderedViews = 11 + OrderedViews = 11, + + /// <summary> + /// A list of allowed tags. + /// </summary> + AllowedTags = 12 } } diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index 8b15d6823..a4b4c1959 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -9,6 +9,7 @@ using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using Jellyfin.Data.Queries; using Jellyfin.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Devices; @@ -185,6 +186,10 @@ namespace Jellyfin.Server.Implementations.Devices if (userId.HasValue) { var user = _userManager.GetUserById(userId.Value); + if (user is null) + { + throw new ResourceNotFoundException(); + } sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)); } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index dc9d78857..f9679510d 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -371,6 +371,7 @@ namespace Jellyfin.Server.Implementations.Users EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing), AccessSchedules = user.AccessSchedules.ToArray(), BlockedTags = user.GetPreference(PreferenceKind.BlockedTags), + AllowedTags = user.GetPreference(PreferenceKind.AllowedTags), EnabledChannels = user.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels), EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices), EnabledFolders = user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders), @@ -696,6 +697,7 @@ namespace Jellyfin.Server.Implementations.Users // TODO: fix this at some point user.SetPreference(PreferenceKind.BlockUnratedItems, policy.BlockUnratedItems ?? Array.Empty<UnratedItem>()); user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags); + user.SetPreference(PreferenceKind.AllowedTags, policy.AllowedTags); user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels); user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices); user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders); diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index 89aafc84f..22453f0f7 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CA1002 using System.Collections.Generic; @@ -28,7 +27,7 @@ namespace MediaBrowser.Controller.Dto /// <param name="user">The user.</param> /// <param name="owner">The owner.</param> /// <returns>BaseItemDto.</returns> - BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null); + BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null); /// <summary> /// Gets the base item dtos. @@ -38,7 +37,7 @@ namespace MediaBrowser.Controller.Dto /// <param name="user">The user.</param> /// <param name="owner">The owner.</param> /// <returns>The <see cref="IReadOnlyList{T}"/> of <see cref="BaseItemDto"/>.</returns> - IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null); + IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User? user = null, BaseItem? owner = null); /// <summary> /// Gets the item by name dto. @@ -48,6 +47,6 @@ namespace MediaBrowser.Controller.Dto /// <param name="taggedItems">The list of tagged items.</param> /// <param name="user">The user.</param> /// <returns>The item dto.</returns> - BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, User user = null); + BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem>? taggedItems, User? user = null); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index f2c2007f7..3d683052f 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1607,6 +1607,12 @@ namespace MediaBrowser.Controller.Entities return false; } + var allowedTagsPreference = user.GetPreference(PreferenceKind.AllowedTags); + if (allowedTagsPreference.Any() && !allowedTagsPreference.Any(i => Tags.Contains(i, StringComparison.OrdinalIgnoreCase))) + { + return false; + } + return true; } diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index a1e531904..a51299284 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -26,6 +26,7 @@ namespace MediaBrowser.Controller.Entities EnableTotalRecordCount = true; ExcludeArtistIds = Array.Empty<Guid>(); ExcludeInheritedTags = Array.Empty<string>(); + IncludeInheritedTags = Array.Empty<string>(); ExcludeItemIds = Array.Empty<Guid>(); ExcludeItemTypes = Array.Empty<BaseItemKind>(); ExcludeTags = Array.Empty<string>(); @@ -95,6 +96,8 @@ namespace MediaBrowser.Controller.Entities public string[] ExcludeInheritedTags { get; set; } + public string[] IncludeInheritedTags { get; set; } + public IReadOnlyList<string> Genres { get; set; } public bool? IsSpecialSeason { get; set; } @@ -368,6 +371,7 @@ namespace MediaBrowser.Controller.Entities } ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags); + IncludeInheritedTags = user.GetPreference(PreferenceKind.AllowedTags); User = user; } diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 993e3e18f..37b4afcf3 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -47,14 +45,14 @@ namespace MediaBrowser.Controller.Library /// <param name="id">The id.</param> /// <returns>The user with the specified Id, or <c>null</c> if the user doesn't exist.</returns> /// <exception cref="ArgumentException"><c>id</c> is an empty Guid.</exception> - User GetUserById(Guid id); + User? GetUserById(Guid id); /// <summary> /// Gets the name of the user by. /// </summary> /// <param name="name">The name.</param> /// <returns>User.</returns> - User GetUserByName(string name); + User? GetUserByName(string name); /// <summary> /// Renames the user. @@ -128,7 +126,7 @@ namespace MediaBrowser.Controller.Library /// <param name="user">The user.</param> /// <param name="remoteEndPoint">The remote end point.</param> /// <returns>UserDto.</returns> - UserDto GetUserDto(User user, string remoteEndPoint = null); + UserDto GetUserDto(User user, string? remoteEndPoint = null); /// <summary> /// Authenticates the user. @@ -139,7 +137,7 @@ namespace MediaBrowser.Controller.Library /// <param name="remoteEndPoint">Remove endpoint to use.</param> /// <param name="isUserSession">Specifies if a user session.</param> /// <returns>User wrapped in awaitable task.</returns> - Task<User> AuthenticateUser(string username, string password, string passwordSha1, string remoteEndPoint, bool isUserSession); + Task<User?> AuthenticateUser(string username, string password, string passwordSha1, string remoteEndPoint, bool isUserSession); /// <summary> /// Starts the forgot password process. diff --git a/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs b/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs index 7bc8fa5ab..6d2c3c3d2 100644 --- a/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs +++ b/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -9,7 +7,7 @@ namespace MediaBrowser.Controller.Library { public static class LibraryManagerExtensions { - public static BaseItem GetItemById(this ILibraryManager manager, string id) + public static BaseItem? GetItemById(this ILibraryManager manager, string id) { return manager.GetItemById(new Guid(id)); } diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 3634d0705..1619dac5a 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -35,6 +35,7 @@ namespace MediaBrowser.Model.Users EnableSharedDeviceControl = true; BlockedTags = Array.Empty<string>(); + AllowedTags = Array.Empty<string>(); BlockUnratedItems = Array.Empty<UnratedItem>(); EnableUserPreferenceAccess = true; @@ -86,6 +87,8 @@ namespace MediaBrowser.Model.Users public string[] BlockedTags { get; set; } + public string[] AllowedTags { get; set; } + public bool EnableUserPreferenceAccess { get; set; } public AccessSchedule[] AccessSchedules { get; set; } diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj index 15261bb65..4f80aa941 100644 --- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj +++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj @@ -33,7 +33,7 @@ </ItemGroup> <!-- Code Analyzers--> - <ItemGroup> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> diff --git a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj index f489d6fd0..3f4f55ee4 100644 --- a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj +++ b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj @@ -6,7 +6,7 @@ </PropertyGroup> <!-- Code Analyzers--> - <ItemGroup> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj b/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj index 3801a1cc3..71572bcf6 100644 --- a/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj +++ b/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj @@ -10,7 +10,7 @@ </ItemGroup> <!-- Code Analyzers--> - <ItemGroup> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 000000000..de8fc1bb8 --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,23 @@ +<Project> + <!-- Sets defaults for all test projects --> + + <Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" /> + + <PropertyGroup> + <TargetFramework>net7.0</TargetFramework> + <IsPackable>false</IsPackable> + <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin-tests.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + + <!-- Code Analyzers --> + <ItemGroup> + <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> + <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> + </ItemGroup> + +</Project> diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 6202f83dc..015018910 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -5,12 +5,6 @@ <ProjectGuid>{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}</ProjectGuid> </PropertyGroup> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <PackageReference Include="AutoFixture" /> <PackageReference Include="AutoFixture.AutoMoq" /> @@ -27,17 +21,6 @@ <PackageReference Include="Moq" /> </ItemGroup> - <!-- Code Analyzers --> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="../../Jellyfin.Api/Jellyfin.Api.csproj" /> <ProjectReference Include="../../Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj" /> diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index 699c12217..8fef7fde0 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -5,12 +5,6 @@ <ProjectGuid>{DF194677-DFD3-42AF-9F75-D44D5A416478}</ProjectGuid> </PropertyGroup> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="xunit" /> @@ -22,17 +16,6 @@ <PackageReference Include="FsCheck.Xunit" /> </ItemGroup> - <!-- Code Analyzers --> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="../../MediaBrowser.Common/MediaBrowser.Common.csproj" /> <ProjectReference Include="../../MediaBrowser.Providers/MediaBrowser.Providers.csproj" /> diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index 1e729a46a..54d93b48c 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -5,12 +5,6 @@ <ProjectGuid>{462584F7-5023-4019-9EAC-B98CA458C0A0}</ProjectGuid> </PropertyGroup> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Moq" /> @@ -22,17 +16,6 @@ <PackageReference Include="coverlet.collector" /> </ItemGroup> - <!-- Code Analyzers --> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="../../MediaBrowser.Controller/MediaBrowser.Controller.csproj" /> </ItemGroup> diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj index 2be5da2c2..69677ce42 100644 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj @@ -1,11 +1,5 @@ <Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Moq" /> @@ -17,17 +11,6 @@ <PackageReference Include="coverlet.collector" /> </ItemGroup> - <!-- Code Analyzers --> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="../../Emby.Dlna/Emby.Dlna.csproj" /> </ItemGroup> diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj index dbbb61cc4..036489829 100644 --- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj +++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj @@ -1,11 +1,5 @@ <Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="xunit" /> @@ -20,17 +14,6 @@ <PackageReference Include="FsCheck.Xunit" /> </ItemGroup> - <!-- Code Analyzers --> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="../../MediaBrowser.Model/MediaBrowser.Model.csproj" /> <ProjectReference Include="../../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" /> diff --git a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj index 10c141873..eab003715 100644 --- a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj @@ -1,11 +1,5 @@ <Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="xunit" /> @@ -19,16 +13,6 @@ </PackageReference> </ItemGroup> - <!-- Code Analyzers --> - <ItemGroup> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\src\Jellyfin.MediaEncoding.Hls\Jellyfin.MediaEncoding.Hls.csproj" /> <ProjectReference Include="..\..\src\Jellyfin.MediaEncoding.Keyframes\Jellyfin.MediaEncoding.Keyframes.csproj" /> diff --git a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj index 4910a041a..894bec6aa 100644 --- a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj @@ -1,12 +1,5 @@ <Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - <RootNamespace>Jellyfin.MediaEncoding.Keyframes</RootNamespace> - </PropertyGroup> - <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="xunit" /> @@ -20,17 +13,6 @@ </PackageReference> </ItemGroup> - <!-- Code Analyzers --> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="../../src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" /> </ItemGroup> diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index 077466b6e..6b703e741 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -5,12 +5,6 @@ <ProjectGuid>{28464062-0939-4AA7-9F7B-24DDDA61A7C0}</ProjectGuid> </PropertyGroup> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <None Include="Test Data\**\*.*"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> @@ -31,17 +25,6 @@ </PackageReference> </ItemGroup> - <!-- Code Analyzers --> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="../../MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj" /> </ItemGroup> diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj index cffd7bc0b..8345b610e 100644 --- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -1,11 +1,5 @@ <Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Moq" /> @@ -24,17 +18,6 @@ </None> </ItemGroup> - <!-- Code Analyzers --> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="../../MediaBrowser.Model/MediaBrowser.Model.csproj" /> </ItemGroup> diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index c5e93f0bb..112dd780e 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -5,12 +5,6 @@ <ProjectGuid>{3998657B-1CCC-49DD-A19F-275DC8495F57}</ProjectGuid> </PropertyGroup> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Moq" /> @@ -26,15 +20,4 @@ <ProjectReference Include="..\..\Emby.Naming\Emby.Naming.csproj" /> </ItemGroup> - <!-- Code Analyzers--> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - </Project> diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj index e24569926..4b4bdd2a5 100644 --- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj +++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj @@ -5,12 +5,6 @@ <ProjectGuid>{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}</ProjectGuid> </PropertyGroup> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="xunit" /> @@ -23,17 +17,6 @@ <PackageReference Include="Moq" /> </ItemGroup> - <!-- Code Analyzers--> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="../../Emby.Server.Implementations/Emby.Server.Implementations.csproj" /> <ProjectReference Include="../../MediaBrowser.Common/MediaBrowser.Common.csproj" /> diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj index 27151c847..c12f0cd68 100644 --- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj +++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj @@ -1,11 +1,5 @@ <Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <None Include="Test Data\**\*.*"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> @@ -26,17 +20,6 @@ </PackageReference> </ItemGroup> - <!-- Code Analyzers --> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="../../MediaBrowser.Providers/MediaBrowser.Providers.csproj" /> </ItemGroup> diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 2150520e5..9b6cb40b0 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -5,13 +5,6 @@ <ProjectGuid>{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}</ProjectGuid> </PropertyGroup> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - <RootNamespace>Jellyfin.Server.Implementations.Tests</RootNamespace> - </PropertyGroup> - <ItemGroup> <None Include="Test Data\**\*.*"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> @@ -32,17 +25,6 @@ <PackageReference Include="coverlet.collector" /> </ItemGroup> - <!-- Code Analyzers --> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="..\..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" /> <ProjectReference Include="..\..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" /> diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/Listings/XmlTvListingsProviderTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/Listings/XmlTvListingsProviderTests.cs index 82ce8fc4e..92b4178fd 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/Listings/XmlTvListingsProviderTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/Listings/XmlTvListingsProviderTests.cs @@ -67,4 +67,23 @@ public class XmlTvListingsProviderTests Assert.Equal("https://domain.tld/image.png", program.ImageUrl); Assert.Equal("3297", program.ChannelId); } + + [Theory] + [InlineData("Test Data/LiveTv/Listings/XmlTv/emptycategory.xml")] + [InlineData("https://example.com/emptycategory.xml")] + public async Task GetProgramsAsync_EmptyCategories_Success(string path) + { + var info = new ListingsProviderInfo() + { + Path = path + }; + + var startDate = new DateTime(2022, 11, 4); + var programs = await _xmlTvListingsProvider.GetProgramsAsync(info, "3297", startDate, startDate.AddDays(1), CancellationToken.None); + var programsList = programs.ToList(); + Assert.Single(programsList); + var program = programsList[0]; + Assert.DoesNotContain(program.Genres, g => string.IsNullOrEmpty(g)); + Assert.Equal("3297", program.ChannelId); + } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/Listings/XmlTv/emptycategory.xml b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/Listings/XmlTv/emptycategory.xml new file mode 100644 index 000000000..dd4aa8977 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/Listings/XmlTv/emptycategory.xml @@ -0,0 +1,6 @@ +<tv date="20221104"> + <programme channel="3297" start="20221104130000 -0400" stop="20221105235959 -0400"> + <category lang="en" /> + <category lang="en">sports</category> + </programme> +</tv> diff --git a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs index 9eb0beda4..3737fee0a 100644 --- a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs +++ b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Models.StartupDtos; using Jellyfin.Api.Models.UserDtos; using Jellyfin.Extensions.Json; +using MediaBrowser.Model.Dto; using Xunit; namespace Jellyfin.Server.Integration.Tests @@ -43,6 +44,33 @@ namespace Jellyfin.Server.Integration.Tests return auth!.AccessToken; } + public static async Task<UserDto> GetUserDtoAsync(HttpClient client) + { + using var response = await client.GetAsync("Users/Me").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var userDto = await JsonSerializer.DeserializeAsync<UserDto>( + await response.Content.ReadAsStreamAsync().ConfigureAwait(false), JsonDefaults.Options).ConfigureAwait(false); + Assert.NotNull(userDto); + return userDto; + } + + public static async Task<BaseItemDto> GetRootFolderDtoAsync(HttpClient client, Guid userId = default) + { + if (userId.Equals(default)) + { + var userDto = await GetUserDtoAsync(client).ConfigureAwait(false); + userId = userDto.Id; + } + + var response = await client.GetAsync($"Users/{userId}/Items/Root").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var rootDto = await JsonSerializer.DeserializeAsync<BaseItemDto>( + await response.Content.ReadAsStreamAsync().ConfigureAwait(false), + JsonDefaults.Options).ConfigureAwait(false); + Assert.NotNull(rootDto); + return rootDto; + } + public static void AddAuthHeader(this HttpHeaders headers, string accessToken) { headers.Add(AuthHeaderName, DummyAuthHeader + $", Token={accessToken}"); diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs new file mode 100644 index 000000000..62b32b92e --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs @@ -0,0 +1,64 @@ +using System; +using System.Globalization; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Extensions.Json; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; +using Xunit; + +namespace Jellyfin.Server.Integration.Tests.Controllers; + +public sealed class ItemsControllerTests : IClassFixture<JellyfinApplicationFactory> +{ + private readonly JellyfinApplicationFactory _factory; + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + private static string? _accessToken; + + public ItemsControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetItems_NoApiKeyOrUserId_BadRequest() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var response = await client.GetAsync("Items").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Theory] + [InlineData("Users/{0}/Items")] + [InlineData("Users/{0}/Items/Resume")] + public async Task GetUserItems_NonExistentUserId_NotFound(string format) + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid())).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData("Items?userId={0}")] + [InlineData("Users/{0}/Items")] + [InlineData("Users/{0}/Items/Resume")] + public async Task GetItems_UserId_Ok(string format) + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var userDto = await AuthHelper.GetUserDtoAsync(client).ConfigureAwait(false); + + var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, userDto.Id)).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var items = await JsonSerializer.DeserializeAsync<QueryResult<BaseItemDto>>( + await response.Content.ReadAsStreamAsync().ConfigureAwait(false), + _jsonOptions).ConfigureAwait(false); + Assert.NotNull(items); + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs new file mode 100644 index 000000000..013d19a9f --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs @@ -0,0 +1,40 @@ +using System; +using System.Globalization; +using System.Net; +using System.Threading.Tasks; +using Xunit; + +namespace Jellyfin.Server.Integration.Tests.Controllers; + +public sealed class LibraryControllerTests : IClassFixture<JellyfinApplicationFactory> +{ + private readonly JellyfinApplicationFactory _factory; + private static string? _accessToken; + + public LibraryControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Theory] + [InlineData("Items/{0}/File")] + [InlineData("Items/{0}/ThemeSongs")] + [InlineData("Items/{0}/ThemeVideos")] + [InlineData("Items/{0}/ThemeMedia")] + [InlineData("Items/{0}/Ancestors")] + [InlineData("Items/{0}/Download")] + [InlineData("Artists/{0}/Similar")] + [InlineData("Items/{0}/Similar")] + [InlineData("Albums/{0}/Similar")] + [InlineData("Shows/{0}/Similar")] + [InlineData("Movies/{0}/Similar")] + [InlineData("Trailers/{0}/Similar")] + public async Task Get_NonExistentItemId_NotFound(string format) + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid())).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs new file mode 100644 index 000000000..17f3dc99f --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs @@ -0,0 +1,26 @@ +using System.Net; +using System.Threading.Tasks; +using Xunit; + +namespace Jellyfin.Server.Integration.Tests.Controllers; + +public sealed class MusicGenreControllerTests : IClassFixture<JellyfinApplicationFactory> +{ + private readonly JellyfinApplicationFactory _factory; + private static string? _accessToken; + + public MusicGenreControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task MusicGenres_FakeMusicGenre_NotFound() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var response = await client.GetAsync("MusicGenres/Fake-MusicGenre").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs index f8f5fecec..868ecd53f 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs @@ -1,18 +1,13 @@ using System; using System.Net; -using System.Net.Http; using System.Threading.Tasks; using Xunit; -using Xunit.Priority; namespace Jellyfin.Server.Integration.Tests.Controllers; -[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] public class PlaystateControllerTests : IClassFixture<JellyfinApplicationFactory> { private readonly JellyfinApplicationFactory _factory; - private static readonly Guid _testUserId = Guid.NewGuid(); - private static readonly Guid _testItemId = Guid.NewGuid(); private static string? _accessToken; public PlaystateControllerTests(JellyfinApplicationFactory factory) @@ -20,31 +15,47 @@ public class PlaystateControllerTests : IClassFixture<JellyfinApplicationFactory _factory = factory; } - private Task<HttpResponseMessage> DeleteUserPlayedItems(HttpClient httpClient, Guid userId, Guid itemId) - => httpClient.DeleteAsync($"Users/{userId}/PlayedItems/{itemId}"); + [Fact] + public async Task DeleteMarkUnplayedItem_NonExistentUserId_NotFound() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + using var response = await client.DeleteAsync($"Users/{Guid.NewGuid()}/PlayedItems/{Guid.NewGuid()}").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task PostMarkPlayedItem_NonExistentUserId_NotFound() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); - private Task<HttpResponseMessage> PostUserPlayedItems(HttpClient httpClient, Guid userId, Guid itemId) - => httpClient.PostAsync($"Users/{userId}/PlayedItems/{itemId}", null); + using var response = await client.PostAsync($"Users/{Guid.NewGuid()}/PlayedItems/{Guid.NewGuid()}", null).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } [Fact] - [Priority(0)] - public async Task DeleteMarkUnplayedItem_DoesNotExist_NotFound() + public async Task DeleteMarkUnplayedItem_NonExistentItemId_NotFound() { var client = _factory.CreateClient(); client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); - using var response = await DeleteUserPlayedItems(client, _testUserId, _testItemId).ConfigureAwait(false); + var userDto = await AuthHelper.GetUserDtoAsync(client).ConfigureAwait(false); + + using var response = await client.DeleteAsync($"Users/{userDto.Id}/PlayedItems/{Guid.NewGuid()}").ConfigureAwait(false); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } [Fact] - [Priority(0)] - public async Task PostMarkPlayedItem_DoesNotExist_NotFound() + public async Task PostMarkPlayedItem_NonExistentItemId_NotFound() { var client = _factory.CreateClient(); client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); - using var response = await PostUserPlayedItems(client, _testUserId, _testItemId).ConfigureAwait(false); + var userDto = await AuthHelper.GetUserDtoAsync(client).ConfigureAwait(false); + + using var response = await client.PostAsync($"Users/{userDto.Id}/PlayedItems/{Guid.NewGuid()}", null).ConfigureAwait(false); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } } diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/SessionControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/SessionControllerTests.cs new file mode 100644 index 000000000..cb0a829e8 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/SessionControllerTests.cs @@ -0,0 +1,27 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Xunit; + +namespace Jellyfin.Server.Integration.Tests.Controllers; + +public class SessionControllerTests : IClassFixture<JellyfinApplicationFactory> +{ + private readonly JellyfinApplicationFactory _factory; + private static string? _accessToken; + + public SessionControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetSessions_NonExistentUserId_NotFound() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + using var response = await client.GetAsync($"Session/Sessions?userId={Guid.NewGuid()}").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs index 2b825a93a..2a3c53dbe 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs @@ -67,6 +67,16 @@ namespace Jellyfin.Server.Integration.Tests.Controllers } [Fact] + [Priority(-1)] + public async Task Me_Valid_Success() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + _ = await AuthHelper.GetUserDtoAsync(client).ConfigureAwait(false); + } + + [Fact] [Priority(0)] public async Task New_Valid_Success() { @@ -108,7 +118,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers var createRequest = new CreateUserByName() { - Name = username + Name = username! }; using var response = await CreateUserByName(client, createRequest).ConfigureAwait(false); @@ -116,6 +126,19 @@ namespace Jellyfin.Server.Integration.Tests.Controllers } [Fact] + [Priority(0)] + public async Task Delete_DoesntExist_NotFound() + { + var client = _factory.CreateClient(); + + // access token can't be null here as the previous test populated it + client.DefaultRequestHeaders.AddAuthHeader(_accessToken!); + + using var response = await client.DeleteAsync($"User/{Guid.NewGuid()}").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] [Priority(1)] public async Task UpdateUserPassword_Valid_Success() { diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs new file mode 100644 index 000000000..69f2ccf33 --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs @@ -0,0 +1,129 @@ +using System; +using System.Globalization; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Extensions.Json; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; +using Xunit; + +namespace Jellyfin.Server.Integration.Tests.Controllers; + +public sealed class UserLibraryControllerTests : IClassFixture<JellyfinApplicationFactory> +{ + private readonly JellyfinApplicationFactory _factory; + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + private static string? _accessToken; + + public UserLibraryControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetRootFolder_NonExistenUserId_NotFound() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var response = await client.GetAsync($"Users/{Guid.NewGuid()}/Items/Root").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetRootFolder_UserId_Valid() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + _ = await AuthHelper.GetRootFolderDtoAsync(client).ConfigureAwait(false); + } + + [Theory] + [InlineData("Users/{0}/Items/{1}")] + [InlineData("Users/{0}/Items/{1}/Intros")] + [InlineData("Users/{0}/Items/{1}/LocalTrailers")] + [InlineData("Users/{0}/Items/{1}/SpecialFeatures")] + [InlineData("Users/{0}/Items/{1}/Lyrics")] + public async Task GetItem_NonExistenUserId_NotFound(string format) + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var rootFolderDto = await AuthHelper.GetRootFolderDtoAsync(client).ConfigureAwait(false); + + var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid(), rootFolderDto.Id)).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData("Users/{0}/Items/{1}")] + [InlineData("Users/{0}/Items/{1}/Intros")] + [InlineData("Users/{0}/Items/{1}/LocalTrailers")] + [InlineData("Users/{0}/Items/{1}/SpecialFeatures")] + [InlineData("Users/{0}/Items/{1}/Lyrics")] + public async Task GetItem_NonExistentItemId_NotFound(string format) + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var userDto = await AuthHelper.GetUserDtoAsync(client).ConfigureAwait(false); + + var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, userDto.Id, Guid.NewGuid())).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetItem_UserIdAndItemId_Valid() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var userDto = await AuthHelper.GetUserDtoAsync(client).ConfigureAwait(false); + var rootFolderDto = await AuthHelper.GetRootFolderDtoAsync(client, userDto.Id).ConfigureAwait(false); + + var response = await client.GetAsync($"Users/{userDto.Id}/Items/{rootFolderDto.Id}").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var rootDto = await JsonSerializer.DeserializeAsync<BaseItemDto>( + await response.Content.ReadAsStreamAsync().ConfigureAwait(false), + _jsonOptions).ConfigureAwait(false); + Assert.NotNull(rootDto); + } + + [Fact] + public async Task GetIntros_UserIdAndItemId_Valid() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var userDto = await AuthHelper.GetUserDtoAsync(client).ConfigureAwait(false); + var rootFolderDto = await AuthHelper.GetRootFolderDtoAsync(client, userDto.Id).ConfigureAwait(false); + + var response = await client.GetAsync($"Users/{userDto.Id}/Items/{rootFolderDto.Id}/Intros").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var rootDto = await JsonSerializer.DeserializeAsync<QueryResult<BaseItemDto>>( + await response.Content.ReadAsStreamAsync().ConfigureAwait(false), + _jsonOptions).ConfigureAwait(false); + Assert.NotNull(rootDto); + } + + [Theory] + [InlineData("Users/{0}/Items/{1}/LocalTrailers")] + [InlineData("Users/{0}/Items/{1}/SpecialFeatures")] + public async Task LocalTrailersAndSpecialFeatures_UserIdAndItemId_Valid(string format) + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var userDto = await AuthHelper.GetUserDtoAsync(client).ConfigureAwait(false); + var rootFolderDto = await AuthHelper.GetRootFolderDtoAsync(client, userDto.Id).ConfigureAwait(false); + + var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, userDto.Id, rootFolderDto.Id)).ConfigureAwait(false); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var rootDto = await JsonSerializer.DeserializeAsync<BaseItemDto[]>( + await response.Content.ReadAsStreamAsync().ConfigureAwait(false), + _jsonOptions).ConfigureAwait(false); + Assert.NotNull(rootDto); + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs new file mode 100644 index 000000000..0f9a2e90a --- /dev/null +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs @@ -0,0 +1,27 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Xunit; + +namespace Jellyfin.Server.Integration.Tests.Controllers; + +public sealed class VideosControllerTests : IClassFixture<JellyfinApplicationFactory> +{ + private readonly JellyfinApplicationFactory _factory; + private static string? _accessToken; + + public VideosControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task DeleteAlternateSources_NonExistentItemId_NotFound() + { + var client = _factory.CreateClient(); + client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false)); + + var response = await client.DeleteAsync($"Videos/{Guid.NewGuid()}").ConfigureAwait(false); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } +} 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 26b2cd239..a5296d8c9 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj +++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj @@ -1,9 +1,4 @@ <Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> <ItemGroup> <PackageReference Include="AutoFixture" /> @@ -29,17 +24,6 @@ </None> </ItemGroup> - <!-- Code Analyzers --> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="../../Jellyfin.Server/Jellyfin.Server.csproj" /> </ItemGroup> diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj index d47f70cff..5fea805ae 100644 --- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj +++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj @@ -1,11 +1,5 @@ <Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <PackageReference Include="AutoFixture" /> <PackageReference Include="AutoFixture.AutoMoq" /> @@ -22,17 +16,6 @@ <PackageReference Include="Moq" /> </ItemGroup> - <!-- Code Analyzers --> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="../../Jellyfin.Server/Jellyfin.Server.csproj" /> </ItemGroup> diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj index fb7864cd1..9fe0744de 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj +++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj @@ -1,11 +1,5 @@ <Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <TargetFramework>net7.0</TargetFramework> - <IsPackable>false</IsPackable> - <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <None Include="Test Data\**\*.*"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> @@ -23,17 +17,6 @@ <PackageReference Include="coverlet.collector" /> </ItemGroup> - <!-- Code Analyzers --> - <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> - </PackageReference> - <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" /> - <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" /> - <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="../../MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj" /> <ProjectReference Include="../../MediaBrowser.Providers/MediaBrowser.Providers.csproj" /> |
