You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

378 lines
9.7 KiB

  1. // Copyright 2018 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package httpreplay_test
  15. import (
  16. "bytes"
  17. "context"
  18. "encoding/json"
  19. "fmt"
  20. "io/ioutil"
  21. "log"
  22. "net/http"
  23. "net/http/httptest"
  24. "os"
  25. "testing"
  26. "time"
  27. "cloud.google.com/go/httpreplay"
  28. "cloud.google.com/go/internal/testutil"
  29. "cloud.google.com/go/storage"
  30. "google.golang.org/api/option"
  31. )
  32. func TestIntegration_RecordAndReplay(t *testing.T) {
  33. httpreplay.DebugHeaders()
  34. if testing.Short() {
  35. t.Skip("Integration tests skipped in short mode")
  36. }
  37. replayFilename := tempFilename(t, "RecordAndReplay*.replay")
  38. defer os.Remove(replayFilename)
  39. projectID := testutil.ProjID()
  40. if projectID == "" {
  41. t.Skip("Need project ID. See CONTRIBUTING.md for details.")
  42. }
  43. ctx := context.Background()
  44. // Record.
  45. initial := time.Now()
  46. ibytes, err := json.Marshal(initial)
  47. if err != nil {
  48. t.Fatal(err)
  49. }
  50. rec, err := httpreplay.NewRecorder(replayFilename, ibytes)
  51. if err != nil {
  52. t.Fatal(err)
  53. }
  54. hc, err := rec.Client(ctx, option.WithTokenSource(
  55. testutil.TokenSource(ctx, storage.ScopeFullControl)))
  56. if err != nil {
  57. t.Fatal(err)
  58. }
  59. wanta, wantc := run(t, hc)
  60. testReadCRC(t, hc, "recording")
  61. if err := rec.Close(); err != nil {
  62. t.Fatalf("rec.Close: %v", err)
  63. }
  64. // Replay.
  65. rep, err := httpreplay.NewReplayer(replayFilename)
  66. if err != nil {
  67. t.Fatal(err)
  68. }
  69. defer rep.Close()
  70. hc, err = rep.Client(ctx)
  71. if err != nil {
  72. t.Fatal(err)
  73. }
  74. gota, gotc := run(t, hc)
  75. testReadCRC(t, hc, "replaying")
  76. if diff := testutil.Diff(gota, wanta); diff != "" {
  77. t.Error(diff)
  78. }
  79. if !bytes.Equal(gotc, wantc) {
  80. t.Errorf("got %q, want %q", gotc, wantc)
  81. }
  82. var gotInitial time.Time
  83. if err := json.Unmarshal(rep.Initial(), &gotInitial); err != nil {
  84. t.Fatal(err)
  85. }
  86. if !gotInitial.Equal(initial) {
  87. t.Errorf("initial: got %v, want %v", gotInitial, initial)
  88. }
  89. }
  90. // TODO(jba): test errors
  91. func run(t *testing.T, hc *http.Client) (*storage.BucketAttrs, []byte) {
  92. ctx := context.Background()
  93. client, err := storage.NewClient(ctx, option.WithHTTPClient(hc))
  94. if err != nil {
  95. t.Fatal(err)
  96. }
  97. defer client.Close()
  98. b := client.Bucket(testutil.ProjID())
  99. attrs, err := b.Attrs(ctx)
  100. if err != nil {
  101. t.Fatal(err)
  102. }
  103. obj := b.Object("replay-test")
  104. w := obj.NewWriter(ctx)
  105. data := []byte{150, 151, 152}
  106. if _, err := w.Write(data); err != nil {
  107. t.Fatal(err)
  108. }
  109. if err := w.Close(); err != nil {
  110. t.Fatal(err)
  111. }
  112. r, err := obj.NewReader(ctx)
  113. if err != nil {
  114. t.Fatal(err)
  115. }
  116. defer r.Close()
  117. contents, err := ioutil.ReadAll(r)
  118. if err != nil {
  119. t.Fatal(err)
  120. }
  121. return attrs, contents
  122. }
  123. func testReadCRC(t *testing.T, hc *http.Client, mode string) {
  124. const (
  125. // This is an uncompressed file.
  126. // See https://cloud.google.com/storage/docs/public-datasets/landsat
  127. uncompressedBucket = "gcp-public-data-landsat"
  128. uncompressedObject = "LC08/PRE/044/034/LC80440342016259LGN00/LC80440342016259LGN00_MTL.txt"
  129. gzippedBucket = "storage-library-test-bucket"
  130. gzippedObject = "gzipped-text.txt"
  131. )
  132. ctx := context.Background()
  133. client, err := storage.NewClient(ctx, option.WithHTTPClient(hc))
  134. if err != nil {
  135. t.Fatalf("%s: %v", mode, err)
  136. }
  137. defer client.Close()
  138. uncompressedObj := client.Bucket(uncompressedBucket).Object(uncompressedObject)
  139. gzippedObj := client.Bucket(gzippedBucket).Object(gzippedObject)
  140. for _, test := range []struct {
  141. desc string
  142. obj *storage.ObjectHandle
  143. offset, length int64
  144. readCompressed bool // don't decompress a gzipped file
  145. wantErr bool
  146. wantLen int // length of contents
  147. }{
  148. {
  149. desc: "uncompressed, entire file",
  150. obj: uncompressedObj,
  151. offset: 0,
  152. length: -1,
  153. readCompressed: false,
  154. wantLen: 7903,
  155. },
  156. {
  157. desc: "uncompressed, entire file, don't decompress",
  158. obj: uncompressedObj,
  159. offset: 0,
  160. length: -1,
  161. readCompressed: true,
  162. wantLen: 7903,
  163. },
  164. {
  165. desc: "uncompressed, suffix",
  166. obj: uncompressedObj,
  167. offset: 3,
  168. length: -1,
  169. readCompressed: false,
  170. wantLen: 7900,
  171. },
  172. {
  173. desc: "uncompressed, prefix",
  174. obj: uncompressedObj,
  175. offset: 0,
  176. length: 18,
  177. readCompressed: false,
  178. wantLen: 18,
  179. },
  180. {
  181. // When a gzipped file is unzipped by GCS, we can't verify the checksum
  182. // because it was computed against the zipped contents. There is no
  183. // header that indicates that a gzipped file is being served unzipped.
  184. // But our CRC check only happens if there is a Content-Length header,
  185. // and that header is absent for this read.
  186. desc: "compressed, entire file, server unzips",
  187. obj: gzippedObj,
  188. offset: 0,
  189. length: -1,
  190. readCompressed: false,
  191. wantLen: 11,
  192. },
  193. {
  194. // When we read a gzipped file uncompressed, it's like reading a regular file:
  195. // the served content and the CRC match.
  196. desc: "compressed, entire file, read compressed",
  197. obj: gzippedObj,
  198. offset: 0,
  199. length: -1,
  200. readCompressed: true,
  201. wantLen: 31,
  202. },
  203. {
  204. desc: "compressed, partial, read compressed",
  205. obj: gzippedObj,
  206. offset: 1,
  207. length: 8,
  208. readCompressed: true,
  209. wantLen: 8,
  210. },
  211. {
  212. desc: "uncompressed, HEAD",
  213. obj: uncompressedObj,
  214. offset: 0,
  215. length: 0,
  216. wantLen: 0,
  217. },
  218. {
  219. desc: "compressed, HEAD",
  220. obj: gzippedObj,
  221. offset: 0,
  222. length: 0,
  223. wantLen: 0,
  224. },
  225. } {
  226. obj := test.obj.ReadCompressed(test.readCompressed)
  227. r, err := obj.NewRangeReader(ctx, test.offset, test.length)
  228. if err != nil {
  229. if test.wantErr {
  230. continue
  231. }
  232. t.Errorf("%s: %s: %v", mode, test.desc, err)
  233. continue
  234. }
  235. data, err := ioutil.ReadAll(r)
  236. _ = r.Close()
  237. if err != nil {
  238. t.Errorf("%s: %s: %v", mode, test.desc, err)
  239. continue
  240. }
  241. if got, want := len(data), test.wantLen; got != want {
  242. t.Errorf("%s: %s: len: got %d, want %d", mode, test.desc, got, want)
  243. }
  244. }
  245. }
  246. func TestRemoveAndClear(t *testing.T) {
  247. // Disable logging for this test, since it generates a lot.
  248. log.SetOutput(ioutil.Discard)
  249. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
  250. fmt.Fprintln(w, "LGTM")
  251. }))
  252. defer srv.Close()
  253. replayFilename := tempFilename(t, "TestRemoveAndClear*.replay")
  254. defer os.Remove(replayFilename)
  255. ctx := context.Background()
  256. // Record
  257. rec, err := httpreplay.NewRecorder(replayFilename, nil)
  258. if err != nil {
  259. t.Fatal(err)
  260. }
  261. rec.ClearHeaders("Clear")
  262. rec.RemoveRequestHeaders("Rem*")
  263. rec.ClearQueryParams("c")
  264. rec.RemoveQueryParams("r")
  265. hc, err := rec.Client(ctx, option.WithoutAuthentication())
  266. if err != nil {
  267. t.Fatal(err)
  268. }
  269. query := "k=1&r=2&c=3"
  270. req, err := http.NewRequest("GET", srv.URL+"?"+query, nil)
  271. if err != nil {
  272. t.Fatal(err)
  273. }
  274. headers := map[string]string{"Keep": "ok", "Clear": "secret", "Remove": "bye"}
  275. for k, v := range headers {
  276. req.Header.Set(k, v)
  277. }
  278. if _, err := hc.Do(req); err != nil {
  279. t.Fatal(err)
  280. }
  281. if err := rec.Close(); err != nil {
  282. t.Fatal(err)
  283. }
  284. // Replay
  285. // For both headers and query param:
  286. // - k or Keep must be present and identical
  287. // - c or Clear must be present, but can be different
  288. // - r or Remove can be anything
  289. for _, test := range []struct {
  290. query string
  291. headers map[string]string
  292. wantSuccess bool
  293. }{
  294. {query, headers, true}, // same query string and headers
  295. {query,
  296. map[string]string{"Keep": "oops", "Clear": "secret", "Remove": "bye"},
  297. false, // different Keep
  298. },
  299. {query, map[string]string{}, false}, // missing Keep and Clear
  300. {query, map[string]string{"Keep": "ok"}, false}, // missing Clear
  301. {query, map[string]string{"Keep": "ok", "Clear": "secret"}, true}, // missing Remove is OK
  302. {
  303. query,
  304. map[string]string{"Keep": "ok", "Clear": "secret", "Remove": "whatev"},
  305. true,
  306. }, // different Remove is OK
  307. {query, map[string]string{"Keep": "ok", "Clear": "diff"}, true}, // different Clear is OK
  308. {"", headers, false}, // no query string
  309. {"k=x&r=2&c=3", headers, false}, // different k
  310. {"r=2", headers, false}, // missing k and c
  311. {"k=1&r=2", headers, false}, // missing c
  312. {"k=1&c=3", headers, true}, // missing r is OK
  313. {"k=1&r=x&c=3", headers, true}, // different r is OK,
  314. {"k=1&r=2&c=x", headers, true}, // different clear is OK
  315. } {
  316. rep, err := httpreplay.NewReplayer(replayFilename)
  317. if err != nil {
  318. t.Fatal(err)
  319. }
  320. hc, err = rep.Client(ctx)
  321. if err != nil {
  322. t.Fatal(err)
  323. }
  324. url := srv.URL
  325. if test.query != "" {
  326. url += "?" + test.query
  327. }
  328. req, err = http.NewRequest("GET", url, nil)
  329. if err != nil {
  330. t.Fatal(err)
  331. }
  332. for k, v := range test.headers {
  333. req.Header.Set(k, v)
  334. }
  335. resp, err := hc.Do(req)
  336. if err != nil {
  337. t.Fatal(err)
  338. }
  339. rep.Close()
  340. if (resp.StatusCode == 200) != test.wantSuccess {
  341. t.Errorf("%q, %v: got %d, wanted success=%t",
  342. test.query, test.headers, resp.StatusCode, test.wantSuccess)
  343. }
  344. }
  345. }
  346. func tempFilename(t *testing.T, pattern string) string {
  347. f, err := ioutil.TempFile("", pattern)
  348. if err != nil {
  349. t.Fatal(err)
  350. }
  351. filename := f.Name()
  352. if err := f.Close(); err != nil {
  353. t.Fatal(err)
  354. }
  355. return filename
  356. }