github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/client/client_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package client_test 21 22 import ( 23 "encoding/json" 24 "errors" 25 "fmt" 26 "io" 27 "io/ioutil" 28 "net" 29 "net/http" 30 "net/http/httptest" 31 "net/url" 32 "os" 33 "path/filepath" 34 "strings" 35 "testing" 36 "time" 37 38 . "gopkg.in/check.v1" 39 40 "github.com/snapcore/snapd/client" 41 "github.com/snapcore/snapd/dirs" 42 "github.com/snapcore/snapd/testutil" 43 ) 44 45 // Hook up check.v1 into the "go test" runner 46 func Test(t *testing.T) { TestingT(t) } 47 48 type clientSuite struct { 49 testutil.BaseTest 50 51 cli *client.Client 52 req *http.Request 53 reqs []*http.Request 54 rsp string 55 rsps []string 56 err error 57 doCalls int 58 header http.Header 59 status int 60 contentLength int64 61 62 countingCloser *countingCloser 63 } 64 65 var _ = Suite(&clientSuite{}) 66 67 func (cs *clientSuite) SetUpTest(c *C) { 68 os.Setenv(client.TestAuthFileEnvKey, filepath.Join(c.MkDir(), "auth.json")) 69 cs.AddCleanup(func() { os.Unsetenv(client.TestAuthFileEnvKey) }) 70 71 cs.cli = client.New(nil) 72 cs.cli.SetDoer(cs) 73 cs.err = nil 74 cs.req = nil 75 cs.reqs = nil 76 cs.rsp = "" 77 cs.rsps = nil 78 cs.req = nil 79 cs.header = nil 80 cs.status = 200 81 cs.doCalls = 0 82 cs.contentLength = 0 83 cs.countingCloser = nil 84 85 dirs.SetRootDir(c.MkDir()) 86 cs.AddCleanup(func() { dirs.SetRootDir("") }) 87 88 cs.AddCleanup(client.MockDoTimings(time.Millisecond, 100*time.Millisecond)) 89 } 90 91 type countingCloser struct { 92 io.Reader 93 closeCalled int 94 } 95 96 func (n *countingCloser) Close() error { 97 n.closeCalled++ 98 return nil 99 } 100 101 func (cs *clientSuite) Do(req *http.Request) (*http.Response, error) { 102 cs.req = req 103 cs.reqs = append(cs.reqs, req) 104 body := cs.rsp 105 if cs.doCalls < len(cs.rsps) { 106 body = cs.rsps[cs.doCalls] 107 } 108 cs.countingCloser = &countingCloser{Reader: strings.NewReader(body)} 109 rsp := &http.Response{ 110 Body: cs.countingCloser, 111 Header: cs.header, 112 StatusCode: cs.status, 113 ContentLength: cs.contentLength, 114 } 115 cs.doCalls++ 116 return rsp, cs.err 117 } 118 119 func (cs *clientSuite) TestNewPanics(c *C) { 120 c.Assert(func() { 121 client.New(&client.Config{BaseURL: ":"}) 122 }, PanicMatches, `cannot parse server base URL: ":" \(parse \"?:\"?: missing protocol scheme\)`) 123 } 124 125 func (cs *clientSuite) TestClientDoReportsErrors(c *C) { 126 cs.err = errors.New("ouchie") 127 _, err := cs.cli.Do("GET", "/", nil, nil, nil, nil) 128 c.Check(err, ErrorMatches, "cannot communicate with server: ouchie") 129 if cs.doCalls < 2 { 130 c.Fatalf("do did not retry") 131 } 132 } 133 134 func (cs *clientSuite) TestClientWorks(c *C) { 135 var v []int 136 cs.rsp = `[1,2]` 137 reqBody := ioutil.NopCloser(strings.NewReader("")) 138 statusCode, err := cs.cli.Do("GET", "/this", nil, reqBody, &v, nil) 139 c.Check(err, IsNil) 140 c.Check(statusCode, Equals, 200) 141 c.Check(v, DeepEquals, []int{1, 2}) 142 c.Assert(cs.req, NotNil) 143 c.Assert(cs.req.URL, NotNil) 144 c.Check(cs.req.Method, Equals, "GET") 145 c.Check(cs.req.Body, Equals, reqBody) 146 c.Check(cs.req.URL.Path, Equals, "/this") 147 } 148 149 func makeMaintenanceFile(c *C, b []byte) { 150 c.Assert(os.MkdirAll(filepath.Dir(dirs.SnapdMaintenanceFile), 0755), IsNil) 151 c.Assert(ioutil.WriteFile(dirs.SnapdMaintenanceFile, b, 0644), IsNil) 152 } 153 154 func (cs *clientSuite) TestClientSetMaintenanceForMaintenanceJSON(c *C) { 155 // write a maintenance.json that says snapd is down for a restart 156 maintErr := &client.Error{ 157 Kind: client.ErrorKindSystemRestart, 158 Message: "system is restarting", 159 } 160 b, err := json.Marshal(maintErr) 161 c.Assert(err, IsNil) 162 makeMaintenanceFile(c, b) 163 164 // now after a Do(), we will have maintenance set to what we wrote 165 // originally 166 _, err = cs.cli.Do("GET", "/this", nil, nil, nil, nil) 167 c.Check(err, IsNil) 168 169 returnedErr := cs.cli.Maintenance() 170 c.Assert(returnedErr, DeepEquals, maintErr) 171 } 172 173 func (cs *clientSuite) TestClientIgnoresGarbageMaintenanceJSON(c *C) { 174 // write a garbage maintenance.json that can't be unmarshalled 175 makeMaintenanceFile(c, []byte("blah blah blah not json")) 176 177 // after a Do(), no maintenance set and also no error returned from Do() 178 _, err := cs.cli.Do("GET", "/this", nil, nil, nil, nil) 179 c.Check(err, IsNil) 180 181 returnedErr := cs.cli.Maintenance() 182 c.Assert(returnedErr, IsNil) 183 } 184 185 func (cs *clientSuite) TestClientDoNoTimeoutIgnoresRetry(c *C) { 186 var v []int 187 cs.rsp = `[1,2]` 188 cs.err = fmt.Errorf("borken") 189 reqBody := ioutil.NopCloser(strings.NewReader("")) 190 doOpts := &client.DoOptions{ 191 // Timeout is unset, thus 0, and thus we ignore the retry and only run 192 // once even though there is an error 193 Retry: time.Duration(time.Second), 194 } 195 _, err := cs.cli.Do("GET", "/this", nil, reqBody, &v, doOpts) 196 c.Check(err, ErrorMatches, "cannot communicate with server: borken") 197 c.Assert(cs.doCalls, Equals, 1) 198 } 199 200 func (cs *clientSuite) TestClientDoRetryValidation(c *C) { 201 var v []int 202 cs.rsp = `[1,2]` 203 reqBody := ioutil.NopCloser(strings.NewReader("")) 204 doOpts := &client.DoOptions{ 205 Retry: time.Duration(-1), 206 Timeout: time.Duration(time.Minute), 207 } 208 _, err := cs.cli.Do("GET", "/this", nil, reqBody, &v, doOpts) 209 c.Check(err, ErrorMatches, "internal error: retry setting.*invalid") 210 c.Assert(cs.req, IsNil) 211 } 212 213 func (cs *clientSuite) TestClientDoRetryWorks(c *C) { 214 reqBody := ioutil.NopCloser(strings.NewReader("")) 215 cs.err = fmt.Errorf("borken") 216 doOpts := &client.DoOptions{ 217 Retry: time.Duration(time.Millisecond), 218 Timeout: time.Duration(time.Second), 219 } 220 _, err := cs.cli.Do("GET", "/this", nil, reqBody, nil, doOpts) 221 c.Check(err, ErrorMatches, "cannot communicate with server: borken") 222 // best effort checking given that execution could be slow 223 // on some machines 224 c.Assert(cs.doCalls > 100, Equals, true, Commentf("got only %v calls", cs.doCalls)) 225 c.Assert(cs.doCalls < 1100, Equals, true, Commentf("got %v calls", cs.doCalls)) 226 } 227 228 func (cs *clientSuite) TestClientUnderstandsStatusCode(c *C) { 229 var v []int 230 cs.status = 202 231 cs.rsp = `[1,2]` 232 reqBody := ioutil.NopCloser(strings.NewReader("")) 233 statusCode, err := cs.cli.Do("GET", "/this", nil, reqBody, &v, nil) 234 c.Check(err, IsNil) 235 c.Check(statusCode, Equals, 202) 236 c.Check(v, DeepEquals, []int{1, 2}) 237 c.Assert(cs.req, NotNil) 238 c.Assert(cs.req.URL, NotNil) 239 c.Check(cs.req.Method, Equals, "GET") 240 c.Check(cs.req.Body, Equals, reqBody) 241 c.Check(cs.req.URL.Path, Equals, "/this") 242 } 243 244 func (cs *clientSuite) TestClientDefaultsToNoAuthorization(c *C) { 245 os.Setenv(client.TestAuthFileEnvKey, filepath.Join(c.MkDir(), "json")) 246 defer os.Unsetenv(client.TestAuthFileEnvKey) 247 248 var v string 249 _, _ = cs.cli.Do("GET", "/this", nil, nil, &v, nil) 250 c.Assert(cs.req, NotNil) 251 authorization := cs.req.Header.Get("Authorization") 252 c.Check(authorization, Equals, "") 253 } 254 255 func (cs *clientSuite) TestClientSetsAuthorization(c *C) { 256 os.Setenv(client.TestAuthFileEnvKey, filepath.Join(c.MkDir(), "json")) 257 defer os.Unsetenv(client.TestAuthFileEnvKey) 258 259 mockUserData := client.User{ 260 Macaroon: "macaroon", 261 Discharges: []string{"discharge"}, 262 } 263 err := client.TestWriteAuth(mockUserData) 264 c.Assert(err, IsNil) 265 266 var v string 267 _, _ = cs.cli.Do("GET", "/this", nil, nil, &v, nil) 268 authorization := cs.req.Header.Get("Authorization") 269 c.Check(authorization, Equals, `Macaroon root="macaroon", discharge="discharge"`) 270 } 271 272 func (cs *clientSuite) TestClientHonorsDisableAuth(c *C) { 273 os.Setenv(client.TestAuthFileEnvKey, filepath.Join(c.MkDir(), "json")) 274 defer os.Unsetenv(client.TestAuthFileEnvKey) 275 276 mockUserData := client.User{ 277 Macaroon: "macaroon", 278 Discharges: []string{"discharge"}, 279 } 280 err := client.TestWriteAuth(mockUserData) 281 c.Assert(err, IsNil) 282 283 var v string 284 cli := client.New(&client.Config{DisableAuth: true}) 285 cli.SetDoer(cs) 286 _, _ = cli.Do("GET", "/this", nil, nil, &v, nil) 287 authorization := cs.req.Header.Get("Authorization") 288 c.Check(authorization, Equals, "") 289 } 290 291 func (cs *clientSuite) TestClientHonorsInteractive(c *C) { 292 var v string 293 cli := client.New(&client.Config{Interactive: false}) 294 cli.SetDoer(cs) 295 _, _ = cli.Do("GET", "/this", nil, nil, &v, nil) 296 interactive := cs.req.Header.Get(client.AllowInteractionHeader) 297 c.Check(interactive, Equals, "") 298 299 cli = client.New(&client.Config{Interactive: true}) 300 cli.SetDoer(cs) 301 _, _ = cli.Do("GET", "/this", nil, nil, &v, nil) 302 interactive = cs.req.Header.Get(client.AllowInteractionHeader) 303 c.Check(interactive, Equals, "true") 304 } 305 306 func (cs *clientSuite) TestClientWhoAmINobody(c *C) { 307 email, err := cs.cli.WhoAmI() 308 c.Assert(err, IsNil) 309 c.Check(email, Equals, "") 310 } 311 312 func (cs *clientSuite) TestClientWhoAmIRubbish(c *C) { 313 c.Assert(ioutil.WriteFile(client.TestStoreAuthFilename(os.Getenv("HOME")), []byte("rubbish"), 0644), IsNil) 314 315 email, err := cs.cli.WhoAmI() 316 c.Check(err, NotNil) 317 c.Check(email, Equals, "") 318 } 319 320 func (cs *clientSuite) TestClientWhoAmISomebody(c *C) { 321 mockUserData := client.User{ 322 Email: "foo@example.com", 323 } 324 c.Assert(client.TestWriteAuth(mockUserData), IsNil) 325 326 email, err := cs.cli.WhoAmI() 327 c.Check(err, IsNil) 328 c.Check(email, Equals, "foo@example.com") 329 } 330 331 func (cs *clientSuite) TestClientSysInfo(c *C) { 332 cs.rsp = `{"type": "sync", "result": 333 {"series": "16", 334 "version": "2", 335 "os-release": {"id": "ubuntu", "version-id": "16.04"}, 336 "on-classic": true, 337 "build-id": "1234", 338 "confinement": "strict", 339 "architecture": "TI-99/4A", 340 "virtualization": "MESS", 341 "sandbox-features": {"backend": ["feature-1", "feature-2"]}}}` 342 sysInfo, err := cs.cli.SysInfo() 343 c.Check(err, IsNil) 344 c.Check(sysInfo, DeepEquals, &client.SysInfo{ 345 Version: "2", 346 Series: "16", 347 OSRelease: client.OSRelease{ 348 ID: "ubuntu", 349 VersionID: "16.04", 350 }, 351 OnClassic: true, 352 Confinement: "strict", 353 SandboxFeatures: map[string][]string{ 354 "backend": {"feature-1", "feature-2"}, 355 }, 356 BuildID: "1234", 357 Architecture: "TI-99/4A", 358 Virtualization: "MESS", 359 }) 360 } 361 362 func (cs *clientSuite) TestServerVersion(c *C) { 363 cs.rsp = `{"type": "sync", "result": 364 {"series": "16", 365 "version": "2", 366 "os-release": {"id": "zyggy", "version-id": "123"}, 367 "architecture": "m32", 368 "virtualization": "qemu" 369 }}}` 370 version, err := cs.cli.ServerVersion() 371 c.Check(err, IsNil) 372 c.Check(version, DeepEquals, &client.ServerVersion{ 373 Version: "2", 374 Series: "16", 375 OSID: "zyggy", 376 OSVersionID: "123", 377 Architecture: "m32", 378 Virtualization: "qemu", 379 }) 380 } 381 382 func (cs *clientSuite) TestSnapdClientIntegration(c *C) { 383 c.Assert(os.MkdirAll(filepath.Dir(dirs.SnapdSocket), 0755), IsNil) 384 l, err := net.Listen("unix", dirs.SnapdSocket) 385 if err != nil { 386 c.Fatalf("unable to listen on %q: %v", dirs.SnapdSocket, err) 387 } 388 389 f := func(w http.ResponseWriter, r *http.Request) { 390 c.Check(r.URL.Path, Equals, "/v2/system-info") 391 c.Check(r.URL.RawQuery, Equals, "") 392 393 fmt.Fprintln(w, `{"type":"sync", "result":{"series":"42"}}`) 394 } 395 396 srv := &httptest.Server{ 397 Listener: l, 398 Config: &http.Server{Handler: http.HandlerFunc(f)}, 399 } 400 srv.Start() 401 defer srv.Close() 402 403 cli := client.New(nil) 404 si, err := cli.SysInfo() 405 c.Assert(err, IsNil) 406 c.Check(si.Series, Equals, "42") 407 } 408 409 func (cs *clientSuite) TestSnapClientIntegration(c *C) { 410 c.Assert(os.MkdirAll(filepath.Dir(dirs.SnapSocket), 0755), IsNil) 411 l, err := net.Listen("unix", dirs.SnapSocket) 412 if err != nil { 413 c.Fatalf("unable to listen on %q: %v", dirs.SnapSocket, err) 414 } 415 416 f := func(w http.ResponseWriter, r *http.Request) { 417 c.Check(r.URL.Path, Equals, "/v2/snapctl") 418 c.Check(r.URL.RawQuery, Equals, "") 419 420 fmt.Fprintln(w, `{"type":"sync", "result":{"stdout":"test stdout","stderr":"test stderr"}}`) 421 } 422 423 srv := &httptest.Server{ 424 Listener: l, 425 Config: &http.Server{Handler: http.HandlerFunc(f)}, 426 } 427 srv.Start() 428 defer srv.Close() 429 430 cli := client.New(&client.Config{ 431 Socket: dirs.SnapSocket, 432 }) 433 options := &client.SnapCtlOptions{ 434 ContextID: "foo", 435 Args: []string{"bar", "--baz"}, 436 } 437 438 stdout, stderr, err := cli.RunSnapctl(options, nil) 439 c.Check(err, IsNil) 440 c.Check(string(stdout), Equals, "test stdout") 441 c.Check(string(stderr), Equals, "test stderr") 442 } 443 444 func (cs *clientSuite) TestClientReportsOpError(c *C) { 445 cs.status = 500 446 cs.rsp = `{"type": "error"}` 447 _, err := cs.cli.SysInfo() 448 c.Check(err, ErrorMatches, `.*server error: "Internal Server Error"`) 449 } 450 451 func (cs *clientSuite) TestClientReportsOpErrorStr(c *C) { 452 cs.status = 400 453 cs.rsp = `{ 454 "result": {}, 455 "status": "Bad Request", 456 "status-code": 400, 457 "type": "error" 458 }` 459 _, err := cs.cli.SysInfo() 460 c.Check(err, ErrorMatches, `.*server error: "Bad Request"`) 461 } 462 463 func (cs *clientSuite) TestClientReportsBadType(c *C) { 464 cs.rsp = `{"type": "what"}` 465 _, err := cs.cli.SysInfo() 466 c.Check(err, ErrorMatches, `.*expected sync response, got "what"`) 467 } 468 469 func (cs *clientSuite) TestClientReportsOuterJSONError(c *C) { 470 cs.rsp = "this isn't really json is it" 471 _, err := cs.cli.SysInfo() 472 c.Check(err, ErrorMatches, `.*invalid character .*`) 473 } 474 475 func (cs *clientSuite) TestClientReportsInnerJSONError(c *C) { 476 cs.rsp = `{"type": "sync", "result": "this isn't really json is it"}` 477 _, err := cs.cli.SysInfo() 478 c.Check(err, ErrorMatches, `.*cannot unmarshal.*`) 479 } 480 481 func (cs *clientSuite) TestClientMaintenance(c *C) { 482 cs.rsp = `{"type":"sync", "result":{"series":"42"}, "maintenance": {"kind": "system-restart", "message": "system is restarting"}}` 483 _, err := cs.cli.SysInfo() 484 c.Assert(err, IsNil) 485 c.Check(cs.cli.Maintenance().(*client.Error), DeepEquals, &client.Error{ 486 Kind: client.ErrorKindSystemRestart, 487 Message: "system is restarting", 488 }) 489 490 cs.rsp = `{"type":"sync", "result":{"series":"42"}}` 491 _, err = cs.cli.SysInfo() 492 c.Assert(err, IsNil) 493 c.Check(cs.cli.Maintenance(), Equals, error(nil)) 494 } 495 496 func (cs *clientSuite) TestClientAsyncOpMaintenance(c *C) { 497 cs.status = 202 498 cs.rsp = `{"type":"async", "status-code": 202, "change": "42", "maintenance": {"kind": "system-restart", "message": "system is restarting"}}` 499 _, err := cs.cli.Install("foo", nil) 500 c.Assert(err, IsNil) 501 c.Check(cs.cli.Maintenance().(*client.Error), DeepEquals, &client.Error{ 502 Kind: client.ErrorKindSystemRestart, 503 Message: "system is restarting", 504 }) 505 506 cs.rsp = `{"type":"async", "status-code": 202, "change": "42"}` 507 _, err = cs.cli.Install("foo", nil) 508 c.Assert(err, IsNil) 509 c.Check(cs.cli.Maintenance(), Equals, error(nil)) 510 } 511 512 func (cs *clientSuite) TestParseError(c *C) { 513 resp := &http.Response{ 514 Status: "404 Not Found", 515 } 516 err := client.ParseErrorInTest(resp) 517 c.Check(err, ErrorMatches, `server error: "404 Not Found"`) 518 519 h := http.Header{} 520 h.Add("Content-Type", "application/json") 521 resp = &http.Response{ 522 Status: "400 Bad Request", 523 Header: h, 524 Body: ioutil.NopCloser(strings.NewReader(`{ 525 "status-code": 400, 526 "type": "error", 527 "result": { 528 "message": "invalid" 529 } 530 }`)), 531 } 532 err = client.ParseErrorInTest(resp) 533 c.Check(err, ErrorMatches, "invalid") 534 535 resp = &http.Response{ 536 Status: "400 Bad Request", 537 Header: h, 538 Body: ioutil.NopCloser(strings.NewReader("{}")), 539 } 540 err = client.ParseErrorInTest(resp) 541 c.Check(err, ErrorMatches, `server error: "400 Bad Request"`) 542 } 543 544 func (cs *clientSuite) TestIsTwoFactor(c *C) { 545 c.Check(client.IsTwoFactorError(&client.Error{Kind: client.ErrorKindTwoFactorRequired}), Equals, true) 546 c.Check(client.IsTwoFactorError(&client.Error{Kind: client.ErrorKindTwoFactorFailed}), Equals, true) 547 c.Check(client.IsTwoFactorError(&client.Error{Kind: "some other kind"}), Equals, false) 548 c.Check(client.IsTwoFactorError(errors.New("test")), Equals, false) 549 c.Check(client.IsTwoFactorError(nil), Equals, false) 550 c.Check(client.IsTwoFactorError((*client.Error)(nil)), Equals, false) 551 } 552 553 func (cs *clientSuite) TestIsRetryable(c *C) { 554 // unhappy 555 c.Check(client.IsRetryable(nil), Equals, false) 556 c.Check(client.IsRetryable(errors.New("some-error")), Equals, false) 557 c.Check(client.IsRetryable(&client.Error{Kind: "something-else"}), Equals, false) 558 // happy 559 c.Check(client.IsRetryable(&client.Error{Kind: client.ErrorKindSnapChangeConflict}), Equals, true) 560 } 561 562 func (cs *clientSuite) TestUserAgent(c *C) { 563 cli := client.New(&client.Config{UserAgent: "some-agent/9.87"}) 564 cli.SetDoer(cs) 565 566 var v string 567 _, _ = cli.Do("GET", "/", nil, nil, &v, nil) 568 c.Assert(cs.req, NotNil) 569 c.Check(cs.req.Header.Get("User-Agent"), Equals, "some-agent/9.87") 570 } 571 572 func (cs *clientSuite) TestDebugEnsureStateSoon(c *C) { 573 cs.rsp = `{"type": "sync", "result":true}` 574 err := cs.cli.Debug("ensure-state-soon", nil, nil) 575 c.Check(err, IsNil) 576 c.Check(cs.reqs, HasLen, 1) 577 c.Check(cs.reqs[0].Method, Equals, "POST") 578 c.Check(cs.reqs[0].URL.Path, Equals, "/v2/debug") 579 data, err := ioutil.ReadAll(cs.reqs[0].Body) 580 c.Assert(err, IsNil) 581 c.Check(data, DeepEquals, []byte(`{"action":"ensure-state-soon"}`)) 582 } 583 584 func (cs *clientSuite) TestDebugGeneric(c *C) { 585 cs.rsp = `{"type": "sync", "result":["res1","res2"]}` 586 587 var result []string 588 err := cs.cli.Debug("do-something", []string{"param1", "param2"}, &result) 589 c.Check(err, IsNil) 590 c.Check(result, DeepEquals, []string{"res1", "res2"}) 591 c.Check(cs.reqs, HasLen, 1) 592 c.Check(cs.reqs[0].Method, Equals, "POST") 593 c.Check(cs.reqs[0].URL.Path, Equals, "/v2/debug") 594 data, err := ioutil.ReadAll(cs.reqs[0].Body) 595 c.Assert(err, IsNil) 596 c.Check(string(data), DeepEquals, `{"action":"do-something","params":["param1","param2"]}`) 597 } 598 599 func (cs *clientSuite) TestDebugGet(c *C) { 600 cs.rsp = `{"type": "sync", "result":["res1","res2"]}` 601 602 var result []string 603 err := cs.cli.DebugGet("do-something", &result, map[string]string{"foo": "bar"}) 604 c.Check(err, IsNil) 605 c.Check(result, DeepEquals, []string{"res1", "res2"}) 606 c.Check(cs.reqs, HasLen, 1) 607 c.Check(cs.reqs[0].Method, Equals, "GET") 608 c.Check(cs.reqs[0].URL.Path, Equals, "/v2/debug") 609 c.Check(cs.reqs[0].URL.Query(), DeepEquals, url.Values{"aspect": []string{"do-something"}, "foo": []string{"bar"}}) 610 } 611 612 type integrationSuite struct{} 613 614 var _ = Suite(&integrationSuite{}) 615 616 func (cs *integrationSuite) TestClientTimeoutLP1837804(c *C) { 617 restore := client.MockDoTimings(time.Millisecond, 5*time.Millisecond) 618 defer restore() 619 620 testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { 621 time.Sleep(25 * time.Millisecond) 622 })) 623 defer func() { testServer.Close() }() 624 625 cli := client.New(&client.Config{BaseURL: testServer.URL}) 626 _, err := cli.Do("GET", "/", nil, nil, nil, nil) 627 c.Assert(err, ErrorMatches, `.* timeout exceeded while waiting for response`) 628 629 _, err = cli.Do("POST", "/", nil, nil, nil, nil) 630 c.Assert(err, ErrorMatches, `.* timeout exceeded while waiting for response`) 631 } 632 633 func (cs *clientSuite) TestClientSystemRecoveryKeys(c *C) { 634 cs.rsp = `{"type":"sync", "result":{"recovery-key":"42"}}` 635 636 var key client.SystemRecoveryKeysResponse 637 err := cs.cli.SystemRecoveryKeys(&key) 638 c.Assert(err, IsNil) 639 c.Check(cs.reqs, HasLen, 1) 640 c.Check(cs.reqs[0].Method, Equals, "GET") 641 c.Check(cs.reqs[0].URL.Path, Equals, "/v2/system-recovery-keys") 642 c.Check(key.RecoveryKey, Equals, "42") 643 }