aspnetcore/test/Microsoft.AspNetCore.Respon.../ResponseCachingContextTests.cs

488 lines
20 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.Collections.Generic;
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.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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
Assert.False(context.RequestIsCacheable());
}
[Theory]
[InlineData("no-cache")]
[InlineData("no-store")]
[InlineData("no-cache, no-store")]
public void RequestIsCacheable_ExplicitDisablingDirectives_NotAllowed(string directive)
{
var httpContext = new DefaultHttpContext();
httpContext.Request.Method = "GET";
httpContext.Request.Headers[HeaderNames.CacheControl] = directive;
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
Assert.False(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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
Assert.True(context.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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
Assert.Equal("GET;/;HeaderA=ValueA;HeaderC=null;", context.CreateCacheKey(new CachedVaryBy()
{
Headers = new string[] { "HeaderA", "HeaderC" }
}));
}
[Fact]
public void ResponseIsCacheable_NoPublic_NotAllowed()
{
var httpContext = new DefaultHttpContext();
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
Assert.False(context.ResponseIsCacheable());
}
[Fact]
public void ResponseIsCacheable_Public_Allowed()
{
var httpContext = new DefaultHttpContext();
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
{
Public = true
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
Assert.False(context.ResponseIsCacheable());
}
[Fact]
public void ResponseIsCacheable_NoStore_NotAllowed()
{
var httpContext = new DefaultHttpContext();
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
{
Public = true,
NoStore = true
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
Assert.False(context.ResponseIsCacheable());
}
[Fact]
public void EntryIsFresh_NoExpiryRequirements_IsFresh()
{
var httpContext = new DefaultHttpContext();
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
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 = new ResponseCachingContext(httpContext, new TestResponseCache());
Assert.True(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(3), verifyAgainstRequest: false));
}
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);
}
}
}
}