95220e2ed6
The startup configuration only reads JELLYFIN_ prefixed environment variables, so the bare Jellyfin__TranscodeStore__* form used by the chart, the manifests and the README is dropped and the Redis store is never registered. Nothing logs the selected store, so the fallback is invisible. - Read bare Jellyfin__* variables into the Jellyfin:* configuration root - Keep an explicit JELLYFIN_ variable winning over the bare form - Log the selected transcode session store at startup - Ping Redis once at startup and log an unreachable store at Error - Log endpoints only, never the connection string
104 lines
4.1 KiB
C#
104 lines
4.1 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using Emby.Server.Implementations.MediaEncoding;
|
|
using Jellyfin.Server;
|
|
using Jellyfin.Server.Extensions;
|
|
using MediaBrowser.Common.Configuration;
|
|
using MediaBrowser.Controller.MediaEncoding;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Moq;
|
|
using StackExchange.Redis;
|
|
using Testcontainers.Redis;
|
|
using Xunit;
|
|
|
|
namespace Jellyfin.Server.Implementations.Tests.MediaEncoding;
|
|
|
|
/// <summary>
|
|
/// Drives the whole configuration path a deployment uses: a bare <c>Jellyfin__TranscodeStore__*</c>
|
|
/// environment variable, the server's own configuration builder, the store registration, and a
|
|
/// session round-trip against a real Valkey server.
|
|
/// </summary>
|
|
[Trait("Category", "RequiresDocker")]
|
|
public sealed class TranscodeStoreWiringTests : IAsyncLifetime
|
|
{
|
|
private const string RedisConnectionStringVariable = "Jellyfin__TranscodeStore__RedisConnectionString";
|
|
|
|
private readonly RedisContainer _container;
|
|
private string _configDirectory = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="TranscodeStoreWiringTests"/> class.
|
|
/// </summary>
|
|
public TranscodeStoreWiringTests()
|
|
{
|
|
_container = new RedisBuilder("valkey/valkey:8-alpine").Build();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts Valkey and lays out the configuration directory the server reads at startup.
|
|
/// </summary>
|
|
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
|
|
public async ValueTask InitializeAsync()
|
|
{
|
|
await _container.StartAsync().ConfigureAwait(false);
|
|
_configDirectory = Directory.CreateTempSubdirectory("jellyfin-wiring-test").FullName;
|
|
await File.WriteAllTextAsync(Path.Combine(_configDirectory, "logging.default.json"), "{}").ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the environment variable, configuration directory and container.
|
|
/// </summary>
|
|
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
Environment.SetEnvironmentVariable(RedisConnectionStringVariable, null);
|
|
|
|
if (_configDirectory.Length > 0)
|
|
{
|
|
Directory.Delete(_configDirectory, true);
|
|
}
|
|
|
|
await _container.DisposeAsync().ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The variable form deployments set selects the Redis store and that store really talks to Valkey.
|
|
/// </summary>
|
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task ManifestStyleEnvironmentVariable_Should_Reach_Valkey()
|
|
{
|
|
Environment.SetEnvironmentVariable(
|
|
RedisConnectionStringVariable,
|
|
_container.GetConnectionString() + ",abortConnect=false");
|
|
|
|
var appPaths = new Mock<IApplicationPaths>();
|
|
appPaths.Setup(paths => paths.ConfigurationDirectoryPath).Returns(_configDirectory);
|
|
var configuration = Program.CreateAppConfiguration(new StartupOptions(), appPaths.Object);
|
|
|
|
var services = new ServiceCollection();
|
|
services.AddLogging();
|
|
services.AddTranscodeSessionStore(configuration, NullLogger.Instance);
|
|
|
|
await using var provider = services.BuildServiceProvider();
|
|
|
|
var store = provider.GetRequiredService<ITranscodeSessionStore>();
|
|
Assert.IsType<RedisTranscodeSessionStore>(store);
|
|
|
|
var playSessionId = Guid.NewGuid().ToString("N");
|
|
await store.SetAsync(
|
|
TranscodeSession.CreateForPlaylist(playSessionId, "media-1", "pod-a", "/transcodes/abc.m3u8", TimeSpan.FromSeconds(30)),
|
|
TestContext.Current.CancellationToken);
|
|
|
|
var stored = await store.TryGetAsync(playSessionId, TestContext.Current.CancellationToken);
|
|
|
|
Assert.NotNull(stored);
|
|
Assert.Equal("pod-a", stored.OwnerPod);
|
|
|
|
var redis = provider.GetRequiredService<IConnectionMultiplexer>();
|
|
Assert.True(await redis.GetDatabase().KeyExistsAsync("jellyfin:transcode:" + playSessionId));
|
|
}
|
|
}
|