using System;
using System.Threading.Tasks;
using StackExchange.Redis;
using Testcontainers.Redis;
namespace Jellyfin.Server.Tests.HighAvailability;
///
/// Hands out a Redis server for the tests that need one. A server named by JELLYFIN_TEST_REDIS is
/// used as is, so CI can run one beside the step instead of a docker daemon of its own; without it a
/// container is started through testcontainers.
///
public sealed class RedisTestServer : IAsyncDisposable
{
///
/// The connection string of an already running server.
///
public const string ConnectionStringVariable = "JELLYFIN_TEST_REDIS";
private readonly RedisContainer? _container;
private RedisTestServer(RedisContainer? container, string connectionString)
{
_container = container;
ConnectionString = connectionString;
}
///
/// Gets the connection string of the running server.
///
public string ConnectionString { get; }
///
/// Starts or attaches to a Redis server and waits until it accepts connections.
///
/// The running server.
public static async Task StartAsync()
{
var provided = Environment.GetEnvironmentVariable(ConnectionStringVariable);
if (!string.IsNullOrWhiteSpace(provided))
{
var attached = new RedisTestServer(null, provided);
await attached.WaitUntilReadyAsync().ConfigureAwait(false);
return attached;
}
var container = new RedisBuilder("redis:7-alpine").Build();
await container.StartAsync().ConfigureAwait(false);
var started = new RedisTestServer(container, container.GetConnectionString());
await started.WaitUntilReadyAsync().ConfigureAwait(false);
return started;
}
///
/// Opens a connection of its own, so each in-process stand-in for a replica talks to the server the
/// way a separate pod would.
///
/// A new multiplexer.
public async Task ConnectAsync()
=> await ConnectionMultiplexer.ConnectAsync(ConnectionString).ConfigureAwait(false);
///
public async ValueTask DisposeAsync()
{
if (_container is not null)
{
await _container.DisposeAsync().ConfigureAwait(false);
}
}
private async Task WaitUntilReadyAsync()
{
for (var attempt = 1; ; attempt++)
{
try
{
var connection = await ConnectionMultiplexer.ConnectAsync(ConnectionString).ConfigureAwait(false);
await using (connection.ConfigureAwait(false))
{
await connection.GetDatabase().PingAsync().ConfigureAwait(false);
return;
}
}
catch (RedisException) when (attempt < 60)
{
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
}
}
}
}