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:
@@ -5,16 +5,29 @@ namespace Jellyfin.Plugin.Multilang.Tests;
|
||||
public sealed class ItemsProxyCacheTests
|
||||
{
|
||||
[Fact]
|
||||
public void StoreRetrievesAndInvalidatesByItemId()
|
||||
public void PolicyChangesInvalidateCachedDataAndOldInFlightWrites()
|
||||
{
|
||||
var cache = new ItemsProxyCache();
|
||||
var generation = cache.ObserveUserPolicy("user", "all libraries");
|
||||
Store(cache, "key");
|
||||
Assert.Equal(generation, cache.ObserveUserPolicy("user", "all libraries"));
|
||||
Assert.True(cache.TryGet("key", TimeSpan.FromMinutes(1), out _));
|
||||
Assert.True(cache.ObserveUserPolicy("user", "no libraries") > generation);
|
||||
Assert.False(cache.TryGet("key", TimeSpan.FromMinutes(1), out _));
|
||||
Assert.False(cache.Store("key", "user", "/Items", 1, "body", "application/json", 1, 100, TimeSpan.FromMinutes(1), generation));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StoreRetrievesAndClearsCachedResponses()
|
||||
{
|
||||
var cache = new ItemsProxyCache();
|
||||
|
||||
Assert.True(Store(cache, "key", ["A"]));
|
||||
Assert.True(Store(cache, "key"));
|
||||
Assert.True(cache.TryGet("key", TimeSpan.FromMinutes(1), out var response));
|
||||
Assert.Equal("body-key", response.Body);
|
||||
Assert.Equal(1, response.ItemCount);
|
||||
|
||||
cache.InvalidateItems(["a"]);
|
||||
cache.ClearAll();
|
||||
|
||||
Assert.False(cache.TryGet("key", TimeSpan.FromMinutes(1), out _));
|
||||
}
|
||||
@@ -24,11 +37,11 @@ public sealed class ItemsProxyCacheTests
|
||||
{
|
||||
var cache = new ItemsProxyCache();
|
||||
|
||||
Assert.True(cache.StoreMicro("key", "user", "/Items", ["A"], "micro", "application/json", 1, 1024));
|
||||
Assert.True(cache.StoreMicro("key", "user", "/Items", 1, "micro", "application/json", 1, 1024));
|
||||
Assert.True(cache.TryGet("key", TimeSpan.FromMinutes(1), out var response));
|
||||
Assert.Equal("micro", response.Body);
|
||||
|
||||
cache.InvalidateItems(["A"]);
|
||||
cache.ClearAll();
|
||||
|
||||
Assert.False(cache.TryGet("key", TimeSpan.FromMinutes(1), out _));
|
||||
}
|
||||
@@ -38,9 +51,9 @@ public sealed class ItemsProxyCacheTests
|
||||
{
|
||||
var cache = new ItemsProxyCache();
|
||||
|
||||
Assert.True(Store(cache, "first", ["A"], body: "1111", maxBytes: 6));
|
||||
Assert.True(Store(cache, "first", body: "1111", maxBytes: 6));
|
||||
Thread.Sleep(10);
|
||||
Assert.True(Store(cache, "second", ["B"], body: "2222", maxBytes: 6));
|
||||
Assert.True(Store(cache, "second", body: "2222", maxBytes: 6));
|
||||
|
||||
Assert.False(cache.TryGet("first", TimeSpan.FromMinutes(1), out _));
|
||||
Assert.True(cache.TryGet("second", TimeSpan.FromMinutes(1), out _));
|
||||
@@ -51,7 +64,7 @@ public sealed class ItemsProxyCacheTests
|
||||
{
|
||||
var cache = new ItemsProxyCache();
|
||||
|
||||
Assert.True(Store(cache, "key", ["A"], ttl: TimeSpan.FromMilliseconds(1)));
|
||||
Assert.True(Store(cache, "key", ttl: TimeSpan.FromMilliseconds(1)));
|
||||
Thread.Sleep(20);
|
||||
|
||||
Assert.False(cache.TryGet("key", TimeSpan.FromMilliseconds(1), out _));
|
||||
@@ -62,14 +75,14 @@ public sealed class ItemsProxyCacheTests
|
||||
{
|
||||
var cache = new ItemsProxyCache();
|
||||
|
||||
Assert.False(Store(cache, "key", ["A"], body: "12345", maxBytes: 4));
|
||||
Assert.False(Store(cache, "key", body: "12345", maxBytes: 4));
|
||||
Assert.False(cache.TryGet("key", TimeSpan.FromMinutes(1), out _));
|
||||
}
|
||||
|
||||
private static bool Store(
|
||||
ItemsProxyCache cache,
|
||||
string key,
|
||||
string[] itemIds,
|
||||
int itemCount = 1,
|
||||
string? body = null,
|
||||
long maxBytes = 1024,
|
||||
TimeSpan? ttl = null)
|
||||
@@ -77,10 +90,43 @@ public sealed class ItemsProxyCacheTests
|
||||
key,
|
||||
"user",
|
||||
"/Items",
|
||||
itemIds,
|
||||
itemCount,
|
||||
body ?? "body-" + key,
|
||||
"application/json",
|
||||
100,
|
||||
maxBytes,
|
||||
ttl ?? TimeSpan.FromMinutes(1));
|
||||
|
||||
[Fact]
|
||||
public void MicroAndMainEntriesShareOneBudget()
|
||||
{
|
||||
var cache = new ItemsProxyCache();
|
||||
Assert.True(Store(cache, "main", body: "1111", maxBytes: 6));
|
||||
Assert.True(cache.StoreMicro("micro1", "user", "/Items", 1, "2222", "application/json", 1, 6));
|
||||
Assert.False(cache.TryGet("main", TimeSpan.FromMinutes(1), out _));
|
||||
Assert.True(cache.StoreMicro("micro2", "user", "/Items", 1, "3333", "application/json", 1, 6));
|
||||
Assert.False(cache.TryGet("micro1", TimeSpan.FromMinutes(1), out _));
|
||||
Assert.True(cache.TryGet("micro2", TimeSpan.FromMinutes(1), out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidationRejectsStaleInFlightWrites()
|
||||
{
|
||||
var cache = new ItemsProxyCache();
|
||||
var generation = cache.Generation;
|
||||
cache.ClearAll();
|
||||
Assert.False(cache.Store("key", "user", "/Items", 1, "body", "application/json", 1, 100, TimeSpan.FromMinutes(1), generation));
|
||||
Assert.False(cache.StoreMicro("key", "user", "/Items", 1, "body", "application/json", 1, 100, generation));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClearingOneUserPreservesOtherUsersCachedResponses()
|
||||
{
|
||||
var cache = new ItemsProxyCache();
|
||||
Store(cache, "one");
|
||||
cache.Store("two", "other", "/Items", 1, "body", "application/json", 1, 100, TimeSpan.FromMinutes(1));
|
||||
cache.ClearUser("user");
|
||||
Assert.False(cache.TryGet("one", TimeSpan.FromMinutes(1), out _));
|
||||
Assert.True(cache.TryGet("two", TimeSpan.FromMinutes(1), out _));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user