Files
jellyfin-ha-src/tests/Jellyfin.Server.Implementations.Tests/MediaEncoding/TranscodeStoreWiringTests.cs
T
unkin-agent 95220e2ed6
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
fix(ha): read transcode store config from the variable form deployments set
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
2026-09-13 13:10:52 +10:00

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));
}
}