691 lines
27 KiB
C#
691 lines
27 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Http.Features;
|
|
using Microsoft.AspNetCore.Http.Headers;
|
|
using Microsoft.AspNetCore.ResponseCaching.Internal;
|
|
using Microsoft.Extensions.ObjectPool;
|
|
using Microsoft.Net.Http.Headers;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
|
{
|
|
public class ResponseCachingContextTests
|
|
{
|
|
[Theory]
|
|
[InlineData("GET")]
|
|
[InlineData("HEAD")]
|
|
public void RequestIsCacheable_CacheableMethods_Allowed(string method)
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Method = method;
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.True(context.RequestIsCacheable());
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("POST")]
|
|
[InlineData("OPTIONS")]
|
|
[InlineData("PUT")]
|
|
[InlineData("DELETE")]
|
|
[InlineData("TRACE")]
|
|
[InlineData("CONNECT")]
|
|
[InlineData("")]
|
|
[InlineData(null)]
|
|
public void RequestIsCacheable_UncacheableMethods_NotAllowed(string method)
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Method = method;
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.RequestIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void RequestIsCacheable_AuthorizationHeaders_NotAllowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Method = "GET";
|
|
httpContext.Request.Headers[HeaderNames.Authorization] = "Basic plaintextUN:plaintextPW";
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.RequestIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void RequestIsCacheable_NoCache_NotAllowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Method = "GET";
|
|
httpContext.Request.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
NoCache = true
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.RequestIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void RequestIsCacheable_NoStore_Allowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Method = "GET";
|
|
httpContext.Request.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
NoStore = true
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.True(context.RequestIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void RequestIsCacheable_LegacyDirectives_NotAllowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Method = "GET";
|
|
httpContext.Request.Headers[HeaderNames.Pragma] = "no-cache";
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.RequestIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void RequestIsCacheable_LegacyDirectives_OverridenByCacheControl()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Method = "GET";
|
|
httpContext.Request.Headers[HeaderNames.Pragma] = "no-cache";
|
|
httpContext.Request.Headers[HeaderNames.CacheControl] = "max-age=10";
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.True(context.RequestIsCacheable());
|
|
}
|
|
|
|
private class AllowUnrecognizedHTTPMethodRequests : IResponseCachingCacheabilityValidator
|
|
{
|
|
public OverrideResult RequestIsCacheableOverride(HttpContext httpContext) =>
|
|
httpContext.Request.Method == "UNRECOGNIZED" ? OverrideResult.Cache : OverrideResult.DoNotCache;
|
|
|
|
public OverrideResult ResponseIsCacheableOverride(HttpContext httpContext) => OverrideResult.UseDefaultLogic;
|
|
}
|
|
|
|
[Fact]
|
|
public void RequestIsCacheableOverride_OverridesDefaultBehavior_ToAllowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Method = "UNRECOGNIZED";
|
|
var responseCachingContext = CreateTestContext(httpContext, new AllowUnrecognizedHTTPMethodRequests());
|
|
|
|
Assert.True(responseCachingContext.RequestIsCacheable());
|
|
}
|
|
|
|
private class DisallowGetHTTPMethodRequests : IResponseCachingCacheabilityValidator
|
|
{
|
|
public OverrideResult RequestIsCacheableOverride(HttpContext httpContext) =>
|
|
httpContext.Request.Method == "GET" ? OverrideResult.DoNotCache : OverrideResult.Cache;
|
|
|
|
public OverrideResult ResponseIsCacheableOverride(HttpContext httpContext) => OverrideResult.UseDefaultLogic;
|
|
}
|
|
|
|
[Fact]
|
|
public void RequestIsCacheableOverride_OverridesDefaultBehavior_ToNotAllowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Method = "GET";
|
|
var responseCachingContext = CreateTestContext(httpContext, new DisallowGetHTTPMethodRequests());
|
|
|
|
Assert.False(responseCachingContext.RequestIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void RequestIsCacheableOverride_IgnoreFallsBackToDefaultBehavior()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Method = "GET";
|
|
var responseCachingContext = CreateTestContext(httpContext, new NoopCacheabilityValidator());
|
|
|
|
Assert.True(responseCachingContext.RequestIsCacheable());
|
|
|
|
httpContext.Request.Method = "UNRECOGNIZED";
|
|
|
|
Assert.False(responseCachingContext.RequestIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void CreateCacheKey_Includes_UppercaseMethodAndPath()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Method = "head";
|
|
httpContext.Request.Path = "/path/subpath";
|
|
httpContext.Request.Scheme = "https";
|
|
httpContext.Request.Host = new HostString("example.com", 80);
|
|
httpContext.Request.PathBase = "/pathBase";
|
|
httpContext.Request.QueryString = new QueryString("?query.Key=a&query.Value=b");
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.Equal("HEAD;/PATH/SUBPATH", context.CreateCacheKey());
|
|
}
|
|
|
|
[Fact]
|
|
public void CreateCacheKey_Includes_ListedVaryByHeadersOnly()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Method = "GET";
|
|
httpContext.Request.Path = "/";
|
|
httpContext.Request.Headers["HeaderA"] = "ValueA";
|
|
httpContext.Request.Headers["HeaderB"] = "ValueB";
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.Equal("GET;/;HeaderA=ValueA;HeaderC=null", context.CreateCacheKey(new CachedVaryBy()
|
|
{
|
|
Headers = new string[] { "HeaderA", "HeaderC" }
|
|
}));
|
|
}
|
|
|
|
private class CustomizeKeySuffixProvider : IResponseCachingCacheKeySuffixProvider
|
|
{
|
|
public string CreateCustomKeySuffix(HttpContext httpContext) => "CustomizedKey";
|
|
}
|
|
|
|
[Fact]
|
|
public void CreateCacheKey_OptionalCacheKey_AppendedToDefaultKey()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Method = "GET";
|
|
httpContext.Request.Path = "/";
|
|
httpContext.Request.Headers["HeaderA"] = "ValueA";
|
|
httpContext.Request.Headers["HeaderB"] = "ValueB";
|
|
var responseCachingContext = CreateTestContext(httpContext, new CustomizeKeySuffixProvider());
|
|
|
|
Assert.Equal("GET;/;HeaderA=ValueA;HeaderC=null;CustomizedKey", responseCachingContext.CreateCacheKey(new CachedVaryBy()
|
|
{
|
|
Headers = new string[] { "HeaderA", "HeaderC" }
|
|
}));
|
|
}
|
|
|
|
[Fact]
|
|
public void ResponseIsCacheable_NoPublic_NotAllowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.ResponseIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void ResponseIsCacheable_Public_Allowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
Public = true
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.True(context.ResponseIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void ResponseIsCacheable_NoCache_NotAllowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
Public = true,
|
|
NoCache = true
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.ResponseIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void ResponseIsCacheable_RequestNoStore_NotAllowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
NoStore = true
|
|
};
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
Public = true
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.ResponseIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void ResponseIsCacheable_ResponseNoStore_NotAllowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
Public = true,
|
|
NoStore = true
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.ResponseIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void ResponseIsCacheable_VaryByStar_NotAllowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
Public = true
|
|
};
|
|
httpContext.Response.Headers[HeaderNames.Vary] = "*";
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.ResponseIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void ResponseIsCacheable_Private_NotAllowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
Public = true,
|
|
Private = true
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.ResponseIsCacheable());
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(StatusCodes.Status200OK)]
|
|
public void ResponseIsCacheable_SuccessStatusCodes_Allowed(int statusCode)
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Response.StatusCode = statusCode;
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
Public = true
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.True(context.ResponseIsCacheable());
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(StatusCodes.Status201Created)]
|
|
[InlineData(StatusCodes.Status202Accepted)]
|
|
[InlineData(StatusCodes.Status203NonAuthoritative)]
|
|
[InlineData(StatusCodes.Status204NoContent)]
|
|
[InlineData(StatusCodes.Status205ResetContent)]
|
|
[InlineData(StatusCodes.Status206PartialContent)]
|
|
[InlineData(StatusCodes.Status207MultiStatus)]
|
|
[InlineData(StatusCodes.Status300MultipleChoices)]
|
|
[InlineData(StatusCodes.Status301MovedPermanently)]
|
|
[InlineData(StatusCodes.Status302Found)]
|
|
[InlineData(StatusCodes.Status303SeeOther)]
|
|
[InlineData(StatusCodes.Status304NotModified)]
|
|
[InlineData(StatusCodes.Status305UseProxy)]
|
|
[InlineData(StatusCodes.Status306SwitchProxy)]
|
|
[InlineData(StatusCodes.Status307TemporaryRedirect)]
|
|
[InlineData(StatusCodes.Status308PermanentRedirect)]
|
|
[InlineData(StatusCodes.Status400BadRequest)]
|
|
[InlineData(StatusCodes.Status401Unauthorized)]
|
|
[InlineData(StatusCodes.Status402PaymentRequired)]
|
|
[InlineData(StatusCodes.Status403Forbidden)]
|
|
[InlineData(StatusCodes.Status404NotFound)]
|
|
[InlineData(StatusCodes.Status405MethodNotAllowed)]
|
|
[InlineData(StatusCodes.Status406NotAcceptable)]
|
|
[InlineData(StatusCodes.Status407ProxyAuthenticationRequired)]
|
|
[InlineData(StatusCodes.Status408RequestTimeout)]
|
|
[InlineData(StatusCodes.Status409Conflict)]
|
|
[InlineData(StatusCodes.Status410Gone)]
|
|
[InlineData(StatusCodes.Status411LengthRequired)]
|
|
[InlineData(StatusCodes.Status412PreconditionFailed)]
|
|
[InlineData(StatusCodes.Status413RequestEntityTooLarge)]
|
|
[InlineData(StatusCodes.Status414RequestUriTooLong)]
|
|
[InlineData(StatusCodes.Status415UnsupportedMediaType)]
|
|
[InlineData(StatusCodes.Status416RequestedRangeNotSatisfiable)]
|
|
[InlineData(StatusCodes.Status417ExpectationFailed)]
|
|
[InlineData(StatusCodes.Status418ImATeapot)]
|
|
[InlineData(StatusCodes.Status419AuthenticationTimeout)]
|
|
[InlineData(StatusCodes.Status422UnprocessableEntity)]
|
|
[InlineData(StatusCodes.Status423Locked)]
|
|
[InlineData(StatusCodes.Status424FailedDependency)]
|
|
[InlineData(StatusCodes.Status451UnavailableForLegalReasons)]
|
|
[InlineData(StatusCodes.Status500InternalServerError)]
|
|
[InlineData(StatusCodes.Status501NotImplemented)]
|
|
[InlineData(StatusCodes.Status502BadGateway)]
|
|
[InlineData(StatusCodes.Status503ServiceUnavailable)]
|
|
[InlineData(StatusCodes.Status504GatewayTimeout)]
|
|
[InlineData(StatusCodes.Status505HttpVersionNotsupported)]
|
|
[InlineData(StatusCodes.Status506VariantAlsoNegotiates)]
|
|
[InlineData(StatusCodes.Status507InsufficientStorage)]
|
|
public void ResponseIsCacheable_NonSuccessStatusCodes_NotAllowed(int statusCode)
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Response.StatusCode = statusCode;
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
Public = true
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.ResponseIsCacheable());
|
|
}
|
|
|
|
private class Allow500Response : IResponseCachingCacheabilityValidator
|
|
{
|
|
public OverrideResult RequestIsCacheableOverride(HttpContext httpContext) => OverrideResult.UseDefaultLogic;
|
|
|
|
public OverrideResult ResponseIsCacheableOverride(HttpContext httpContext) =>
|
|
httpContext.Response.StatusCode == StatusCodes.Status500InternalServerError ? OverrideResult.Cache : OverrideResult.DoNotCache;
|
|
}
|
|
|
|
[Fact]
|
|
public void ResponseIsCacheableOverride_OverridesDefaultBehavior_ToAllowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
Public = true
|
|
};
|
|
var responseCachingContext = CreateTestContext(httpContext, new Allow500Response());
|
|
|
|
Assert.True(responseCachingContext.ResponseIsCacheable());
|
|
}
|
|
|
|
private class Disallow200Response : IResponseCachingCacheabilityValidator
|
|
{
|
|
public OverrideResult RequestIsCacheableOverride(HttpContext httpContext) => OverrideResult.UseDefaultLogic;
|
|
|
|
public OverrideResult ResponseIsCacheableOverride(HttpContext httpContext) =>
|
|
httpContext.Response.StatusCode == StatusCodes.Status200OK ? OverrideResult.DoNotCache : OverrideResult.Cache;
|
|
}
|
|
|
|
[Fact]
|
|
public void ResponseIsCacheableOverride_OverridesDefaultBehavior_ToNotAllowed()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Response.StatusCode = StatusCodes.Status200OK;
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
Public = true
|
|
};
|
|
var responseCachingContext = CreateTestContext(httpContext, new Disallow200Response());
|
|
|
|
Assert.False(responseCachingContext.ResponseIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void ResponseIsCacheableOverride_IgnoreFallsBackToDefaultBehavior()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Response.StatusCode = StatusCodes.Status200OK;
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
Public = true
|
|
};
|
|
var responseCachingContext = CreateTestContext(httpContext, new NoopCacheabilityValidator());
|
|
|
|
Assert.True(responseCachingContext.ResponseIsCacheable());
|
|
|
|
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
|
|
|
Assert.False(responseCachingContext.ResponseIsCacheable());
|
|
}
|
|
|
|
[Fact]
|
|
public void EntryIsFresh_NoExpiryRequirements_IsFresh()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.True(context.EntryIsFresh(new ResponseHeaders(new HeaderDictionary()), TimeSpan.MaxValue, verifyAgainstRequest: false));
|
|
}
|
|
|
|
[Fact]
|
|
public void EntryIsFresh_PastExpiry_IsNotFresh()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
var utcNow = DateTimeOffset.UtcNow;
|
|
httpContext.Response.GetTypedHeaders().Expires = utcNow;
|
|
var context = CreateTestContext(httpContext);
|
|
context._responseTime = utcNow;
|
|
|
|
Assert.False(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.MaxValue, verifyAgainstRequest: false));
|
|
}
|
|
|
|
[Fact]
|
|
public void EntryIsFresh_MaxAgeOverridesExpiry_ToFresh()
|
|
{
|
|
var utcNow = DateTimeOffset.UtcNow;
|
|
var httpContext = new DefaultHttpContext();
|
|
|
|
var responseHeaders = httpContext.Response.GetTypedHeaders();
|
|
responseHeaders.Expires = utcNow;
|
|
responseHeaders.CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
MaxAge = TimeSpan.FromSeconds(10)
|
|
};
|
|
|
|
var context = CreateTestContext(httpContext);
|
|
context._responseTime = utcNow;
|
|
|
|
Assert.True(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(10), verifyAgainstRequest: false));
|
|
}
|
|
|
|
[Fact]
|
|
public void EntryIsFresh_MaxAgeOverridesExpiry_ToNotFresh()
|
|
{
|
|
var utcNow = DateTimeOffset.UtcNow;
|
|
var httpContext = new DefaultHttpContext();
|
|
|
|
var responseHeaders = httpContext.Response.GetTypedHeaders();
|
|
responseHeaders.Expires = utcNow;
|
|
responseHeaders.CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
MaxAge = TimeSpan.FromSeconds(10)
|
|
};
|
|
|
|
var context = CreateTestContext(httpContext);
|
|
context._responseTime = utcNow;
|
|
|
|
Assert.False(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(11), verifyAgainstRequest: false));
|
|
}
|
|
|
|
[Fact]
|
|
public void EntryIsFresh_SharedMaxAgeOverridesMaxAge_ToFresh()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
MaxAge = TimeSpan.FromSeconds(10),
|
|
SharedMaxAge = TimeSpan.FromSeconds(15)
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.True(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(11), verifyAgainstRequest: false));
|
|
}
|
|
|
|
[Fact]
|
|
public void EntryIsFresh_SharedMaxAgeOverridesMaxAge_ToNotFresh()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
MaxAge = TimeSpan.FromSeconds(10),
|
|
SharedMaxAge = TimeSpan.FromSeconds(5)
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(6), verifyAgainstRequest: false));
|
|
}
|
|
|
|
[Fact]
|
|
public void EntryIsFresh_MinFreshReducesFreshness_ToNotFresh()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
MinFresh = TimeSpan.FromSeconds(3)
|
|
};
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
MaxAge = TimeSpan.FromSeconds(10),
|
|
SharedMaxAge = TimeSpan.FromSeconds(5)
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(3), verifyAgainstRequest: true));
|
|
}
|
|
|
|
[Fact]
|
|
public void EntryIsFresh_RequestMaxAgeRestrictAge_ToNotFresh()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
MaxAge = TimeSpan.FromSeconds(5)
|
|
};
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
MaxAge = TimeSpan.FromSeconds(10),
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(6), verifyAgainstRequest: true));
|
|
}
|
|
|
|
[Fact]
|
|
public void EntryIsFresh_MaxStaleOverridesFreshness_ToFresh()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
MaxAge = TimeSpan.FromSeconds(5),
|
|
MaxStale = true, // This value must be set to true in order to specify MaxStaleLimit
|
|
MaxStaleLimit = TimeSpan.FromSeconds(10)
|
|
};
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
MaxAge = TimeSpan.FromSeconds(5),
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.True(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(6), verifyAgainstRequest: true));
|
|
}
|
|
|
|
[Fact]
|
|
public void EntryIsFresh_MustRevalidateOverridesRequestMaxStale_ToNotFresh()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
MaxAge = TimeSpan.FromSeconds(5),
|
|
MaxStale = true, // This value must be set to true in order to specify MaxStaleLimit
|
|
MaxStaleLimit = TimeSpan.FromSeconds(10)
|
|
};
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
MaxAge = TimeSpan.FromSeconds(5),
|
|
MustRevalidate = true
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.False(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(6), verifyAgainstRequest: true));
|
|
}
|
|
|
|
[Fact]
|
|
public void EntryIsFresh_IgnoresRequestVerificationWhenSpecified()
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
MinFresh = TimeSpan.FromSeconds(1),
|
|
MaxAge = TimeSpan.FromSeconds(3)
|
|
};
|
|
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
|
{
|
|
MaxAge = TimeSpan.FromSeconds(10),
|
|
SharedMaxAge = TimeSpan.FromSeconds(5)
|
|
};
|
|
var context = CreateTestContext(httpContext);
|
|
|
|
Assert.True(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(3), verifyAgainstRequest: false));
|
|
}
|
|
|
|
private static ResponseCachingContext CreateTestContext(HttpContext httpContext)
|
|
{
|
|
return CreateTestContext(
|
|
httpContext,
|
|
new NoopCacheKeySuffixProvider(),
|
|
new NoopCacheabilityValidator());
|
|
}
|
|
|
|
private static ResponseCachingContext CreateTestContext(HttpContext httpContext, IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider)
|
|
{
|
|
return CreateTestContext(
|
|
httpContext,
|
|
cacheKeySuffixProvider,
|
|
new NoopCacheabilityValidator());
|
|
}
|
|
|
|
private static ResponseCachingContext CreateTestContext(HttpContext httpContext, IResponseCachingCacheabilityValidator cacheabilityValidator)
|
|
{
|
|
return CreateTestContext(
|
|
httpContext,
|
|
new NoopCacheKeySuffixProvider(),
|
|
cacheabilityValidator);
|
|
}
|
|
|
|
private static ResponseCachingContext CreateTestContext(
|
|
HttpContext httpContext,
|
|
IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider,
|
|
IResponseCachingCacheabilityValidator cacheabilityValidator)
|
|
{
|
|
return new ResponseCachingContext(
|
|
httpContext,
|
|
new TestResponseCache(),
|
|
new SystemClock(),
|
|
new DefaultObjectPool<StringBuilder>(new StringBuilderPooledObjectPolicy()),
|
|
cacheabilityValidator,
|
|
cacheKeySuffixProvider);
|
|
}
|
|
|
|
private class TestResponseCache : IResponseCache
|
|
{
|
|
public object Get(string key)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public void Remove(string key)
|
|
{
|
|
}
|
|
|
|
public void Set(string key, object entry, TimeSpan validFor)
|
|
{
|
|
}
|
|
}
|
|
|
|
private class TestHttpSendFileFeature : IHttpSendFileFeature
|
|
{
|
|
public Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellation)
|
|
{
|
|
return Task.FromResult(0);
|
|
}
|
|
}
|
|
}
|
|
}
|