| // Copyright 2018 Google LLC |
| // |
| // 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 httpreplay_test |
| |
| // import ( |
| // "bytes" |
| // "context" |
| // "encoding/json" |
| // "fmt" |
| // "io" |
| // "io/ioutil" |
| // "log" |
| // "net/http" |
| // "net/http/httptest" |
| // "os" |
| // "path/filepath" |
| // "testing" |
| // "time" |
| |
| // "cloud.google.com/go/httpreplay" |
| // "cloud.google.com/go/internal/testutil" |
| // "cloud.google.com/go/storage" |
| // "google.golang.org/api/option" |
| // ) |
| |
| // const ( |
| // compressedFile = "httpreplay_compressed.txt" |
| // uncompressedFile = "httpreplay_uncompressed.txt" |
| // ) |
| |
| // func TestIntegration_RecordAndReplay(t *testing.T) { |
| // httpreplay.DebugHeaders() |
| // if testing.Short() { |
| // t.Skip("Integration tests skipped in short mode") |
| // } |
| // replayFilename := tempFilename(t, "RecordAndReplay*.replay") |
| // defer os.Remove(replayFilename) |
| // projectID := testutil.ProjID() |
| // if projectID == "" { |
| // t.Skip("Need project ID. See CONTRIBUTING.md for details.") |
| // } |
| // ctx := context.Background() |
| // cleanup, err := setup(ctx) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // defer cleanup() |
| |
| // // Record. |
| // initial := time.Now() |
| // ibytes, err := json.Marshal(initial) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // rec, err := httpreplay.NewRecorder(replayFilename, ibytes) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // hc, err := rec.Client(ctx, option.WithTokenSource( |
| // testutil.TokenSource(ctx, storage.ScopeFullControl))) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // wanta, wantc := run(t, hc) |
| // testReadCRC(t, hc, "recording") |
| // if err := rec.Close(); err != nil { |
| // t.Fatalf("rec.Close: %v", err) |
| // } |
| |
| // // Replay. |
| // rep, err := httpreplay.NewReplayer(replayFilename) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // defer rep.Close() |
| // hc, err = rep.Client(ctx) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // gota, gotc := run(t, hc) |
| // testReadCRC(t, hc, "replaying") |
| |
| // if diff := testutil.Diff(gota, wanta); diff != "" { |
| // t.Error(diff) |
| // } |
| // if !bytes.Equal(gotc, wantc) { |
| // t.Errorf("got %q, want %q", gotc, wantc) |
| // } |
| // var gotInitial time.Time |
| // if err := json.Unmarshal(rep.Initial(), &gotInitial); err != nil { |
| // t.Fatal(err) |
| // } |
| // if !gotInitial.Equal(initial) { |
| // t.Errorf("initial: got %v, want %v", gotInitial, initial) |
| // } |
| // } |
| |
| // func setup(ctx context.Context) (cleanup func(), err error) { |
| // ts := testutil.TokenSource(ctx, storage.ScopeFullControl) |
| // client, err := storage.NewClient(ctx, option.WithTokenSource(ts)) |
| // if err != nil { |
| // return nil, err |
| // } |
| // bucket := testutil.ProjID() |
| |
| // // upload compressed object |
| // f1, err := os.Open(filepath.Join("internal", "testdata", "compressed.txt")) |
| // if err != nil { |
| // return nil, err |
| // } |
| // defer f1.Close() |
| // w := client.Bucket(bucket).Object(compressedFile).NewWriter(ctx) |
| // w.ContentEncoding = "gzip" |
| // w.ContentType = "text/plain" |
| // if _, err = io.Copy(w, f1); err != nil { |
| // return nil, err |
| // } |
| // if err := w.Close(); err != nil { |
| // return nil, err |
| // } |
| // // upload uncompressed object |
| // f2, err := os.Open(filepath.Join("internal", "testdata", "uncompressed.txt")) |
| // if err != nil { |
| // return nil, err |
| // } |
| // defer f2.Close() |
| // w = client.Bucket(testutil.ProjID()).Object(uncompressedFile).NewWriter(ctx) |
| // if _, err = io.Copy(w, f2); err != nil { |
| // return nil, err |
| // } |
| // if err := w.Close(); err != nil { |
| // return nil, err |
| // } |
| // return func() { |
| // client.Bucket(bucket).Object(compressedFile).Delete(ctx) |
| // client.Bucket(bucket).Object(uncompressedFile).Delete(ctx) |
| // client.Close() |
| // }, nil |
| // } |
| |
| // // TODO(jba): test errors |
| |
| // func run(t *testing.T, hc *http.Client) (*storage.BucketAttrs, []byte) { |
| // ctx := context.Background() |
| // client, err := storage.NewClient(ctx, option.WithHTTPClient(hc)) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // defer client.Close() |
| // b := client.Bucket(testutil.ProjID()) |
| // attrs, err := b.Attrs(ctx) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // obj := b.Object("replay-test") |
| // w := obj.NewWriter(ctx) |
| // data := []byte{150, 151, 152} |
| // if _, err := w.Write(data); err != nil { |
| // t.Fatal(err) |
| // } |
| // if err := w.Close(); err != nil { |
| // t.Fatal(err) |
| // } |
| |
| // r, err := obj.NewReader(ctx) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // defer r.Close() |
| // contents, err := ioutil.ReadAll(r) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| |
| // return attrs, contents |
| // } |
| |
| // func testReadCRC(t *testing.T, hc *http.Client, mode string) { |
| // ctx := context.Background() |
| // client, err := storage.NewClient(ctx, option.WithHTTPClient(hc)) |
| // if err != nil { |
| // t.Fatalf("%s: %v", mode, err) |
| // } |
| // defer client.Close() |
| |
| // bucket := testutil.ProjID() |
| // uncompressedObj := client.Bucket(bucket).Object(uncompressedFile) |
| // gzippedObj := client.Bucket(bucket).Object(compressedFile) |
| |
| // for _, test := range []struct { |
| // desc string |
| // obj *storage.ObjectHandle |
| // offset, length int64 |
| // readCompressed bool // don't decompress a gzipped file |
| |
| // wantErr bool |
| // wantLen int // length of contents |
| // }{ |
| // { |
| // desc: "uncompressed, entire file", |
| // obj: uncompressedObj, |
| // offset: 0, |
| // length: -1, |
| // readCompressed: false, |
| // wantLen: 179, |
| // }, |
| // { |
| // desc: "uncompressed, entire file, don't decompress", |
| // obj: uncompressedObj, |
| // offset: 0, |
| // length: -1, |
| // readCompressed: true, |
| // wantLen: 179, |
| // }, |
| // { |
| // desc: "uncompressed, suffix", |
| // obj: uncompressedObj, |
| // offset: 9, |
| // length: -1, |
| // readCompressed: false, |
| // wantLen: 170, |
| // }, |
| // { |
| // desc: "uncompressed, prefix", |
| // obj: uncompressedObj, |
| // offset: 0, |
| // length: 18, |
| // readCompressed: false, |
| // wantLen: 18, |
| // }, |
| // { |
| // // When a gzipped file is unzipped by GCS, we can't verify the checksum |
| // // because it was computed against the zipped contents. There is no |
| // // header that indicates that a gzipped file is being served unzipped. |
| // // But our CRC check only happens if there is a Content-Length header, |
| // // and that header is absent for this read. |
| // desc: "compressed, entire file, server unzips", |
| // obj: gzippedObj, |
| // offset: 0, |
| // length: -1, |
| // readCompressed: false, |
| // wantLen: 179, |
| // }, |
| // { |
| // // When we read a gzipped file uncompressed, it's like reading a regular file: |
| // // the served content and the CRC match. |
| // desc: "compressed, entire file, read compressed", |
| // obj: gzippedObj, |
| // offset: 0, |
| // length: -1, |
| // readCompressed: true, |
| // wantLen: 128, |
| // }, |
| // { |
| // desc: "compressed, partial, read compressed", |
| // obj: gzippedObj, |
| // offset: 1, |
| // length: 8, |
| // readCompressed: true, |
| // wantLen: 8, |
| // }, |
| // { |
| // desc: "uncompressed, HEAD", |
| // obj: uncompressedObj, |
| // offset: 0, |
| // length: 0, |
| // wantLen: 0, |
| // }, |
| // { |
| // desc: "compressed, HEAD", |
| // obj: gzippedObj, |
| // offset: 0, |
| // length: 0, |
| // wantLen: 0, |
| // }, |
| // } { |
| // obj := test.obj.ReadCompressed(test.readCompressed) |
| // r, err := obj.NewRangeReader(ctx, test.offset, test.length) |
| // if err != nil { |
| // if test.wantErr { |
| // continue |
| // } |
| // t.Errorf("%s: %s: %v", mode, test.desc, err) |
| // continue |
| // } |
| // data, err := ioutil.ReadAll(r) |
| // _ = r.Close() |
| // if err != nil { |
| // t.Errorf("%s: %s: %v", mode, test.desc, err) |
| // continue |
| // } |
| // if got, want := len(data), test.wantLen; got != want { |
| // t.Errorf("%s: %s: len: got %d, want %d", mode, test.desc, got, want) |
| // } |
| // } |
| // } |
| |
| // func TestRemoveAndClear(t *testing.T) { |
| // // Disable logging for this test, since it generates a lot. |
| // log.SetOutput(ioutil.Discard) |
| // srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { |
| // fmt.Fprintln(w, "LGTM") |
| // })) |
| // defer srv.Close() |
| |
| // replayFilename := tempFilename(t, "TestRemoveAndClear*.replay") |
| // defer os.Remove(replayFilename) |
| |
| // ctx := context.Background() |
| // // Record |
| // rec, err := httpreplay.NewRecorder(replayFilename, nil) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // rec.ClearHeaders("Clear") |
| // rec.RemoveRequestHeaders("Rem*") |
| // rec.ClearQueryParams("c") |
| // rec.RemoveQueryParams("r") |
| // hc, err := rec.Client(ctx, option.WithoutAuthentication()) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // query := "k=1&r=2&c=3" |
| // req, err := http.NewRequest("GET", srv.URL+"?"+query, nil) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // headers := map[string]string{"Keep": "ok", "Clear": "secret", "Remove": "bye"} |
| // for k, v := range headers { |
| // req.Header.Set(k, v) |
| // } |
| // if _, err := hc.Do(req); err != nil { |
| // t.Fatal(err) |
| // } |
| // if err := rec.Close(); err != nil { |
| // t.Fatal(err) |
| // } |
| |
| // // Replay |
| // // For both headers and query param: |
| // // - k or Keep must be present and identical |
| // // - c or Clear must be present, but can be different |
| // // - r or Remove can be anything |
| // for _, test := range []struct { |
| // query string |
| // headers map[string]string |
| // wantSuccess bool |
| // }{ |
| // {query, headers, true}, // same query string and headers |
| // {query, |
| // map[string]string{"Keep": "oops", "Clear": "secret", "Remove": "bye"}, |
| // false, // different Keep |
| // }, |
| // {query, map[string]string{}, false}, // missing Keep and Clear |
| // {query, map[string]string{"Keep": "ok"}, false}, // missing Clear |
| // {query, map[string]string{"Keep": "ok", "Clear": "secret"}, true}, // missing Remove is OK |
| // { |
| // query, |
| // map[string]string{"Keep": "ok", "Clear": "secret", "Remove": "whatev"}, |
| // true, |
| // }, // different Remove is OK |
| // {query, map[string]string{"Keep": "ok", "Clear": "diff"}, true}, // different Clear is OK |
| // {"", headers, false}, // no query string |
| // {"k=x&r=2&c=3", headers, false}, // different k |
| // {"r=2", headers, false}, // missing k and c |
| // {"k=1&r=2", headers, false}, // missing c |
| // {"k=1&c=3", headers, true}, // missing r is OK |
| // {"k=1&r=x&c=3", headers, true}, // different r is OK, |
| // {"k=1&r=2&c=x", headers, true}, // different clear is OK |
| // } { |
| // rep, err := httpreplay.NewReplayer(replayFilename) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // hc, err = rep.Client(ctx) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // url := srv.URL |
| // if test.query != "" { |
| // url += "?" + test.query |
| // } |
| // req, err = http.NewRequest("GET", url, nil) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // for k, v := range test.headers { |
| // req.Header.Set(k, v) |
| // } |
| // resp, err := hc.Do(req) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // rep.Close() |
| // if (resp.StatusCode == 200) != test.wantSuccess { |
| // t.Errorf("%q, %v: got %d, wanted success=%t", |
| // test.query, test.headers, resp.StatusCode, test.wantSuccess) |
| // } |
| // } |
| // } |
| |
| // func tempFilename(t *testing.T, pattern string) string { |
| // f, err := ioutil.TempFile("", pattern) |
| // if err != nil { |
| // t.Fatal(err) |
| // } |
| // filename := f.Name() |
| // if err := f.Close(); err != nil { |
| // t.Fatal(err) |
| // } |
| // return filename |
| // } |