using System; using System.Collections.Concurrent; using System.Globalization; using System.IO; using Emby.Server.Implementations; using Jellyfin.Server.Extensions; using Jellyfin.Server.Helpers; using MediaBrowser.Common; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Serilog; using Serilog.Extensions.Logging; namespace Jellyfin.Server.Integration.Tests { /// /// Factory for bootstrapping the Jellyfin application in memory for functional end to end tests. /// public class JellyfinApplicationFactory : WebApplicationFactory { private static readonly string _testPathRoot = Path.Combine(Path.GetTempPath(), "jellyfin-test-data"); private readonly ConcurrentBag _disposableComponents = new ConcurrentBag(); /// /// Initializes static members of the class. /// static JellyfinApplicationFactory() { // Perform static initialization that only needs to happen once per test-run Log.Logger = new LoggerConfiguration() .WriteTo.Console(formatProvider: CultureInfo.InvariantCulture) .CreateLogger(); StartupHelpers.PerformStaticInitialization(); } /// protected override IHostBuilder CreateHostBuilder() { return new HostBuilder(); } /// protected override void ConfigureWebHost(IWebHostBuilder builder) { // Skip ffmpeg check for testing Environment.SetEnvironmentVariable("JELLYFIN_FFMPEG__NOVALIDATION", "true"); // Specify the startup command line options var commandLineOpts = new StartupOptions(); // 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 StartupHelpers.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(); _disposableComponents.Add(loggerFactory); // Create the app host and initialize it var appHost = new TestAppHost( appPaths, loggerFactory, commandLineOpts, startupConfig); _disposableComponents.Add(appHost); builder.ConfigureServices(services => appHost.Init(services)) .ConfigureWebHostBuilder(appHost, startupConfig, appPaths, NullLogger.Instance) .ConfigureAppConfiguration((context, builder) => { builder .SetBasePath(appPaths.ConfigurationDirectoryPath) .AddInMemoryCollection(ConfigurationOptions.DefaultConfiguration) .AddEnvironmentVariables("JELLYFIN_") .AddInMemoryCollection(commandLineOpts.ConvertToConfig()); }); } /// protected override IHost CreateHost(IHostBuilder builder) { var host = builder.Build(); var appHost = (TestAppHost)host.Services.GetRequiredService(); appHost.ServiceProvider = host.Services; appHost.InitializeServices().GetAwaiter().GetResult(); host.Start(); appHost.RunStartupTasksAsync().GetAwaiter().GetResult(); return host; } /// protected override void Dispose(bool disposing) { foreach (var disposable in _disposableComponents) { disposable.Dispose(); } _disposableComponents.Clear(); base.Dispose(disposing); } } }