|
|
@@ -161,49 +161,112 @@ func TestBuildResponseWithCachingEnabled(t *testing.T) {
|
|
|
t.Fatalf(`Unexpected cache control header, got %q instead of %q`, actualHeader, expectedHeader)
|
|
|
}
|
|
|
|
|
|
+ if actualETag := resp.Header.Get("ETag"); actualETag != `"etag"` {
|
|
|
+ t.Fatalf(`Unexpected etag header, got %q instead of %q`, actualETag, `"etag"`)
|
|
|
+ }
|
|
|
+
|
|
|
if resp.Header.Get("Expires") == "" {
|
|
|
t.Fatalf(`Expires header should not be empty`)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func TestBuildResponseWithCachingAndEtag(t *testing.T) {
|
|
|
- r, err := http.NewRequest("GET", "/", nil)
|
|
|
- r.Header.Set("If-None-Match", "etag")
|
|
|
- if err != nil {
|
|
|
- t.Fatal(err)
|
|
|
- }
|
|
|
-
|
|
|
- w := httptest.NewRecorder()
|
|
|
-
|
|
|
- handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
- New(w, r).WithCaching("etag", 1*time.Minute, func(b *Builder) {
|
|
|
- b.WithBodyAsString("cached body")
|
|
|
- b.Write()
|
|
|
+func TestBuildResponseWithCachingAndIfNoneMatch(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ ifNoneMatch string
|
|
|
+ expectedStatus int
|
|
|
+ expectedBody string
|
|
|
+ }{
|
|
|
+ {"matching strong etag", `"etag"`, http.StatusNotModified, ""},
|
|
|
+ {"matching weak etag", `W/"etag"`, http.StatusNotModified, ""},
|
|
|
+ {"multiple etags with match", `"other", W/"etag"`, http.StatusNotModified, ""},
|
|
|
+ {"wildcard", `*`, http.StatusNotModified, ""},
|
|
|
+ {"non-matching etag", `"different"`, http.StatusOK, "cached body"},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ r, err := http.NewRequest("GET", "/", nil)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ r.Header.Set("If-None-Match", tt.ifNoneMatch)
|
|
|
+
|
|
|
+ w := httptest.NewRecorder()
|
|
|
+
|
|
|
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
+ New(w, r).WithCaching("etag", 1*time.Minute, func(b *Builder) {
|
|
|
+ b.WithBodyAsString("cached body")
|
|
|
+ b.Write()
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ handler.ServeHTTP(w, r)
|
|
|
+ resp := w.Result()
|
|
|
+
|
|
|
+ if resp.StatusCode != tt.expectedStatus {
|
|
|
+ t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, tt.expectedStatus)
|
|
|
+ }
|
|
|
+
|
|
|
+ if actual := w.Body.String(); actual != tt.expectedBody {
|
|
|
+ t.Fatalf(`Unexpected body, got %q instead of %q`, actual, tt.expectedBody)
|
|
|
+ }
|
|
|
+
|
|
|
+ if resp.Header.Get("Cache-Control") != "public" {
|
|
|
+ t.Fatalf(`Unexpected Cache-Control header: %q`, resp.Header.Get("Cache-Control"))
|
|
|
+ }
|
|
|
+
|
|
|
+ if resp.Header.Get("Expires") == "" {
|
|
|
+ t.Fatalf(`Expires header should not be empty`)
|
|
|
+ }
|
|
|
})
|
|
|
- })
|
|
|
-
|
|
|
- handler.ServeHTTP(w, r)
|
|
|
- resp := w.Result()
|
|
|
-
|
|
|
- expectedStatusCode := http.StatusNotModified
|
|
|
- if resp.StatusCode != expectedStatusCode {
|
|
|
- t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
|
|
|
- }
|
|
|
-
|
|
|
- expectedBody := ``
|
|
|
- actualBody := w.Body.String()
|
|
|
- if actualBody != expectedBody {
|
|
|
- t.Fatalf(`Unexpected body, got %s instead of %s`, actualBody, expectedBody)
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- expectedHeader := "public"
|
|
|
- actualHeader := resp.Header.Get("Cache-Control")
|
|
|
- if actualHeader != expectedHeader {
|
|
|
- t.Fatalf(`Unexpected cache control header, got %q instead of %q`, actualHeader, expectedHeader)
|
|
|
+func TestNormalizeETag(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ input string
|
|
|
+ expected string
|
|
|
+ }{
|
|
|
+ {"abc", `"abc"`},
|
|
|
+ {`"already-quoted"`, `"already-quoted"`},
|
|
|
+ {`W/"weak"`, `W/"weak"`},
|
|
|
+ {"", ""},
|
|
|
+ {" spaced ", `"spaced"`},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.input, func(t *testing.T) {
|
|
|
+ if actual := normalizeETag(tt.input); actual != tt.expected {
|
|
|
+ t.Fatalf(`normalizeETag(%q) = %q, want %q`, tt.input, actual, tt.expected)
|
|
|
+ }
|
|
|
+ })
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- if resp.Header.Get("Expires") == "" {
|
|
|
- t.Fatalf(`Expires header should not be empty`)
|
|
|
+func TestIfNoneMatch(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ headerValue string
|
|
|
+ etag string
|
|
|
+ expected bool
|
|
|
+ }{
|
|
|
+ {"empty header", "", `"etag"`, false},
|
|
|
+ {"empty etag", `"etag"`, "", false},
|
|
|
+ {"exact match", `"etag"`, `"etag"`, true},
|
|
|
+ {"weak vs strong match", `W/"etag"`, `"etag"`, true},
|
|
|
+ {"wildcard", `*`, `"etag"`, true},
|
|
|
+ {"no match", `"other"`, `"etag"`, false},
|
|
|
+ {"match in list", `"a", "etag", "b"`, `"etag"`, true},
|
|
|
+ {"no match in list", `"a", "b", "c"`, `"etag"`, false},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ if actual := ifNoneMatch(tt.headerValue, tt.etag); actual != tt.expected {
|
|
|
+ t.Fatalf(`ifNoneMatch(%q, %q) = %v, want %v`, tt.headerValue, tt.etag, actual, tt.expected)
|
|
|
+ }
|
|
|
+ })
|
|
|
}
|
|
|
}
|
|
|
|