f30287a448e45b92c1ca135b927b673a51a0baa6
[arvados.git] / services / keepstore / handler_test.go
1 // Tests for Keep HTTP handlers:
2 //
3 //     GetBlockHandler
4 //     PutBlockHandler
5 //     IndexHandler
6 //
7 // The HTTP handlers are responsible for enforcing permission policy,
8 // so these tests must exercise all possible permission permutations.
9
10 package main
11
12 import (
13         "bytes"
14         "encoding/json"
15         "fmt"
16         "github.com/gorilla/mux"
17         "net/http"
18         "net/http/httptest"
19         "os"
20         "regexp"
21         "strings"
22         "testing"
23         "time"
24         "git.curoverse.com/arvados.git/services/keep"
25 )
26
27 // A RequestTester represents the parameters for an HTTP request to
28 // be issued on behalf of a unit test.
29 type RequestTester struct {
30         uri          string
31         api_token    string
32         method       string
33         request_body []byte
34 }
35
36 // Test GetBlockHandler on the following situations:
37 //   - permissions off, unauthenticated request, unsigned locator
38 //   - permissions on, authenticated request, signed locator
39 //   - permissions on, authenticated request, unsigned locator
40 //   - permissions on, unauthenticated request, signed locator
41 //   - permissions on, authenticated request, expired locator
42 //
43 func TestGetHandler(t *testing.T) {
44         defer teardown()
45
46         // Prepare two test Keep volumes. Our block is stored on the second volume.
47         KeepVM = MakeTestVolumeManager(2)
48         defer KeepVM.Quit()
49
50         vols := KeepVM.Volumes()
51         if err := vols[0].Put(TEST_HASH, TEST_BLOCK); err != nil {
52                 t.Error(err)
53         }
54
55         // Set up a REST router for testing the handlers.
56         rest := MakeRESTRouter()
57
58         // Create locators for testing.
59         // Turn on permission settings so we can generate signed locators.
60         enforce_permissions = true
61         PermissionSecret = []byte(known_key)
62         permission_ttl = time.Duration(300) * time.Second
63
64         var (
65                 unsigned_locator  = "/" + TEST_HASH
66                 valid_timestamp   = time.Now().Add(permission_ttl)
67                 expired_timestamp = time.Now().Add(-time.Hour)
68                 signed_locator    = "/" + SignLocator(TEST_HASH, known_token, valid_timestamp)
69                 expired_locator   = "/" + SignLocator(TEST_HASH, known_token, expired_timestamp)
70         )
71
72         // -----------------
73         // Test unauthenticated request with permissions off.
74         enforce_permissions = false
75
76         // Unauthenticated request, unsigned locator
77         // => OK
78         response := IssueRequest(rest,
79                 &RequestTester{
80                         method: "GET",
81                         uri:    unsigned_locator,
82                 })
83         ExpectStatusCode(t,
84                 "Unauthenticated request, unsigned locator", http.StatusOK, response)
85         ExpectBody(t,
86                 "Unauthenticated request, unsigned locator",
87                 string(TEST_BLOCK),
88                 response)
89         received_xbs := response.Header().Get("X-Block-Size")
90         expected_xbs := fmt.Sprintf("%d", len(TEST_BLOCK))
91         if received_xbs != expected_xbs {
92                 t.Errorf("expected X-Block-Size %s, got %s", expected_xbs, received_xbs)
93         }
94
95         // ----------------
96         // Permissions: on.
97         enforce_permissions = true
98
99         // Authenticated request, signed locator
100         // => OK
101         response = IssueRequest(rest, &RequestTester{
102                 method:    "GET",
103                 uri:       signed_locator,
104                 api_token: known_token,
105         })
106         ExpectStatusCode(t,
107                 "Authenticated request, signed locator", http.StatusOK, response)
108         ExpectBody(t,
109                 "Authenticated request, signed locator", string(TEST_BLOCK), response)
110         received_xbs = response.Header().Get("X-Block-Size")
111         expected_xbs = fmt.Sprintf("%d", len(TEST_BLOCK))
112         if received_xbs != expected_xbs {
113                 t.Errorf("expected X-Block-Size %s, got %s", expected_xbs, received_xbs)
114         }
115
116         // Authenticated request, unsigned locator
117         // => PermissionError
118         response = IssueRequest(rest, &RequestTester{
119                 method:    "GET",
120                 uri:       unsigned_locator,
121                 api_token: known_token,
122         })
123         ExpectStatusCode(t, "unsigned locator", PermissionError.HTTPCode, response)
124
125         // Unauthenticated request, signed locator
126         // => PermissionError
127         response = IssueRequest(rest, &RequestTester{
128                 method: "GET",
129                 uri:    signed_locator,
130         })
131         ExpectStatusCode(t,
132                 "Unauthenticated request, signed locator",
133                 PermissionError.HTTPCode, response)
134
135         // Authenticated request, expired locator
136         // => ExpiredError
137         response = IssueRequest(rest, &RequestTester{
138                 method:    "GET",
139                 uri:       expired_locator,
140                 api_token: known_token,
141         })
142         ExpectStatusCode(t,
143                 "Authenticated request, expired locator",
144                 ExpiredError.HTTPCode, response)
145 }
146
147 // Test PutBlockHandler on the following situations:
148 //   - no server key
149 //   - with server key, authenticated request, unsigned locator
150 //   - with server key, unauthenticated request, unsigned locator
151 //
152 func TestPutHandler(t *testing.T) {
153         defer teardown()
154
155         // Prepare two test Keep volumes.
156         KeepVM = MakeTestVolumeManager(2)
157         defer KeepVM.Quit()
158
159         // Set up a REST router for testing the handlers.
160         rest := MakeRESTRouter()
161
162         // --------------
163         // No server key.
164
165         // Unauthenticated request, no server key
166         // => OK (unsigned response)
167         unsigned_locator := "/" + TEST_HASH
168         response := IssueRequest(rest,
169                 &RequestTester{
170                         method:       "PUT",
171                         uri:          unsigned_locator,
172                         request_body: TEST_BLOCK,
173                 })
174
175         ExpectStatusCode(t,
176                 "Unauthenticated request, no server key", http.StatusOK, response)
177         ExpectBody(t,
178                 "Unauthenticated request, no server key",
179                 TEST_HASH_PUT_RESPONSE, response)
180
181         // ------------------
182         // With a server key.
183
184         PermissionSecret = []byte(known_key)
185         permission_ttl = time.Duration(300) * time.Second
186
187         // When a permission key is available, the locator returned
188         // from an authenticated PUT request will be signed.
189
190         // Authenticated PUT, signed locator
191         // => OK (signed response)
192         response = IssueRequest(rest,
193                 &RequestTester{
194                         method:       "PUT",
195                         uri:          unsigned_locator,
196                         request_body: TEST_BLOCK,
197                         api_token:    known_token,
198                 })
199
200         ExpectStatusCode(t,
201                 "Authenticated PUT, signed locator, with server key",
202                 http.StatusOK, response)
203         response_locator := strings.TrimSpace(response.Body.String())
204         if !VerifySignature(response_locator, known_token) {
205                 t.Errorf("Authenticated PUT, signed locator, with server key:\n"+
206                         "response '%s' does not contain a valid signature",
207                         response_locator)
208         }
209
210         // Unauthenticated PUT, unsigned locator
211         // => OK
212         response = IssueRequest(rest,
213                 &RequestTester{
214                         method:       "PUT",
215                         uri:          unsigned_locator,
216                         request_body: TEST_BLOCK,
217                 })
218
219         ExpectStatusCode(t,
220                 "Unauthenticated PUT, unsigned locator, with server key",
221                 http.StatusOK, response)
222         ExpectBody(t,
223                 "Unauthenticated PUT, unsigned locator, with server key",
224                 TEST_HASH_PUT_RESPONSE, response)
225 }
226
227 // Test /index requests:
228 //   - unauthenticated /index request
229 //   - unauthenticated /index/prefix request
230 //   - authenticated   /index request        | non-superuser
231 //   - authenticated   /index/prefix request | non-superuser
232 //   - authenticated   /index request        | superuser
233 //   - authenticated   /index/prefix request | superuser
234 //
235 // The only /index requests that should succeed are those issued by the
236 // superuser. They should pass regardless of the value of enforce_permissions.
237 //
238 func TestIndexHandler(t *testing.T) {
239         defer teardown()
240
241         // Set up Keep volumes and populate them.
242         // Include multiple blocks on different volumes, and
243         // some metadata files (which should be omitted from index listings)
244         KeepVM = MakeTestVolumeManager(2)
245         defer KeepVM.Quit()
246
247         vols := KeepVM.Volumes()
248         vols[0].Put(TEST_HASH, TEST_BLOCK)
249         vols[1].Put(TEST_HASH_2, TEST_BLOCK_2)
250         vols[0].Put(TEST_HASH+".meta", []byte("metadata"))
251         vols[1].Put(TEST_HASH_2+".meta", []byte("metadata"))
252
253         // Set up a REST router for testing the handlers.
254         rest := MakeRESTRouter()
255
256         data_manager_token = "DATA MANAGER TOKEN"
257
258         unauthenticated_req := &RequestTester{
259                 method: "GET",
260                 uri:    "/index",
261         }
262         authenticated_req := &RequestTester{
263                 method:    "GET",
264                 uri:       "/index",
265                 api_token: known_token,
266         }
267         superuser_req := &RequestTester{
268                 method:    "GET",
269                 uri:       "/index",
270                 api_token: data_manager_token,
271         }
272         unauth_prefix_req := &RequestTester{
273                 method: "GET",
274                 uri:    "/index/" + TEST_HASH[0:3],
275         }
276         auth_prefix_req := &RequestTester{
277                 method:    "GET",
278                 uri:       "/index/" + TEST_HASH[0:3],
279                 api_token: known_token,
280         }
281         superuser_prefix_req := &RequestTester{
282                 method:    "GET",
283                 uri:       "/index/" + TEST_HASH[0:3],
284                 api_token: data_manager_token,
285         }
286
287         // -------------------------------------------------------------
288         // Only the superuser should be allowed to issue /index requests.
289
290   // ---------------------------
291   // enforce_permissions enabled
292         // This setting should not affect tests passing.
293   enforce_permissions = true
294
295         // unauthenticated /index request
296         // => UnauthorizedError
297         response := IssueRequest(rest, unauthenticated_req)
298         ExpectStatusCode(t,
299                 "enforce_permissions on, unauthenticated request",
300                 UnauthorizedError.HTTPCode,
301                 response)
302
303         // unauthenticated /index/prefix request
304         // => UnauthorizedError
305         response = IssueRequest(rest, unauth_prefix_req)
306         ExpectStatusCode(t,
307                 "permissions on, unauthenticated /index/prefix request",
308                 UnauthorizedError.HTTPCode,
309                 response)
310
311         // authenticated /index request, non-superuser
312         // => UnauthorizedError
313         response = IssueRequest(rest, authenticated_req)
314         ExpectStatusCode(t,
315                 "permissions on, authenticated request, non-superuser",
316                 UnauthorizedError.HTTPCode,
317                 response)
318
319         // authenticated /index/prefix request, non-superuser
320         // => UnauthorizedError
321         response = IssueRequest(rest, auth_prefix_req)
322         ExpectStatusCode(t,
323                 "permissions on, authenticated /index/prefix request, non-superuser",
324                 UnauthorizedError.HTTPCode,
325                 response)
326
327         // superuser /index request
328         // => OK
329         response = IssueRequest(rest, superuser_req)
330         ExpectStatusCode(t,
331                 "permissions on, superuser request",
332                 http.StatusOK,
333                 response)
334
335         // ----------------------------
336         // enforce_permissions disabled
337         // Valid Request should still pass.
338         enforce_permissions = false
339
340         // superuser /index request
341         // => OK
342         response = IssueRequest(rest, superuser_req)
343         ExpectStatusCode(t,
344                 "permissions on, superuser request",
345                 http.StatusOK,
346                 response)
347
348
349
350         expected := `^` + TEST_HASH + `\+\d+ \d+\n` +
351                 TEST_HASH_2 + `\+\d+ \d+\n$`
352         match, _ := regexp.MatchString(expected, response.Body.String())
353         if !match {
354                 t.Errorf(
355                         "permissions on, superuser request: expected %s, got:\n%s",
356                         expected, response.Body.String())
357         }
358
359         // superuser /index/prefix request
360         // => OK
361         response = IssueRequest(rest, superuser_prefix_req)
362         ExpectStatusCode(t,
363                 "permissions on, superuser request",
364                 http.StatusOK,
365                 response)
366
367         expected = `^` + TEST_HASH + `\+\d+ \d+\n$`
368         match, _ = regexp.MatchString(expected, response.Body.String())
369         if !match {
370                 t.Errorf(
371                         "permissions on, superuser /index/prefix request: expected %s, got:\n%s",
372                         expected, response.Body.String())
373         }
374 }
375
376 // TestDeleteHandler
377 //
378 // Cases tested:
379 //
380 //   With no token and with a non-data-manager token:
381 //   * Delete existing block
382 //     (test for 403 Forbidden, confirm block not deleted)
383 //
384 //   With data manager token:
385 //
386 //   * Delete existing block
387 //     (test for 200 OK, response counts, confirm block deleted)
388 //
389 //   * Delete nonexistent block
390 //     (test for 200 OK, response counts)
391 //
392 //   TODO(twp):
393 //
394 //   * Delete block on read-only and read-write volume
395 //     (test for 200 OK, response with copies_deleted=1,
396 //     copies_failed=1, confirm block deleted only on r/w volume)
397 //
398 //   * Delete block on read-only volume only
399 //     (test for 200 OK, response with copies_deleted=0, copies_failed=1,
400 //     confirm block not deleted)
401 //
402 func TestDeleteHandler(t *testing.T) {
403         defer teardown()
404
405         // Set up Keep volumes and populate them.
406         // Include multiple blocks on different volumes, and
407         // some metadata files (which should be omitted from index listings)
408         KeepVM = MakeTestVolumeManager(2)
409         defer KeepVM.Quit()
410
411         vols := KeepVM.Volumes()
412         vols[0].Put(TEST_HASH, TEST_BLOCK)
413
414         // Explicitly set the permission_ttl to 0 for these
415         // tests, to ensure the MockVolume deletes the blocks
416         // even though they have just been created.
417         permission_ttl = time.Duration(0)
418
419         // Set up a REST router for testing the handlers.
420         rest := MakeRESTRouter()
421
422         var user_token = "NOT DATA MANAGER TOKEN"
423         data_manager_token = "DATA MANAGER TOKEN"
424
425         unauth_req := &RequestTester{
426                 method: "DELETE",
427                 uri:    "/" + TEST_HASH,
428         }
429
430         user_req := &RequestTester{
431                 method:    "DELETE",
432                 uri:       "/" + TEST_HASH,
433                 api_token: user_token,
434         }
435
436         superuser_existing_block_req := &RequestTester{
437                 method:    "DELETE",
438                 uri:       "/" + TEST_HASH,
439                 api_token: data_manager_token,
440         }
441
442         superuser_nonexistent_block_req := &RequestTester{
443                 method:    "DELETE",
444                 uri:       "/" + TEST_HASH_2,
445                 api_token: data_manager_token,
446         }
447
448         // Unauthenticated request returns PermissionError.
449         var response *httptest.ResponseRecorder
450         response = IssueRequest(rest, unauth_req)
451         ExpectStatusCode(t,
452                 "unauthenticated request",
453                 PermissionError.HTTPCode,
454                 response)
455
456         // Authenticated non-admin request returns PermissionError.
457         response = IssueRequest(rest, user_req)
458         ExpectStatusCode(t,
459                 "authenticated non-admin request",
460                 PermissionError.HTTPCode,
461                 response)
462
463         // Authenticated admin request for nonexistent block.
464         type deletecounter struct {
465                 Deleted int `json:"copies_deleted"`
466                 Failed  int `json:"copies_failed"`
467         }
468         var response_dc, expected_dc deletecounter
469
470         response = IssueRequest(rest, superuser_nonexistent_block_req)
471         ExpectStatusCode(t,
472                 "data manager request, nonexistent block",
473                 http.StatusNotFound,
474                 response)
475
476         // Authenticated admin request for existing block while never_delete is set.
477         never_delete = true
478         response = IssueRequest(rest, superuser_existing_block_req)
479         ExpectStatusCode(t,
480                 "authenticated request, existing block, method disabled",
481                 MethodDisabledError.HTTPCode,
482                 response)
483         never_delete = false
484
485         // Authenticated admin request for existing block.
486         response = IssueRequest(rest, superuser_existing_block_req)
487         ExpectStatusCode(t,
488                 "data manager request, existing block",
489                 http.StatusOK,
490                 response)
491         // Expect response {"copies_deleted":1,"copies_failed":0}
492         expected_dc = deletecounter{1, 0}
493         json.NewDecoder(response.Body).Decode(&response_dc)
494         if response_dc != expected_dc {
495                 t.Errorf("superuser_existing_block_req\nexpected: %+v\nreceived: %+v",
496                         expected_dc, response_dc)
497         }
498         // Confirm the block has been deleted
499         _, err := vols[0].Get(TEST_HASH)
500         var block_deleted = os.IsNotExist(err)
501         if !block_deleted {
502                 t.Error("superuser_existing_block_req: block not deleted")
503         }
504
505         // A DELETE request on a block newer than permission_ttl should return
506         // success but leave the block on the volume.
507         vols[0].Put(TEST_HASH, TEST_BLOCK)
508         permission_ttl = time.Duration(1) * time.Hour
509
510         response = IssueRequest(rest, superuser_existing_block_req)
511         ExpectStatusCode(t,
512                 "data manager request, existing block",
513                 http.StatusOK,
514                 response)
515         // Expect response {"copies_deleted":1,"copies_failed":0}
516         expected_dc = deletecounter{1, 0}
517         json.NewDecoder(response.Body).Decode(&response_dc)
518         if response_dc != expected_dc {
519                 t.Errorf("superuser_existing_block_req\nexpected: %+v\nreceived: %+v",
520                         expected_dc, response_dc)
521         }
522         // Confirm the block has NOT been deleted.
523         _, err = vols[0].Get(TEST_HASH)
524         if err != nil {
525                 t.Errorf("testing delete on new block: %s\n", err)
526         }
527 }
528
529 // TestPullHandler
530 //
531 // Test handling of the PUT /pull statement.
532 //
533 // Cases tested: syntactically valid and invalid pull lists, from the
534 // data manager and from unprivileged users:
535 //
536 //   1. Valid pull list from an ordinary user
537 //      (expected result: 401 Unauthorized)
538 //
539 //   2. Invalid pull request from an ordinary user
540 //      (expected result: 401 Unauthorized)
541 //
542 //   3. Valid pull request from the data manager
543 //      (expected result: 200 OK with request body "Received 3 pull
544 //      requests"
545 //
546 //   4. Invalid pull request from the data manager
547 //      (expected result: 400 Bad Request)
548 //
549 // Test that in the end, the pull manager received a good pull list with
550 // the expected number of requests.
551 //
552 // TODO(twp): test concurrency: launch 100 goroutines to update the
553 // pull list simultaneously.  Make sure that none of them return 400
554 // Bad Request and that pullq.GetList() returns a valid list.
555 //
556 func TestPullHandler(t *testing.T) {
557         defer teardown()
558
559         // Set up a REST router for testing the handlers.
560         rest := MakeRESTRouter()
561
562         var user_token = "USER TOKEN"
563         data_manager_token = "DATA MANAGER TOKEN"
564
565         good_json := []byte(`[
566                 {
567                         "locator":"locator_with_two_servers",
568                         "servers":[
569                                 "server1",
570                                 "server2"
571                         ]
572                 },
573                 {
574                         "locator":"locator_with_no_servers",
575                         "servers":[]
576                 },
577                 {
578                         "locator":"",
579                         "servers":["empty_locator"]
580                 }
581         ]`)
582
583         bad_json := []byte(`{ "key":"I'm a little teapot" }`)
584
585         type pullTest struct {
586                 name          string
587                 req           RequestTester
588                 response_code int
589                 response_body string
590         }
591         var testcases = []pullTest{
592                 {
593                         "Valid pull list from an ordinary user",
594                         RequestTester{"/pull", user_token, "PUT", good_json},
595                         http.StatusUnauthorized,
596                         "Unauthorized\n",
597                 },
598                 {
599                         "Invalid pull request from an ordinary user",
600                         RequestTester{"/pull", user_token, "PUT", bad_json},
601                         http.StatusUnauthorized,
602                         "Unauthorized\n",
603                 },
604                 {
605                         "Valid pull request from the data manager",
606                         RequestTester{"/pull", data_manager_token, "PUT", good_json},
607                         http.StatusOK,
608                         "Received 3 pull requests\n",
609                 },
610                 {
611                         "Invalid pull request from the data manager",
612                         RequestTester{"/pull", data_manager_token, "PUT", bad_json},
613                         http.StatusBadRequest,
614                         "Bad Request\n",
615                 },
616         }
617
618         for _, tst := range testcases {
619                 response := IssueRequest(rest, &tst.req)
620                 ExpectStatusCode(t, tst.name, tst.response_code, response)
621                 ExpectBody(t, tst.name, tst.response_body, response)
622         }
623
624         // The Keep pull manager should have received one good list with 3
625         // requests on it.
626         for i := 0; i < 3; i++ {
627                 item := <-pullq.NextItem
628                 if _, ok := item.(PullRequest); !ok {
629                         t.Errorf("item %v could not be parsed as a PullRequest", item)
630                 }
631         }
632
633         expectChannelEmpty(t, pullq.NextItem)
634 }
635
636 // TestTrashHandler
637 //
638 // Test cases:
639 //
640 // Cases tested: syntactically valid and invalid trash lists, from the
641 // data manager and from unprivileged users:
642 //
643 //   1. Valid trash list from an ordinary user
644 //      (expected result: 401 Unauthorized)
645 //
646 //   2. Invalid trash list from an ordinary user
647 //      (expected result: 401 Unauthorized)
648 //
649 //   3. Valid trash list from the data manager
650 //      (expected result: 200 OK with request body "Received 3 trash
651 //      requests"
652 //
653 //   4. Invalid trash list from the data manager
654 //      (expected result: 400 Bad Request)
655 //
656 // Test that in the end, the trash collector received a good list
657 // trash list with the expected number of requests.
658 //
659 // TODO(twp): test concurrency: launch 100 goroutines to update the
660 // pull list simultaneously.  Make sure that none of them return 400
661 // Bad Request and that replica.Dump() returns a valid list.
662 //
663 func TestTrashHandler(t *testing.T) {
664         defer teardown()
665
666         // Set up a REST router for testing the handlers.
667         rest := MakeRESTRouter()
668
669         var user_token = "USER TOKEN"
670         data_manager_token = "DATA MANAGER TOKEN"
671
672         good_json := []byte(`[
673                 {
674                         "locator":"block1",
675                         "block_mtime":1409082153
676                 },
677                 {
678                         "locator":"block2",
679                         "block_mtime":1409082153
680                 },
681                 {
682                         "locator":"block3",
683                         "block_mtime":1409082153
684                 }
685         ]`)
686
687         bad_json := []byte(`I am not a valid JSON string`)
688
689         type trashTest struct {
690                 name          string
691                 req           RequestTester
692                 response_code int
693                 response_body string
694         }
695
696         var testcases = []trashTest{
697                 {
698                         "Valid trash list from an ordinary user",
699                         RequestTester{"/trash", user_token, "PUT", good_json},
700                         http.StatusUnauthorized,
701                         "Unauthorized\n",
702                 },
703                 {
704                         "Invalid trash list from an ordinary user",
705                         RequestTester{"/trash", user_token, "PUT", bad_json},
706                         http.StatusUnauthorized,
707                         "Unauthorized\n",
708                 },
709                 {
710                         "Valid trash list from the data manager",
711                         RequestTester{"/trash", data_manager_token, "PUT", good_json},
712                         http.StatusOK,
713                         "Received 3 trash requests\n",
714                 },
715                 {
716                         "Invalid trash list from the data manager",
717                         RequestTester{"/trash", data_manager_token, "PUT", bad_json},
718                         http.StatusBadRequest,
719                         "Bad Request\n",
720                 },
721         }
722
723         for _, tst := range testcases {
724                 response := IssueRequest(rest, &tst.req)
725                 ExpectStatusCode(t, tst.name, tst.response_code, response)
726                 ExpectBody(t, tst.name, tst.response_body, response)
727         }
728
729         // The trash collector should have received one good list with 3
730         // requests on it.
731         for i := 0; i < 3; i++ {
732                 item := <-trashq.NextItem
733                 if _, ok := item.(TrashRequest); !ok {
734                         t.Errorf("item %v could not be parsed as a TrashRequest", item)
735                 }
736         }
737
738         expectChannelEmpty(t, trashq.NextItem)
739 }
740
741 // ====================
742 // Helper functions
743 // ====================
744
745 // IssueTestRequest executes an HTTP request described by rt, to a
746 // specified REST router.  It returns the HTTP response to the request.
747 func IssueRequest(router *mux.Router, rt *RequestTester) *httptest.ResponseRecorder {
748         response := httptest.NewRecorder()
749         body := bytes.NewReader(rt.request_body)
750         req, _ := http.NewRequest(rt.method, rt.uri, body)
751         if rt.api_token != "" {
752                 req.Header.Set("Authorization", "OAuth2 "+rt.api_token)
753         }
754   routerWrapper := keep_utils.MakeRESTRouterWrapper(router)
755   routerWrapper.ServeHTTP(response, req)
756         return response
757 }
758
759 // ExpectStatusCode checks whether a response has the specified status code,
760 // and reports a test failure if not.
761 func ExpectStatusCode(
762         t *testing.T,
763         testname string,
764         expected_status int,
765         response *httptest.ResponseRecorder) {
766         if response.Code != expected_status {
767                 t.Errorf("%s: expected status %s, got %+v",
768                         testname, expected_status, response)
769         }
770 }
771
772 func ExpectBody(
773         t *testing.T,
774         testname string,
775         expected_body string,
776         response *httptest.ResponseRecorder) {
777         if response.Body.String() != expected_body {
778                 t.Errorf("%s: expected response body '%s', got %+v",
779                         testname, expected_body, response)
780         }
781 }