aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com')
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/Gopkg.lock13
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/Gopkg.toml4
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/client_test.go236
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/cmd/statuscake/main.go246
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/auth_error.json4
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/sslCreateOk.json14
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/sslDeleteOk.json4
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/sslListAllOk.json100
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/sslUpdateOk.json4
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_all_ok.json70
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_create_ok.json13
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_update_error.json13
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_update_ok.json4
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_all_ok.json23
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_delete_error.json5
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_delete_ok.json6
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_detail_ok.json39
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_update_error.json9
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_update_error_slice_of_issues.json5
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_update_ok.json6
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/ssl.go273
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/ssl_test.go343
-rw-r--r--vendor/github.com/DreamItGetIT/statuscake/tests_test.go422
-rw-r--r--vendor/github.com/google/go-querystring/.gitignore1
-rw-r--r--vendor/github.com/google/go-querystring/.travis.yml5
-rw-r--r--vendor/github.com/google/go-querystring/CONTRIBUTING.md67
-rw-r--r--vendor/github.com/google/go-querystring/LICENSE27
-rw-r--r--vendor/github.com/google/go-querystring/README.md37
-rw-r--r--vendor/github.com/google/go-querystring/go.mod1
-rw-r--r--vendor/github.com/google/go-querystring/query/encode.go320
-rw-r--r--vendor/github.com/google/go-querystring/query/encode_test.go328
31 files changed, 2640 insertions, 2 deletions
diff --git a/vendor/github.com/DreamItGetIT/statuscake/Gopkg.lock b/vendor/github.com/DreamItGetIT/statuscake/Gopkg.lock
index c5b189e..b433daf 100644
--- a/vendor/github.com/DreamItGetIT/statuscake/Gopkg.lock
+++ b/vendor/github.com/DreamItGetIT/statuscake/Gopkg.lock
@@ -18,6 +18,14 @@
18 version = "v1.1.1" 18 version = "v1.1.1"
19 19
20[[projects]] 20[[projects]]
21 digest = "1:a63cff6b5d8b95638bfe300385d93b2a6d9d687734b863da8e09dc834510a690"
22 name = "github.com/google/go-querystring"
23 packages = ["query"]
24 pruneopts = "UT"
25 revision = "44c6ddd0a2342c386950e880b658017258da92fc"
26 version = "v1.0.0"
27
28[[projects]]
21 digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" 29 digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
22 name = "github.com/pmezard/go-difflib" 30 name = "github.com/pmezard/go-difflib"
23 packages = ["difflib"] 31 packages = ["difflib"]
@@ -30,7 +38,7 @@
30 name = "github.com/stretchr/testify" 38 name = "github.com/stretchr/testify"
31 packages = [ 39 packages = [
32 "assert", 40 "assert",
33 "require", 41 "require"
34 ] 42 ]
35 pruneopts = "UT" 43 pruneopts = "UT"
36 revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" 44 revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
@@ -41,8 +49,9 @@
41 analyzer-version = 1 49 analyzer-version = 1
42 input-imports = [ 50 input-imports = [
43 "github.com/DreamItGetIT/statuscake", 51 "github.com/DreamItGetIT/statuscake",
52 "github.com/google/go-querystring/query",
44 "github.com/stretchr/testify/assert", 53 "github.com/stretchr/testify/assert",
45 "github.com/stretchr/testify/require", 54 "github.com/stretchr/testify/require"
46 ] 55 ]
47 solver-name = "gps-cdcl" 56 solver-name = "gps-cdcl"
48 solver-version = 1 57 solver-version = 1
diff --git a/vendor/github.com/DreamItGetIT/statuscake/Gopkg.toml b/vendor/github.com/DreamItGetIT/statuscake/Gopkg.toml
index b0c1687..e02ee65 100644
--- a/vendor/github.com/DreamItGetIT/statuscake/Gopkg.toml
+++ b/vendor/github.com/DreamItGetIT/statuscake/Gopkg.toml
@@ -36,3 +36,7 @@
36[prune] 36[prune]
37 go-tests = true 37 go-tests = true
38 unused-packages = true 38 unused-packages = true
39
40[[constraint]]
41 name = "github.com/google/go-querystring"
42 version = "1.0.0"
diff --git a/vendor/github.com/DreamItGetIT/statuscake/client_test.go b/vendor/github.com/DreamItGetIT/statuscake/client_test.go
new file mode 100644
index 0000000..7c40c81
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/client_test.go
@@ -0,0 +1,236 @@
1package statuscake
2
3import (
4 "bytes"
5 "io"
6 "io/ioutil"
7 "log"
8 "net/http"
9 "net/url"
10 "os"
11 "path/filepath"
12 "testing"
13
14 "github.com/stretchr/testify/assert"
15 "github.com/stretchr/testify/require"
16)
17
18func TestAuth_validate(t *testing.T) {
19 assert := assert.New(t)
20 require := require.New(t)
21
22 auth := &Auth{}
23 err := auth.validate()
24
25 require.NotNil(err)
26 assert.Contains(err.Error(), "Username is required")
27 assert.Contains(err.Error(), "Apikey is required")
28
29 auth.Username = "foo"
30 err = auth.validate()
31
32 require.NotNil(err)
33 assert.Equal("Apikey is required", err.Error())
34
35 auth.Apikey = "bar"
36 err = auth.validate()
37 assert.Nil(err)
38}
39
40func TestClient(t *testing.T) {
41 require := require.New(t)
42 assert := assert.New(t)
43
44 c, err := New(Auth{Username: "random-user", Apikey: "my-pass"})
45 require.Nil(err)
46
47 assert.Equal("random-user", c.username)
48 assert.Equal("my-pass", c.apiKey)
49}
50
51func TestClient_newRequest(t *testing.T) {
52 assert := assert.New(t)
53 require := require.New(t)
54
55 c, err := New(Auth{Username: "random-user", Apikey: "my-pass"})
56 require.Nil(err)
57
58 r, err := c.newRequest("GET", "/hello", nil, nil)
59
60 require.Nil(err)
61 assert.Equal("GET", r.Method)
62 assert.Equal("https://app.statuscake.com/API/hello", r.URL.String())
63 assert.Equal("random-user", r.Header.Get("Username"))
64 assert.Equal("my-pass", r.Header.Get("API"))
65}
66
67func TestClient_doRequest(t *testing.T) {
68 assert := assert.New(t)
69 require := require.New(t)
70
71 c, err := New(Auth{Username: "random-user", Apikey: "my-pass"})
72 require.Nil(err)
73
74 hc := &fakeHTTPClient{StatusCode: 200}
75 c.c = hc
76
77 req, err := http.NewRequest("GET", "http://example.com/test", nil)
78 require.Nil(err)
79
80 _, err = c.doRequest(req)
81 require.Nil(err)
82
83 assert.Len(hc.requests, 1)
84 assert.Equal("http://example.com/test", hc.requests[0].URL.String())
85}
86
87func TestClient_doRequest_WithHTTPErrors(t *testing.T) {
88 assert := assert.New(t)
89 require := require.New(t)
90
91 c, err := New(Auth{Username: "random-user", Apikey: "my-pass"})
92 require.Nil(err)
93
94 hc := &fakeHTTPClient{
95 StatusCode: 500,
96 }
97 c.c = hc
98
99 req, err := http.NewRequest("GET", "http://example.com/test", nil)
100 require.Nil(err)
101
102 _, err = c.doRequest(req)
103 require.NotNil(err)
104 assert.IsType(&httpError{}, err)
105}
106
107func TestClient_doRequest_HttpAuthenticationErrors(t *testing.T) {
108 assert := assert.New(t)
109 require := require.New(t)
110
111 c, err := New(Auth{Username: "random-user", Apikey: "my-pass"})
112 require.Nil(err)
113
114 hc := &fakeHTTPClient{
115 StatusCode: 200,
116 Fixture: "auth_error.json",
117 }
118 c.c = hc
119
120 req, err := http.NewRequest("GET", "http://example.com/test", nil)
121 require.Nil(err)
122
123 _, err = c.doRequest(req)
124 require.NotNil(err)
125 assert.IsType(&AuthenticationError{}, err)
126}
127
128func TestClient_get(t *testing.T) {
129 require := require.New(t)
130 assert := assert.New(t)
131
132 c, err := New(Auth{Username: "random-user", Apikey: "my-pass"})
133 require.Nil(err)
134
135 hc := &fakeHTTPClient{}
136 c.c = hc
137
138 c.get("/hello", nil)
139 assert.Len(hc.requests, 1)
140 assert.Equal("GET", hc.requests[0].Method)
141 assert.Equal("https://app.statuscake.com/API/hello", hc.requests[0].URL.String())
142}
143
144func TestClient_put(t *testing.T) {
145 require := require.New(t)
146 assert := assert.New(t)
147
148 c, err := New(Auth{Username: "random-user", Apikey: "my-pass"})
149 require.Nil(err)
150
151 hc := &fakeHTTPClient{}
152 c.c = hc
153
154 v := url.Values{"foo": {"bar"}}
155 c.put("/hello", v)
156 assert.Len(hc.requests, 1)
157 assert.Equal("PUT", hc.requests[0].Method)
158 assert.Equal("https://app.statuscake.com/API/hello", hc.requests[0].URL.String())
159
160 b, err := ioutil.ReadAll(hc.requests[0].Body)
161 require.Nil(err)
162 assert.Equal("foo=bar", string(b))
163}
164
165func TestClient_delete(t *testing.T) {
166 require := require.New(t)
167 assert := assert.New(t)
168
169 c, err := New(Auth{Username: "random-user", Apikey: "my-pass"})
170 require.Nil(err)
171
172 hc := &fakeHTTPClient{}
173 c.c = hc
174
175 v := url.Values{"foo": {"bar"}}
176 c.delete("/hello", v)
177 assert.Len(hc.requests, 1)
178 assert.Equal("DELETE", hc.requests[0].Method)
179 assert.Equal("https://app.statuscake.com/API/hello?foo=bar", hc.requests[0].URL.String())
180}
181
182func TestClient_Tests(t *testing.T) {
183 require := require.New(t)
184 assert := assert.New(t)
185
186 c, err := New(Auth{Username: "random-user", Apikey: "my-pass"})
187 require.Nil(err)
188
189 expected := &tests{
190 client: c,
191 }
192
193 assert.Equal(expected, c.Tests())
194}
195
196type fakeBody struct {
197 io.Reader
198}
199
200func (f *fakeBody) Close() error {
201 return nil
202}
203
204type fakeHTTPClient struct {
205 StatusCode int
206 Fixture string
207 requests []*http.Request
208}
209
210func (c *fakeHTTPClient) Do(r *http.Request) (*http.Response, error) {
211 c.requests = append(c.requests, r)
212 var body []byte
213
214 if c.Fixture != "" {
215 p := filepath.Join("fixtures", c.Fixture)
216 f, err := os.Open(p)
217 if err != nil {
218 log.Fatal(err)
219 }
220 defer f.Close()
221
222 b, err := ioutil.ReadAll(f)
223 if err != nil {
224 log.Fatal(err)
225 }
226
227 body = b
228 }
229
230 resp := &http.Response{
231 StatusCode: c.StatusCode,
232 Body: &fakeBody{Reader: bytes.NewReader(body)},
233 }
234
235 return resp, nil
236}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/cmd/statuscake/main.go b/vendor/github.com/DreamItGetIT/statuscake/cmd/statuscake/main.go
new file mode 100644
index 0000000..59f41a9
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/cmd/statuscake/main.go
@@ -0,0 +1,246 @@
1package main
2
3import (
4 "fmt"
5 logpkg "log"
6 "os"
7 "strconv"
8
9 "github.com/DreamItGetIT/statuscake"
10 "strings"
11)
12
13var log *logpkg.Logger
14
15type command func(*statuscake.Client, ...string) error
16
17var commands map[string]command
18
19func init() {
20 log = logpkg.New(os.Stderr, "", 0)
21 commands = map[string]command{
22 "list": cmdList,
23 "detail": cmdDetail,
24 "delete": cmdDelete,
25 "create": cmdCreate,
26 "update": cmdUpdate,
27 }
28}
29
30func colouredStatus(s string) string {
31 switch s {
32 case "Up":
33 return fmt.Sprintf("\033[0;32m%s\033[0m", s)
34 case "Down":
35 return fmt.Sprintf("\033[0;31m%s\033[0m", s)
36 default:
37 return s
38 }
39}
40
41func getEnv(name string) string {
42 v := os.Getenv(name)
43 if v == "" {
44 log.Fatalf("`%s` env variable is required", name)
45 }
46
47 return v
48}
49
50func cmdList(c *statuscake.Client, args ...string) error {
51 tt := c.Tests()
52 tests, err := tt.All()
53 if err != nil {
54 return err
55 }
56
57 for _, t := range tests {
58 var paused string
59 if t.Paused {
60 paused = "yes"
61 } else {
62 paused = "no"
63 }
64
65 fmt.Printf("* %d: %s\n", t.TestID, colouredStatus(t.Status))
66 fmt.Printf(" WebsiteName: %s\n", t.WebsiteName)
67 fmt.Printf(" TestType: %s\n", t.TestType)
68 fmt.Printf(" Paused: %s\n", paused)
69 fmt.Printf(" ContactGroup: %s\n", fmt.Sprint(t.ContactGroup))
70 fmt.Printf(" Uptime: %f\n", t.Uptime)
71 }
72
73 return nil
74}
75
76func cmdDetail(c *statuscake.Client, args ...string) error {
77 if len(args) != 1 {
78 return fmt.Errorf("command `detail` requires a single argument `TestID`")
79 }
80
81 id, err := strconv.Atoi(args[0])
82 if err != nil {
83 return err
84 }
85
86 tt := c.Tests()
87 t, err := tt.Detail(id)
88 if err != nil {
89 return err
90 }
91
92 var paused string
93 if t.Paused {
94 paused = "yes"
95 } else {
96 paused = "no"
97 }
98
99 fmt.Printf("* %d: %s\n", t.TestID, colouredStatus(t.Status))
100 fmt.Printf(" WebsiteName: %s\n", t.WebsiteName)
101 fmt.Printf(" WebsiteURL: %s\n", t.WebsiteURL)
102 fmt.Printf(" PingURL: %s\n", t.PingURL)
103 fmt.Printf(" TestType: %s\n", t.TestType)
104 fmt.Printf(" Paused: %s\n", paused)
105 fmt.Printf(" ContactGroup: %s\n", fmt.Sprint(t.ContactGroup))
106 fmt.Printf(" Uptime: %f\n", t.Uptime)
107 fmt.Printf(" NodeLocations: %s\n", fmt.Sprint(t.NodeLocations))
108
109 return nil
110}
111
112func cmdDelete(c *statuscake.Client, args ...string) error {
113 if len(args) != 1 {
114 return fmt.Errorf("command `delete` requires a single argument `TestID`")
115 }
116
117 id, err := strconv.Atoi(args[0])
118 if err != nil {
119 return err
120 }
121
122 return c.Tests().Delete(id)
123}
124
125func askString(name string) string {
126 var v string
127
128 fmt.Printf("%s: ", name)
129 _, err := fmt.Scanln(&v)
130 if err != nil {
131 log.Fatal(err)
132 }
133
134 return v
135}
136
137func askInt(name string) int {
138 v := askString(name)
139 i, err := strconv.Atoi(v)
140 if err != nil {
141 log.Fatalf("Invalid number `%s`", v)
142 }
143
144 return i
145}
146
147func cmdCreate(c *statuscake.Client, args ...string) error {
148 websiteName := askString("WebsiteName")
149 websiteURL := askString("WebsiteURL")
150 testType := askString("TestType")
151 checkRate := askInt("CheckRate")
152 contactGroupString := askString("ContactGroup (comma separated list)")
153 contactGroup := strings.Split(contactGroupString, ",")
154 nodeLocationsString := askString("NodeLocations (comma separated list)")
155 nodeLocations := strings.Split(nodeLocationsString, ",")
156
157 t := &statuscake.Test{
158 WebsiteName: websiteName,
159 WebsiteURL: websiteURL,
160 TestType: testType,
161 CheckRate: checkRate,
162 NodeLocations: nodeLocations,
163 ContactGroup: contactGroup,
164 }
165
166 t2, err := c.Tests().Update(t)
167 if err != nil {
168 return err
169 }
170
171 fmt.Printf("CREATED: \n%+v\n", t2)
172
173 return nil
174}
175
176func cmdUpdate(c *statuscake.Client, args ...string) error {
177 if len(args) != 1 {
178 return fmt.Errorf("command `update` requires a single argument `TestID`")
179 }
180
181 id, err := strconv.Atoi(args[0])
182 if err != nil {
183 return err
184 }
185
186 tt := c.Tests()
187 t, err := tt.Detail(id)
188 if err != nil {
189 return err
190 }
191
192 t.TestID = id
193 t.WebsiteName = askString(fmt.Sprintf("WebsiteName [%s]", t.WebsiteName))
194 t.WebsiteURL = askString(fmt.Sprintf("WebsiteURL [%s]", t.WebsiteURL))
195 t.TestType = askString(fmt.Sprintf("TestType [%s]", t.TestType))
196 t.CheckRate = askInt(fmt.Sprintf("CheckRate [%d]", t.CheckRate))
197 contactGroupString := askString("ContactGroup (comma separated list)")
198 t.ContactGroup = strings.Split(contactGroupString, ",")
199 nodeLocationsString := askString("NodeLocations (comma separated list)")
200 t.NodeLocations = strings.Split(nodeLocationsString, ",")
201
202 t2, err := c.Tests().Update(t)
203 if err != nil {
204 return err
205 }
206
207 fmt.Printf("UPDATED: \n%+v\n", t2)
208
209 return nil
210}
211
212func usage() {
213 fmt.Printf("Usage:\n")
214 fmt.Printf(" %s COMMAND\n", os.Args[0])
215 fmt.Printf("Available commands:\n")
216 for k := range commands {
217 fmt.Printf(" %+v\n", k)
218 }
219}
220
221func main() {
222 username := getEnv("STATUSCAKE_USERNAME")
223 apikey := getEnv("STATUSCAKE_APIKEY")
224
225 if len(os.Args) < 2 {
226 usage()
227 os.Exit(1)
228 }
229
230 var err error
231
232 c, err := statuscake.New(statuscake.Auth{Username: username, Apikey: apikey})
233 if err != nil {
234 log.Fatal(err)
235 }
236
237 if cmd, ok := commands[os.Args[1]]; ok {
238 err = cmd(c, os.Args[2:]...)
239 } else {
240 err = fmt.Errorf("Unknown command `%s`", os.Args[1])
241 }
242
243 if err != nil {
244 log.Fatalf("Error running command `%s`: %s", os.Args[1], err.Error())
245 }
246}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/auth_error.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/auth_error.json
new file mode 100644
index 0000000..4f14be5
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/auth_error.json
@@ -0,0 +1,4 @@
1{
2 "ErrNo": 0,
3 "Error": "Can not access account. Was both Username and API Key provided?"
4}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/sslCreateOk.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/sslCreateOk.json
new file mode 100644
index 0000000..615e2db
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/sslCreateOk.json
@@ -0,0 +1,14 @@
1{
2 "Success": true,
3 "Message": 143616,
4 "Input": {
5 "domain": "https://www.exemple.com",
6 "checkrate": "2073600",
7 "contact_groups": "",
8 "alert_reminder": true,
9 "alert_expiry": true,
10 "alert_broken": true,
11 "alert_mixed": true,
12 "alert_at": "7,18,2019"
13 }
14}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/sslDeleteOk.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/sslDeleteOk.json
new file mode 100644
index 0000000..a394239
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/sslDeleteOk.json
@@ -0,0 +1,4 @@
1{
2 "Success": true,
3 "Message": "Deletion successful"
4}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/sslListAllOk.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/sslListAllOk.json
new file mode 100644
index 0000000..15c428f
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/sslListAllOk.json
@@ -0,0 +1,100 @@
1[
2 {
3 "id": "143615",
4 "checkrate": 2073600,
5 "paused": false,
6 "domain": "https://www.exemple.com",
7 "issuer_cn": "Let's Encrypt Authority X3",
8 "cert_score": "95",
9 "cipher_score": "100",
10 "cert_status": "CERT_OK",
11 "cipher": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
12 "valid_from_utc": "2019-05-28 01:22:00",
13 "valid_until_utc": "2019-08-26 01:22:00",
14 "mixed_content": [],
15 "flags": {
16 "is_extended": false,
17 "has_pfs": true,
18 "is_broken": false,
19 "is_expired": false,
20 "is_missing": false,
21 "is_revoked": false,
22 "has_mixed": false
23 },
24 "contact_groups": [],
25 "alert_at": "7,18,2019",
26 "last_reminder": 2019,
27 "alert_reminder": true,
28 "alert_expiry": true,
29 "alert_broken": true,
30 "alert_mixed": true,
31 "last_updated_utc": "2019-06-20 10:11:03"
32 },
33 {
34 "id": "143616",
35 "checkrate": 2073600,
36 "paused": false,
37 "domain": "https://www.exemple.com",
38 "issuer_cn": "Let's Encrypt Authority X3",
39 "cert_score": "95",
40 "cipher_score": "100",
41 "cert_status": "CERT_OK",
42 "cipher": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
43 "valid_from_utc": "2019-05-28 01:22:00",
44 "valid_until_utc": "2019-08-26 01:22:00",
45 "mixed_content": [
46 {
47 "type": "img",
48 "src": "http://example.com/image.gif"
49 }
50 ],
51 "flags": {
52 "is_extended": false,
53 "has_pfs": true,
54 "is_broken": false,
55 "is_expired": false,
56 "is_missing": false,
57 "is_revoked": false,
58 "has_mixed": false
59 },
60 "contact_groups": ["12","13","34"],
61 "alert_at": "7,18,2019",
62 "last_reminder": 2019,
63 "alert_reminder": true,
64 "alert_expiry": true,
65 "alert_broken": true,
66 "alert_mixed": true,
67 "last_updated_utc": "2019-06-20 10:23:14"
68 },
69 {
70 "id": "143617",
71 "checkrate": 2073600,
72 "paused": false,
73 "domain": "https://www.exemple.com",
74 "issuer_cn": "Let's Encrypt Authority X3",
75 "cert_score": "95",
76 "cipher_score": "100",
77 "cert_status": "CERT_OK",
78 "cipher": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
79 "valid_from_utc": "2019-05-28 01:22:00",
80 "valid_until_utc": "2019-08-26 01:22:00",
81 "mixed_content": [],
82 "flags": {
83 "is_extended": false,
84 "has_pfs": true,
85 "is_broken": false,
86 "is_expired": false,
87 "is_missing": false,
88 "is_revoked": false,
89 "has_mixed": false
90 },
91 "contact_groups": [],
92 "alert_at": "7,18,2019",
93 "last_reminder": 2019,
94 "alert_reminder": true,
95 "alert_expiry": true,
96 "alert_broken": true,
97 "alert_mixed": true,
98 "last_updated_utc": "2019-06-20 10:23:20"
99 }
100]
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/sslUpdateOk.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/sslUpdateOk.json
new file mode 100644
index 0000000..23b5932
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/sslUpdateOk.json
@@ -0,0 +1,4 @@
1{
2 "Success": true,
3 "Message": "SSL test has been updated successfully"
4}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_all_ok.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_all_ok.json
new file mode 100644
index 0000000..4dd6b51
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_all_ok.json
@@ -0,0 +1,70 @@
1[
2 {
3 "id": "12345",
4 "paused": false,
5 "domain": "https://google.com",
6 "cert_score": "95",
7 "cipher_score": "100",
8 "cert_status": "CERT_OK",
9 "cipher": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
10 "valid_from_utc": "2017-10-10 14:06:00",
11 "valid_until_utc": "2017-12-29 00:00:00",
12 "mixed_content": [
13 {
14 "type": "img",
15 "src": "http://example.com/image.gif"
16 }
17 ],
18 "flags": {
19 "is_extended": false,
20 "has_pfs": true,
21 "is_broken": false,
22 "is_expired": false,
23 "is_missing": false,
24 "is_revoked": false,
25 "is_mixed": false
26 },
27 "contact_groups": [12, 13, 14],
28 "alert_at": "1,7,30",
29 "last_reminder": 0,
30 "alert_reminder": false,
31 "alert_expiry": false,
32 "alert_broken": false,
33 "alert_mixed": false,
34 "last_updated_utc": "2017-10-24 09:02:25"
35 },
36 {
37 "id": "12346",
38 "paused": false,
39 "domain": "https://google2.com",
40 "cert_score": "95",
41 "cipher_score": "100",
42 "cert_status": "CERT_OK",
43 "cipher": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
44 "valid_from_utc": "2017-10-10 14:06:00",
45 "valid_until_utc": "2017-12-29 00:00:00",
46 "mixed_content": [
47 {
48 "type": "img",
49 "src": "http://example.com/image.gif"
50 }
51 ],
52 "flags": {
53 "is_extended": false,
54 "has_pfs": true,
55 "is_broken": false,
56 "is_expired": false,
57 "is_missing": false,
58 "is_revoked": false,
59 "is_mixed": false
60 },
61 "contact_groups": [12, 13, 14],
62 "alert_at": "1,7,30",
63 "last_reminder": 0,
64 "alert_reminder": false,
65 "alert_expiry": false,
66 "alert_broken": false,
67 "alert_mixed": false,
68 "last_updated_utc": "2017-10-24 09:02:25"
69 }
70]
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_create_ok.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_create_ok.json
new file mode 100644
index 0000000..c7734e0
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_create_ok.json
@@ -0,0 +1,13 @@
1{
2 "Success": true,
3 "Message": 12345,
4 "Input": {
5 "domain": "https://example.com",
6 "checkrate": 86400,
7 "contact_groups": "1000,2000",
8 "alert_reminder": false,
9 "alert_expiry": false,
10 "alert_broken": false,
11 "alert_at": "59,60,61"
12 }
13}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_update_error.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_update_error.json
new file mode 100644
index 0000000..5f0f4d5
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_update_error.json
@@ -0,0 +1,13 @@
1{
2 "Success": false,
3 "Message": "Error creating test",
4 "Input": {
5 "domain": "https://example.com",
6 "checkrate": 86400,
7 "contact_groups": "1000,2000",
8 "alert_reminder": false,
9 "alert_expiry": false,
10 "alert_broken": false,
11 "alert_at": "59,60,61"
12 }
13}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_update_ok.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_update_ok.json
new file mode 100644
index 0000000..f77f4f3
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/ssls_update_ok.json
@@ -0,0 +1,4 @@
1{
2 "Success": true,
3 "Message": "SSL test has been updated successfully"
4}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_all_ok.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_all_ok.json
new file mode 100644
index 0000000..dee80d4
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_all_ok.json
@@ -0,0 +1,23 @@
1[
2 {
3 "TestID": 100,
4 "Paused": false,
5 "TestType": "HTTP",
6 "WebsiteName": "www 1",
7 "ContactGroup": ["1"],
8 "Status": "Up",
9 "Uptime": 100,
10 "NodeLocations": ["foo", "bar"]
11 },
12 {
13 "TestID": 101,
14 "Paused": true,
15 "TestType": "HTTP",
16 "WebsiteName": "www 2",
17 "ContactGroup": ["2"],
18 "Status": "Down",
19 "Uptime": 0,
20 "NodeLocations": ["foo"],
21 "TestTags": ["test1", "test2"]
22 }
23]
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_delete_error.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_delete_error.json
new file mode 100644
index 0000000..6fad58e
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_delete_error.json
@@ -0,0 +1,5 @@
1{
2 "Success": false,
3 "Error": "this is an error"
4}
5
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_delete_ok.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_delete_ok.json
new file mode 100644
index 0000000..0dd279b
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_delete_ok.json
@@ -0,0 +1,6 @@
1{
2 "TestID": 6735,
3 "Affected": 1,
4 "Success": true,
5 "Message": "This Check Has Been Deleted. It can not be recovered."
6}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_detail_ok.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_detail_ok.json
new file mode 100644
index 0000000..a64989b
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_detail_ok.json
@@ -0,0 +1,39 @@
1{
2 "TestID": 6735,
3 "TestType": "HTTP",
4 "Paused": false,
5 "WebsiteName": "NL",
6 "CustomHeader": "{\"some\":{\"json\": [\"value\"]}}",
7 "UserAgent": "product/version (comment)",
8 "ContactGroups": [
9 {
10 "ID": 536,
11 "Name": "Dummy ContactGroup",
12 "Email": "github-dreamitgetit-statuscake@maildrop.cc"
13 }
14 ],
15 "ContactID": 536,
16 "Status": "Up",
17 "Uptime": 0,
18 "CheckRate": 60,
19 "Timeout": 40,
20 "LogoImage": "",
21 "WebsiteHost": "Various",
22 "NodeLocations": [
23 "foo",
24 "bar"
25 ],
26 "FindString": "",
27 "DoNotFind": false,
28 "LastTested": "2013-01-20 14:38:18",
29 "NextLocation": "USNY",
30 "Processing": false,
31 "ProcessingState": "Pretest",
32 "ProcessingOn": "dalas.localdomain",
33 "DownTimes": "0",
34 "UseJar": 0,
35 "PostRaw": "",
36 "FinalEndpoint": "",
37 "EnableSSLWarning": false,
38 "FollowRedirect": false
39}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_update_error.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_update_error.json
new file mode 100644
index 0000000..a76c5eb
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_update_error.json
@@ -0,0 +1,9 @@
1{
2 "Issues": {
3 "WebsiteName": "issue a",
4 "WebsiteURL": "issue b",
5 "CheckRate": "issue c"
6 },
7 "Success": false,
8 "Message": "Required Data is Missing."
9}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_update_error_slice_of_issues.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_update_error_slice_of_issues.json
new file mode 100644
index 0000000..02e98eb
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_update_error_slice_of_issues.json
@@ -0,0 +1,5 @@
1{
2 "Issues": ["hello", "world"],
3 "Success": false,
4 "Message": "Required Data is Missing."
5}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_update_ok.json b/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_update_ok.json
new file mode 100644
index 0000000..7c2dee2
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/fixtures/tests_update_ok.json
@@ -0,0 +1,6 @@
1{
2 "Issues": {},
3 "Success": true,
4 "Message": "",
5 "InsertID": 1234
6}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/ssl.go b/vendor/github.com/DreamItGetIT/statuscake/ssl.go
new file mode 100644
index 0000000..3f73d8d
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/ssl.go
@@ -0,0 +1,273 @@
1package statuscake
2
3import (
4 "encoding/json"
5 "fmt"
6 "net/url"
7 "strings"
8 "strconv"
9
10 "github.com/google/go-querystring/query"
11)
12
13//Ssl represent the data received by the API with GET
14type Ssl struct {
15 ID string `json:"id" url:"id,omitempty"`
16 Domain string `json:"domain" url:"domain,omitempty"`
17 Checkrate int `json:"checkrate" url:"checkrate,omitempty"`
18 ContactGroupsC string ` url:"contact_groups,omitempty"`
19 AlertAt string `json:"alert_at" url:"alert_at,omitempty"`
20 AlertReminder bool `json:"alert_reminder" url:"alert_expiry,omitempty"`
21 AlertExpiry bool `json:"alert_expiry" url:"alert_reminder,omitempty"`
22 AlertBroken bool `json:"alert_broken" url:"alert_broken,omitempty"`
23 AlertMixed bool `json:"alert_mixed" url:"alert_mixed,omitempty"`
24 Paused bool `json:"paused"`
25 IssuerCn string `json:"issuer_cn"`
26 CertScore string `json:"cert_score"`
27 CipherScore string `json:"cipher_score"`
28 CertStatus string `json:"cert_status"`
29 Cipher string `json:"cipher"`
30 ValidFromUtc string `json:"valid_from_utc"`
31 ValidUntilUtc string `json:"valid_until_utc"`
32 MixedContent []map[string]string `json:"mixed_content"`
33 Flags map[string]bool `json:"flags"`
34 ContactGroups []string `json:"contact_groups"`
35 LastReminder int `json:"last_reminder"`
36 LastUpdatedUtc string `json:"last_updated_utc"`
37}
38
39//PartialSsl represent a ssl test creation or modification
40type PartialSsl struct {
41 ID int
42 Domain string
43 Checkrate string
44 ContactGroupsC string
45 AlertAt string
46 AlertExpiry bool
47 AlertReminder bool
48 AlertBroken bool
49 AlertMixed bool
50}
51
52type createSsl struct {
53 ID int `url:"id,omitempty"`
54 Domain string `url:"domain" json:"domain"`
55 Checkrate string `url:"checkrate" json:"checkrate"`
56 ContactGroupsC string `url:"contact_groups" json:"contact_groups"`
57 AlertAt string `url:"alert_at" json:"alert_at"`
58 AlertExpiry bool `url:"alert_expiry" json:"alert_expiry"`
59 AlertReminder bool `url:"alert_reminder" json:"alert_reminder"`
60 AlertBroken bool `url:"alert_broken" json:"alert_broken"`
61 AlertMixed bool `url:"alert_mixed" json:"alert_mixed"`
62}
63
64type updateSsl struct {
65 ID int `url:"id"`
66 Domain string `url:"domain" json:"domain"`
67 Checkrate string `url:"checkrate" json:"checkrate"`
68 ContactGroupsC string `url:"contact_groups" json:"contact_groups"`
69 AlertAt string `url:"alert_at" json:"alert_at"`
70 AlertExpiry bool `url:"alert_expiry" json:"alert_expiry"`
71 AlertReminder bool `url:"alert_reminder" json:"alert_reminder"`
72 AlertBroken bool `url:"alert_broken" json:"alert_broken"`
73 AlertMixed bool `url:"alert_mixed" json:"alert_mixed"`
74}
75
76
77type sslUpdateResponse struct {
78 Success bool `json:"Success"`
79 Message interface{} `json:"Message"`
80}
81
82type sslCreateResponse struct {
83 Success bool `json:"Success"`
84 Message interface{} `json:"Message"`
85 Input createSsl `json:"Input"`
86}
87
88//Ssls represent the actions done wit the API
89type Ssls interface {
90 All() ([]*Ssl, error)
91 completeSsl(*PartialSsl) (*Ssl, error)
92 Detail(string) (*Ssl, error)
93 Update(*PartialSsl) (*Ssl, error)
94 UpdatePartial(*PartialSsl) (*PartialSsl, error)
95 Delete(ID string) error
96 CreatePartial(*PartialSsl) (*PartialSsl, error)
97 Create(*PartialSsl) (*Ssl, error)
98}
99
100func consolidateSsl(s *Ssl) {
101 (*s).ContactGroupsC = strings.Trim(strings.Join(strings.Fields(fmt.Sprint((*s).ContactGroups)), ","), "[]")
102}
103
104func findSsl(responses []*Ssl, id string) (*Ssl, error) {
105 var response *Ssl
106 for _, elem := range responses {
107 if (*elem).ID == id {
108 return elem, nil
109 }
110 }
111 return response, fmt.Errorf("%s Not found", id)
112}
113
114func (tt *ssls) completeSsl(s *PartialSsl) (*Ssl, error) {
115 full, err := tt.Detail(strconv.Itoa((*s).ID))
116 if err != nil {
117 return nil, err
118 }
119 (*full).ContactGroups = strings.Split((*s).ContactGroupsC,",")
120 return full, nil
121}
122
123//Partial return a PartialSsl corresponding to the Ssl
124func Partial(s *Ssl) (*PartialSsl,error) {
125 if s==nil {
126 return nil,fmt.Errorf("s is nil")
127 }
128 id,err:=strconv.Atoi(s.ID)
129 if(err!=nil){
130 return nil,err
131 }
132 return &PartialSsl{
133 ID: id,
134 Domain: s.Domain,
135 Checkrate: strconv.Itoa(s.Checkrate),
136 ContactGroupsC: s.ContactGroupsC,
137 AlertReminder: s.AlertReminder,
138 AlertExpiry: s.AlertExpiry,
139 AlertBroken: s.AlertBroken,
140 AlertMixed: s.AlertMixed,
141 AlertAt: s.AlertAt,
142 },nil
143
144}
145
146type ssls struct {
147 client apiClient
148}
149
150//NewSsls return a new ssls
151func NewSsls(c apiClient) Ssls {
152 return &ssls{
153 client: c,
154 }
155}
156
157//All return a list of all the ssl from the API
158func (tt *ssls) All() ([]*Ssl, error) {
159 rawResponse, err := tt.client.get("/SSL", nil)
160 if err != nil {
161 return nil, fmt.Errorf("Error getting StatusCake Ssl: %s", err.Error())
162 }
163 var getResponse []*Ssl
164 err = json.NewDecoder(rawResponse.Body).Decode(&getResponse)
165 if err != nil {
166 return nil, err
167 }
168
169 for ssl := range getResponse {
170 consolidateSsl(getResponse[ssl])
171 }
172
173 return getResponse, err
174}
175
176//Detail return the ssl corresponding to the id
177func (tt *ssls) Detail(id string) (*Ssl, error) {
178 responses, err := tt.All()
179 if err != nil {
180 return nil, err
181 }
182 mySsl, errF := findSsl(responses, id)
183 if errF != nil {
184 return nil, errF
185 }
186 return mySsl, nil
187}
188
189//Update update the API with s and create one if s.ID=0 then return the corresponding Ssl
190func (tt *ssls) Update(s *PartialSsl) (*Ssl, error) {
191 var err error
192 s, err = tt.UpdatePartial(s)
193 if err!= nil {
194 return nil, err
195 }
196 return tt.completeSsl(s)
197}
198
199//UpdatePartial update the API with s and create one if s.ID=0 then return the corresponding PartialSsl
200func (tt *ssls) UpdatePartial(s *PartialSsl) (*PartialSsl, error) {
201
202 if((*s).ID == 0){
203 return tt.CreatePartial(s)
204 }
205 var v url.Values
206
207 v, _ = query.Values(updateSsl(*s))
208
209 rawResponse, err := tt.client.put("/SSL/Update", v)
210 if err != nil {
211 return nil, fmt.Errorf("Error creating StatusCake Ssl: %s", err.Error())
212 }
213
214 var updateResponse sslUpdateResponse
215 err = json.NewDecoder(rawResponse.Body).Decode(&updateResponse)
216 if err != nil {
217 return nil, err
218 }
219
220 if !updateResponse.Success {
221 return nil, fmt.Errorf("%s", updateResponse.Message.(string))
222 }
223
224
225 return s, nil
226}
227
228//Delete delete the ssl which ID is id
229func (tt *ssls) Delete(id string) error {
230 _, err := tt.client.delete("/SSL/Update", url.Values{"id": {fmt.Sprint(id)}})
231 if err != nil {
232 return err
233 }
234
235 return nil
236}
237
238//Create create the ssl whith the data in s and return the Ssl created
239func (tt *ssls) Create(s *PartialSsl) (*Ssl, error) {
240 var err error
241 s, err = tt.CreatePartial(s)
242 if err!= nil {
243 return nil, err
244 }
245 return tt.completeSsl(s)
246}
247
248//CreatePartial create the ssl whith the data in s and return the PartialSsl created
249func (tt *ssls) CreatePartial(s *PartialSsl) (*PartialSsl, error) {
250 (*s).ID=0
251 var v url.Values
252 v, _ = query.Values(createSsl(*s))
253
254 rawResponse, err := tt.client.put("/SSL/Update", v)
255 if err != nil {
256 return nil, fmt.Errorf("Error creating StatusCake Ssl: %s", err.Error())
257 }
258
259 var createResponse sslCreateResponse
260 err = json.NewDecoder(rawResponse.Body).Decode(&createResponse)
261 if err != nil {
262 return nil, err
263 }
264
265 if !createResponse.Success {
266 return nil, fmt.Errorf("%s", createResponse.Message.(string))
267 }
268 *s = PartialSsl(createResponse.Input)
269 (*s).ID = int(createResponse.Message.(float64))
270
271 return s,nil
272}
273
diff --git a/vendor/github.com/DreamItGetIT/statuscake/ssl_test.go b/vendor/github.com/DreamItGetIT/statuscake/ssl_test.go
new file mode 100644
index 0000000..48a961c
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/ssl_test.go
@@ -0,0 +1,343 @@
1package statuscake
2
3import (
4 "testing"
5 //"fmt"
6 "github.com/stretchr/testify/assert"
7 "github.com/stretchr/testify/require"
8 "net/url"
9)
10
11func TestSsl_All(t *testing.T) {
12 assert := assert.New(t)
13 require := require.New(t)
14
15 c := &fakeAPIClient{
16 fixture: "sslListAllOk.json",
17 }
18 tt := NewSsls(c)
19 ssls, err := tt.All()
20 require.Nil(err)
21
22 assert.Equal("/SSL", c.sentRequestPath)
23 assert.Equal("GET", c.sentRequestMethod)
24 assert.Nil(c.sentRequestValues)
25 assert.Len(ssls, 3)
26 mixed := make(map[string]string)
27 flags := make(map[string]bool)
28 flags["is_extended"] = false
29 flags["has_pfs"] = true
30 flags["is_broken"] = false
31 flags["is_expired"] = false
32 flags["is_missing"] = false
33 flags["is_revoked"] = false
34 flags["has_mixed"] = false
35 expectedTest := &Ssl{
36 ID: "143615",
37 Checkrate: 2073600,
38 Paused: false,
39 Domain: "https://www.exemple.com",
40 IssuerCn: "Let's Encrypt Authority X3",
41 CertScore: "95",
42 CipherScore: "100",
43 CertStatus: "CERT_OK",
44 Cipher: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
45 ValidFromUtc: "2019-05-28 01:22:00",
46 ValidUntilUtc: "2019-08-26 01:22:00",
47 MixedContent: []map[string]string{},
48 Flags: flags,
49 ContactGroups: []string{},
50 ContactGroupsC: "",
51 AlertAt: "7,18,2019",
52 LastReminder: 2019,
53 AlertReminder: true,
54 AlertExpiry: true,
55 AlertBroken: true,
56 AlertMixed: true,
57 LastUpdatedUtc: "2019-06-20 10:11:03",
58 }
59 assert.Equal(expectedTest, ssls[0])
60
61 expectedTest.ID="143617"
62 expectedTest.LastUpdatedUtc="2019-06-20 10:23:20"
63 assert.Equal(expectedTest, ssls[2])
64
65 expectedTest.ID="143616"
66 expectedTest.LastUpdatedUtc="2019-06-20 10:23:14"
67 mixed["type"]="img"
68 mixed["src"]="http://example.com/image.gif"
69 expectedTest.MixedContent=[]map[string]string{mixed}
70 expectedTest.ContactGroupsC="12,13,34"
71 expectedTest.ContactGroups=[]string{"12","13","34"}
72 assert.Equal(expectedTest, ssls[1])
73}
74
75func TestSsls_Detail_OK(t *testing.T) {
76 assert := assert.New(t)
77 require := require.New(t)
78
79 c := &fakeAPIClient{
80 fixture: "sslListAllOk.json",
81 }
82 tt := NewSsls(c)
83
84 ssl, err := tt.Detail("143616")
85 require.Nil(err)
86 assert.Equal("/SSL", c.sentRequestPath)
87 assert.Equal("GET", c.sentRequestMethod)
88 assert.Nil(c.sentRequestValues)
89
90 mixed := make(map[string]string)
91 flags := make(map[string]bool)
92
93 mixed["type"]="img"
94 mixed["src"]="http://example.com/image.gif"
95
96 flags["is_extended"] = false
97 flags["has_pfs"] = true
98 flags["is_broken"] = false
99 flags["is_expired"] = false
100 flags["is_missing"] = false
101 flags["is_revoked"] = false
102 flags["has_mixed"] = false
103 expectedTest := &Ssl{
104 ID: "143616",
105 Checkrate: 2073600,
106 Paused: false,
107 Domain: "https://www.exemple.com",
108 IssuerCn: "Let's Encrypt Authority X3",
109 CertScore: "95",
110 CipherScore: "100",
111 CertStatus: "CERT_OK",
112 Cipher: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
113 ValidFromUtc: "2019-05-28 01:22:00",
114 ValidUntilUtc: "2019-08-26 01:22:00",
115 MixedContent: []map[string]string{mixed},
116 Flags: flags,
117 ContactGroups: []string{"12","13","34"},
118 ContactGroupsC: "12,13,34",
119 AlertAt: "7,18,2019",
120 LastReminder: 2019,
121 AlertReminder: true,
122 AlertExpiry: true,
123 AlertBroken: true,
124 AlertMixed: true,
125 LastUpdatedUtc: "2019-06-20 10:23:14",
126 }
127
128 assert.Equal(expectedTest, ssl)
129}
130
131func TestSsls_CreatePartial_OK(t *testing.T) {
132 assert := assert.New(t)
133 require := require.New(t)
134
135 c := &fakeAPIClient{
136 fixture: "sslCreateOk.json",
137 }
138 tt := NewSsls(c)
139 partial := &PartialSsl{
140 Domain: "https://www.exemple.com",
141 Checkrate: "2073600",
142 ContactGroupsC: "",
143 AlertReminder: true,
144 AlertExpiry: true,
145 AlertBroken: true,
146 AlertMixed: true,
147 AlertAt: "7,18,2019",
148 }
149 expectedRes := &PartialSsl {
150 ID: 143616,
151 Domain: "https://www.exemple.com",
152 Checkrate: "2073600",
153 ContactGroupsC: "",
154 AlertReminder: true,
155 AlertExpiry: true,
156 AlertBroken: true,
157 AlertMixed: true,
158 AlertAt: "7,18,2019",
159 }
160 res, err := tt.CreatePartial(partial)
161 require.Nil(err)
162 assert.Equal("/SSL/Update", c.sentRequestPath)
163 assert.Equal("PUT", c.sentRequestMethod)
164 assert.Equal(c.sentRequestValues,url.Values(url.Values{"domain":[]string{"https://www.exemple.com"}, "checkrate":[]string{"2073600"}, "contact_groups":[]string{""}, "alert_at":[]string{"7,18,2019"}, "alert_expiry":[]string{"true"}, "alert_reminder":[]string{"true"}, "alert_broken":[]string{"true"}, "alert_mixed":[]string{"true"}}))
165
166 assert.Equal(expectedRes, res)
167}
168
169func TestSsls_UpdatePartial_OK(t *testing.T) {
170 assert := assert.New(t)
171 require := require.New(t)
172
173 c := &fakeAPIClient{
174 fixture: "sslUpdateOk.json",
175 }
176 tt := NewSsls(c)
177 partial := &PartialSsl{
178 ID: 143616,
179 Domain: "https://www.exemple.com",
180 Checkrate: "2073600",
181 ContactGroupsC: "",
182 AlertReminder: false,
183 AlertExpiry: true,
184 AlertBroken: true,
185 AlertMixed: true,
186 AlertAt: "7,18,2019",
187 }
188 expectedRes := &PartialSsl {
189 ID: 143616,
190 Domain: "https://www.exemple.com",
191 Checkrate: "2073600",
192 ContactGroupsC: "",
193 AlertReminder: false,
194 AlertExpiry: true,
195 AlertBroken: true,
196 AlertMixed: true,
197 AlertAt: "7,18,2019",
198 }
199 res, err := tt.UpdatePartial(partial)
200 require.Nil(err)
201 assert.Equal(expectedRes, res)
202 assert.Equal("/SSL/Update", c.sentRequestPath)
203 assert.Equal("PUT", c.sentRequestMethod)
204 assert.Equal(c.sentRequestValues,url.Values(url.Values{"id":[]string{"143616"},"domain":[]string{"https://www.exemple.com"}, "checkrate":[]string{"2073600"}, "contact_groups":[]string{""}, "alert_at":[]string{"7,18,2019"}, "alert_expiry":[]string{"true"}, "alert_reminder":[]string{"false"}, "alert_broken":[]string{"true"}, "alert_mixed":[]string{"true"}}))
205}
206
207func TestSsl_complete_OK(t *testing.T) {
208 assert := assert.New(t)
209 require := require.New(t)
210
211 c := &fakeAPIClient{
212 fixture: "sslListAllOk.json",
213 }
214 tt := NewSsls(c)
215
216 partial := &PartialSsl {
217 ID: 143616,
218 Domain: "https://www.exemple.com",
219 Checkrate: "2073600",
220 ContactGroupsC: "12,13,34",
221 AlertReminder: true,
222 AlertExpiry: true,
223 AlertBroken: true,
224 AlertMixed: true,
225 AlertAt: "7,18,2019",
226 }
227 full, err := tt.completeSsl(partial)
228 require.Nil(err)
229 mixed := make(map[string]string)
230 flags := make(map[string]bool)
231
232 mixed["type"]="img"
233 mixed["src"]="http://example.com/image.gif"
234
235 flags["is_extended"] = false
236 flags["has_pfs"] = true
237 flags["is_broken"] = false
238 flags["is_expired"] = false
239 flags["is_missing"] = false
240 flags["is_revoked"] = false
241 flags["has_mixed"] = false
242 expectedTest := &Ssl{
243 ID: "143616",
244 Checkrate: 2073600,
245 Paused: false,
246 Domain: "https://www.exemple.com",
247 IssuerCn: "Let's Encrypt Authority X3",
248 CertScore: "95",
249 CipherScore: "100",
250 CertStatus: "CERT_OK",
251 Cipher: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
252 ValidFromUtc: "2019-05-28 01:22:00",
253 ValidUntilUtc: "2019-08-26 01:22:00",
254 MixedContent: []map[string]string{mixed},
255 Flags: flags,
256 ContactGroups: []string{"12","13","34"},
257 ContactGroupsC: "12,13,34",
258 AlertAt: "7,18,2019",
259 LastReminder: 2019,
260 AlertReminder: true,
261 AlertExpiry: true,
262 AlertBroken: true,
263 AlertMixed: true,
264 LastUpdatedUtc: "2019-06-20 10:23:14",
265 }
266
267 assert.Equal(expectedTest, full)
268
269}
270
271func TestSsl_partial_OK(t *testing.T) {
272 assert := assert.New(t)
273 require := require.New(t)
274
275 mixed := make(map[string]string)
276 flags := make(map[string]bool)
277
278 mixed["type"]="img"
279 mixed["src"]="http://example.com/image.gif"
280
281 flags["is_extended"] = false
282 flags["has_pfs"] = true
283 flags["is_broken"] = false
284 flags["is_expired"] = false
285 flags["is_missing"] = false
286 flags["is_revoked"] = false
287 flags["has_mixed"] = false
288 full := &Ssl{
289 ID: "143616",
290 Checkrate: 2073600,
291 Paused: false,
292 Domain: "https://www.exemple.com",
293 IssuerCn: "Let's Encrypt Authority X3",
294 CertScore: "95",
295 CipherScore: "100",
296 CertStatus: "CERT_OK",
297 Cipher: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
298 ValidFromUtc: "2019-05-28 01:22:00",
299 ValidUntilUtc: "2019-08-26 01:22:00",
300 MixedContent: []map[string]string{mixed},
301 Flags: flags,
302 ContactGroups: []string{"12","13","34"},
303 ContactGroupsC: "12,13,34",
304 AlertAt: "7,18,2019",
305 LastReminder: 2019,
306 AlertReminder: true,
307 AlertExpiry: true,
308 AlertBroken: true,
309 AlertMixed: true,
310 LastUpdatedUtc: "2019-06-20 10:23:14",
311 }
312 expectedTest:=&PartialSsl {
313 ID: 143616,
314 Domain: "https://www.exemple.com",
315 Checkrate: "2073600",
316 ContactGroupsC: "12,13,34",
317 AlertReminder: true,
318 AlertExpiry: true,
319 AlertBroken: true,
320 AlertMixed: true,
321 AlertAt: "7,18,2019",
322 }
323 partial,err:=Partial(full)
324 require.Nil(err)
325 assert.Equal(expectedTest, partial)
326
327}
328
329func TestSsls_Delete_OK(t *testing.T) {
330 assert := assert.New(t)
331 require := require.New(t)
332
333 c := &fakeAPIClient{
334 fixture: "sslDeleteOk.json",
335 }
336 tt := NewSsls(c)
337
338 err := tt.Delete("143616")
339 require.Nil(err)
340 assert.Equal("/SSL/Update", c.sentRequestPath)
341 assert.Equal("DELETE", c.sentRequestMethod)
342 assert.Equal(c.sentRequestValues,url.Values(url.Values{"id":[]string{"143616"},},))
343}
diff --git a/vendor/github.com/DreamItGetIT/statuscake/tests_test.go b/vendor/github.com/DreamItGetIT/statuscake/tests_test.go
new file mode 100644
index 0000000..efeb720
--- /dev/null
+++ b/vendor/github.com/DreamItGetIT/statuscake/tests_test.go
@@ -0,0 +1,422 @@
1package statuscake
2
3import (
4 "bytes"
5 "encoding/json"
6 "io/ioutil"
7 "log"
8 "net/http"
9 "net/url"
10 "os"
11 "path/filepath"
12 "strings"
13 "testing"
14
15 "github.com/stretchr/testify/assert"
16 "github.com/stretchr/testify/require"
17)
18
19func TestTest_Validate(t *testing.T) {
20 assert := assert.New(t)
21 require := require.New(t)
22
23 test := &Test{
24 Timeout: 200,
25 Confirmation: 100,
26 Public: 200,
27 Virus: 200,
28 TestType: "FTP",
29 RealBrowser: 100,
30 TriggerRate: 100,
31 CheckRate: 100000,
32 CustomHeader: "here be dragons",
33 WebsiteName: "",
34 WebsiteURL: "",
35 }
36
37 err := test.Validate()
38 require.NotNil(err)
39
40 message := err.Error()
41 assert.Contains(message, "WebsiteName is required")
42 assert.Contains(message, "WebsiteURL is required")
43 assert.Contains(message, "Timeout must be 0 or between 6 and 99")
44 assert.Contains(message, "Confirmation must be between 0 and 9")
45 assert.Contains(message, "CheckRate must be between 0 and 23999")
46 assert.Contains(message, "Public must be 0 or 1")
47 assert.Contains(message, "Virus must be 0 or 1")
48 assert.Contains(message, "TestType must be HTTP, TCP, or PING")
49 assert.Contains(message, "RealBrowser must be 0 or 1")
50 assert.Contains(message, "TriggerRate must be between 0 and 59")
51 assert.Contains(message, "CustomHeader must be provided as json string")
52
53 test.Timeout = 10
54 test.Confirmation = 2
55 test.Public = 1
56 test.Virus = 1
57 test.TestType = "HTTP"
58 test.RealBrowser = 1
59 test.TriggerRate = 50
60 test.CheckRate = 10
61 test.WebsiteName = "Foo"
62 test.WebsiteURL = "http://example.com"
63 test.CustomHeader = `{"test": 15}`
64 test.NodeLocations = []string{"foo", "bar"}
65
66 err = test.Validate()
67 assert.Nil(err)
68}
69
70func TestTest_ToURLValues(t *testing.T) {
71 assert := assert.New(t)
72
73 test := &Test{
74 TestID: 123,
75 Paused: true,
76 WebsiteName: "Foo Bar",
77 CustomHeader: `{"some":{"json": ["value"]}}`,
78 WebsiteURL: "http://example.com",
79 Port: 3000,
80 NodeLocations: []string{"foo", "bar"},
81 Timeout: 11,
82 PingURL: "http://example.com/ping",
83 Confirmation: 1,
84 CheckRate: 500,
85 BasicUser: "myuser",
86 BasicPass: "mypass",
87 Public: 1,
88 LogoImage: "http://example.com/logo.jpg",
89 Branding: 1,
90 WebsiteHost: "hoster",
91 Virus: 1,
92 FindString: "hello",
93 DoNotFind: true,
94 TestType: "HTTP",
95 RealBrowser: 1,
96 TriggerRate: 50,
97 TestTags: []string{"tag1", "tag2"},
98 StatusCodes: "500",
99 EnableSSLAlert: false,
100 FollowRedirect: false,
101 }
102
103 expected := url.Values{
104 "TestID": {"123"},
105 "Paused": {"1"},
106 "WebsiteName": {"Foo Bar"},
107 "WebsiteURL": {"http://example.com"},
108 "CustomHeader": {`{"some":{"json": ["value"]}}`},
109 "Port": {"3000"},
110 "NodeLocations": {"foo,bar"},
111 "Timeout": {"11"},
112 "PingURL": {"http://example.com/ping"},
113 "ContactGroup": {""},
114 "Confirmation": {"1"},
115 "CheckRate": {"500"},
116 "BasicUser": {"myuser"},
117 "BasicPass": {"mypass"},
118 "Public": {"1"},
119 "LogoImage": {"http://example.com/logo.jpg"},
120 "Branding": {"1"},
121 "WebsiteHost": {"hoster"},
122 "Virus": {"1"},
123 "FindString": {"hello"},
124 "DoNotFind": {"1"},
125 "TestType": {"HTTP"},
126 "RealBrowser": {"1"},
127 "TriggerRate": {"50"},
128 "TestTags": {"tag1,tag2"},
129 "StatusCodes": {"500"},
130 "UseJar": {"0"},
131 "PostRaw": {""},
132 "FinalEndpoint": {""},
133 "EnableSSLAlert": {"0"},
134 "FollowRedirect": {"0"},
135 }
136
137 assert.Equal(expected, test.ToURLValues())
138
139 test.TestID = 0
140 delete(expected, "TestID")
141
142 assert.Equal(expected.Encode(), test.ToURLValues().Encode())
143}
144
145func TestTests_All(t *testing.T) {
146 assert := assert.New(t)
147 require := require.New(t)
148
149 c := &fakeAPIClient{
150 fixture: "tests_all_ok.json",
151 }
152 tt := newTests(c)
153 tests, err := tt.All()
154 require.Nil(err)
155
156 assert.Equal("/Tests", c.sentRequestPath)
157 assert.Equal("GET", c.sentRequestMethod)
158 assert.Nil(c.sentRequestValues)
159 assert.Len(tests, 2)
160
161 expectedTest := &Test{
162 TestID: 100,
163 Paused: false,
164 TestType: "HTTP",
165 WebsiteName: "www 1",
166 ContactGroup: []string{"1"},
167 Status: "Up",
168 Uptime: 100,
169 NodeLocations: []string{"foo", "bar"},
170 }
171 assert.Equal(expectedTest, tests[0])
172
173 expectedTest = &Test{
174 TestID: 101,
175 Paused: true,
176 TestType: "HTTP",
177 WebsiteName: "www 2",
178 ContactGroup: []string{"2"},
179 Status: "Down",
180 Uptime: 0,
181 NodeLocations: []string{"foo"},
182 TestTags: []string{"test1", "test2"},
183 }
184 assert.Equal(expectedTest, tests[1])
185}
186
187func TestTests_AllWithFilter(t *testing.T) {
188 assert := assert.New(t)
189 require := require.New(t)
190
191 c := &fakeAPIClient{
192 fixture: "tests_all_ok.json",
193 }
194
195 v := url.Values{}
196 v.Set("tags", "test1,test2")
197 tt := newTests(c)
198 tests, err := tt.AllWithFilter(v)
199 require.Nil(err)
200
201 assert.Equal("/Tests", c.sentRequestPath)
202 assert.Equal("GET", c.sentRequestMethod)
203 assert.NotNil(c.sentRequestValues)
204 assert.Len(tests, 1)
205
206 expectedTest := &Test{
207 TestID: 101,
208 Paused: true,
209 TestType: "HTTP",
210 WebsiteName: "www 2",
211 ContactGroup: []string{"2"},
212 Status: "Down",
213 Uptime: 0,
214 NodeLocations: []string{"foo"},
215 TestTags: []string{"test1", "test2"},
216 }
217 assert.Equal(expectedTest, tests[0])
218}
219
220func TestTests_Update_OK(t *testing.T) {
221 assert := assert.New(t)
222 require := require.New(t)
223
224 c := &fakeAPIClient{
225 fixture: "tests_update_ok.json",
226 }
227 tt := newTests(c)
228
229 test1 := &Test{
230 WebsiteName: "foo",
231 }
232
233 test2, err := tt.Update(test1)
234 require.Nil(err)
235
236 assert.Equal("/Tests/Update", c.sentRequestPath)
237 assert.Equal("PUT", c.sentRequestMethod)
238 assert.NotNil(c.sentRequestValues)
239 assert.NotNil(test2)
240
241 assert.Equal(1234, test2.TestID)
242}
243
244func TestTests_Update_Error(t *testing.T) {
245 assert := assert.New(t)
246 require := require.New(t)
247
248 c := &fakeAPIClient{
249 fixture: "tests_update_error.json",
250 }
251 tt := newTests(c)
252
253 test1 := &Test{
254 WebsiteName: "foo",
255 }
256
257 test2, err := tt.Update(test1)
258 assert.Nil(test2)
259
260 require.NotNil(err)
261 assert.IsType(&updateError{}, err)
262 assert.Contains(err.Error(), "issue a")
263}
264
265func TestTests_Update_ErrorWithSliceOfIssues(t *testing.T) {
266 assert := assert.New(t)
267 require := require.New(t)
268
269 c := &fakeAPIClient{
270 fixture: "tests_update_error_slice_of_issues.json",
271 }
272 tt := newTests(c)
273
274 test1 := &Test{
275 WebsiteName: "foo",
276 }
277
278 test2, err := tt.Update(test1)
279 assert.Nil(test2)
280
281 require.NotNil(err)
282 assert.IsType(&updateError{}, err)
283 assert.Equal("Required Data is Missing., hello, world", err.Error())
284}
285
286func TestTests_Delete_OK(t *testing.T) {
287 assert := assert.New(t)
288 require := require.New(t)
289
290 c := &fakeAPIClient{
291 fixture: "tests_delete_ok.json",
292 }
293 tt := newTests(c)
294
295 err := tt.Delete(1234)
296 require.Nil(err)
297
298 assert.Equal("/Tests/Details", c.sentRequestPath)
299 assert.Equal("DELETE", c.sentRequestMethod)
300 assert.Equal(url.Values{"TestID": {"1234"}}, c.sentRequestValues)
301}
302
303func TestTests_Delete_Error(t *testing.T) {
304 assert := assert.New(t)
305 require := require.New(t)
306
307 c := &fakeAPIClient{
308 fixture: "tests_delete_error.json",
309 }
310 tt := newTests(c)
311
312 err := tt.Delete(1234)
313 require.NotNil(err)
314 assert.Equal("this is an error", err.Error())
315}
316
317func TestTests_Detail_OK(t *testing.T) {
318 assert := assert.New(t)
319 require := require.New(t)
320
321 c := &fakeAPIClient{
322 fixture: "tests_detail_ok.json",
323 }
324 tt := newTests(c)
325
326 test, err := tt.Detail(1234)
327 require.Nil(err)
328
329 assert.Equal("/Tests/Details", c.sentRequestPath)
330 assert.Equal("GET", c.sentRequestMethod)
331 assert.Equal(url.Values{"TestID": {"1234"}}, c.sentRequestValues)
332
333 assert.Equal(test.TestID, 6735)
334 assert.Equal(test.TestType, "HTTP")
335 assert.Equal(test.Paused, false)
336 assert.Equal(test.WebsiteName, "NL")
337 assert.Equal(test.CustomHeader, `{"some":{"json": ["value"]}}`)
338 assert.Equal(test.UserAgent, "product/version (comment)")
339 assert.Equal(test.ContactGroup, []string{"536"})
340 assert.Equal(test.Status, "Up")
341 assert.Equal(test.Uptime, 0.0)
342 assert.Equal(test.CheckRate, 60)
343 assert.Equal(test.Timeout, 40)
344 assert.Equal(test.LogoImage, "")
345 assert.Equal(test.WebsiteHost, "Various")
346 assert.Equal(test.FindString, "")
347 assert.Equal(test.DoNotFind, false)
348 assert.Equal(test.NodeLocations, []string{"foo", "bar"})
349}
350
351type fakeAPIClient struct {
352 sentRequestPath string
353 sentRequestMethod string
354 sentRequestValues url.Values
355 fixture string
356}
357
358func (c *fakeAPIClient) put(path string, v url.Values) (*http.Response, error) {
359 return c.all("PUT", path, v)
360}
361
362func (c *fakeAPIClient) delete(path string, v url.Values) (*http.Response, error) {
363 return c.all("DELETE", path, v)
364}
365
366func (c *fakeAPIClient) get(path string, v url.Values) (*http.Response, error) {
367 return c.all("GET", path, v)
368}
369
370func (c *fakeAPIClient) all(method string, path string, v url.Values) (*http.Response, error) {
371 c.sentRequestMethod = method
372 c.sentRequestPath = path
373 c.sentRequestValues = v
374
375 p := filepath.Join("fixtures", c.fixture)
376 f, err := os.Open(p)
377 if err != nil {
378 log.Fatal(err)
379 }
380
381 var resp *http.Response
382 if len(c.sentRequestValues.Get("tags")) > 0 {
383 var storedResponses []Test
384 var returnResponses []Test
385 byteValue, _ := ioutil.ReadAll(f)
386 json.Unmarshal(byteValue, &storedResponses)
387 requestedTags := strings.Split(c.sentRequestValues.Get("tags"), ",")
388
389 for _, storedResponse := range storedResponses {
390 if len(requestedTags) > len(storedResponse.TestTags) { // if we are requesting more tags than whats stored then there are no matches
391 continue
392 }
393
394 match := true
395 for i, tag := range requestedTags {
396 if tag != storedResponse.TestTags[i] {
397 match = false
398 }
399 }
400
401 if match { // we can add it to the response now
402 returnResponses = append(returnResponses, storedResponse)
403 }
404 }
405
406 if len(returnResponses) == 0 {
407 return nil, nil
408 }
409
410 newByteValue, _ := json.Marshal(&returnResponses)
411 resp = &http.Response{
412 Body: ioutil.NopCloser(bytes.NewBuffer(newByteValue)),
413 }
414
415 } else {
416 resp = &http.Response{
417 Body: f,
418 }
419 }
420
421 return resp, nil
422}
diff --git a/vendor/github.com/google/go-querystring/.gitignore b/vendor/github.com/google/go-querystring/.gitignore
new file mode 100644
index 0000000..9ed3b07
--- /dev/null
+++ b/vendor/github.com/google/go-querystring/.gitignore
@@ -0,0 +1 @@
*.test
diff --git a/vendor/github.com/google/go-querystring/.travis.yml b/vendor/github.com/google/go-querystring/.travis.yml
new file mode 100644
index 0000000..881d087
--- /dev/null
+++ b/vendor/github.com/google/go-querystring/.travis.yml
@@ -0,0 +1,5 @@
1language: go
2
3go:
4 - 1.x
5 - 1.11.x
diff --git a/vendor/github.com/google/go-querystring/CONTRIBUTING.md b/vendor/github.com/google/go-querystring/CONTRIBUTING.md
new file mode 100644
index 0000000..51cf5cd
--- /dev/null
+++ b/vendor/github.com/google/go-querystring/CONTRIBUTING.md
@@ -0,0 +1,67 @@
1# How to contribute #
2
3We'd love to accept your patches and contributions to this project. There are
4a just a few small guidelines you need to follow.
5
6
7## Contributor License Agreement ##
8
9Contributions to any Google project must be accompanied by a Contributor
10License Agreement. This is not a copyright **assignment**, it simply gives
11Google permission to use and redistribute your contributions as part of the
12project.
13
14 * If you are an individual writing original source code and you're sure you
15 own the intellectual property, then you'll need to sign an [individual
16 CLA][].
17
18 * If you work for a company that wants to allow you to contribute your work,
19 then you'll need to sign a [corporate CLA][].
20
21You generally only need to submit a CLA once, so if you've already submitted
22one (even if it was for a different project), you probably don't need to do it
23again.
24
25[individual CLA]: https://developers.google.com/open-source/cla/individual
26[corporate CLA]: https://developers.google.com/open-source/cla/corporate
27
28
29## Submitting a patch ##
30
31 1. It's generally best to start by opening a new issue describing the bug or
32 feature you're intending to fix. Even if you think it's relatively minor,
33 it's helpful to know what people are working on. Mention in the initial
34 issue that you are planning to work on that bug or feature so that it can
35 be assigned to you.
36
37 1. Follow the normal process of [forking][] the project, and setup a new
38 branch to work in. It's important that each group of changes be done in
39 separate branches in order to ensure that a pull request only includes the
40 commits related to that bug or feature.
41
42 1. Go makes it very simple to ensure properly formatted code, so always run
43 `go fmt` on your code before committing it. You should also run
44 [golint][] over your code. As noted in the [golint readme][], it's not
45 strictly necessary that your code be completely "lint-free", but this will
46 help you find common style issues.
47
48 1. Any significant changes should almost always be accompanied by tests. The
49 project already has good test coverage, so look at some of the existing
50 tests if you're unsure how to go about it. [gocov][] and [gocov-html][]
51 are invaluable tools for seeing which parts of your code aren't being
52 exercised by your tests.
53
54 1. Do your best to have [well-formed commit messages][] for each change.
55 This provides consistency throughout the project, and ensures that commit
56 messages are able to be formatted properly by various git tools.
57
58 1. Finally, push the commits to your fork and submit a [pull request][].
59
60[forking]: https://help.github.com/articles/fork-a-repo
61[golint]: https://github.com/golang/lint
62[golint readme]: https://github.com/golang/lint/blob/master/README
63[gocov]: https://github.com/axw/gocov
64[gocov-html]: https://github.com/matm/gocov-html
65[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
66[squash]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
67[pull request]: https://help.github.com/articles/creating-a-pull-request
diff --git a/vendor/github.com/google/go-querystring/LICENSE b/vendor/github.com/google/go-querystring/LICENSE
new file mode 100644
index 0000000..ae121a1
--- /dev/null
+++ b/vendor/github.com/google/go-querystring/LICENSE
@@ -0,0 +1,27 @@
1Copyright (c) 2013 Google. All rights reserved.
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are
5met:
6
7 * Redistributions of source code must retain the above copyright
8notice, this list of conditions and the following disclaimer.
9 * Redistributions in binary form must reproduce the above
10copyright notice, this list of conditions and the following disclaimer
11in the documentation and/or other materials provided with the
12distribution.
13 * Neither the name of Google Inc. nor the names of its
14contributors may be used to endorse or promote products derived from
15this software without specific prior written permission.
16
17THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/google/go-querystring/README.md b/vendor/github.com/google/go-querystring/README.md
new file mode 100644
index 0000000..0e600be
--- /dev/null
+++ b/vendor/github.com/google/go-querystring/README.md
@@ -0,0 +1,37 @@
1# go-querystring #
2
3[![GoDoc](https://godoc.org/github.com/google/go-querystring/query?status.svg)](https://godoc.org/github.com/google/go-querystring/query) [![Build Status](https://travis-ci.org/google/go-querystring.svg?branch=master)](https://travis-ci.org/google/go-querystring)
4
5go-querystring is Go library for encoding structs into URL query parameters.
6
7## Usage ##
8
9```go
10import "github.com/google/go-querystring/query"
11```
12
13go-querystring is designed to assist in scenarios where you want to construct a
14URL using a struct that represents the URL query parameters. You might do this
15to enforce the type safety of your parameters, for example, as is done in the
16[go-github][] library.
17
18The query package exports a single `Values()` function. A simple example:
19
20```go
21type Options struct {
22 Query string `url:"q"`
23 ShowAll bool `url:"all"`
24 Page int `url:"page"`
25}
26
27opt := Options{ "foo", true, 2 }
28v, _ := query.Values(opt)
29fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2"
30```
31
32[go-github]: https://github.com/google/go-github/commit/994f6f8405f052a117d2d0b500054341048fbb08
33
34## License ##
35
36This library is distributed under the BSD-style license found in the [LICENSE](./LICENSE)
37file.
diff --git a/vendor/github.com/google/go-querystring/go.mod b/vendor/github.com/google/go-querystring/go.mod
new file mode 100644
index 0000000..45dca2d
--- /dev/null
+++ b/vendor/github.com/google/go-querystring/go.mod
@@ -0,0 +1 @@
module github.com/google/go-querystring
diff --git a/vendor/github.com/google/go-querystring/query/encode.go b/vendor/github.com/google/go-querystring/query/encode.go
new file mode 100644
index 0000000..37080b1
--- /dev/null
+++ b/vendor/github.com/google/go-querystring/query/encode.go
@@ -0,0 +1,320 @@
1// Copyright 2013 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
5// Package query implements encoding of structs into URL query parameters.
6//
7// As a simple example:
8//
9// type Options struct {
10// Query string `url:"q"`
11// ShowAll bool `url:"all"`
12// Page int `url:"page"`
13// }
14//
15// opt := Options{ "foo", true, 2 }
16// v, _ := query.Values(opt)
17// fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2"
18//
19// The exact mapping between Go values and url.Values is described in the
20// documentation for the Values() function.
21package query
22
23import (
24 "bytes"
25 "fmt"
26 "net/url"
27 "reflect"
28 "strconv"
29 "strings"
30 "time"
31)
32
33var timeType = reflect.TypeOf(time.Time{})
34
35var encoderType = reflect.TypeOf(new(Encoder)).Elem()
36
37// Encoder is an interface implemented by any type that wishes to encode
38// itself into URL values in a non-standard way.
39type Encoder interface {
40 EncodeValues(key string, v *url.Values) error
41}
42
43// Values returns the url.Values encoding of v.
44//
45// Values expects to be passed a struct, and traverses it recursively using the
46// following encoding rules.
47//
48// Each exported struct field is encoded as a URL parameter unless
49//
50// - the field's tag is "-", or
51// - the field is empty and its tag specifies the "omitempty" option
52//
53// The empty values are false, 0, any nil pointer or interface value, any array
54// slice, map, or string of length zero, and any time.Time that returns true
55// for IsZero().
56//
57// The URL parameter name defaults to the struct field name but can be
58// specified in the struct field's tag value. The "url" key in the struct
59// field's tag value is the key name, followed by an optional comma and
60// options. For example:
61//
62// // Field is ignored by this package.
63// Field int `url:"-"`
64//
65// // Field appears as URL parameter "myName".
66// Field int `url:"myName"`
67//
68// // Field appears as URL parameter "myName" and the field is omitted if
69// // its value is empty
70// Field int `url:"myName,omitempty"`
71//
72// // Field appears as URL parameter "Field" (the default), but the field
73// // is skipped if empty. Note the leading comma.
74// Field int `url:",omitempty"`
75//
76// For encoding individual field values, the following type-dependent rules
77// apply:
78//
79// Boolean values default to encoding as the strings "true" or "false".
80// Including the "int" option signals that the field should be encoded as the
81// strings "1" or "0".
82//
83// time.Time values default to encoding as RFC3339 timestamps. Including the
84// "unix" option signals that the field should be encoded as a Unix time (see
85// time.Unix())
86//
87// Slice and Array values default to encoding as multiple URL values of the
88// same name. Including the "comma" option signals that the field should be
89// encoded as a single comma-delimited value. Including the "space" option
90// similarly encodes the value as a single space-delimited string. Including
91// the "semicolon" option will encode the value as a semicolon-delimited string.
92// Including the "brackets" option signals that the multiple URL values should
93// have "[]" appended to the value name. "numbered" will append a number to
94// the end of each incidence of the value name, example:
95// name0=value0&name1=value1, etc.
96//
97// Anonymous struct fields are usually encoded as if their inner exported
98// fields were fields in the outer struct, subject to the standard Go
99// visibility rules. An anonymous struct field with a name given in its URL
100// tag is treated as having that name, rather than being anonymous.
101//
102// Non-nil pointer values are encoded as the value pointed to.
103//
104// Nested structs are encoded including parent fields in value names for
105// scoping. e.g:
106//
107// "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO"
108//
109// All other values are encoded using their default string representation.
110//
111// Multiple fields that encode to the same URL parameter name will be included
112// as multiple URL values of the same name.
113func Values(v interface{}) (url.Values, error) {
114 values := make(url.Values)
115 val := reflect.ValueOf(v)
116 for val.Kind() == reflect.Ptr {
117 if val.IsNil() {
118 return values, nil
119 }
120 val = val.Elem()
121 }
122
123 if v == nil {
124 return values, nil
125 }
126
127 if val.Kind() != reflect.Struct {
128 return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind())
129 }
130
131 err := reflectValue(values, val, "")
132 return values, err
133}
134
135// reflectValue populates the values parameter from the struct fields in val.
136// Embedded structs are followed recursively (using the rules defined in the
137// Values function documentation) breadth-first.
138func reflectValue(values url.Values, val reflect.Value, scope string) error {
139 var embedded []reflect.Value
140
141 typ := val.Type()
142 for i := 0; i < typ.NumField(); i++ {
143 sf := typ.Field(i)
144 if sf.PkgPath != "" && !sf.Anonymous { // unexported
145 continue
146 }
147
148 sv := val.Field(i)
149 tag := sf.Tag.Get("url")
150 if tag == "-" {
151 continue
152 }
153 name, opts := parseTag(tag)
154 if name == "" {
155 if sf.Anonymous && sv.Kind() == reflect.Struct {
156 // save embedded struct for later processing
157 embedded = append(embedded, sv)
158 continue
159 }
160
161 name = sf.Name
162 }
163
164 if scope != "" {
165 name = scope + "[" + name + "]"
166 }
167
168 if opts.Contains("omitempty") && isEmptyValue(sv) {
169 continue
170 }
171
172 if sv.Type().Implements(encoderType) {
173 if !reflect.Indirect(sv).IsValid() {
174 sv = reflect.New(sv.Type().Elem())
175 }
176
177 m := sv.Interface().(Encoder)
178 if err := m.EncodeValues(name, &values); err != nil {
179 return err
180 }
181 continue
182 }
183
184 if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array {
185 var del byte
186 if opts.Contains("comma") {
187 del = ','
188 } else if opts.Contains("space") {
189 del = ' '
190 } else if opts.Contains("semicolon") {
191 del = ';'
192 } else if opts.Contains("brackets") {
193 name = name + "[]"
194 }
195
196 if del != 0 {
197 s := new(bytes.Buffer)
198 first := true
199 for i := 0; i < sv.Len(); i++ {
200 if first {
201 first = false
202 } else {
203 s.WriteByte(del)
204 }
205 s.WriteString(valueString(sv.Index(i), opts))
206 }
207 values.Add(name, s.String())
208 } else {
209 for i := 0; i < sv.Len(); i++ {
210 k := name
211 if opts.Contains("numbered") {
212 k = fmt.Sprintf("%s%d", name, i)
213 }
214 values.Add(k, valueString(sv.Index(i), opts))
215 }
216 }
217 continue
218 }
219
220 for sv.Kind() == reflect.Ptr {
221 if sv.IsNil() {
222 break
223 }
224 sv = sv.Elem()
225 }
226
227 if sv.Type() == timeType {
228 values.Add(name, valueString(sv, opts))
229 continue
230 }
231
232 if sv.Kind() == reflect.Struct {
233 reflectValue(values, sv, name)
234 continue
235 }
236
237 values.Add(name, valueString(sv, opts))
238 }
239
240 for _, f := range embedded {
241 if err := reflectValue(values, f, scope); err != nil {
242 return err
243 }
244 }
245
246 return nil
247}
248
249// valueString returns the string representation of a value.
250func valueString(v reflect.Value, opts tagOptions) string {
251 for v.Kind() == reflect.Ptr {
252 if v.IsNil() {
253 return ""
254 }
255 v = v.Elem()
256 }
257
258 if v.Kind() == reflect.Bool && opts.Contains("int") {
259 if v.Bool() {
260 return "1"
261 }
262 return "0"
263 }
264
265 if v.Type() == timeType {
266 t := v.Interface().(time.Time)
267 if opts.Contains("unix") {
268 return strconv.FormatInt(t.Unix(), 10)
269 }
270 return t.Format(time.RFC3339)
271 }
272
273 return fmt.Sprint(v.Interface())
274}
275
276// isEmptyValue checks if a value should be considered empty for the purposes
277// of omitting fields with the "omitempty" option.
278func isEmptyValue(v reflect.Value) bool {
279 switch v.Kind() {
280 case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
281 return v.Len() == 0
282 case reflect.Bool:
283 return !v.Bool()
284 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
285 return v.Int() == 0
286 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
287 return v.Uint() == 0
288 case reflect.Float32, reflect.Float64:
289 return v.Float() == 0
290 case reflect.Interface, reflect.Ptr:
291 return v.IsNil()
292 }
293
294 if v.Type() == timeType {
295 return v.Interface().(time.Time).IsZero()
296 }
297
298 return false
299}
300
301// tagOptions is the string following a comma in a struct field's "url" tag, or
302// the empty string. It does not include the leading comma.
303type tagOptions []string
304
305// parseTag splits a struct field's url tag into its name and comma-separated
306// options.
307func parseTag(tag string) (string, tagOptions) {
308 s := strings.Split(tag, ",")
309 return s[0], s[1:]
310}
311
312// Contains checks whether the tagOptions contains the specified option.
313func (o tagOptions) Contains(option string) bool {
314 for _, s := range o {
315 if s == option {
316 return true
317 }
318 }
319 return false
320}
diff --git a/vendor/github.com/google/go-querystring/query/encode_test.go b/vendor/github.com/google/go-querystring/query/encode_test.go
new file mode 100644
index 0000000..77bea5a
--- /dev/null
+++ b/vendor/github.com/google/go-querystring/query/encode_test.go
@@ -0,0 +1,328 @@
1// Copyright 2013 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
5package query
6
7import (
8 "fmt"
9 "net/url"
10 "reflect"
11 "testing"
12 "time"
13)
14
15type Nested struct {
16 A SubNested `url:"a"`
17 B *SubNested `url:"b"`
18 Ptr *SubNested `url:"ptr,omitempty"`
19}
20
21type SubNested struct {
22 Value string `url:"value"`
23}
24
25func TestValues_types(t *testing.T) {
26 str := "string"
27 strPtr := &str
28 timeVal := time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC)
29
30 tests := []struct {
31 in interface{}
32 want url.Values
33 }{
34 {
35 // basic primitives
36 struct {
37 A string
38 B int
39 C uint
40 D float32
41 E bool
42 }{},
43 url.Values{
44 "A": {""},
45 "B": {"0"},
46 "C": {"0"},
47 "D": {"0"},
48 "E": {"false"},
49 },
50 },
51 {
52 // pointers
53 struct {
54 A *string
55 B *int
56 C **string
57 D *time.Time
58 }{
59 A: strPtr,
60 C: &strPtr,
61 D: &timeVal,
62 },
63 url.Values{
64 "A": {str},
65 "B": {""},
66 "C": {str},
67 "D": {"2000-01-01T12:34:56Z"},
68 },
69 },
70 {
71 // slices and arrays
72 struct {
73 A []string
74 B []string `url:",comma"`
75 C []string `url:",space"`
76 D [2]string
77 E [2]string `url:",comma"`
78 F [2]string `url:",space"`
79 G []*string `url:",space"`
80 H []bool `url:",int,space"`
81 I []string `url:",brackets"`
82 J []string `url:",semicolon"`
83 K []string `url:",numbered"`
84 }{
85 A: []string{"a", "b"},
86 B: []string{"a", "b"},
87 C: []string{"a", "b"},
88 D: [2]string{"a", "b"},
89 E: [2]string{"a", "b"},
90 F: [2]string{"a", "b"},
91 G: []*string{&str, &str},
92 H: []bool{true, false},
93 I: []string{"a", "b"},
94 J: []string{"a", "b"},
95 K: []string{"a", "b"},
96 },
97 url.Values{
98 "A": {"a", "b"},
99 "B": {"a,b"},
100 "C": {"a b"},
101 "D": {"a", "b"},
102 "E": {"a,b"},
103 "F": {"a b"},
104 "G": {"string string"},
105 "H": {"1 0"},
106 "I[]": {"a", "b"},
107 "J": {"a;b"},
108 "K0": {"a"},
109 "K1": {"b"},
110 },
111 },
112 {
113 // other types
114 struct {
115 A time.Time
116 B time.Time `url:",unix"`
117 C bool `url:",int"`
118 D bool `url:",int"`
119 }{
120 A: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC),
121 B: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC),
122 C: true,
123 D: false,
124 },
125 url.Values{
126 "A": {"2000-01-01T12:34:56Z"},
127 "B": {"946730096"},
128 "C": {"1"},
129 "D": {"0"},
130 },
131 },
132 {
133 struct {
134 Nest Nested `url:"nest"`
135 }{
136 Nested{
137 A: SubNested{
138 Value: "that",
139 },
140 },
141 },
142 url.Values{
143 "nest[a][value]": {"that"},
144 "nest[b]": {""},
145 },
146 },
147 {
148 struct {
149 Nest Nested `url:"nest"`
150 }{
151 Nested{
152 Ptr: &SubNested{
153 Value: "that",
154 },
155 },
156 },
157 url.Values{
158 "nest[a][value]": {""},
159 "nest[b]": {""},
160 "nest[ptr][value]": {"that"},
161 },
162 },
163 {
164 nil,
165 url.Values{},
166 },
167 }
168
169 for i, tt := range tests {
170 v, err := Values(tt.in)
171 if err != nil {
172 t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err)
173 }
174
175 if !reflect.DeepEqual(tt.want, v) {
176 t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want)
177 }
178 }
179}
180
181func TestValues_omitEmpty(t *testing.T) {
182 str := ""
183 s := struct {
184 a string
185 A string
186 B string `url:",omitempty"`
187 C string `url:"-"`
188 D string `url:"omitempty"` // actually named omitempty, not an option
189 E *string `url:",omitempty"`
190 }{E: &str}
191
192 v, err := Values(s)
193 if err != nil {
194 t.Errorf("Values(%v) returned error: %v", s, err)
195 }
196
197 want := url.Values{
198 "A": {""},
199 "omitempty": {""},
200 "E": {""}, // E is included because the pointer is not empty, even though the string being pointed to is
201 }
202 if !reflect.DeepEqual(want, v) {
203 t.Errorf("Values(%v) returned %v, want %v", s, v, want)
204 }
205}
206
207type A struct {
208 B
209}
210
211type B struct {
212 C string
213}
214
215type D struct {
216 B
217 C string
218}
219
220type e struct {
221 B
222 C string
223}
224
225type F struct {
226 e
227}
228
229func TestValues_embeddedStructs(t *testing.T) {
230 tests := []struct {
231 in interface{}
232 want url.Values
233 }{
234 {
235 A{B{C: "foo"}},
236 url.Values{"C": {"foo"}},
237 },
238 {
239 D{B: B{C: "bar"}, C: "foo"},
240 url.Values{"C": {"foo", "bar"}},
241 },
242 {
243 F{e{B: B{C: "bar"}, C: "foo"}}, // With unexported embed
244 url.Values{"C": {"foo", "bar"}},
245 },
246 }
247
248 for i, tt := range tests {
249 v, err := Values(tt.in)
250 if err != nil {
251 t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err)
252 }
253
254 if !reflect.DeepEqual(tt.want, v) {
255 t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want)
256 }
257 }
258}
259
260func TestValues_invalidInput(t *testing.T) {
261 _, err := Values("")
262 if err == nil {
263 t.Errorf("expected Values() to return an error on invalid input")
264 }
265}
266
267type EncodedArgs []string
268
269func (m EncodedArgs) EncodeValues(key string, v *url.Values) error {
270 for i, arg := range m {
271 v.Set(fmt.Sprintf("%s.%d", key, i), arg)
272 }
273 return nil
274}
275
276func TestValues_Marshaler(t *testing.T) {
277 s := struct {
278 Args EncodedArgs `url:"arg"`
279 }{[]string{"a", "b", "c"}}
280 v, err := Values(s)
281 if err != nil {
282 t.Errorf("Values(%q) returned error: %v", s, err)
283 }
284
285 want := url.Values{
286 "arg.0": {"a"},
287 "arg.1": {"b"},
288 "arg.2": {"c"},
289 }
290 if !reflect.DeepEqual(want, v) {
291 t.Errorf("Values(%q) returned %v, want %v", s, v, want)
292 }
293}
294
295func TestValues_MarshalerWithNilPointer(t *testing.T) {
296 s := struct {
297 Args *EncodedArgs `url:"arg"`
298 }{}
299 v, err := Values(s)
300 if err != nil {
301 t.Errorf("Values(%v) returned error: %v", s, err)
302 }
303
304 want := url.Values{}
305 if !reflect.DeepEqual(want, v) {
306 t.Errorf("Values(%v) returned %v, want %v", s, v, want)
307 }
308}
309
310func TestTagParsing(t *testing.T) {
311 name, opts := parseTag("field,foobar,foo")
312 if name != "field" {
313 t.Fatalf("name = %q, want field", name)
314 }
315 for _, tt := range []struct {
316 opt string
317 want bool
318 }{
319 {"foobar", true},
320 {"foo", true},
321 {"bar", false},
322 {"field", false},
323 } {
324 if opts.Contains(tt.opt) != tt.want {
325 t.Errorf("Contains(%q) = %v", tt.opt, !tt.want)
326 }
327 }
328}