From 14bdf8014f64feecc07beaf0f3fa58ce7926ac7e Mon Sep 17 00:00:00 2001 From: Vangelis Katikaridis Date: Sat, 14 Mar 2020 19:34:02 +0100 Subject: [PATCH] #128 refine requirements Signed-off-by: Vangelis Katikaridis --- sync/http/cache.go | 19 ++++++++- sync/http/cache_test.go | 94 +++++++++++++++++++++++++++++++++++------ 2 files changed, 99 insertions(+), 14 deletions(-) diff --git a/sync/http/cache.go b/sync/http/cache.go index c4c62bf724..6b5df77b16 100644 --- a/sync/http/cache.go +++ b/sync/http/cache.go @@ -11,6 +11,8 @@ import ( "github.com/beatlabs/patron/sync" ) +// TODO : add comments where applicable + type CacheHeader int const ( @@ -21,11 +23,13 @@ const ( no_store no_transform only_if_cached + public + private CacheControlHeader = "CACHE-CONTROL" ) -var cacheHeaders = map[string]CacheHeader{"max-age": max_age, "max-stale": max_stale, "min-fresh": min_fresh, "no-cache": no_cache, "no-store": no_store, "no-transform": no_transform, "only-if-cached": only_if_cached} +var cacheHeaders = map[string]CacheHeader{"public": public, "private": private, "max-age": max_age, "max-stale": max_stale, "min-fresh": min_fresh, "no-cache": no_cache, "no-store": no_store, "no-transform": no_transform, "only-if-cached": only_if_cached} type TimeInstant func() int64 @@ -47,6 +51,8 @@ func cacheHandler(hnd sync.ProcessorFunc, cache cache.Cache, instant TimeInstant // TODO : cache also errors ??? if r, ok := resp.(cachedResponse); ok && notExpired(now, r.lastValid, ttl) { println(fmt.Sprintf("cache = %v", cache)) + // TODO : set the headers + // ETag , Last-Modified return r.response, r.err } else { log.Errorf("could not parse cached response from %v", resp) @@ -121,14 +127,22 @@ func extractCacheHeaders(request *sync.Request) (bool, bool, int64) { } case no_cache: /** - retrieve from the store + return response if entity has changed + e.g. (304 response if nothing has changed : 304 Not Modified) it SHOULD NOT include min-fresh, max-stale, or max-age. + reqeust should be accompanied by an ETag token */ fallthrough case no_store: /** no storage whatsoever */ + fallthrough + case private: + /** + server does not support private caching, + this should be the responsibility of the client + */ noCache = true case no_transform: /** @@ -159,6 +173,7 @@ type cachedResponse struct { } func createRequestKey(request *sync.Request) string { + // TODO : define the key requirements in more detail return fmt.Sprintf("%s:%s", request.Headers, request.Fields) } diff --git a/sync/http/cache_test.go b/sync/http/cache_test.go index a2ad57d80d..1e7f7971e0 100644 --- a/sync/http/cache_test.go +++ b/sync/http/cache_test.go @@ -41,17 +41,20 @@ func TestExtractCacheHeaders(t *testing.T) { } -func TestCacheHandler(t *testing.T) { +type testArgs struct { + header map[string]string + fields map[string]string + response *sync.Response + timeInstance int64 + err error +} - type args struct { - header map[string]string - fields map[string]string - response *sync.Response - timeInstance int64 - err error - } +// TestCacheMaxAgeHeader tests the cache implementation +// for the same request, +// for header max-age +func TestCacheMaxAgeHeader(t *testing.T) { - params := [][]args{ + args := [][]testArgs{ // cache expiration with max-age header { // initial request @@ -105,20 +108,88 @@ func TestCacheHandler(t *testing.T) { }, } + run(t, args) + +} + +// TestCacheMaxAgeHeader tests the cache implementation +// for the same request, +// for header max-age +func TestCacheMinFreshHeader(t *testing.T) { + + args := [][]testArgs{ + // cache expiration with max-age header + { + // initial request + { + fields: map[string]string{"VALUE": "1"}, + header: map[string]string{CacheControlHeader: "min-fresh=10"}, + response: sync.NewResponse(10), + timeInstance: 1, + err: nil, + }, + // cache response + { + fields: map[string]string{"VALUE": "1"}, + header: map[string]string{CacheControlHeader: "max-age=10"}, + response: sync.NewResponse(10), + timeInstance: 9, + err: nil, + }, + // still cached response because we are at the edge of the expiry e.g. 11 - 1 = 10 + { + fields: map[string]string{"VALUE": "1"}, + header: map[string]string{CacheControlHeader: "max-age=10"}, + response: sync.NewResponse(10), + timeInstance: 11, + err: nil, + }, + // new response because cache has expired + { + fields: map[string]string{"VALUE": "1"}, + header: map[string]string{CacheControlHeader: "max-age=10"}, + response: sync.NewResponse(120), + timeInstance: 12, + err: nil, + }, + // make an extra request with the new cache value + { + fields: map[string]string{"VALUE": "1"}, + header: map[string]string{CacheControlHeader: "max-age=10"}, + response: sync.NewResponse(120), + timeInstance: 15, + err: nil, + }, + // and another when the previous has expired 12 + 10 = 22 + { + fields: map[string]string{"VALUE": "1"}, + header: map[string]string{CacheControlHeader: "max-age=10"}, + response: sync.NewResponse(230), + timeInstance: 23, + err: nil, + }, + }, + } + + run(t, args) + +} +func run(t *testing.T, args [][]testArgs) { handler := func(timeInstance int64) func(ctx context.Context, request *sync.Request) (*sync.Response, error) { return func(ctx context.Context, request *sync.Request) (*sync.Response, error) { i, err := strconv.Atoi(request.Fields["VALUE"]) if err != nil { return nil, err } + // return the specified parameter multiplied by the time instant return sync.NewResponse(i * 10 * int(timeInstance)), nil } } cache := &testingCache{cache: make(map[string]interface{})} - for _, param := range params { - for _, arg := range param { + for _, testArg := range args { + for _, arg := range testArg { request := sync.NewRequest(arg.fields, nil, arg.header, nil) // initial request response, err := cacheHandler(handler(arg.timeInstance), cache, func() int64 { @@ -135,7 +206,6 @@ func TestCacheHandler(t *testing.T) { } } } - } type testingCache struct {