aboutsummaryrefslogtreecommitdiff
path: root/tests/Jellyfin.Server.Integration.Tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests/Jellyfin.Server.Integration.Tests')
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs54
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs86
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj40
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs119
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs42
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs55
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/TestPage.html9
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs43
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs27
9 files changed, 475 insertions, 0 deletions
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs
new file mode 100644
index 000000000..87136dfc8
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs
@@ -0,0 +1,54 @@
+using System.Net;
+using System.Net.Mime;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Branding;
+using Xunit;
+
+namespace Jellyfin.Server.Integration.Tests
+{
+ public sealed class BrandingControllerTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private readonly JellyfinApplicationFactory _factory;
+
+ public BrandingControllerTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ public async Task GetConfiguration_ReturnsCorrectResponse()
+ {
+ // Arrange
+ var client = _factory.CreateClient();
+
+ // Act
+ var response = await client.GetAsync("/Branding/Configuration");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
+ Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
+ var responseBody = await response.Content.ReadAsStreamAsync();
+ _ = await JsonSerializer.DeserializeAsync<BrandingOptions>(responseBody);
+ }
+
+ [Theory]
+ [InlineData("/Branding/Css")]
+ [InlineData("/Branding/Css.css")]
+ public async Task GetCss_ReturnsCorrectResponse(string url)
+ {
+ // Arrange
+ var client = _factory.CreateClient();
+
+ // Act
+ var response = await client.GetAsync(url);
+
+ // Assert
+ Assert.True(response.IsSuccessStatusCode);
+ Assert.Equal("text/css", response.Content.Headers.ContentType?.MediaType);
+ Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
new file mode 100644
index 000000000..86d6326d8
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
@@ -0,0 +1,86 @@
+using System.IO;
+using System.Net;
+using System.Net.Mime;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Jellyfin.Api.Models;
+using MediaBrowser.Common.Json;
+using Xunit;
+
+namespace Jellyfin.Server.Integration.Tests.Controllers
+{
+ public sealed class DashboardControllerTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private readonly JellyfinApplicationFactory _factory;
+ private readonly JsonSerializerOptions _jsonOpions = JsonDefaults.GetOptions();
+
+ public DashboardControllerTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ public async Task GetDashboardConfigurationPage_NonExistingPage_NotFound()
+ {
+ var client = _factory.CreateClient();
+
+ var response = await client.GetAsync("web/ConfigurationPage?name=ThisPageDoesntExists").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.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Text.Html, response.Content.Headers.ContentType?.MediaType);
+ StreamReader reader = new StreamReader(typeof(TestPlugin).Assembly.GetManifestResourceStream("Jellyfin.Server.Integration.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.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ var res = await response.Content.ReadAsStreamAsync();
+ _ = await JsonSerializer.DeserializeAsync<ConfigurationPageInfo[]>(res, _jsonOpions);
+ // 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.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
+ Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
+
+ var res = await response.Content.ReadAsStreamAsync();
+ var data = await JsonSerializer.DeserializeAsync<ConfigurationPageInfo[]>(res, _jsonOpions);
+ Assert.Empty(data);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
new file mode 100644
index 000000000..49004966b
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
@@ -0,0 +1,40 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>net5.0</TargetFramework>
+ <IsPackable>false</IsPackable>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
+ <AnalysisMode>AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="AutoFixture" Version="4.15.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.3" />
+ <PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+ <PackageReference Include="xunit" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="coverlet.collector" Version="3.0.3" />
+ <PackageReference Include="Moq" Version="4.16.0" />
+ </ItemGroup>
+
+ <!-- Code Analyzers -->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="../../Jellyfin.Server/Jellyfin.Server.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <EmbeddedResource Include="TestPage.html" />
+ </ItemGroup>
+
+</Project>
diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs
new file mode 100644
index 000000000..d9ec81a27
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Threading;
+using Emby.Server.Implementations;
+using Emby.Server.Implementations.IO;
+using MediaBrowser.Common;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Serilog;
+using Serilog.Extensions.Logging;
+
+namespace Jellyfin.Server.Integration.Tests
+{
+ /// <summary>
+ /// Factory for bootstrapping the Jellyfin application in memory for functional end to end tests.
+ /// </summary>
+ public class JellyfinApplicationFactory : WebApplicationFactory<Startup>
+ {
+ private static readonly string _testPathRoot = Path.Combine(Path.GetTempPath(), "jellyfin-test-data");
+ private readonly ConcurrentBag<IDisposable> _disposableComponents = new ConcurrentBag<IDisposable>();
+
+ /// <summary>
+ /// Initializes static members of the <see cref="JellyfinApplicationFactory"/> class.
+ /// </summary>
+ static JellyfinApplicationFactory()
+ {
+ // Perform static initialization that only needs to happen once per test-run
+ Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger();
+ Program.PerformStaticInitialization();
+ }
+
+ /// <inheritdoc/>
+ protected override IWebHostBuilder CreateWebHostBuilder()
+ {
+ return new WebHostBuilder();
+ }
+
+ /// <inheritdoc/>
+ protected override void ConfigureWebHost(IWebHostBuilder builder)
+ {
+ // Specify the startup command line options
+ var commandLineOpts = new StartupOptions
+ {
+ NoWebClient = true
+ };
+
+ // Use a temporary directory for the application paths
+ var webHostPathRoot = Path.Combine(_testPathRoot, "test-host-" + Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));
+ Directory.CreateDirectory(Path.Combine(webHostPathRoot, "logs"));
+ Directory.CreateDirectory(Path.Combine(webHostPathRoot, "config"));
+ Directory.CreateDirectory(Path.Combine(webHostPathRoot, "cache"));
+ Directory.CreateDirectory(Path.Combine(webHostPathRoot, "jellyfin-web"));
+ var appPaths = new ServerApplicationPaths(
+ webHostPathRoot,
+ Path.Combine(webHostPathRoot, "logs"),
+ Path.Combine(webHostPathRoot, "config"),
+ Path.Combine(webHostPathRoot, "cache"),
+ Path.Combine(webHostPathRoot, "jellyfin-web"));
+
+ // Create the logging config file
+ // TODO: We shouldn't need to do this since we are only logging to console
+ Program.InitLoggingConfigFile(appPaths).GetAwaiter().GetResult();
+
+ // Create a copy of the application configuration to use for startup
+ var startupConfig = Program.CreateAppConfiguration(commandLineOpts, appPaths);
+
+ ILoggerFactory loggerFactory = new SerilogLoggerFactory();
+ var serviceCollection = new ServiceCollection();
+ _disposableComponents.Add(loggerFactory);
+
+ // Create the app host and initialize it
+ var appHost = new TestAppHost(
+ appPaths,
+ loggerFactory,
+ commandLineOpts,
+ new ConfigurationBuilder().Build(),
+ new ManagedFileSystem(loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
+ serviceCollection);
+ _disposableComponents.Add(appHost);
+ appHost.Init();
+
+ // Configure the web host builder
+ Program.ConfigureWebHostBuilder(builder, appHost, serviceCollection, commandLineOpts, startupConfig, appPaths);
+ }
+
+ /// <inheritdoc/>
+ protected override TestServer CreateServer(IWebHostBuilder builder)
+ {
+ // Create the test server using the base implementation
+ var testServer = base.CreateServer(builder);
+
+ // Finish initializing the app host
+ var appHost = (TestAppHost)testServer.Services.GetRequiredService<IApplicationHost>();
+ appHost.ServiceProvider = testServer.Services;
+ appHost.InitializeServices().GetAwaiter().GetResult();
+ appHost.RunStartupTasksAsync(CancellationToken.None).GetAwaiter().GetResult();
+
+ return testServer;
+ }
+
+ /// <inheritdoc/>
+ protected override void Dispose(bool disposing)
+ {
+ foreach (var disposable in _disposableComponents)
+ {
+ disposable.Dispose();
+ }
+
+ _disposableComponents.Clear();
+
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
new file mode 100644
index 000000000..3cbd638f9
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
@@ -0,0 +1,42 @@
+using System.IO;
+using System.Reflection;
+using System.Text.Json;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Branding;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Jellyfin.Server.Integration.Tests
+{
+ public sealed class OpenApiSpecTests : IClassFixture<JellyfinApplicationFactory>
+ {
+ private readonly JellyfinApplicationFactory _factory;
+ private readonly ITestOutputHelper _outputHelper;
+
+ public OpenApiSpecTests(JellyfinApplicationFactory factory, ITestOutputHelper outputHelper)
+ {
+ _factory = factory;
+ _outputHelper = outputHelper;
+ }
+
+ [Fact]
+ public async Task GetSpec_ReturnsCorrectResponse()
+ {
+ // Arrange
+ var client = _factory.CreateClient();
+
+ // Act
+ var response = await client.GetAsync("/api-docs/openapi.json");
+
+ // Assert
+ response.EnsureSuccessStatusCode();
+ Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString());
+
+ // Write out for publishing
+ var responseBody = await response.Content.ReadAsStringAsync();
+ string outputPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "openapi.json"));
+ _outputHelper.WriteLine("Writing OpenAPI Spec JSON to '{0}'.", outputPath);
+ File.WriteAllText(outputPath, responseBody);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs
new file mode 100644
index 000000000..4e5d0fcb6
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/TestAppHost.cs
@@ -0,0 +1,55 @@
+using System.Collections.Generic;
+using System.Reflection;
+using Emby.Server.Implementations;
+using Jellyfin.Server;
+using MediaBrowser.Controller;
+using MediaBrowser.Model.IO;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Server.Integration.Tests
+{
+ /// <summary>
+ /// Implementation of the abstract <see cref="ApplicationHost" /> class.
+ /// </summary>
+ public class TestAppHost : CoreAppHost
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TestAppHost" /> class.
+ /// </summary>
+ /// <param name="applicationPaths">The <see cref="ServerApplicationPaths" /> to be used by the <see cref="CoreAppHost" />.</param>
+ /// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
+ /// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
+ /// <param name="startup">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
+ /// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
+ /// <param name="collection">The <see cref="IServiceCollection"/> to be used by the <see cref="CoreAppHost"/>.</param>
+ public TestAppHost(
+ IServerApplicationPaths applicationPaths,
+ ILoggerFactory loggerFactory,
+ IStartupOptions options,
+ IConfiguration startup,
+ IFileSystem fileSystem,
+ IServiceCollection collection)
+ : base(
+ applicationPaths,
+ loggerFactory,
+ options,
+ startup,
+ fileSystem,
+ collection)
+ {
+ }
+
+ /// <inheritdoc />
+ protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
+ {
+ foreach (var a in base.GetAssembliesWithPartsInternal())
+ {
+ yield return a;
+ }
+
+ yield return typeof(TestPlugin).Assembly;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/TestPage.html b/tests/Jellyfin.Server.Integration.Tests/TestPage.html
new file mode 100644
index 000000000..8037af8a6
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/TestPage.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>TestPlugin</title>
+</head>
+<body>
+ <h1>This is a Test Page.</h1>
+</body>
+</html>
diff --git a/tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs b/tests/Jellyfin.Server.Integration.Tests/TestPlugin.cs
new file mode 100644
index 000000000..1d67ac487
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.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.Server.Integration.Tests
+{
+ public class TestPlugin : BasePlugin<BasePluginConfiguration>, 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<PluginPageInfo> GetPages()
+ {
+ yield return new PluginPageInfo
+ {
+ Name = Name,
+ EmbeddedResourcePath = GetType().Namespace + ".TestPage.html"
+ };
+
+ yield return new PluginPageInfo
+ {
+ Name = "BrokenPage",
+ EmbeddedResourcePath = GetType().Namespace + ".foobar"
+ };
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs b/tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs
new file mode 100644
index 000000000..ac10c4784
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/TestPluginWithoutPages.cs
@@ -0,0 +1,27 @@
+#pragma warning disable CS1591
+
+using System;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Model.Plugins;
+using MediaBrowser.Model.Serialization;
+
+namespace Jellyfin.Server.Integration.Tests
+{
+ public class TestPluginWithoutPages : BasePlugin<BasePluginConfiguration>
+ {
+ public TestPluginWithoutPages(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
+ : base(applicationPaths, xmlSerializer)
+ {
+ Instance = this;
+ }
+
+ public static TestPluginWithoutPages? Instance { get; private set; }
+
+ public override Guid Id => new Guid("ae95cbe6-bd3d-4d73-8596-490db334611e");
+
+ public override string Name => nameof(TestPluginWithoutPages);
+
+ public override string Description => "Server test Plugin without web pages.";
+ }
+}