diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs index b2baa9cea0..0c19f28183 100644 --- a/Jellyfin.Api/Controllers/DashboardController.cs +++ b/Jellyfin.Api/Controllers/DashboardController.cs @@ -22,22 +22,18 @@ namespace Jellyfin.Api.Controllers public class DashboardController : BaseJellyfinApiController { private readonly ILogger _logger; - private readonly IServerApplicationHost _appHost; private readonly IPluginManager _pluginManager; /// /// Initializes a new instance of the class. /// /// Instance of interface. - /// Instance of interface. /// Instance of interface. public DashboardController( ILogger logger, - IServerApplicationHost appHost, IPluginManager pluginManager) { _logger = logger; - _appHost = appHost; _pluginManager = pluginManager; } @@ -51,7 +47,7 @@ namespace Jellyfin.Api.Controllers [HttpGet("web/ConfigurationPages")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult> GetConfigurationPages( + public ActionResult> GetConfigurationPages( [FromQuery] bool? enableInMainMenu) { var configPages = _pluginManager.Plugins.SelectMany(GetConfigPages).ToList(); @@ -77,38 +73,22 @@ namespace Jellyfin.Api.Controllers [ProducesFile(MediaTypeNames.Text.Html, "application/x-javascript")] public ActionResult GetDashboardConfigurationPage([FromQuery] string? name) { - IPlugin? plugin = null; - Stream? stream = null; - - var isJs = false; - var isTemplate = false; - var altPage = GetPluginPages().FirstOrDefault(p => string.Equals(p.Item1.Name, name, StringComparison.OrdinalIgnoreCase)); - if (altPage != null) + if (altPage == null) { - plugin = altPage.Item2; - stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath); - - isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase); - isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html", StringComparison.Ordinal); + return NotFound(); } - if (plugin != null && stream != null) + IPlugin plugin = altPage.Item2; + string resourcePath = altPage.Item1.EmbeddedResourcePath; + Stream? stream = plugin.GetType().Assembly.GetManifestResourceStream(resourcePath); + if (stream == null) { - if (isJs) - { - return File(stream, MimeTypes.GetMimeType("page.js")); - } - - if (isTemplate) - { - return File(stream, MimeTypes.GetMimeType("page.html")); - } - - return File(stream, MimeTypes.GetMimeType("page.html")); + _logger.LogError("Failed to get resource {Resource} from plugin {Plugin}", resourcePath, plugin.Name); + return NotFound(); } - return NotFound(); + return File(stream, MimeTypes.GetMimeType(resourcePath)); } private IEnumerable GetConfigPages(LocalPlugin plugin) @@ -120,7 +100,7 @@ namespace Jellyfin.Api.Controllers { if (plugin?.Instance is not IHasWebPages hasWebPages) { - return new List>(); + return Enumerable.Empty>(); } return hasWebPages.GetPages().Select(i => new Tuple(i, plugin.Instance)); diff --git a/Jellyfin.Api/Models/ConfigurationPageInfo.cs b/Jellyfin.Api/Models/ConfigurationPageInfo.cs index a7bbe42fe5..d214280292 100644 --- a/Jellyfin.Api/Models/ConfigurationPageInfo.cs +++ b/Jellyfin.Api/Models/ConfigurationPageInfo.cs @@ -1,6 +1,5 @@ using System; using MediaBrowser.Common.Plugins; -using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Plugins; namespace Jellyfin.Api.Models diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index b76aa5e141..ae2fb39999 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -11,7 +11,6 @@ using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; using Jellyfin.Server.Implementations.Events; using Jellyfin.Server.Implementations.Users; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.BaseItemManager; using MediaBrowser.Controller.Drawing; diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index e228ae7ec5..7b162c0e1c 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -1,10 +1,7 @@ using System; using System.IO; using System.Reflection; -using System.Runtime.InteropServices; -using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Plugins; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Common.Plugins { diff --git a/tests/Jellyfin.Api.Tests/BrandingServiceTests.cs b/tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs similarity index 72% rename from tests/Jellyfin.Api.Tests/BrandingServiceTests.cs rename to tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs index 1cbe94c5b9..3207a0f257 100644 --- a/tests/Jellyfin.Api.Tests/BrandingServiceTests.cs +++ b/tests/Jellyfin.Api.Tests/Controllers/BrandingControllerTests.cs @@ -5,11 +5,11 @@ using Xunit; namespace Jellyfin.Api.Tests { - public sealed class BrandingServiceTests : IClassFixture + public sealed class BrandingControllerTests : IClassFixture { private readonly JellyfinApplicationFactory _factory; - public BrandingServiceTests(JellyfinApplicationFactory factory) + public BrandingControllerTests(JellyfinApplicationFactory factory) { _factory = factory; } @@ -24,8 +24,9 @@ namespace Jellyfin.Api.Tests var response = await client.GetAsync("/Branding/Configuration"); // Assert - response.EnsureSuccessStatusCode(); - Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString()); + Assert.True(response.IsSuccessStatusCode); + Assert.Equal("application/json", response.Content.Headers.ContentType?.MediaType); + Assert.Equal("utf-8", response.Content.Headers.ContentType?.CharSet); var responseBody = await response.Content.ReadAsStreamAsync(); _ = await JsonSerializer.DeserializeAsync(responseBody); } @@ -42,7 +43,7 @@ namespace Jellyfin.Api.Tests var response = await client.GetAsync(url); // Assert - response.EnsureSuccessStatusCode(); + Assert.True(response.IsSuccessStatusCode); Assert.Equal("text/css; charset=utf-8", response.Content.Headers.ContentType?.ToString()); } } diff --git a/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs new file mode 100644 index 0000000000..fadeddba55 --- /dev/null +++ b/tests/Jellyfin.Api.Tests/Controllers/DashboardControllerTests.cs @@ -0,0 +1,76 @@ +using System.IO; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Api.Models; +using Xunit; + +namespace Jellyfin.Api.Tests.Controllers +{ + public sealed class DashboardControllerTests : IClassFixture + { + private readonly JellyfinApplicationFactory _factory; + + public DashboardControllerTests(JellyfinApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetDashboardConfigurationPage_NonExistingPage_NotFound() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("web/ConfigurationPage/ThisPageTotally/Doesnt/Exists.html").ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetDashboardConfigurationPage_ExistingPage_CorrectPage() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("/web/ConfigurationPage?name=TestPlugin").ConfigureAwait(false); + + Assert.True(response.IsSuccessStatusCode); + Assert.Equal("text/html", response.Content.Headers.ContentType?.MediaType); + StreamReader reader = new StreamReader(typeof(TestPlugin).Assembly.GetManifestResourceStream("Jellyfin.Api.Tests.TestPage.html")!); + Assert.Equal(await response.Content.ReadAsStringAsync(), reader.ReadToEnd()); + } + + [Fact] + public async Task GetDashboardConfigurationPage_BrokenPage_NotFound() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("/web/ConfigurationPage?name=BrokenPage").ConfigureAwait(false); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetConfigurationPages_NoParams_AllConfigurationPages() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("/web/ConfigurationPages").ConfigureAwait(false); + + Assert.True(response.IsSuccessStatusCode); + var res = await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync()); + // TODO: check content + } + + [Fact] + public async Task GetConfigurationPages_True_MainMenuConfigurationPages() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true").ConfigureAwait(false); + + Assert.True(response.IsSuccessStatusCode); + var res = await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync()); + Assert.Empty(res); + } + } +} diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index eca3df79b3..52dd8ae9c1 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -41,4 +41,8 @@ ../jellyfin-tests.ruleset + + + + diff --git a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs index 54f8eb225f..dbbd5ac28c 100644 --- a/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Api.Tests/JellyfinApplicationFactory.cs @@ -73,7 +73,7 @@ namespace Jellyfin.Api.Tests _disposableComponents.Add(loggerFactory); // Create the app host and initialize it - var appHost = new CoreAppHost( + var appHost = new TestAppHost( appPaths, loggerFactory, commandLineOpts, @@ -93,7 +93,7 @@ namespace Jellyfin.Api.Tests var testServer = base.CreateServer(builder); // Finish initializing the app host - var appHost = (CoreAppHost)testServer.Services.GetRequiredService(); + var appHost = (TestAppHost)testServer.Services.GetRequiredService(); appHost.ServiceProvider = testServer.Services; appHost.InitializeServices().GetAwaiter().GetResult(); appHost.RunStartupTasksAsync().GetAwaiter().GetResult(); diff --git a/tests/Jellyfin.Api.Tests/TestAppHost.cs b/tests/Jellyfin.Api.Tests/TestAppHost.cs new file mode 100644 index 0000000000..772e98d049 --- /dev/null +++ b/tests/Jellyfin.Api.Tests/TestAppHost.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Reflection; +using Emby.Server.Implementations; +using Jellyfin.Server; +using MediaBrowser.Controller; +using MediaBrowser.Model.IO; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Tests +{ + /// + /// Implementation of the abstract class. + /// + public class TestAppHost : CoreAppHost + { + /// + /// Initializes a new instance of the class. + /// + /// The to be used by the . + /// The to be used by the . + /// The to be used by the . + /// The to be used by the . + /// The to be used by the . + public TestAppHost( + IServerApplicationPaths applicationPaths, + ILoggerFactory loggerFactory, + IStartupOptions options, + IFileSystem fileSystem, + IServiceCollection collection) + : base( + applicationPaths, + loggerFactory, + options, + fileSystem, + collection) + { + } + + /// + protected override IEnumerable GetAssembliesWithPartsInternal() + { + foreach (var a in base.GetAssembliesWithPartsInternal()) + { + yield return a; + } + + yield return typeof(TestPlugin).Assembly; + } + } +} diff --git a/tests/Jellyfin.Api.Tests/TestPage.html b/tests/Jellyfin.Api.Tests/TestPage.html new file mode 100644 index 0000000000..8037af8a60 --- /dev/null +++ b/tests/Jellyfin.Api.Tests/TestPage.html @@ -0,0 +1,9 @@ + + + + TestPlugin + + +

This is a Test Page.

+ + diff --git a/tests/Jellyfin.Api.Tests/TestPlugin.cs b/tests/Jellyfin.Api.Tests/TestPlugin.cs new file mode 100644 index 0000000000..a3b4b6994f --- /dev/null +++ b/tests/Jellyfin.Api.Tests/TestPlugin.cs @@ -0,0 +1,43 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; + +namespace Jellyfin.Api.Tests +{ + public class TestPlugin : BasePlugin, IHasWebPages + { + public TestPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + + public static TestPlugin? Instance { get; private set; } + + public override Guid Id => new Guid("2d350a13-0bf7-4b61-859c-d5e601b5facf"); + + public override string Name => nameof(TestPlugin); + + public override string Description => "Server test Plugin."; + + public IEnumerable GetPages() + { + yield return new PluginPageInfo + { + Name = Name, + EmbeddedResourcePath = GetType().Namespace + ".TestPage.html" + }; + + yield return new PluginPageInfo + { + Name = "BrokenPage", + EmbeddedResourcePath = GetType().Namespace + ".foobar" + }; + } + } +}