File: ExpirationTests.cs
Web Access
Project: src\test\Libraries\Microsoft.Extensions.Caching.Hybrid.Tests\Microsoft.Extensions.Caching.Hybrid.Tests.csproj (Microsoft.Extensions.Caching.Hybrid.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Internal;
using Xunit.Abstractions;
using static Microsoft.Extensions.Caching.Hybrid.Tests.DistributedCacheTests;
using static Microsoft.Extensions.Caching.Hybrid.Tests.L2Tests;
 
namespace Microsoft.Extensions.Caching.Hybrid.Tests;
public class ExpirationTests(ITestOutputHelper log)
{
    [Fact]
    public async Task ExpirationRespected()
    {
        // we want set up separate cache instances with a shared L2 to show relative expiration
        // being respected
        var clock = new FakeTime();
        using var l1 = new MemoryCache(new MemoryCacheOptions { Clock = clock });
        var l2 = new LoggingCache(log, new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions { Clock = clock })));
 
        Guid guid0;
        string key = nameof(ExpirationRespected);
        Func<CancellationToken, ValueTask<Guid>> callback = static _ => new(Guid.NewGuid());
        HybridCacheEntryOptions options = new() { Expiration = TimeSpan.FromMinutes(2), LocalCacheExpiration = TimeSpan.FromMinutes(1) };
 
        ServiceCollection services = new();
        services.AddSingleton<ISystemClock>(clock);
        services.AddSingleton<TimeProvider>(clock);
        services.AddSingleton<IMemoryCache>(l1);
        services.AddSingleton<IDistributedCache>(l2);
        services.AddHybridCache();
        using var provider = services.BuildServiceProvider();
        var cache = provider.GetRequiredService<HybridCache>();
 
        guid0 = await cache.GetOrCreateAsync(key, callback, options);
 
        // should be fine immediately
        Assert.Equal(guid0, await cache.GetOrCreateAsync(key, callback, options));
 
        clock.Add(TimeSpan.FromSeconds(45)); // should still be fine from L1 (L1 has 1 minute expiration)
        Assert.Equal(guid0, await cache.GetOrCreateAsync(key, callback, options));
 
        clock.Add(TimeSpan.FromSeconds(45)); // should still be fine from L2 (L1 now expired, but fetches from L2 and detects 0:30 remaining, which limits L1 to 0:30)
        Assert.Equal(guid0, await cache.GetOrCreateAsync(key, callback, options));
 
        clock.Add(TimeSpan.FromSeconds(45)); // should now be expired
        Assert.NotEqual(guid0, await cache.GetOrCreateAsync(key, callback, options));
    }
}