浏览代码

Return NotFound when itemId isn't found

Bond_009 2 年之前
父节点
当前提交
52230d1c30
共有 25 个文件被更改,包括 370 次插入30 次删除
  1. 5 0
      Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs
  2. 12 2
      Jellyfin.Api/Controllers/ImageController.cs
  3. 5 0
      Jellyfin.Api/Controllers/ItemsController.cs
  4. 4 0
      Jellyfin.Api/Controllers/LibraryController.cs
  5. 1 1
      Jellyfin.Api/Controllers/LiveTvController.cs
  6. 5 0
      Jellyfin.Api/Controllers/MusicGenresController.cs
  7. 20 0
      Jellyfin.Api/Controllers/PlaystateController.cs
  8. 4 0
      Jellyfin.Api/Controllers/SessionController.cs
  9. 19 6
      Jellyfin.Api/Controllers/UserController.cs
  10. 41 0
      Jellyfin.Api/Controllers/UserLibraryController.cs
  11. 6 1
      Jellyfin.Api/Controllers/VideosController.cs
  12. 1 1
      Jellyfin.Api/Helpers/MediaInfoHelper.cs
  13. 6 1
      Jellyfin.Api/Helpers/RequestHelpers.cs
  14. 5 2
      Jellyfin.Api/Models/UserDtos/CreateUserByName.cs
  15. 1 1
      Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs
  16. 1 1
      Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs
  17. 5 0
      Jellyfin.Server.Implementations/Devices/DeviceManager.cs
  18. 3 4
      MediaBrowser.Controller/Dto/IDtoService.cs
  19. 4 6
      MediaBrowser.Controller/Library/IUserManager.cs
  20. 1 3
      MediaBrowser.Controller/Library/LibraryManagerExtensions.cs
  21. 28 0
      tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
  22. 26 0
      tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs
  23. 11 1
      tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
  24. 129 0
      tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs
  25. 27 0
      tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs

+ 5 - 0
Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs

@@ -2,6 +2,7 @@
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Data.Enums;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.SyncPlay;
@@ -47,6 +48,10 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
 
             var userId = context.User.GetUserId();
             var user = _userManager.GetUserById(userId);
+            if (user is null)
+            {
+                throw new ResourceNotFoundException();
+            }
 
             if (requirement.RequiredAccess == SyncPlayAccessRequirementType.HasAccess)
             {

+ 12 - 2
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))
         {

+ 5 - 0
Jellyfin.Api/Controllers/ItemsController.cs

@@ -815,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)

+ 4 - 0
Jellyfin.Api/Controllers/LibraryController.cs

@@ -452,6 +452,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));

+ 1 - 1
Jellyfin.Api/Controllers/LiveTvController.cs

@@ -1211,7 +1211,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(),

+ 5 - 0
Jellyfin.Api/Controllers/MusicGenresController.cs

@@ -158,6 +158,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);

+ 20 - 0
Jellyfin.Api/Controllers/PlaystateController.cs

@@ -77,6 +77,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);
@@ -89,6 +94,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);
         }
 
@@ -109,6 +119,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);
 
@@ -121,6 +136,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);
         }
 

+ 4 - 0
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))
             {

+ 19 - 6
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))

+ 41 - 0
Jellyfin.Api/Controllers/UserLibraryController.cs

@@ -79,10 +79,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);
 
@@ -102,6 +110,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);
@@ -119,10 +132,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);
@@ -200,10 +221,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);
 
@@ -230,10 +259,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);
 
@@ -275,6 +312,10 @@ public class UserLibraryController : BaseJellyfinApiController
         [FromQuery] bool groupItems = true)
     {
         var user = _userManager.GetUserById(userId);
+        if (user is null)
+        {
+            return NotFound();
+        }
 
         if (!isPlayed.HasValue)
         {

+ 6 - 1
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())

+ 1 - 1
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)
         {

+ 6 - 1
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;

+ 5 - 2
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.

+ 1 - 1
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; }
 }

+ 1 - 1
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; }
 }

+ 5 - 0
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));
                 }

+ 3 - 4
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);
     }
 }

+ 4 - 6
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.

+ 1 - 3
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));
         }

+ 28 - 0
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}");

+ 26 - 0
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);
+    }
+}

+ 11 - 1
tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs

@@ -66,6 +66,16 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
             Assert.False(users![0].HasConfiguredPassword);
         }
 
+        [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);

+ 129 - 0
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);
+    }
+}

+ 27 - 0
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);
+    }
+}