diff --git a/cache_test.go b/cache_test.go new file mode 100644 index 0000000..9b9ac3f --- /dev/null +++ b/cache_test.go @@ -0,0 +1,27 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// 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 imageproxy + +import "testing" + +func TestNopCache(t *testing.T) { + data, ok := NopCache.Get("foo") + if data != nil { + t.Errorf("NopCache.Get returned non-nil data") + } + if ok != false { + t.Errorf("NopCache.Get returned ok = true, should always be false.") + } +} diff --git a/imageproxy_test.go b/imageproxy_test.go index 5965c4e..59d2183 100644 --- a/imageproxy_test.go +++ b/imageproxy_test.go @@ -2,34 +2,45 @@ package imageproxy import ( "bufio" + "bytes" + "errors" + "fmt" + "image" + "image/png" "net/http" + "net/http/httptest" "net/url" "strings" "testing" ) func TestAllowed(t *testing.T) { - p := &Proxy{ - Whitelist: []string{"a.test", "*.b.test", "*c.test"}, - } + whitelist := []string{"a.test", "*.b.test", "*c.test"} tests := []struct { - url string - allowed bool + url string + whitelist []string + allowed bool }{ - {"http://a.test/image", true}, - {"http://x.a.test/image", false}, + {"http://foo/image", nil, true}, + {"http://foo/image", []string{}, true}, - {"http://b.test/image", true}, - {"http://x.b.test/image", true}, - {"http://x.y.b.test/image", true}, + {"http://a.test/image", whitelist, true}, + {"http://x.a.test/image", whitelist, false}, - {"http://c.test/image", false}, - {"http://xc.test/image", false}, - {"/image", false}, + {"http://b.test/image", whitelist, true}, + {"http://x.b.test/image", whitelist, true}, + {"http://x.y.b.test/image", whitelist, true}, + + {"http://c.test/image", whitelist, false}, + {"http://xc.test/image", whitelist, false}, + {"/image", whitelist, false}, } for _, tt := range tests { + p := NewProxy(nil, nil) + p.Whitelist = tt.whitelist + u, err := url.Parse(tt.url) if err != nil { t.Errorf("error parsing url %q: %v", tt.url, err) @@ -112,3 +123,121 @@ func TestCheck304(t *testing.T) { } } } + +// testTransport is an http.RoundTripper that returns certained canned +// responses for particular requests. +type testTransport struct{} + +func (t testTransport) RoundTrip(req *http.Request) (*http.Response, error) { + var raw string + + switch req.URL.Path { + case "/ok": + raw = "HTTP/1.1 200 OK\n\n" + case "/error": + return nil, errors.New("http protocol error") + case "/nocontent": + raw = "HTTP/1.1 204 No Content\n\n" + case "/etag": + raw = "HTTP/1.1 200 OK\nEtag: \"tag\"\n\n" + case "/png": + m := image.NewNRGBA(image.Rect(0, 0, 1, 1)) + img := new(bytes.Buffer) + png.Encode(img, m) + + raw = fmt.Sprintf("HTTP/1.1 200 OK\nContent-Length: %d\n\n%v", len(img.Bytes()), img.Bytes()) + default: + raw = "HTTP/1.1 404 Not Found\n\n" + } + + buf := bufio.NewReader(bytes.NewBufferString(raw)) + return http.ReadResponse(buf, req) +} + +func TestProxy_ServeHTTP(t *testing.T) { + p := &Proxy{ + Client: &http.Client{ + Transport: testTransport{}, + }, + Whitelist: []string{"good.test"}, + } + + tests := []struct { + url string // request URL + code int // expected response status code + }{ + {"/favicon.ico", http.StatusOK}, + {"//foo", http.StatusBadRequest}, // invalid request URL + {"/http://bad.test/", http.StatusBadRequest}, // Disallowed host + {"/http://good.test/error", http.StatusInternalServerError}, // HTTP protocol error + {"/http://good.test/nocontent", http.StatusNoContent}, // non-OK response + + {"/100/http://good.test/ok", http.StatusOK}, + } + + for _, tt := range tests { + req, _ := http.NewRequest("GET", "http://localhost"+tt.url, nil) + resp := httptest.NewRecorder() + p.ServeHTTP(resp, req) + + if got, want := resp.Code, tt.code; got != want { + t.Errorf("ServeHTTP(%q) returned status %d, want %d", req, got, want) + } + } +} + +// test that 304 Not Modified responses are returned properly. +func TestProxy_ServeHTTP_is304(t *testing.T) { + p := &Proxy{ + Client: &http.Client{ + Transport: testTransport{}, + }, + } + + req, _ := http.NewRequest("GET", "http://localhost/http://good.test/etag", nil) + req.Header.Add("If-None-Match", `"tag"`) + resp := httptest.NewRecorder() + p.ServeHTTP(resp, req) + + if got, want := resp.Code, http.StatusNotModified; got != want { + t.Errorf("ServeHTTP(%q) returned status %d, want %d", req, got, want) + } + if got, want := resp.Header().Get("Etag"), `"tag"`; got != want { + t.Errorf("ServeHTTP(%q) returned etag header %v, want %v", req, got, want) + } +} + +func TestTransformingTransport(t *testing.T) { + client := new(http.Client) + tr := &TransformingTransport{testTransport{}, client} + client.Transport = tr + + tests := []struct { + url string + code int + expectError bool + }{ + {"http://good.test/png#1", http.StatusOK, false}, + {"http://good.test/error#1", http.StatusInternalServerError, true}, + // TODO: test more than just status code... verify that image + // is actually transformed and returned properly and that + // non-image responses are returned as-is + } + + for _, tt := range tests { + req, _ := http.NewRequest("GET", tt.url, nil) + + resp, err := tr.RoundTrip(req) + if err != nil { + if !tt.expectError { + t.Errorf("RoundTrip(%v) returned unexpected error: %v", tt.url, err) + } + continue + } else if tt.expectError { + t.Errorf("RoundTrip(%v) did not return expected error", tt.url) + } + if got, want := resp.StatusCode, tt.code; got != want { + t.Errorf("RoundTrip(%v) returned status code %d, want %d", got, want) + } + } +} diff --git a/transform_test.go b/transform_test.go index 750b11c..f5281bb 100644 --- a/transform_test.go +++ b/transform_test.go @@ -1,9 +1,14 @@ package imageproxy import ( + "bytes" "image" "image/color" "image/draw" + "image/gif" + "image/jpeg" + "image/png" + "io" "reflect" "testing" @@ -32,6 +37,52 @@ func newImage(w, h int, pixels ...color.NRGBA) image.Image { return m } +func TestTransform(t *testing.T) { + src := newImage(2, 2, red, green, blue, yellow) + + buf := new(bytes.Buffer) + png.Encode(buf, src) + + tests := []struct { + name string + encode func(io.Writer, image.Image) + exactOutput bool // whether input and output should match exactly + }{ + {"gif", func(w io.Writer, m image.Image) { gif.Encode(w, m, nil) }, true}, + {"jpeg", func(w io.Writer, m image.Image) { jpeg.Encode(w, m, nil) }, false}, + {"png", func(w io.Writer, m image.Image) { png.Encode(w, m) }, true}, + } + + for _, tt := range tests { + buf := new(bytes.Buffer) + tt.encode(buf, src) + in := buf.Bytes() + + out, err := Transform(in, emptyOptions) + if err != nil { + t.Errorf("Transform with encoder %s returned unexpected error: %v", err) + } + if !reflect.DeepEqual(in, out) { + t.Errorf("Transform with with encoder %s with empty options returned modified result") + } + + out, err = Transform(in, Options{Width: -1, Height: -1}) + if err != nil { + t.Errorf("Transform with encoder %s returned unexpected error: %v", tt.name, err) + } + if len(out) == 0 { + t.Errorf("Transform with encoder %s returned empty bytes", tt.name) + } + if tt.exactOutput && !reflect.DeepEqual(in, out) { + t.Errorf("Transform with encoder %s with noop Options returned modified result", tt.name) + } + } + + if _, err := Transform([]byte{}, Options{Width: 1}); err == nil { + t.Errorf("Transform with invalid image input did not return expected err") + } +} + func TestTransformImage(t *testing.T) { // ref is a 2x2 reference image containing four colors ref := newImage(2, 2, red, green, blue, yellow)