-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhttptestfixtures.go
143 lines (120 loc) · 3.02 KB
/
httptestfixtures.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package httptestfixtures
import (
"bufio"
"io"
"io/ioutil"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"testing"
)
// MustLoadRequest is a helper method that loads a http response from a http response file,
// if the file does not exist func fails quickly.
func MustLoadRequest(t *testing.T, filePath string) *http.Response {
f, err := os.Open(filePath)
if err != nil {
t.Fatal(err)
}
res, err := ResponseFromReader(f)
if err != nil {
t.Fatal(err)
}
return res
}
// ResponseFromReader a reader and attempts to parse contents into a *http.Response
func ResponseFromReader(r io.Reader) (*http.Response, error) {
s := bufio.NewScanner(r)
res := http.Response{
Header: http.Header{},
}
for i := 0; s.Scan(); i++ {
// are we on the first line? eg. HTTP/1.1 200 OK
if i == 0 {
out, err := parseStatusCode(s.Text())
if err != nil {
return nil, err
}
res.StatusCode = out.StatusCode
res.Status = out.Status
continue
}
// Have we hit a blank line? If so parse body
// and then leave
if s.Text() == "" {
res.Body = parseBody(s)
continue
}
// else parse headers
h, err := parseHeader(s.Text())
if err != nil {
return nil, err
}
res.Header.Add(h.key, h.value)
}
return &res, nil
}
// Doer fulfills the famous Do interface for http requests
type Doer interface {
Do(r *http.Request) (*http.Response, error)
}
// Do implements to Doer interface above, give it a response and an error and it will return them
type Do struct {
response *http.Response
err error
}
func (d Do) Do(r *http.Request) (*http.Response, error) {
return d.response, d.err
}
// NewDoer returns a struct that implements to Doer interface. Making it easier to mock http clients
func NewDoer(r *http.Response, err error) Doer {
return Do{
response: r,
err: err,
}
}
// parseBody reads the remainder of the file and concatenates them together
func parseBody(s *bufio.Scanner) io.ReadCloser {
var sb strings.Builder
for s.Scan() {
sb.WriteString(s.Text())
}
stringReader := strings.NewReader(sb.String())
return ioutil.NopCloser(stringReader)
}
// header represents a header of a http request
type header struct {
key string
value string
}
var rxHeader = regexp.MustCompile(`([a-zA-Z0-9\-]+): (.+)`)
func parseHeader(line string) (header, error) {
b := rxHeader.FindAllStringSubmatch(line, -1)
if len(b) != 1 || len(b[0]) != 3 {
return header{}, ErrUnableToParseHeader
}
return header{
key: b[0][1],
value: b[0][2],
}, nil
}
type statusCode struct {
StatusCode int
Status string
}
var rxStatusCode = regexp.MustCompile(`(HTTPS?)\/(\d\.?\d?)\ (\d+)| ([A-Z]+)?`)
func parseStatusCode(line string) (sc statusCode, err error) {
b := rxStatusCode.FindAllStringSubmatch(line, -1)
if len(b) >= 1 && len(b[0]) < 3 {
return sc, ErrUnableToParseStatusCode
}
out := statusCode{
Status: b[0][3],
}
if out.StatusCode, err = strconv.Atoi(b[0][3]); err != nil {
return sc, err
}
out.Status = strconv.Itoa(out.StatusCode) + " " + http.StatusText(out.StatusCode)
return out, nil
}