From eb7fee95906f1c9d8d104777e0214de9115ca82f Mon Sep 17 00:00:00 2001
From: Bond_009 <bond.009@outlook.com>
Date: Sat, 4 Feb 2023 21:08:21 +0100
Subject: [PATCH] Add more tests

---
 Jellyfin.Api/Controllers/ItemsController.cs   |  3 +-
 Jellyfin.Api/Controllers/LibraryController.cs | 10 +++
 .../Controllers/ItemsControllerTests.cs       | 64 +++++++++++++++++++
 .../Controllers/LibraryControllerTests.cs     | 40 ++++++++++++
 .../Controllers/PlaystateControllerTests.cs   | 41 +++++++-----
 .../Controllers/SessionControllerTests.cs     | 27 ++++++++
 .../Controllers/UserControllerTests.cs        | 13 ++++
 7 files changed, 182 insertions(+), 16 deletions(-)
 create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
 create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs
 create mode 100644 tests/Jellyfin.Server.Integration.Tests/Controllers/SessionControllerTests.cs

diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 1bfc111af7..c937176cdf 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -6,6 +6,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;
@@ -241,7 +242,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
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index a311554b4d..c4309412c3 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,
@@ -676,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/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
new file mode 100644
index 0000000000..62b32b92e7
--- /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 0000000000..013d19a9fd
--- /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/PlaystateControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs
index f8f5fecec4..868ecd53f5 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 0000000000..cb0a829e8e
--- /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 e5cde66762..2a3c53dbe4 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
@@ -125,6 +125,19 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
             Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
         }
 
+        [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()