395 lines
9.5 KiB
Go
395 lines
9.5 KiB
Go
|
/**
|
||
|
* Copyright 2015 Paul Querna
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
package cacheobject
|
||
|
|
||
|
import (
|
||
|
"github.com/stretchr/testify/require"
|
||
|
|
||
|
"net/http"
|
||
|
"testing"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
func TestCachableStatusCode(t *testing.T) {
|
||
|
ok := []int{200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501}
|
||
|
for _, v := range ok {
|
||
|
require.True(t, cachableStatusCode(v), "status code should be cacheable: %d", v)
|
||
|
}
|
||
|
|
||
|
notok := []int{201, 429, 500, 504}
|
||
|
for _, v := range notok {
|
||
|
require.False(t, cachableStatusCode(v), "status code should not be cachable: %d", v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func fill(t *testing.T, now time.Time) Object {
|
||
|
RespDirectives, err := ParseResponseCacheControl("")
|
||
|
require.NoError(t, err)
|
||
|
ReqDirectives, err := ParseRequestCacheControl("")
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
obj := Object{
|
||
|
RespDirectives: RespDirectives,
|
||
|
RespHeaders: http.Header{},
|
||
|
RespStatusCode: 200,
|
||
|
RespDateHeader: now,
|
||
|
|
||
|
ReqDirectives: ReqDirectives,
|
||
|
ReqHeaders: http.Header{},
|
||
|
ReqMethod: "GET",
|
||
|
|
||
|
NowUTC: now,
|
||
|
}
|
||
|
|
||
|
return obj
|
||
|
}
|
||
|
|
||
|
func TestGETPrivate(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
RespDirectives, err := ParseResponseCacheControl("private")
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
obj.RespDirectives = RespDirectives
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 1)
|
||
|
require.Contains(t, rv.OutReasons, ReasonResponsePrivate)
|
||
|
}
|
||
|
|
||
|
func TestGETPrivateWithPrivateCache(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
RespDirectives, err := ParseResponseCacheControl("private")
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
obj.CacheIsPrivate = true
|
||
|
obj.RespDirectives = RespDirectives
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 0)
|
||
|
}
|
||
|
|
||
|
func TestUncachableMethods(t *testing.T) {
|
||
|
type methodPair struct {
|
||
|
m string
|
||
|
r Reason
|
||
|
}
|
||
|
|
||
|
tc := []methodPair{
|
||
|
{"PUT", ReasonRequestMethodPUT},
|
||
|
{"DELETE", ReasonRequestMethodDELETE},
|
||
|
{"CONNECT", ReasonRequestMethodCONNECT},
|
||
|
{"OPTIONS", ReasonRequestMethodOPTIONS},
|
||
|
{"CONNECT", ReasonRequestMethodCONNECT},
|
||
|
{"TRACE", ReasonRequestMethodTRACE},
|
||
|
{"MADEUP", ReasonRequestMethodUnkown},
|
||
|
}
|
||
|
|
||
|
for _, mp := range tc {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.ReqMethod = mp.m
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 1)
|
||
|
require.Contains(t, rv.OutReasons, mp.r)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestHEAD(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.ReqMethod = "HEAD"
|
||
|
obj.RespLastModifiedHeader = now.Add(time.Hour * -1)
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 0)
|
||
|
|
||
|
ExpirationObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 0)
|
||
|
require.False(t, rv.OutExpirationTime.IsZero())
|
||
|
}
|
||
|
|
||
|
func TestHEADLongLastModified(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.ReqMethod = "HEAD"
|
||
|
obj.RespLastModifiedHeader = now.Add(time.Hour * -70000)
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 0)
|
||
|
|
||
|
ExpirationObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 0)
|
||
|
require.False(t, rv.OutExpirationTime.IsZero())
|
||
|
require.WithinDuration(t, now.Add(twentyFourHours), rv.OutExpirationTime, time.Second*60)
|
||
|
}
|
||
|
|
||
|
func TestNonCachablePOST(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.ReqMethod = "POST"
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 1)
|
||
|
require.Contains(t, rv.OutReasons, ReasonRequestMethodPOST)
|
||
|
}
|
||
|
|
||
|
func TestCachablePOSTExpiresHeader(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.ReqMethod = "POST"
|
||
|
obj.RespExpiresHeader = now.Add(time.Hour * 1)
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 0)
|
||
|
}
|
||
|
|
||
|
func TestCachablePOSTSMax(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.ReqMethod = "POST"
|
||
|
obj.RespDirectives.SMaxAge = DeltaSeconds(900)
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 0)
|
||
|
}
|
||
|
|
||
|
func TestNonCachablePOSTSMax(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.ReqMethod = "POST"
|
||
|
obj.CacheIsPrivate = true
|
||
|
obj.RespDirectives.SMaxAge = DeltaSeconds(900)
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 1)
|
||
|
require.Contains(t, rv.OutReasons, ReasonRequestMethodPOST)
|
||
|
}
|
||
|
|
||
|
func TestCachablePOSTMax(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.ReqMethod = "POST"
|
||
|
obj.RespDirectives.MaxAge = DeltaSeconds(9000)
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 0)
|
||
|
}
|
||
|
|
||
|
func TestPUTs(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.ReqMethod = "PUT"
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 1)
|
||
|
require.Contains(t, rv.OutReasons, ReasonRequestMethodPUT)
|
||
|
}
|
||
|
|
||
|
func TestPUTWithExpires(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.ReqMethod = "PUT"
|
||
|
obj.RespExpiresHeader = now.Add(time.Hour * 1)
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 1)
|
||
|
require.Contains(t, rv.OutReasons, ReasonRequestMethodPUT)
|
||
|
}
|
||
|
|
||
|
func TestAuthorization(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.ReqHeaders.Set("Authorization", "bearer random")
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 1)
|
||
|
require.Contains(t, rv.OutReasons, ReasonRequestAuthorizationHeader)
|
||
|
}
|
||
|
|
||
|
func TestCachableAuthorization(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.ReqHeaders.Set("Authorization", "bearer random")
|
||
|
obj.RespDirectives.Public = true
|
||
|
obj.RespDirectives.MaxAge = DeltaSeconds(300)
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.NoError(t, rv.OutErr)
|
||
|
require.Len(t, rv.OutReasons, 0)
|
||
|
}
|
||
|
|
||
|
func TestRespNoStore(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.RespDirectives.NoStore = true
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.Len(t, rv.OutReasons, 1)
|
||
|
require.Contains(t, rv.OutReasons, ReasonResponseNoStore)
|
||
|
}
|
||
|
|
||
|
func TestReqNoStore(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.ReqDirectives.NoStore = true
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.Len(t, rv.OutReasons, 1)
|
||
|
require.Contains(t, rv.OutReasons, ReasonRequestNoStore)
|
||
|
}
|
||
|
|
||
|
func TestResp500(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.RespStatusCode = 500
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
CachableObject(&obj, &rv)
|
||
|
require.Len(t, rv.OutReasons, 1)
|
||
|
require.Contains(t, rv.OutReasons, ReasonResponseUncachableByDefault)
|
||
|
}
|
||
|
|
||
|
func TestExpirationSMaxShared(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.RespDirectives.SMaxAge = DeltaSeconds(60)
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
ExpirationObject(&obj, &rv)
|
||
|
require.Len(t, rv.OutWarnings, 0)
|
||
|
require.WithinDuration(t, now.Add(time.Second*60), rv.OutExpirationTime, time.Second*1)
|
||
|
}
|
||
|
|
||
|
func TestExpirationSMaxPrivate(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.CacheIsPrivate = true
|
||
|
obj.RespDirectives.SMaxAge = DeltaSeconds(60)
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
ExpirationObject(&obj, &rv)
|
||
|
require.Len(t, rv.OutWarnings, 0)
|
||
|
require.True(t, rv.OutExpirationTime.IsZero())
|
||
|
}
|
||
|
|
||
|
func TestExpirationMax(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
obj.RespDirectives.MaxAge = DeltaSeconds(60)
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
ExpirationObject(&obj, &rv)
|
||
|
require.Len(t, rv.OutWarnings, 0)
|
||
|
require.WithinDuration(t, now.Add(time.Second*60), rv.OutExpirationTime, time.Second*1)
|
||
|
}
|
||
|
|
||
|
func TestExpirationMaxAndSMax(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
// cache should select the SMax age since this is a shared cache.
|
||
|
obj.RespDirectives.MaxAge = DeltaSeconds(60)
|
||
|
obj.RespDirectives.SMaxAge = DeltaSeconds(900)
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
ExpirationObject(&obj, &rv)
|
||
|
require.Len(t, rv.OutWarnings, 0)
|
||
|
require.WithinDuration(t, now.Add(time.Second*900), rv.OutExpirationTime, time.Second*1)
|
||
|
}
|
||
|
|
||
|
func TestExpirationExpires(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
// cache should select the SMax age since this is a shared cache.
|
||
|
obj.RespExpiresHeader = now.Add(time.Second * 1500)
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
ExpirationObject(&obj, &rv)
|
||
|
require.Len(t, rv.OutWarnings, 0)
|
||
|
require.WithinDuration(t, now.Add(time.Second*1500), rv.OutExpirationTime, time.Second*1)
|
||
|
}
|
||
|
|
||
|
func TestExpirationExpiresNoServerDate(t *testing.T) {
|
||
|
now := time.Now().UTC()
|
||
|
|
||
|
obj := fill(t, now)
|
||
|
// cache should select the SMax age since this is a shared cache.
|
||
|
obj.RespDateHeader = time.Time{}
|
||
|
obj.RespExpiresHeader = now.Add(time.Second * 1500)
|
||
|
|
||
|
rv := ObjectResults{}
|
||
|
ExpirationObject(&obj, &rv)
|
||
|
require.Len(t, rv.OutWarnings, 0)
|
||
|
require.WithinDuration(t, now.Add(time.Second*1500), rv.OutExpirationTime, time.Second*1)
|
||
|
}
|