aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/codeql-analysis.yml6
-rw-r--r--CONTRIBUTORS.md1
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs21
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs23
-rw-r--r--Emby.Server.Implementations/Plugins/PluginManager.cs53
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs14
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs8
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs14
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs2
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs5
-rw-r--r--Jellyfin.Api/Controllers/PlaystateController.cs20
-rw-r--r--Jellyfin.Api/Controllers/SessionController.cs4
-rw-r--r--Jellyfin.Api/Controllers/UserController.cs25
-rw-r--r--Jellyfin.Api/Controllers/UserLibraryController.cs41
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs7
-rw-r--r--Jellyfin.Api/Helpers/MediaInfoHelper.cs2
-rw-r--r--Jellyfin.Api/Helpers/RequestHelpers.cs7
-rw-r--r--Jellyfin.Api/Models/UserDtos/CreateUserByName.cs7
-rw-r--r--Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs2
-rw-r--r--Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs2
-rw-r--r--Jellyfin.Data/Enums/PreferenceKind.cs7
-rw-r--r--Jellyfin.Server.Implementations/Devices/DeviceManager.cs5
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs2
-rw-r--r--MediaBrowser.Controller/Dto/IDtoService.cs7
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs6
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs4
-rw-r--r--MediaBrowser.Controller/Library/IUserManager.cs10
-rw-r--r--MediaBrowser.Controller/Library/LibraryManagerExtensions.cs4
-rw-r--r--MediaBrowser.Model/Users/UserPolicy.cs3
-rw-r--r--src/Jellyfin.Extensions/Jellyfin.Extensions.csproj2
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj2
-rw-r--r--src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj2
-rw-r--r--tests/Directory.Build.props23
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj17
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj17
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj17
-rw-r--r--tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj17
-rw-r--r--tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj17
-rw-r--r--tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj16
-rw-r--r--tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj18
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj17
-rw-r--r--tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj17
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj17
-rw-r--r--tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj17
-rw-r--r--tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj17
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj18
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/Listings/XmlTvListingsProviderTests.cs19
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/Listings/XmlTv/emptycategory.xml6
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs28
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs64
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs40
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs26
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs41
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/SessionControllerTests.cs27
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs25
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs129
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs27
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj16
-rw-r--r--tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj17
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj17
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" />