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.
133 lines
4.7 KiB
C#
133 lines
4.7 KiB
C#
using Jellyfin.Plugin.Multilang.Services;
|
|
|
|
namespace Jellyfin.Plugin.Multilang.Tests;
|
|
|
|
public sealed class ItemsProxyCacheTests
|
|
{
|
|
[Fact]
|
|
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"));
|
|
Assert.True(cache.TryGet("key", TimeSpan.FromMinutes(1), out var response));
|
|
Assert.Equal("body-key", response.Body);
|
|
Assert.Equal(1, response.ItemCount);
|
|
|
|
cache.ClearAll();
|
|
|
|
Assert.False(cache.TryGet("key", TimeSpan.FromMinutes(1), out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void MicroCacheServesFastResponsesAndIsInvalidatedWithTheMainCache()
|
|
{
|
|
var cache = new ItemsProxyCache();
|
|
|
|
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.ClearAll();
|
|
|
|
Assert.False(cache.TryGet("key", TimeSpan.FromMinutes(1), out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void StoreEvictsTheOldestEntryWhenCapacityIsExceeded()
|
|
{
|
|
var cache = new ItemsProxyCache();
|
|
|
|
Assert.True(Store(cache, "first", body: "1111", maxBytes: 6));
|
|
Thread.Sleep(10);
|
|
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 _));
|
|
}
|
|
|
|
[Fact]
|
|
public void StoreEntriesExpireAtTheConfiguredTtl()
|
|
{
|
|
var cache = new ItemsProxyCache();
|
|
|
|
Assert.True(Store(cache, "key", ttl: TimeSpan.FromMilliseconds(1)));
|
|
Thread.Sleep(20);
|
|
|
|
Assert.False(cache.TryGet("key", TimeSpan.FromMilliseconds(1), out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void StoreRejectsResponsesLargerThanTheConfiguredLimit()
|
|
{
|
|
var cache = new ItemsProxyCache();
|
|
|
|
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,
|
|
int itemCount = 1,
|
|
string? body = null,
|
|
long maxBytes = 1024,
|
|
TimeSpan? ttl = null)
|
|
=> cache.Store(
|
|
key,
|
|
"user",
|
|
"/Items",
|
|
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 _));
|
|
}
|
|
}
|