http_test.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // Copyright 2018 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package acme
  5. import (
  6. "context"
  7. "fmt"
  8. "io/ioutil"
  9. "net/http"
  10. "net/http/httptest"
  11. "reflect"
  12. "strings"
  13. "testing"
  14. "time"
  15. )
  16. func TestDefaultBackoff(t *testing.T) {
  17. tt := []struct {
  18. nretry int
  19. retryAfter string // Retry-After header
  20. out time.Duration // expected min; max = min + jitter
  21. }{
  22. {-1, "", time.Second}, // verify the lower bound is 1
  23. {0, "", time.Second}, // verify the lower bound is 1
  24. {100, "", 10 * time.Second}, // verify the ceiling
  25. {1, "3600", time.Hour}, // verify the header value is used
  26. {1, "", 1 * time.Second},
  27. {2, "", 2 * time.Second},
  28. {3, "", 4 * time.Second},
  29. {4, "", 8 * time.Second},
  30. }
  31. for i, test := range tt {
  32. r := httptest.NewRequest("GET", "/", nil)
  33. resp := &http.Response{Header: http.Header{}}
  34. if test.retryAfter != "" {
  35. resp.Header.Set("Retry-After", test.retryAfter)
  36. }
  37. d := defaultBackoff(test.nretry, r, resp)
  38. max := test.out + time.Second // + max jitter
  39. if d < test.out || max < d {
  40. t.Errorf("%d: defaultBackoff(%v) = %v; want between %v and %v", i, test.nretry, d, test.out, max)
  41. }
  42. }
  43. }
  44. func TestErrorResponse(t *testing.T) {
  45. s := `{
  46. "status": 400,
  47. "type": "urn:acme:error:xxx",
  48. "detail": "text"
  49. }`
  50. res := &http.Response{
  51. StatusCode: 400,
  52. Status: "400 Bad Request",
  53. Body: ioutil.NopCloser(strings.NewReader(s)),
  54. Header: http.Header{"X-Foo": {"bar"}},
  55. }
  56. err := responseError(res)
  57. v, ok := err.(*Error)
  58. if !ok {
  59. t.Fatalf("err = %+v (%T); want *Error type", err, err)
  60. }
  61. if v.StatusCode != 400 {
  62. t.Errorf("v.StatusCode = %v; want 400", v.StatusCode)
  63. }
  64. if v.ProblemType != "urn:acme:error:xxx" {
  65. t.Errorf("v.ProblemType = %q; want urn:acme:error:xxx", v.ProblemType)
  66. }
  67. if v.Detail != "text" {
  68. t.Errorf("v.Detail = %q; want text", v.Detail)
  69. }
  70. if !reflect.DeepEqual(v.Header, res.Header) {
  71. t.Errorf("v.Header = %+v; want %+v", v.Header, res.Header)
  72. }
  73. }
  74. func TestPostWithRetries(t *testing.T) {
  75. var count int
  76. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  77. count++
  78. w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count))
  79. if r.Method == "HEAD" {
  80. // We expect the client to do 2 head requests to fetch
  81. // nonces, one to start and another after getting badNonce
  82. return
  83. }
  84. head, err := decodeJWSHead(r)
  85. if err != nil {
  86. t.Errorf("decodeJWSHead: %v", err)
  87. } else if head.Nonce == "" {
  88. t.Error("head.Nonce is empty")
  89. } else if head.Nonce == "nonce1" {
  90. // return a badNonce error to force the call to retry
  91. w.WriteHeader(http.StatusBadRequest)
  92. w.Write([]byte(`{"type":"urn:ietf:params:acme:error:badNonce"}`))
  93. return
  94. }
  95. // Make client.Authorize happy; we're not testing its result.
  96. w.WriteHeader(http.StatusCreated)
  97. w.Write([]byte(`{"status":"valid"}`))
  98. }))
  99. defer ts.Close()
  100. client := &Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
  101. // This call will fail with badNonce, causing a retry
  102. if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
  103. t.Errorf("client.Authorize 1: %v", err)
  104. }
  105. if count != 4 {
  106. t.Errorf("total requests count: %d; want 4", count)
  107. }
  108. }
  109. func TestRetryBackoffArgs(t *testing.T) {
  110. const resCode = http.StatusInternalServerError
  111. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  112. w.Header().Set("Replay-Nonce", "test-nonce")
  113. w.WriteHeader(resCode)
  114. }))
  115. defer ts.Close()
  116. // Canceled in backoff.
  117. ctx, cancel := context.WithCancel(context.Background())
  118. var nretry int
  119. backoff := func(n int, r *http.Request, res *http.Response) time.Duration {
  120. nretry++
  121. if n != nretry {
  122. t.Errorf("n = %d; want %d", n, nretry)
  123. }
  124. if nretry == 3 {
  125. cancel()
  126. }
  127. if r == nil {
  128. t.Error("r is nil")
  129. }
  130. if res.StatusCode != resCode {
  131. t.Errorf("res.StatusCode = %d; want %d", res.StatusCode, resCode)
  132. }
  133. return time.Millisecond
  134. }
  135. client := &Client{
  136. Key: testKey,
  137. RetryBackoff: backoff,
  138. dir: &Directory{AuthzURL: ts.URL},
  139. }
  140. if _, err := client.Authorize(ctx, "example.com"); err == nil {
  141. t.Error("err is nil")
  142. }
  143. if nretry != 3 {
  144. t.Errorf("nretry = %d; want 3", nretry)
  145. }
  146. }