...

Source file src/github.com/gregjones/httpcache/httpcache_test.go

Documentation: github.com/gregjones/httpcache

     1  package httpcache
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"flag"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"os"
    12  	"strconv"
    13  	"testing"
    14  	"time"
    15  )
    16  
    17  var s struct {
    18  	server    *httptest.Server
    19  	client    http.Client
    20  	transport *Transport
    21  	done      chan struct{} // Closed to unlock infinite handlers.
    22  }
    23  
    24  type fakeClock struct {
    25  	elapsed time.Duration
    26  }
    27  
    28  func (c *fakeClock) since(t time.Time) time.Duration {
    29  	return c.elapsed
    30  }
    31  
    32  func TestMain(m *testing.M) {
    33  	flag.Parse()
    34  	setup()
    35  	code := m.Run()
    36  	teardown()
    37  	os.Exit(code)
    38  }
    39  
    40  func setup() {
    41  	tp := NewMemoryCacheTransport()
    42  	client := http.Client{Transport: tp}
    43  	s.transport = tp
    44  	s.client = client
    45  	s.done = make(chan struct{})
    46  
    47  	mux := http.NewServeMux()
    48  	s.server = httptest.NewServer(mux)
    49  
    50  	mux.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    51  		w.Header().Set("Cache-Control", "max-age=3600")
    52  	}))
    53  
    54  	mux.HandleFunc("/method", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    55  		w.Header().Set("Cache-Control", "max-age=3600")
    56  		w.Write([]byte(r.Method))
    57  	}))
    58  
    59  	mux.HandleFunc("/range", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    60  		lm := "Fri, 14 Dec 2010 01:01:50 GMT"
    61  		if r.Header.Get("if-modified-since") == lm {
    62  			w.WriteHeader(http.StatusNotModified)
    63  			return
    64  		}
    65  		w.Header().Set("last-modified", lm)
    66  		if r.Header.Get("range") == "bytes=4-9" {
    67  			w.WriteHeader(http.StatusPartialContent)
    68  			w.Write([]byte(" text "))
    69  			return
    70  		}
    71  		w.Write([]byte("Some text content"))
    72  	}))
    73  
    74  	mux.HandleFunc("/nostore", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    75  		w.Header().Set("Cache-Control", "no-store")
    76  	}))
    77  
    78  	mux.HandleFunc("/etag", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    79  		etag := "124567"
    80  		if r.Header.Get("if-none-match") == etag {
    81  			w.WriteHeader(http.StatusNotModified)
    82  			return
    83  		}
    84  		w.Header().Set("etag", etag)
    85  	}))
    86  
    87  	mux.HandleFunc("/lastmodified", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    88  		lm := "Fri, 14 Dec 2010 01:01:50 GMT"
    89  		if r.Header.Get("if-modified-since") == lm {
    90  			w.WriteHeader(http.StatusNotModified)
    91  			return
    92  		}
    93  		w.Header().Set("last-modified", lm)
    94  	}))
    95  
    96  	mux.HandleFunc("/varyaccept", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    97  		w.Header().Set("Cache-Control", "max-age=3600")
    98  		w.Header().Set("Content-Type", "text/plain")
    99  		w.Header().Set("Vary", "Accept")
   100  		w.Write([]byte("Some text content"))
   101  	}))
   102  
   103  	mux.HandleFunc("/doublevary", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   104  		w.Header().Set("Cache-Control", "max-age=3600")
   105  		w.Header().Set("Content-Type", "text/plain")
   106  		w.Header().Set("Vary", "Accept, Accept-Language")
   107  		w.Write([]byte("Some text content"))
   108  	}))
   109  	mux.HandleFunc("/2varyheaders", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   110  		w.Header().Set("Cache-Control", "max-age=3600")
   111  		w.Header().Set("Content-Type", "text/plain")
   112  		w.Header().Add("Vary", "Accept")
   113  		w.Header().Add("Vary", "Accept-Language")
   114  		w.Write([]byte("Some text content"))
   115  	}))
   116  	mux.HandleFunc("/varyunused", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   117  		w.Header().Set("Cache-Control", "max-age=3600")
   118  		w.Header().Set("Content-Type", "text/plain")
   119  		w.Header().Set("Vary", "X-Madeup-Header")
   120  		w.Write([]byte("Some text content"))
   121  	}))
   122  
   123  	mux.HandleFunc("/cachederror", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   124  		etag := "abc"
   125  		if r.Header.Get("if-none-match") == etag {
   126  			w.WriteHeader(http.StatusNotModified)
   127  			return
   128  		}
   129  		w.Header().Set("etag", etag)
   130  		w.WriteHeader(http.StatusNotFound)
   131  		w.Write([]byte("Not found"))
   132  	}))
   133  
   134  	updateFieldsCounter := 0
   135  	mux.HandleFunc("/updatefields", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   136  		w.Header().Set("X-Counter", strconv.Itoa(updateFieldsCounter))
   137  		w.Header().Set("Etag", `"e"`)
   138  		updateFieldsCounter++
   139  		if r.Header.Get("if-none-match") != "" {
   140  			w.WriteHeader(http.StatusNotModified)
   141  			return
   142  		}
   143  		w.Write([]byte("Some text content"))
   144  	}))
   145  
   146  	// Take 3 seconds to return 200 OK (for testing client timeouts).
   147  	mux.HandleFunc("/3seconds", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   148  		time.Sleep(3 * time.Second)
   149  	}))
   150  
   151  	mux.HandleFunc("/infinite", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   152  		for {
   153  			select {
   154  			case <-s.done:
   155  				return
   156  			default:
   157  				w.Write([]byte{0})
   158  			}
   159  		}
   160  	}))
   161  }
   162  
   163  func teardown() {
   164  	close(s.done)
   165  	s.server.Close()
   166  }
   167  
   168  func resetTest() {
   169  	s.transport.Cache = NewMemoryCache()
   170  	clock = &realClock{}
   171  }
   172  
   173  // TestCacheableMethod ensures that uncacheable method does not get stored
   174  // in cache and get incorrectly used for a following cacheable method request.
   175  func TestCacheableMethod(t *testing.T) {
   176  	resetTest()
   177  	{
   178  		req, err := http.NewRequest("POST", s.server.URL+"/method", nil)
   179  		if err != nil {
   180  			t.Fatal(err)
   181  		}
   182  		resp, err := s.client.Do(req)
   183  		if err != nil {
   184  			t.Fatal(err)
   185  		}
   186  		var buf bytes.Buffer
   187  		_, err = io.Copy(&buf, resp.Body)
   188  		if err != nil {
   189  			t.Fatal(err)
   190  		}
   191  		err = resp.Body.Close()
   192  		if err != nil {
   193  			t.Fatal(err)
   194  		}
   195  		if got, want := buf.String(), "POST"; got != want {
   196  			t.Errorf("got %q, want %q", got, want)
   197  		}
   198  		if resp.StatusCode != http.StatusOK {
   199  			t.Errorf("response status code isn't 200 OK: %v", resp.StatusCode)
   200  		}
   201  	}
   202  	{
   203  		req, err := http.NewRequest("GET", s.server.URL+"/method", nil)
   204  		if err != nil {
   205  			t.Fatal(err)
   206  		}
   207  		resp, err := s.client.Do(req)
   208  		if err != nil {
   209  			t.Fatal(err)
   210  		}
   211  		var buf bytes.Buffer
   212  		_, err = io.Copy(&buf, resp.Body)
   213  		if err != nil {
   214  			t.Fatal(err)
   215  		}
   216  		err = resp.Body.Close()
   217  		if err != nil {
   218  			t.Fatal(err)
   219  		}
   220  		if got, want := buf.String(), "GET"; got != want {
   221  			t.Errorf("got wrong body %q, want %q", got, want)
   222  		}
   223  		if resp.StatusCode != http.StatusOK {
   224  			t.Errorf("response status code isn't 200 OK: %v", resp.StatusCode)
   225  		}
   226  		if resp.Header.Get(XFromCache) != "" {
   227  			t.Errorf("XFromCache header isn't blank")
   228  		}
   229  	}
   230  }
   231  
   232  func TestDontServeHeadResponseToGetRequest(t *testing.T) {
   233  	resetTest()
   234  	url := s.server.URL + "/"
   235  	req, err := http.NewRequest(http.MethodHead, url, nil)
   236  	if err != nil {
   237  		t.Fatal(err)
   238  	}
   239  	_, err = s.client.Do(req)
   240  	if err != nil {
   241  		t.Fatal(err)
   242  	}
   243  	req, err = http.NewRequest(http.MethodGet, url, nil)
   244  	if err != nil {
   245  		t.Fatal(err)
   246  	}
   247  	resp, err := s.client.Do(req)
   248  	if err != nil {
   249  		t.Fatal(err)
   250  	}
   251  	if resp.Header.Get(XFromCache) != "" {
   252  		t.Errorf("Cache should not match")
   253  	}
   254  }
   255  
   256  func TestDontStorePartialRangeInCache(t *testing.T) {
   257  	resetTest()
   258  	{
   259  		req, err := http.NewRequest("GET", s.server.URL+"/range", nil)
   260  		if err != nil {
   261  			t.Fatal(err)
   262  		}
   263  		req.Header.Set("range", "bytes=4-9")
   264  		resp, err := s.client.Do(req)
   265  		if err != nil {
   266  			t.Fatal(err)
   267  		}
   268  		var buf bytes.Buffer
   269  		_, err = io.Copy(&buf, resp.Body)
   270  		if err != nil {
   271  			t.Fatal(err)
   272  		}
   273  		err = resp.Body.Close()
   274  		if err != nil {
   275  			t.Fatal(err)
   276  		}
   277  		if got, want := buf.String(), " text "; got != want {
   278  			t.Errorf("got %q, want %q", got, want)
   279  		}
   280  		if resp.StatusCode != http.StatusPartialContent {
   281  			t.Errorf("response status code isn't 206 Partial Content: %v", resp.StatusCode)
   282  		}
   283  	}
   284  	{
   285  		req, err := http.NewRequest("GET", s.server.URL+"/range", nil)
   286  		if err != nil {
   287  			t.Fatal(err)
   288  		}
   289  		resp, err := s.client.Do(req)
   290  		if err != nil {
   291  			t.Fatal(err)
   292  		}
   293  		var buf bytes.Buffer
   294  		_, err = io.Copy(&buf, resp.Body)
   295  		if err != nil {
   296  			t.Fatal(err)
   297  		}
   298  		err = resp.Body.Close()
   299  		if err != nil {
   300  			t.Fatal(err)
   301  		}
   302  		if got, want := buf.String(), "Some text content"; got != want {
   303  			t.Errorf("got %q, want %q", got, want)
   304  		}
   305  		if resp.StatusCode != http.StatusOK {
   306  			t.Errorf("response status code isn't 200 OK: %v", resp.StatusCode)
   307  		}
   308  		if resp.Header.Get(XFromCache) != "" {
   309  			t.Error("XFromCache header isn't blank")
   310  		}
   311  	}
   312  	{
   313  		req, err := http.NewRequest("GET", s.server.URL+"/range", nil)
   314  		if err != nil {
   315  			t.Fatal(err)
   316  		}
   317  		resp, err := s.client.Do(req)
   318  		if err != nil {
   319  			t.Fatal(err)
   320  		}
   321  		var buf bytes.Buffer
   322  		_, err = io.Copy(&buf, resp.Body)
   323  		if err != nil {
   324  			t.Fatal(err)
   325  		}
   326  		err = resp.Body.Close()
   327  		if err != nil {
   328  			t.Fatal(err)
   329  		}
   330  		if got, want := buf.String(), "Some text content"; got != want {
   331  			t.Errorf("got %q, want %q", got, want)
   332  		}
   333  		if resp.StatusCode != http.StatusOK {
   334  			t.Errorf("response status code isn't 200 OK: %v", resp.StatusCode)
   335  		}
   336  		if resp.Header.Get(XFromCache) != "1" {
   337  			t.Errorf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
   338  		}
   339  	}
   340  	{
   341  		req, err := http.NewRequest("GET", s.server.URL+"/range", nil)
   342  		if err != nil {
   343  			t.Fatal(err)
   344  		}
   345  		req.Header.Set("range", "bytes=4-9")
   346  		resp, err := s.client.Do(req)
   347  		if err != nil {
   348  			t.Fatal(err)
   349  		}
   350  		var buf bytes.Buffer
   351  		_, err = io.Copy(&buf, resp.Body)
   352  		if err != nil {
   353  			t.Fatal(err)
   354  		}
   355  		err = resp.Body.Close()
   356  		if err != nil {
   357  			t.Fatal(err)
   358  		}
   359  		if got, want := buf.String(), " text "; got != want {
   360  			t.Errorf("got %q, want %q", got, want)
   361  		}
   362  		if resp.StatusCode != http.StatusPartialContent {
   363  			t.Errorf("response status code isn't 206 Partial Content: %v", resp.StatusCode)
   364  		}
   365  	}
   366  }
   367  
   368  func TestCacheOnlyIfBodyRead(t *testing.T) {
   369  	resetTest()
   370  	{
   371  		req, err := http.NewRequest("GET", s.server.URL, nil)
   372  		if err != nil {
   373  			t.Fatal(err)
   374  		}
   375  		resp, err := s.client.Do(req)
   376  		if err != nil {
   377  			t.Fatal(err)
   378  		}
   379  		if resp.Header.Get(XFromCache) != "" {
   380  			t.Fatal("XFromCache header isn't blank")
   381  		}
   382  		// We do not read the body
   383  		resp.Body.Close()
   384  	}
   385  	{
   386  		req, err := http.NewRequest("GET", s.server.URL, nil)
   387  		if err != nil {
   388  			t.Fatal(err)
   389  		}
   390  		resp, err := s.client.Do(req)
   391  		if err != nil {
   392  			t.Fatal(err)
   393  		}
   394  		defer resp.Body.Close()
   395  		if resp.Header.Get(XFromCache) != "" {
   396  			t.Fatalf("XFromCache header isn't blank")
   397  		}
   398  	}
   399  }
   400  
   401  func TestOnlyReadBodyOnDemand(t *testing.T) {
   402  	resetTest()
   403  
   404  	req, err := http.NewRequest("GET", s.server.URL+"/infinite", nil)
   405  	if err != nil {
   406  		t.Fatal(err)
   407  	}
   408  	resp, err := s.client.Do(req) // This shouldn't hang forever.
   409  	if err != nil {
   410  		t.Fatal(err)
   411  	}
   412  	buf := make([]byte, 10) // Only partially read the body.
   413  	_, err = resp.Body.Read(buf)
   414  	if err != nil {
   415  		t.Fatal(err)
   416  	}
   417  	resp.Body.Close()
   418  }
   419  
   420  func TestGetOnlyIfCachedHit(t *testing.T) {
   421  	resetTest()
   422  	{
   423  		req, err := http.NewRequest("GET", s.server.URL, nil)
   424  		if err != nil {
   425  			t.Fatal(err)
   426  		}
   427  		resp, err := s.client.Do(req)
   428  		if err != nil {
   429  			t.Fatal(err)
   430  		}
   431  		defer resp.Body.Close()
   432  		if resp.Header.Get(XFromCache) != "" {
   433  			t.Fatal("XFromCache header isn't blank")
   434  		}
   435  		_, err = ioutil.ReadAll(resp.Body)
   436  		if err != nil {
   437  			t.Fatal(err)
   438  		}
   439  	}
   440  	{
   441  		req, err := http.NewRequest("GET", s.server.URL, nil)
   442  		if err != nil {
   443  			t.Fatal(err)
   444  		}
   445  		req.Header.Add("cache-control", "only-if-cached")
   446  		resp, err := s.client.Do(req)
   447  		if err != nil {
   448  			t.Fatal(err)
   449  		}
   450  		defer resp.Body.Close()
   451  		if resp.Header.Get(XFromCache) != "1" {
   452  			t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
   453  		}
   454  		if resp.StatusCode != http.StatusOK {
   455  			t.Fatalf("response status code isn't 200 OK: %v", resp.StatusCode)
   456  		}
   457  	}
   458  }
   459  
   460  func TestGetOnlyIfCachedMiss(t *testing.T) {
   461  	resetTest()
   462  	req, err := http.NewRequest("GET", s.server.URL, nil)
   463  	if err != nil {
   464  		t.Fatal(err)
   465  	}
   466  	req.Header.Add("cache-control", "only-if-cached")
   467  	resp, err := s.client.Do(req)
   468  	if err != nil {
   469  		t.Fatal(err)
   470  	}
   471  	defer resp.Body.Close()
   472  	if resp.Header.Get(XFromCache) != "" {
   473  		t.Fatal("XFromCache header isn't blank")
   474  	}
   475  	if resp.StatusCode != http.StatusGatewayTimeout {
   476  		t.Fatalf("response status code isn't 504 GatewayTimeout: %v", resp.StatusCode)
   477  	}
   478  }
   479  
   480  func TestGetNoStoreRequest(t *testing.T) {
   481  	resetTest()
   482  	req, err := http.NewRequest("GET", s.server.URL, nil)
   483  	if err != nil {
   484  		t.Fatal(err)
   485  	}
   486  	req.Header.Add("Cache-Control", "no-store")
   487  	{
   488  		resp, err := s.client.Do(req)
   489  		if err != nil {
   490  			t.Fatal(err)
   491  		}
   492  		defer resp.Body.Close()
   493  		if resp.Header.Get(XFromCache) != "" {
   494  			t.Fatal("XFromCache header isn't blank")
   495  		}
   496  	}
   497  	{
   498  		resp, err := s.client.Do(req)
   499  		if err != nil {
   500  			t.Fatal(err)
   501  		}
   502  		defer resp.Body.Close()
   503  		if resp.Header.Get(XFromCache) != "" {
   504  			t.Fatal("XFromCache header isn't blank")
   505  		}
   506  	}
   507  }
   508  
   509  func TestGetNoStoreResponse(t *testing.T) {
   510  	resetTest()
   511  	req, err := http.NewRequest("GET", s.server.URL+"/nostore", nil)
   512  	if err != nil {
   513  		t.Fatal(err)
   514  	}
   515  	{
   516  		resp, err := s.client.Do(req)
   517  		if err != nil {
   518  			t.Fatal(err)
   519  		}
   520  		defer resp.Body.Close()
   521  		if resp.Header.Get(XFromCache) != "" {
   522  			t.Fatal("XFromCache header isn't blank")
   523  		}
   524  	}
   525  	{
   526  		resp, err := s.client.Do(req)
   527  		if err != nil {
   528  			t.Fatal(err)
   529  		}
   530  		defer resp.Body.Close()
   531  		if resp.Header.Get(XFromCache) != "" {
   532  			t.Fatal("XFromCache header isn't blank")
   533  		}
   534  	}
   535  }
   536  
   537  func TestGetWithEtag(t *testing.T) {
   538  	resetTest()
   539  	req, err := http.NewRequest("GET", s.server.URL+"/etag", nil)
   540  	if err != nil {
   541  		t.Fatal(err)
   542  	}
   543  	{
   544  		resp, err := s.client.Do(req)
   545  		if err != nil {
   546  			t.Fatal(err)
   547  		}
   548  		defer resp.Body.Close()
   549  		if resp.Header.Get(XFromCache) != "" {
   550  			t.Fatal("XFromCache header isn't blank")
   551  		}
   552  		_, err = ioutil.ReadAll(resp.Body)
   553  		if err != nil {
   554  			t.Fatal(err)
   555  		}
   556  
   557  	}
   558  	{
   559  		resp, err := s.client.Do(req)
   560  		if err != nil {
   561  			t.Fatal(err)
   562  		}
   563  		defer resp.Body.Close()
   564  		if resp.Header.Get(XFromCache) != "1" {
   565  			t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
   566  		}
   567  		// additional assertions to verify that 304 response is converted properly
   568  		if resp.StatusCode != http.StatusOK {
   569  			t.Fatalf("response status code isn't 200 OK: %v", resp.StatusCode)
   570  		}
   571  		if _, ok := resp.Header["Connection"]; ok {
   572  			t.Fatalf("Connection header isn't absent")
   573  		}
   574  	}
   575  }
   576  
   577  func TestGetWithLastModified(t *testing.T) {
   578  	resetTest()
   579  	req, err := http.NewRequest("GET", s.server.URL+"/lastmodified", nil)
   580  	if err != nil {
   581  		t.Fatal(err)
   582  	}
   583  	{
   584  		resp, err := s.client.Do(req)
   585  		if err != nil {
   586  			t.Fatal(err)
   587  		}
   588  		defer resp.Body.Close()
   589  		if resp.Header.Get(XFromCache) != "" {
   590  			t.Fatal("XFromCache header isn't blank")
   591  		}
   592  		_, err = ioutil.ReadAll(resp.Body)
   593  		if err != nil {
   594  			t.Fatal(err)
   595  		}
   596  	}
   597  	{
   598  		resp, err := s.client.Do(req)
   599  		if err != nil {
   600  			t.Fatal(err)
   601  		}
   602  		defer resp.Body.Close()
   603  		if resp.Header.Get(XFromCache) != "1" {
   604  			t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
   605  		}
   606  	}
   607  }
   608  
   609  func TestGetWithVary(t *testing.T) {
   610  	resetTest()
   611  	req, err := http.NewRequest("GET", s.server.URL+"/varyaccept", nil)
   612  	if err != nil {
   613  		t.Fatal(err)
   614  	}
   615  	req.Header.Set("Accept", "text/plain")
   616  	{
   617  		resp, err := s.client.Do(req)
   618  		if err != nil {
   619  			t.Fatal(err)
   620  		}
   621  		defer resp.Body.Close()
   622  		if resp.Header.Get("Vary") != "Accept" {
   623  			t.Fatalf(`Vary header isn't "Accept": %v`, resp.Header.Get("Vary"))
   624  		}
   625  		_, err = ioutil.ReadAll(resp.Body)
   626  		if err != nil {
   627  			t.Fatal(err)
   628  		}
   629  	}
   630  	{
   631  		resp, err := s.client.Do(req)
   632  		if err != nil {
   633  			t.Fatal(err)
   634  		}
   635  		defer resp.Body.Close()
   636  		if resp.Header.Get(XFromCache) != "1" {
   637  			t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
   638  		}
   639  	}
   640  	req.Header.Set("Accept", "text/html")
   641  	{
   642  		resp, err := s.client.Do(req)
   643  		if err != nil {
   644  			t.Fatal(err)
   645  		}
   646  		defer resp.Body.Close()
   647  		if resp.Header.Get(XFromCache) != "" {
   648  			t.Fatal("XFromCache header isn't blank")
   649  		}
   650  	}
   651  	req.Header.Set("Accept", "")
   652  	{
   653  		resp, err := s.client.Do(req)
   654  		if err != nil {
   655  			t.Fatal(err)
   656  		}
   657  		defer resp.Body.Close()
   658  		if resp.Header.Get(XFromCache) != "" {
   659  			t.Fatal("XFromCache header isn't blank")
   660  		}
   661  	}
   662  }
   663  
   664  func TestGetWithDoubleVary(t *testing.T) {
   665  	resetTest()
   666  	req, err := http.NewRequest("GET", s.server.URL+"/doublevary", nil)
   667  	if err != nil {
   668  		t.Fatal(err)
   669  	}
   670  	req.Header.Set("Accept", "text/plain")
   671  	req.Header.Set("Accept-Language", "da, en-gb;q=0.8, en;q=0.7")
   672  	{
   673  		resp, err := s.client.Do(req)
   674  		if err != nil {
   675  			t.Fatal(err)
   676  		}
   677  		defer resp.Body.Close()
   678  		if resp.Header.Get("Vary") == "" {
   679  			t.Fatalf(`Vary header is blank`)
   680  		}
   681  		_, err = ioutil.ReadAll(resp.Body)
   682  		if err != nil {
   683  			t.Fatal(err)
   684  		}
   685  	}
   686  	{
   687  		resp, err := s.client.Do(req)
   688  		if err != nil {
   689  			t.Fatal(err)
   690  		}
   691  		defer resp.Body.Close()
   692  		if resp.Header.Get(XFromCache) != "1" {
   693  			t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
   694  		}
   695  	}
   696  	req.Header.Set("Accept-Language", "")
   697  	{
   698  		resp, err := s.client.Do(req)
   699  		if err != nil {
   700  			t.Fatal(err)
   701  		}
   702  		defer resp.Body.Close()
   703  		if resp.Header.Get(XFromCache) != "" {
   704  			t.Fatal("XFromCache header isn't blank")
   705  		}
   706  	}
   707  	req.Header.Set("Accept-Language", "da")
   708  	{
   709  		resp, err := s.client.Do(req)
   710  		if err != nil {
   711  			t.Fatal(err)
   712  		}
   713  		defer resp.Body.Close()
   714  		if resp.Header.Get(XFromCache) != "" {
   715  			t.Fatal("XFromCache header isn't blank")
   716  		}
   717  	}
   718  }
   719  
   720  func TestGetWith2VaryHeaders(t *testing.T) {
   721  	resetTest()
   722  	// Tests that multiple Vary headers' comma-separated lists are
   723  	// merged. See https://github.com/gregjones/httpcache/issues/27.
   724  	const (
   725  		accept         = "text/plain"
   726  		acceptLanguage = "da, en-gb;q=0.8, en;q=0.7"
   727  	)
   728  	req, err := http.NewRequest("GET", s.server.URL+"/2varyheaders", nil)
   729  	if err != nil {
   730  		t.Fatal(err)
   731  	}
   732  	req.Header.Set("Accept", accept)
   733  	req.Header.Set("Accept-Language", acceptLanguage)
   734  	{
   735  		resp, err := s.client.Do(req)
   736  		if err != nil {
   737  			t.Fatal(err)
   738  		}
   739  		defer resp.Body.Close()
   740  		if resp.Header.Get("Vary") == "" {
   741  			t.Fatalf(`Vary header is blank`)
   742  		}
   743  		_, err = ioutil.ReadAll(resp.Body)
   744  		if err != nil {
   745  			t.Fatal(err)
   746  		}
   747  	}
   748  	{
   749  		resp, err := s.client.Do(req)
   750  		if err != nil {
   751  			t.Fatal(err)
   752  		}
   753  		defer resp.Body.Close()
   754  		if resp.Header.Get(XFromCache) != "1" {
   755  			t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
   756  		}
   757  	}
   758  	req.Header.Set("Accept-Language", "")
   759  	{
   760  		resp, err := s.client.Do(req)
   761  		if err != nil {
   762  			t.Fatal(err)
   763  		}
   764  		defer resp.Body.Close()
   765  		if resp.Header.Get(XFromCache) != "" {
   766  			t.Fatal("XFromCache header isn't blank")
   767  		}
   768  	}
   769  	req.Header.Set("Accept-Language", "da")
   770  	{
   771  		resp, err := s.client.Do(req)
   772  		if err != nil {
   773  			t.Fatal(err)
   774  		}
   775  		defer resp.Body.Close()
   776  		if resp.Header.Get(XFromCache) != "" {
   777  			t.Fatal("XFromCache header isn't blank")
   778  		}
   779  	}
   780  	req.Header.Set("Accept-Language", acceptLanguage)
   781  	req.Header.Set("Accept", "")
   782  	{
   783  		resp, err := s.client.Do(req)
   784  		if err != nil {
   785  			t.Fatal(err)
   786  		}
   787  		defer resp.Body.Close()
   788  		if resp.Header.Get(XFromCache) != "" {
   789  			t.Fatal("XFromCache header isn't blank")
   790  		}
   791  	}
   792  	req.Header.Set("Accept", "image/png")
   793  	{
   794  		resp, err := s.client.Do(req)
   795  		if err != nil {
   796  			t.Fatal(err)
   797  		}
   798  		defer resp.Body.Close()
   799  		if resp.Header.Get(XFromCache) != "" {
   800  			t.Fatal("XFromCache header isn't blank")
   801  		}
   802  		_, err = ioutil.ReadAll(resp.Body)
   803  		if err != nil {
   804  			t.Fatal(err)
   805  		}
   806  	}
   807  	{
   808  		resp, err := s.client.Do(req)
   809  		if err != nil {
   810  			t.Fatal(err)
   811  		}
   812  		defer resp.Body.Close()
   813  		if resp.Header.Get(XFromCache) != "1" {
   814  			t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
   815  		}
   816  	}
   817  }
   818  
   819  func TestGetVaryUnused(t *testing.T) {
   820  	resetTest()
   821  	req, err := http.NewRequest("GET", s.server.URL+"/varyunused", nil)
   822  	if err != nil {
   823  		t.Fatal(err)
   824  	}
   825  	req.Header.Set("Accept", "text/plain")
   826  	{
   827  		resp, err := s.client.Do(req)
   828  		if err != nil {
   829  			t.Fatal(err)
   830  		}
   831  		defer resp.Body.Close()
   832  		if resp.Header.Get("Vary") == "" {
   833  			t.Fatalf(`Vary header is blank`)
   834  		}
   835  		_, err = ioutil.ReadAll(resp.Body)
   836  		if err != nil {
   837  			t.Fatal(err)
   838  		}
   839  	}
   840  	{
   841  		resp, err := s.client.Do(req)
   842  		if err != nil {
   843  			t.Fatal(err)
   844  		}
   845  		defer resp.Body.Close()
   846  		if resp.Header.Get(XFromCache) != "1" {
   847  			t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
   848  		}
   849  	}
   850  }
   851  
   852  func TestUpdateFields(t *testing.T) {
   853  	resetTest()
   854  	req, err := http.NewRequest("GET", s.server.URL+"/updatefields", nil)
   855  	if err != nil {
   856  		t.Fatal(err)
   857  	}
   858  	var counter, counter2 string
   859  	{
   860  		resp, err := s.client.Do(req)
   861  		if err != nil {
   862  			t.Fatal(err)
   863  		}
   864  		defer resp.Body.Close()
   865  		counter = resp.Header.Get("x-counter")
   866  		_, err = ioutil.ReadAll(resp.Body)
   867  		if err != nil {
   868  			t.Fatal(err)
   869  		}
   870  	}
   871  	{
   872  		resp, err := s.client.Do(req)
   873  		if err != nil {
   874  			t.Fatal(err)
   875  		}
   876  		defer resp.Body.Close()
   877  		if resp.Header.Get(XFromCache) != "1" {
   878  			t.Fatalf(`XFromCache header isn't "1": %v`, resp.Header.Get(XFromCache))
   879  		}
   880  		counter2 = resp.Header.Get("x-counter")
   881  	}
   882  	if counter == counter2 {
   883  		t.Fatalf(`both "x-counter" values are equal: %v %v`, counter, counter2)
   884  	}
   885  }
   886  
   887  // This tests the fix for https://github.com/gregjones/httpcache/issues/74.
   888  // Previously, after validating a cached response, its StatusCode
   889  // was incorrectly being replaced.
   890  func TestCachedErrorsKeepStatus(t *testing.T) {
   891  	resetTest()
   892  	req, err := http.NewRequest("GET", s.server.URL+"/cachederror", nil)
   893  	if err != nil {
   894  		t.Fatal(err)
   895  	}
   896  	{
   897  		resp, err := s.client.Do(req)
   898  		if err != nil {
   899  			t.Fatal(err)
   900  		}
   901  		defer resp.Body.Close()
   902  		io.Copy(ioutil.Discard, resp.Body)
   903  	}
   904  	{
   905  		resp, err := s.client.Do(req)
   906  		if err != nil {
   907  			t.Fatal(err)
   908  		}
   909  		defer resp.Body.Close()
   910  		if resp.StatusCode != http.StatusNotFound {
   911  			t.Fatalf("Status code isn't 404: %d", resp.StatusCode)
   912  		}
   913  	}
   914  }
   915  
   916  func TestParseCacheControl(t *testing.T) {
   917  	resetTest()
   918  	h := http.Header{}
   919  	for range parseCacheControl(h) {
   920  		t.Fatal("cacheControl should be empty")
   921  	}
   922  
   923  	h.Set("cache-control", "no-cache")
   924  	{
   925  		cc := parseCacheControl(h)
   926  		if _, ok := cc["foo"]; ok {
   927  			t.Error(`Value "foo" shouldn't exist`)
   928  		}
   929  		noCache, ok := cc["no-cache"]
   930  		if !ok {
   931  			t.Fatalf(`"no-cache" value isn't set`)
   932  		}
   933  		if noCache != "" {
   934  			t.Fatalf(`"no-cache" value isn't blank: %v`, noCache)
   935  		}
   936  	}
   937  	h.Set("cache-control", "no-cache, max-age=3600")
   938  	{
   939  		cc := parseCacheControl(h)
   940  		noCache, ok := cc["no-cache"]
   941  		if !ok {
   942  			t.Fatalf(`"no-cache" value isn't set`)
   943  		}
   944  		if noCache != "" {
   945  			t.Fatalf(`"no-cache" value isn't blank: %v`, noCache)
   946  		}
   947  		if cc["max-age"] != "3600" {
   948  			t.Fatalf(`"max-age" value isn't "3600": %v`, cc["max-age"])
   949  		}
   950  	}
   951  }
   952  
   953  func TestNoCacheRequestExpiration(t *testing.T) {
   954  	resetTest()
   955  	respHeaders := http.Header{}
   956  	respHeaders.Set("Cache-Control", "max-age=7200")
   957  
   958  	reqHeaders := http.Header{}
   959  	reqHeaders.Set("Cache-Control", "no-cache")
   960  	if getFreshness(respHeaders, reqHeaders) != transparent {
   961  		t.Fatal("freshness isn't transparent")
   962  	}
   963  }
   964  
   965  func TestNoCacheResponseExpiration(t *testing.T) {
   966  	resetTest()
   967  	respHeaders := http.Header{}
   968  	respHeaders.Set("Cache-Control", "no-cache")
   969  	respHeaders.Set("Expires", "Wed, 19 Apr 3000 11:43:00 GMT")
   970  
   971  	reqHeaders := http.Header{}
   972  	if getFreshness(respHeaders, reqHeaders) != stale {
   973  		t.Fatal("freshness isn't stale")
   974  	}
   975  }
   976  
   977  func TestReqMustRevalidate(t *testing.T) {
   978  	resetTest()
   979  	// not paying attention to request setting max-stale means never returning stale
   980  	// responses, so always acting as if must-revalidate is set
   981  	respHeaders := http.Header{}
   982  
   983  	reqHeaders := http.Header{}
   984  	reqHeaders.Set("Cache-Control", "must-revalidate")
   985  	if getFreshness(respHeaders, reqHeaders) != stale {
   986  		t.Fatal("freshness isn't stale")
   987  	}
   988  }
   989  
   990  func TestRespMustRevalidate(t *testing.T) {
   991  	resetTest()
   992  	respHeaders := http.Header{}
   993  	respHeaders.Set("Cache-Control", "must-revalidate")
   994  
   995  	reqHeaders := http.Header{}
   996  	if getFreshness(respHeaders, reqHeaders) != stale {
   997  		t.Fatal("freshness isn't stale")
   998  	}
   999  }
  1000  
  1001  func TestFreshExpiration(t *testing.T) {
  1002  	resetTest()
  1003  	now := time.Now()
  1004  	respHeaders := http.Header{}
  1005  	respHeaders.Set("date", now.Format(time.RFC1123))
  1006  	respHeaders.Set("expires", now.Add(time.Duration(2)*time.Second).Format(time.RFC1123))
  1007  
  1008  	reqHeaders := http.Header{}
  1009  	if getFreshness(respHeaders, reqHeaders) != fresh {
  1010  		t.Fatal("freshness isn't fresh")
  1011  	}
  1012  
  1013  	clock = &fakeClock{elapsed: 3 * time.Second}
  1014  	if getFreshness(respHeaders, reqHeaders) != stale {
  1015  		t.Fatal("freshness isn't stale")
  1016  	}
  1017  }
  1018  
  1019  func TestMaxAge(t *testing.T) {
  1020  	resetTest()
  1021  	now := time.Now()
  1022  	respHeaders := http.Header{}
  1023  	respHeaders.Set("date", now.Format(time.RFC1123))
  1024  	respHeaders.Set("cache-control", "max-age=2")
  1025  
  1026  	reqHeaders := http.Header{}
  1027  	if getFreshness(respHeaders, reqHeaders) != fresh {
  1028  		t.Fatal("freshness isn't fresh")
  1029  	}
  1030  
  1031  	clock = &fakeClock{elapsed: 3 * time.Second}
  1032  	if getFreshness(respHeaders, reqHeaders) != stale {
  1033  		t.Fatal("freshness isn't stale")
  1034  	}
  1035  }
  1036  
  1037  func TestMaxAgeZero(t *testing.T) {
  1038  	resetTest()
  1039  	now := time.Now()
  1040  	respHeaders := http.Header{}
  1041  	respHeaders.Set("date", now.Format(time.RFC1123))
  1042  	respHeaders.Set("cache-control", "max-age=0")
  1043  
  1044  	reqHeaders := http.Header{}
  1045  	if getFreshness(respHeaders, reqHeaders) != stale {
  1046  		t.Fatal("freshness isn't stale")
  1047  	}
  1048  }
  1049  
  1050  func TestBothMaxAge(t *testing.T) {
  1051  	resetTest()
  1052  	now := time.Now()
  1053  	respHeaders := http.Header{}
  1054  	respHeaders.Set("date", now.Format(time.RFC1123))
  1055  	respHeaders.Set("cache-control", "max-age=2")
  1056  
  1057  	reqHeaders := http.Header{}
  1058  	reqHeaders.Set("cache-control", "max-age=0")
  1059  	if getFreshness(respHeaders, reqHeaders) != stale {
  1060  		t.Fatal("freshness isn't stale")
  1061  	}
  1062  }
  1063  
  1064  func TestMinFreshWithExpires(t *testing.T) {
  1065  	resetTest()
  1066  	now := time.Now()
  1067  	respHeaders := http.Header{}
  1068  	respHeaders.Set("date", now.Format(time.RFC1123))
  1069  	respHeaders.Set("expires", now.Add(time.Duration(2)*time.Second).Format(time.RFC1123))
  1070  
  1071  	reqHeaders := http.Header{}
  1072  	reqHeaders.Set("cache-control", "min-fresh=1")
  1073  	if getFreshness(respHeaders, reqHeaders) != fresh {
  1074  		t.Fatal("freshness isn't fresh")
  1075  	}
  1076  
  1077  	reqHeaders = http.Header{}
  1078  	reqHeaders.Set("cache-control", "min-fresh=2")
  1079  	if getFreshness(respHeaders, reqHeaders) != stale {
  1080  		t.Fatal("freshness isn't stale")
  1081  	}
  1082  }
  1083  
  1084  func TestEmptyMaxStale(t *testing.T) {
  1085  	resetTest()
  1086  	now := time.Now()
  1087  	respHeaders := http.Header{}
  1088  	respHeaders.Set("date", now.Format(time.RFC1123))
  1089  	respHeaders.Set("cache-control", "max-age=20")
  1090  
  1091  	reqHeaders := http.Header{}
  1092  	reqHeaders.Set("cache-control", "max-stale")
  1093  	clock = &fakeClock{elapsed: 10 * time.Second}
  1094  	if getFreshness(respHeaders, reqHeaders) != fresh {
  1095  		t.Fatal("freshness isn't fresh")
  1096  	}
  1097  
  1098  	clock = &fakeClock{elapsed: 60 * time.Second}
  1099  	if getFreshness(respHeaders, reqHeaders) != fresh {
  1100  		t.Fatal("freshness isn't fresh")
  1101  	}
  1102  }
  1103  
  1104  func TestMaxStaleValue(t *testing.T) {
  1105  	resetTest()
  1106  	now := time.Now()
  1107  	respHeaders := http.Header{}
  1108  	respHeaders.Set("date", now.Format(time.RFC1123))
  1109  	respHeaders.Set("cache-control", "max-age=10")
  1110  
  1111  	reqHeaders := http.Header{}
  1112  	reqHeaders.Set("cache-control", "max-stale=20")
  1113  	clock = &fakeClock{elapsed: 5 * time.Second}
  1114  	if getFreshness(respHeaders, reqHeaders) != fresh {
  1115  		t.Fatal("freshness isn't fresh")
  1116  	}
  1117  
  1118  	clock = &fakeClock{elapsed: 15 * time.Second}
  1119  	if getFreshness(respHeaders, reqHeaders) != fresh {
  1120  		t.Fatal("freshness isn't fresh")
  1121  	}
  1122  
  1123  	clock = &fakeClock{elapsed: 30 * time.Second}
  1124  	if getFreshness(respHeaders, reqHeaders) != stale {
  1125  		t.Fatal("freshness isn't stale")
  1126  	}
  1127  }
  1128  
  1129  func containsHeader(headers []string, header string) bool {
  1130  	for _, v := range headers {
  1131  		if http.CanonicalHeaderKey(v) == http.CanonicalHeaderKey(header) {
  1132  			return true
  1133  		}
  1134  	}
  1135  	return false
  1136  }
  1137  
  1138  func TestGetEndToEndHeaders(t *testing.T) {
  1139  	resetTest()
  1140  	var (
  1141  		headers http.Header
  1142  		end2end []string
  1143  	)
  1144  
  1145  	headers = http.Header{}
  1146  	headers.Set("content-type", "text/html")
  1147  	headers.Set("te", "deflate")
  1148  
  1149  	end2end = getEndToEndHeaders(headers)
  1150  	if !containsHeader(end2end, "content-type") {
  1151  		t.Fatal(`doesn't contain "content-type" header`)
  1152  	}
  1153  	if containsHeader(end2end, "te") {
  1154  		t.Fatal(`doesn't contain "te" header`)
  1155  	}
  1156  
  1157  	headers = http.Header{}
  1158  	headers.Set("connection", "content-type")
  1159  	headers.Set("content-type", "text/csv")
  1160  	headers.Set("te", "deflate")
  1161  	end2end = getEndToEndHeaders(headers)
  1162  	if containsHeader(end2end, "connection") {
  1163  		t.Fatal(`doesn't contain "connection" header`)
  1164  	}
  1165  	if containsHeader(end2end, "content-type") {
  1166  		t.Fatal(`doesn't contain "content-type" header`)
  1167  	}
  1168  	if containsHeader(end2end, "te") {
  1169  		t.Fatal(`doesn't contain "te" header`)
  1170  	}
  1171  
  1172  	headers = http.Header{}
  1173  	end2end = getEndToEndHeaders(headers)
  1174  	if len(end2end) != 0 {
  1175  		t.Fatal(`non-zero end2end headers`)
  1176  	}
  1177  
  1178  	headers = http.Header{}
  1179  	headers.Set("connection", "content-type")
  1180  	end2end = getEndToEndHeaders(headers)
  1181  	if len(end2end) != 0 {
  1182  		t.Fatal(`non-zero end2end headers`)
  1183  	}
  1184  }
  1185  
  1186  type transportMock struct {
  1187  	response *http.Response
  1188  	err      error
  1189  }
  1190  
  1191  func (t transportMock) RoundTrip(req *http.Request) (resp *http.Response, err error) {
  1192  	return t.response, t.err
  1193  }
  1194  
  1195  func TestStaleIfErrorRequest(t *testing.T) {
  1196  	resetTest()
  1197  	now := time.Now()
  1198  	tmock := transportMock{
  1199  		response: &http.Response{
  1200  			Status:     http.StatusText(http.StatusOK),
  1201  			StatusCode: http.StatusOK,
  1202  			Header: http.Header{
  1203  				"Date":          []string{now.Format(time.RFC1123)},
  1204  				"Cache-Control": []string{"no-cache"},
  1205  			},
  1206  			Body: ioutil.NopCloser(bytes.NewBuffer([]byte("some data"))),
  1207  		},
  1208  		err: nil,
  1209  	}
  1210  	tp := NewMemoryCacheTransport()
  1211  	tp.Transport = &tmock
  1212  
  1213  	// First time, response is cached on success
  1214  	r, _ := http.NewRequest("GET", "http://somewhere.com/", nil)
  1215  	r.Header.Set("Cache-Control", "stale-if-error")
  1216  	resp, err := tp.RoundTrip(r)
  1217  	if err != nil {
  1218  		t.Fatal(err)
  1219  	}
  1220  	if resp == nil {
  1221  		t.Fatal("resp is nil")
  1222  	}
  1223  	_, err = ioutil.ReadAll(resp.Body)
  1224  	if err != nil {
  1225  		t.Fatal(err)
  1226  	}
  1227  
  1228  	// On failure, response is returned from the cache
  1229  	tmock.response = nil
  1230  	tmock.err = errors.New("some error")
  1231  	resp, err = tp.RoundTrip(r)
  1232  	if err != nil {
  1233  		t.Fatal(err)
  1234  	}
  1235  	if resp == nil {
  1236  		t.Fatal("resp is nil")
  1237  	}
  1238  }
  1239  
  1240  func TestStaleIfErrorRequestLifetime(t *testing.T) {
  1241  	resetTest()
  1242  	now := time.Now()
  1243  	tmock := transportMock{
  1244  		response: &http.Response{
  1245  			Status:     http.StatusText(http.StatusOK),
  1246  			StatusCode: http.StatusOK,
  1247  			Header: http.Header{
  1248  				"Date":          []string{now.Format(time.RFC1123)},
  1249  				"Cache-Control": []string{"no-cache"},
  1250  			},
  1251  			Body: ioutil.NopCloser(bytes.NewBuffer([]byte("some data"))),
  1252  		},
  1253  		err: nil,
  1254  	}
  1255  	tp := NewMemoryCacheTransport()
  1256  	tp.Transport = &tmock
  1257  
  1258  	// First time, response is cached on success
  1259  	r, _ := http.NewRequest("GET", "http://somewhere.com/", nil)
  1260  	r.Header.Set("Cache-Control", "stale-if-error=100")
  1261  	resp, err := tp.RoundTrip(r)
  1262  	if err != nil {
  1263  		t.Fatal(err)
  1264  	}
  1265  	if resp == nil {
  1266  		t.Fatal("resp is nil")
  1267  	}
  1268  	_, err = ioutil.ReadAll(resp.Body)
  1269  	if err != nil {
  1270  		t.Fatal(err)
  1271  	}
  1272  
  1273  	// On failure, response is returned from the cache
  1274  	tmock.response = nil
  1275  	tmock.err = errors.New("some error")
  1276  	resp, err = tp.RoundTrip(r)
  1277  	if err != nil {
  1278  		t.Fatal(err)
  1279  	}
  1280  	if resp == nil {
  1281  		t.Fatal("resp is nil")
  1282  	}
  1283  
  1284  	// Same for http errors
  1285  	tmock.response = &http.Response{StatusCode: http.StatusInternalServerError}
  1286  	tmock.err = nil
  1287  	resp, err = tp.RoundTrip(r)
  1288  	if err != nil {
  1289  		t.Fatal(err)
  1290  	}
  1291  	if resp == nil {
  1292  		t.Fatal("resp is nil")
  1293  	}
  1294  
  1295  	// If failure last more than max stale, error is returned
  1296  	clock = &fakeClock{elapsed: 200 * time.Second}
  1297  	_, err = tp.RoundTrip(r)
  1298  	if err != tmock.err {
  1299  		t.Fatalf("got err %v, want %v", err, tmock.err)
  1300  	}
  1301  }
  1302  
  1303  func TestStaleIfErrorResponse(t *testing.T) {
  1304  	resetTest()
  1305  	now := time.Now()
  1306  	tmock := transportMock{
  1307  		response: &http.Response{
  1308  			Status:     http.StatusText(http.StatusOK),
  1309  			StatusCode: http.StatusOK,
  1310  			Header: http.Header{
  1311  				"Date":          []string{now.Format(time.RFC1123)},
  1312  				"Cache-Control": []string{"no-cache, stale-if-error"},
  1313  			},
  1314  			Body: ioutil.NopCloser(bytes.NewBuffer([]byte("some data"))),
  1315  		},
  1316  		err: nil,
  1317  	}
  1318  	tp := NewMemoryCacheTransport()
  1319  	tp.Transport = &tmock
  1320  
  1321  	// First time, response is cached on success
  1322  	r, _ := http.NewRequest("GET", "http://somewhere.com/", nil)
  1323  	resp, err := tp.RoundTrip(r)
  1324  	if err != nil {
  1325  		t.Fatal(err)
  1326  	}
  1327  	if resp == nil {
  1328  		t.Fatal("resp is nil")
  1329  	}
  1330  	_, err = ioutil.ReadAll(resp.Body)
  1331  	if err != nil {
  1332  		t.Fatal(err)
  1333  	}
  1334  
  1335  	// On failure, response is returned from the cache
  1336  	tmock.response = nil
  1337  	tmock.err = errors.New("some error")
  1338  	resp, err = tp.RoundTrip(r)
  1339  	if err != nil {
  1340  		t.Fatal(err)
  1341  	}
  1342  	if resp == nil {
  1343  		t.Fatal("resp is nil")
  1344  	}
  1345  }
  1346  
  1347  func TestStaleIfErrorResponseLifetime(t *testing.T) {
  1348  	resetTest()
  1349  	now := time.Now()
  1350  	tmock := transportMock{
  1351  		response: &http.Response{
  1352  			Status:     http.StatusText(http.StatusOK),
  1353  			StatusCode: http.StatusOK,
  1354  			Header: http.Header{
  1355  				"Date":          []string{now.Format(time.RFC1123)},
  1356  				"Cache-Control": []string{"no-cache, stale-if-error=100"},
  1357  			},
  1358  			Body: ioutil.NopCloser(bytes.NewBuffer([]byte("some data"))),
  1359  		},
  1360  		err: nil,
  1361  	}
  1362  	tp := NewMemoryCacheTransport()
  1363  	tp.Transport = &tmock
  1364  
  1365  	// First time, response is cached on success
  1366  	r, _ := http.NewRequest("GET", "http://somewhere.com/", nil)
  1367  	resp, err := tp.RoundTrip(r)
  1368  	if err != nil {
  1369  		t.Fatal(err)
  1370  	}
  1371  	if resp == nil {
  1372  		t.Fatal("resp is nil")
  1373  	}
  1374  	_, err = ioutil.ReadAll(resp.Body)
  1375  	if err != nil {
  1376  		t.Fatal(err)
  1377  	}
  1378  
  1379  	// On failure, response is returned from the cache
  1380  	tmock.response = nil
  1381  	tmock.err = errors.New("some error")
  1382  	resp, err = tp.RoundTrip(r)
  1383  	if err != nil {
  1384  		t.Fatal(err)
  1385  	}
  1386  	if resp == nil {
  1387  		t.Fatal("resp is nil")
  1388  	}
  1389  
  1390  	// If failure last more than max stale, error is returned
  1391  	clock = &fakeClock{elapsed: 200 * time.Second}
  1392  	_, err = tp.RoundTrip(r)
  1393  	if err != tmock.err {
  1394  		t.Fatalf("got err %v, want %v", err, tmock.err)
  1395  	}
  1396  }
  1397  
  1398  // This tests the fix for https://github.com/gregjones/httpcache/issues/74.
  1399  // Previously, after a stale response was used after encountering an error,
  1400  // its StatusCode was being incorrectly replaced.
  1401  func TestStaleIfErrorKeepsStatus(t *testing.T) {
  1402  	resetTest()
  1403  	now := time.Now()
  1404  	tmock := transportMock{
  1405  		response: &http.Response{
  1406  			Status:     http.StatusText(http.StatusNotFound),
  1407  			StatusCode: http.StatusNotFound,
  1408  			Header: http.Header{
  1409  				"Date":          []string{now.Format(time.RFC1123)},
  1410  				"Cache-Control": []string{"no-cache"},
  1411  			},
  1412  			Body: ioutil.NopCloser(bytes.NewBuffer([]byte("some data"))),
  1413  		},
  1414  		err: nil,
  1415  	}
  1416  	tp := NewMemoryCacheTransport()
  1417  	tp.Transport = &tmock
  1418  
  1419  	// First time, response is cached on success
  1420  	r, _ := http.NewRequest("GET", "http://somewhere.com/", nil)
  1421  	r.Header.Set("Cache-Control", "stale-if-error")
  1422  	resp, err := tp.RoundTrip(r)
  1423  	if err != nil {
  1424  		t.Fatal(err)
  1425  	}
  1426  	if resp == nil {
  1427  		t.Fatal("resp is nil")
  1428  	}
  1429  	_, err = ioutil.ReadAll(resp.Body)
  1430  	if err != nil {
  1431  		t.Fatal(err)
  1432  	}
  1433  
  1434  	// On failure, response is returned from the cache
  1435  	tmock.response = nil
  1436  	tmock.err = errors.New("some error")
  1437  	resp, err = tp.RoundTrip(r)
  1438  	if err != nil {
  1439  		t.Fatal(err)
  1440  	}
  1441  	if resp == nil {
  1442  		t.Fatal("resp is nil")
  1443  	}
  1444  	if resp.StatusCode != http.StatusNotFound {
  1445  		t.Fatalf("Status wasn't 404: %d", resp.StatusCode)
  1446  	}
  1447  }
  1448  
  1449  // Test that http.Client.Timeout is respected when cache transport is used.
  1450  // That is so as long as request cancellation is propagated correctly.
  1451  // In the past, that required CancelRequest to be implemented correctly,
  1452  // but modern http.Client uses Request.Cancel (or request context) instead,
  1453  // so we don't have to do anything.
  1454  func TestClientTimeout(t *testing.T) {
  1455  	if testing.Short() {
  1456  		t.Skip("skipping timeout test in short mode") // Because it takes at least 3 seconds to run.
  1457  	}
  1458  	resetTest()
  1459  	client := &http.Client{
  1460  		Transport: NewMemoryCacheTransport(),
  1461  		Timeout:   time.Second,
  1462  	}
  1463  	started := time.Now()
  1464  	resp, err := client.Get(s.server.URL + "/3seconds")
  1465  	taken := time.Since(started)
  1466  	if err == nil {
  1467  		t.Error("got nil error, want timeout error")
  1468  	}
  1469  	if resp != nil {
  1470  		t.Error("got non-nil resp, want nil resp")
  1471  	}
  1472  	if taken >= 2*time.Second {
  1473  		t.Error("client.Do took 2+ seconds, want < 2 seconds")
  1474  	}
  1475  }
  1476  

View as plain text