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