Release 0.3.4: Jellyfin 12 stable support and audit fixes
Harden proxy permissions and cache behavior, streamline metadata fetching, remove obsolete rules, and validate the stable SDK with expanded regression coverage. Document backup format changes.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using Jellyfin.Plugin.Multilang.Data;
|
||||
using Jellyfin.Plugin.Multilang.Services.Providers;
|
||||
|
||||
namespace Jellyfin.Plugin.Multilang.Tests;
|
||||
|
||||
public sealed class MaintenanceAndProviderTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ConcurrentRefreshesCanOverlapButMaintenanceWaitsForAllOfThem()
|
||||
{
|
||||
var root = TestPaths.CreateRoot();
|
||||
try
|
||||
{
|
||||
var store = new TranslationStore(new TestApplicationPaths(root));
|
||||
var first = await store.EnterRefreshAsync(CancellationToken.None);
|
||||
var second = await store.EnterRefreshAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(5));
|
||||
using var cancel = new CancellationTokenSource();
|
||||
var maintenance = store.EnterMaintenanceAsync(cancel.Token);
|
||||
Assert.False(maintenance.IsCompleted);
|
||||
first.Dispose();
|
||||
Assert.False(maintenance.IsCompleted);
|
||||
cancel.Cancel();
|
||||
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => maintenance);
|
||||
second.Dispose();
|
||||
using var allSlots = await store.EnterMaintenanceAsync(CancellationToken.None).WaitAsync(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
finally { TestPaths.DeleteRoot(root); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MaintenanceWaitsForWriterAndCanceledWaiterDoesNotLeakLease()
|
||||
{
|
||||
var root = TestPaths.CreateRoot();
|
||||
try
|
||||
{
|
||||
var store = new TranslationStore(new TestApplicationPaths(root));
|
||||
var writer = await store.EnterMaintenanceAsync(CancellationToken.None);
|
||||
using var canceled = new CancellationTokenSource();
|
||||
var waiting = store.EnterMaintenanceAsync(canceled.Token);
|
||||
Assert.False(waiting.IsCompleted);
|
||||
canceled.Cancel();
|
||||
await Assert.ThrowsAnyAsync<OperationCanceledException>(() => waiting);
|
||||
var cleanup = store.EnterMaintenanceAsync(CancellationToken.None);
|
||||
Assert.False(cleanup.IsCompleted);
|
||||
writer.Dispose();
|
||||
using var lease = await cleanup.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
finally { TestPaths.DeleteRoot(root); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetKeepsExistingConnectionsOnTheSameDatabase()
|
||||
{
|
||||
var root = TestPaths.CreateRoot();
|
||||
try
|
||||
{
|
||||
var store = new TranslationStore(new TestApplicationPaths(root));
|
||||
store.SaveUserRules("user", new UserRulesDocument());
|
||||
using var connection = store.Open();
|
||||
connection.Open();
|
||||
using var query = connection.CreateCommand();
|
||||
query.CommandText = "SELECT COUNT(*) FROM user_rules;";
|
||||
Assert.Equal(1L, query.ExecuteScalar());
|
||||
store.ResetAll();
|
||||
Assert.Equal(0L, query.ExecuteScalar());
|
||||
}
|
||||
finally { TestPaths.DeleteRoot(root); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProviderDoesNotRetryProgrammingErrors()
|
||||
{
|
||||
var attempts = 0;
|
||||
using var http = new HttpClient(new TestHttpMessageHandler(_ =>
|
||||
{
|
||||
attempts++;
|
||||
throw new InvalidOperationException("Programming error");
|
||||
}));
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, "https://example.test/");
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => ProviderHttp.SendWithRetryAsync(http, request, CancellationToken.None));
|
||||
Assert.Equal(1, attempts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConcurrentProviderAdmissionsKeepTheirSpacing()
|
||||
{
|
||||
var limiter = new FetchRateLimiter(TimeSpan.FromMilliseconds(50));
|
||||
var starts = await Task.WhenAll(Enumerable.Range(0, 4).Select(async _ =>
|
||||
{
|
||||
await limiter.WaitAsync(CancellationToken.None);
|
||||
return Stopwatch.GetTimestamp();
|
||||
}));
|
||||
Array.Sort(starts);
|
||||
Assert.All(starts.Zip(starts.Skip(1)), pair =>
|
||||
Assert.True(Stopwatch.GetElapsedTime(pair.First, pair.Second) >= TimeSpan.FromMilliseconds(40)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user