diff --git a/test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/CacheDependencyTests.cs b/test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/CacheDependencyTests.cs new file mode 100644 index 0000000000..a6d5f192a6 --- /dev/null +++ b/test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/CacheDependencyTests.cs @@ -0,0 +1,162 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Caching; +using System.Threading.Tasks; +using System.Web; +using System.Web.Caching; +using AutoFixture; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.SystemWebAdapters.Tests; + +/// +/// Tests for . These use in some cases and must be tested within a host if cache keys are used +/// +[Collection(nameof(SelfHostedTests))] +public class CacheDependencyTests +{ + private readonly Fixture _fixture; + + public CacheDependencyTests() + { + _fixture = new Fixture(); + } + + [Fact] + public void InsertWithDependency() + { + // Arrange + var memCache = new Mock(_fixture.Create(), null); + var cache = new Cache(memCache.Object); + var key = _fixture.Create(); + var item = new object(); + var cacheDependency = new Mock(); + + // Act + cache.Insert(key, item, cacheDependency.Object); + + // Assert + memCache.Verify(m => m.Set(key, item, It.Is(e => e.AbsoluteExpiration.DateTime.Equals(Cache.NoAbsoluteExpiration) && e.SlidingExpiration.Equals(Cache.NoSlidingExpiration)), null), Times.Once); + } + + [Fact] + public async Task DependentFileCallback() + { + // Arrange + using var memCache = new MemoryCache(_fixture.Create()); + var cache = new Cache(memCache); + var item = new object(); + var key = _fixture.Create(); + var updated = false; + var slidingExpiration = TimeSpan.FromMilliseconds(1); + CacheItemUpdateReason? updateReason = default; + + var tcs = new TaskCompletionSource(); + + void Callback(string key, CacheItemUpdateReason reason, out object? expensiveObject, out CacheDependency? dependency, out DateTime absoluteExpiration, out TimeSpan slidingExpiration) + { + expensiveObject = null; + dependency = null; + absoluteExpiration = Cache.NoAbsoluteExpiration; + slidingExpiration = TimeSpan.FromMilliseconds(5); + + updated = true; + updateReason = reason; + + tcs.SetResult(); + } + + var file = Path.GetTempFileName(); + await File.WriteAllTextAsync(file, key); + + using var cd = new CacheDependency(file); + // Act + cache.Insert(key, item, cd, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, Callback); + + // Ensure file is updated + await File.WriteAllTextAsync(file, DateTime.UtcNow.ToString("O")); + + // Wait for callback to be called + await tcs.Task; + + // Wait for callback to be finalized + await Task.Delay(100); + + // Assert + Assert.True(updated); + Assert.Null(cache[key]); + Assert.Equal(CacheItemUpdateReason.DependencyChanged, updateReason); + } + + [Fact] + public async Task DependentItemCallback() + { + // Arrange + using var memCache = new MemoryCache(_fixture.Create()); + var cache = new Cache(memCache); + using var host = StartHost(cache); + + var item1 = new object(); + var item2 = new object(); + var key1 = _fixture.Create(); + var key2 = _fixture.Create(); + var updateReason = new Dictionary(); + var slidingExpiration = TimeSpan.FromMilliseconds(1); + + void Callback(string key, CacheItemUpdateReason reason, out object? expensiveObject, out CacheDependency? dependency, out DateTime absoluteExpiration, out TimeSpan slidingExpiration) + { + expensiveObject = null; + dependency = null; + absoluteExpiration = Cache.NoAbsoluteExpiration; + slidingExpiration = Cache.NoSlidingExpiration; + + updateReason[key] = reason; + } + + // Act + cache.Insert(key1, item1, null, Cache.NoAbsoluteExpiration, slidingExpiration, Callback); + + using var cd = new CacheDependency(null, new[] { key1 }); + cache.Insert(key2, item2, cd, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, Callback); + + Assert.Empty(updateReason); + + // Ensure sliding expiration has hit + await Task.Delay(slidingExpiration); + + // Force cleanup to initiate callbacks on current thread + memCache.Trim(100); + + // Assert + Assert.Contains(key1, updateReason.Keys); + Assert.Contains(key2, updateReason.Keys); + + Assert.Null(cache[key1]); + Assert.Null(cache[key2]); + + Assert.Equal(CacheItemUpdateReason.Expired, updateReason[key1]); + Assert.Equal(CacheItemUpdateReason.DependencyChanged, updateReason[key2]); + } + + private static IDisposable StartHost(Cache cache) => Host.CreateDefaultBuilder() + .ConfigureWebHost(app => + { + app.UseTestServer(); + app.Configure(app => { }); + app.ConfigureServices(services => + { + services.AddSystemWebAdapters(); + services.AddSingleton(cache); + }); + }) + .Start(); +} diff --git a/test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/CacheTests.cs b/test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/CacheTests.cs deleted file mode 100644 index f5dc410d40..0000000000 --- a/test/Microsoft.AspNetCore.SystemWebAdapters.CoreServices.Tests/CacheTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Web; -using System.Web.Caching; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Xunit; - -namespace Microsoft.AspNetCore.SystemWebAdapters.Tests -{ - public class CacheTests - { - - [Fact] - public void CacheFromHttpContext() - { - // Arrange - var cache = new Cache(); - - var serviceProvider = new Mock(); - serviceProvider.Setup(s => s.GetService(typeof(Cache))).Returns(cache); - - var coreContext = new Mock(); - coreContext.Setup(c => c.RequestServices).Returns(serviceProvider.Object); - - var context = new HttpContext(coreContext.Object); - - // Act - var result = context.Cache; - - // Assert - Assert.Same(cache, result); - - //Act via GetService - var cacheFromService = context.GetService(); - Assert.Same(cache, cacheFromService); - } - - [Fact] - public void CacheFromHttpContextWrapper() - { - // Arrange - var cache = new Cache(); - - var serviceProvider = new Mock(); - serviceProvider.Setup(s => s.GetService(typeof(Cache))).Returns(cache); - - var coreContext = new Mock(); - coreContext.Setup(c => c.RequestServices).Returns(serviceProvider.Object); - - var context = new HttpContext(coreContext.Object); - var contextWrapper = new HttpContextWrapper(context); - - // Act - var result = contextWrapper.Cache; - - // Assert - Assert.Same(cache, result); - - //Act via GetService - var cacheFromService = contextWrapper.GetService(); - Assert.Same(cache, cacheFromService); - } - } -} diff --git a/test/Microsoft.AspNetCore.SystemWebAdapters.Tests/Caching/CacheTests.cs b/test/Microsoft.AspNetCore.SystemWebAdapters.Tests/Caching/CacheTests.cs index ef4eeb102b..d395d68781 100644 --- a/test/Microsoft.AspNetCore.SystemWebAdapters.Tests/Caching/CacheTests.cs +++ b/test/Microsoft.AspNetCore.SystemWebAdapters.Tests/Caching/CacheTests.cs @@ -2,15 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; -using System.Collections.Generic; using System.IO; using System.Runtime.Caching; using System.Threading.Tasks; -using System.Web.Hosting; using AutoFixture; -using Microsoft.AspNetCore.SystemWebAdapters; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -316,154 +312,53 @@ public void InsertNoAbsoluteSlidingExpiration() } [Fact] - public void InsertWithDependency() + public void CacheFromHttpContext() { // Arrange - var memCache = new Mock(_fixture.Create(), null); - var cache = new Cache(memCache.Object); - var key = _fixture.Create(); - var item = new object(); - var cacheDependency = new Mock(); - - // Act - cache.Insert(key, item, cacheDependency.Object); - - // Assert - memCache.Verify(m => m.Set(key, item, It.Is(e => e.AbsoluteExpiration.DateTime.Equals(Cache.NoAbsoluteExpiration) && e.SlidingExpiration.Equals(Cache.NoSlidingExpiration)), null), Times.Once); - } - - [Fact] - public async Task DependentFileCallback() - { - // Arrange - using var memCache = new MemoryCache(_fixture.Create()); - var cache = new Cache(memCache); - var item = new object(); - var key = _fixture.Create(); - var updated = false; - var slidingExpiration = TimeSpan.FromMilliseconds(1); - CacheItemUpdateReason? updateReason = default; + var cache = new Cache(); - var tcs = new TaskCompletionSource(); + var serviceProvider = new Mock(); + serviceProvider.Setup(s => s.GetService(typeof(Cache))).Returns(cache); - void Callback(string key, CacheItemUpdateReason reason, out object? expensiveObject, out CacheDependency? dependency, out DateTime absoluteExpiration, out TimeSpan slidingExpiration) - { - expensiveObject = null; - dependency = null; - absoluteExpiration = Cache.NoAbsoluteExpiration; - slidingExpiration = TimeSpan.FromMilliseconds(5); + var coreContext = new Mock(); + coreContext.Setup(c => c.RequestServices).Returns(serviceProvider.Object); - updated = true; - updateReason = reason; + var context = new HttpContext(coreContext.Object); - tcs.SetResult(); - } - - var file = Path.GetTempFileName(); - await File.WriteAllTextAsync(file, key); - - using var cd = new CacheDependency(file); // Act - cache.Insert(key, item, cd, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, Callback); - - // Ensure file is updated - await File.WriteAllTextAsync(file, DateTime.UtcNow.ToString("O")); - - // Wait for callback to be called - await tcs.Task; - - // Wait for callback to be finalized - await Task.Delay(100); + var result = context.Cache; // Assert - Assert.True(updated); - Assert.Null(cache[key]); - Assert.Equal(CacheItemUpdateReason.DependencyChanged, updateReason); + Assert.Same(cache, result); + + //Act via GetService + var cacheFromService = context.GetService(); + Assert.Same(cache, cacheFromService); } [Fact] - public async Task DependentItemCallback() + public void CacheFromHttpContextWrapper() { // Arrange - using var memCache = new MemoryCache(_fixture.Create()); - - var cache = new Cache(memCache); + var cache = new Cache(); - using var _ = HostingEnvironmentContext.Initialize(cache); + var serviceProvider = new Mock(); + serviceProvider.Setup(s => s.GetService(typeof(Cache))).Returns(cache); - var item1 = new object(); - var item2 = new object(); - var key1 = _fixture.Create(); - var key2 = _fixture.Create(); - var updateReason = new Dictionary(); - var slidingExpiration = TimeSpan.FromMilliseconds(1); + var coreContext = new Mock(); + coreContext.Setup(c => c.RequestServices).Returns(serviceProvider.Object); - void Callback(string key, CacheItemUpdateReason reason, out object? expensiveObject, out CacheDependency? dependency, out DateTime absoluteExpiration, out TimeSpan slidingExpiration) - { - expensiveObject = null; - dependency = null; - absoluteExpiration = Cache.NoAbsoluteExpiration; - slidingExpiration = Cache.NoSlidingExpiration; - - updateReason[key] = reason; - } + var context = new HttpContext(coreContext.Object); + var contextWrapper = new HttpContextWrapper(context); // Act - cache.Insert(key1, item1, null, Cache.NoAbsoluteExpiration, slidingExpiration, Callback); - - using var cd = new CacheDependency(null, new[] { key1 }); - cache.Insert(key2, item2, cd, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, Callback); - - Assert.Empty(updateReason); - - // Ensure sliding expiration has hit - await Task.Delay(slidingExpiration); - - // Force cleanup to initiate callbacks on current thread - memCache.Trim(100); + var result = contextWrapper.Cache; // Assert - Assert.Contains(key1, updateReason.Keys); - Assert.Contains(key2, updateReason.Keys); - - Assert.Null(cache[key1]); - Assert.Null(cache[key2]); - - Assert.Equal(CacheItemUpdateReason.Expired, updateReason[key1]); - Assert.Equal(CacheItemUpdateReason.DependencyChanged, updateReason[key2]); - } - - /// - /// This will ensure we can remove the hosting environment after the test is complete since it is a static instance - /// - private sealed class HostingEnvironmentContext : IDisposable, IServiceProvider - { - private readonly Cache _cache; - - private HostingEnvironmentContext(Cache cache) - { - _cache = cache; - HostingEnvironmentAccessor.Current = new(this, Options.Create(new SystemWebAdaptersOptions())); - } + Assert.Same(cache, result); - public void Dispose() - { - HostingEnvironmentAccessor.Current = null; - } - - public static HostingEnvironmentContext Initialize(Cache cache) - { - return new HostingEnvironmentContext(cache); - } - - object? IServiceProvider.GetService(Type serviceType) - { - if (serviceType == typeof(Cache)) - { - return _cache; - } - - return null; - } + //Act via GetService + var cacheFromService = contextWrapper.GetService(); + Assert.Same(cache, cacheFromService); } }