Files
snapd/client/client_test.go
Oliver Calder b78a22a2ee client,daemon: expose features supported/enabled in /v2/system-info
Add a "features" field containing a map of feature flag names to boolean
subfields for whether the feature is "supported" and/or "enabled", along
with an "unsupported-reason" if the feature is not supported.

Feature flags which are not set to true or false are omitted from this
map. Feature flags may be unsupported but nonetheless enabled. This
indicates that the feature flag has been set to true, but the backing
feature itself is not currently supported.

Signed-off-by: Oliver Calder <oliver.calder@canonical.com>
2024-03-14 08:39:09 +01:00

733 lines
22 KiB
Go

// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2015-2024 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package client_test
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"time"
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/features"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/testutil"
)
// Hook up check.v1 into the "go test" runner
func Test(t *testing.T) { TestingT(t) }
type clientSuite struct {
testutil.BaseTest
cli *client.Client
req *http.Request
reqs []*http.Request
rsp string
rsps []string
err error
doCalls int
header http.Header
status int
contentLength int64
countingCloser *countingCloser
}
var _ = Suite(&clientSuite{})
func (cs *clientSuite) SetUpTest(c *C) {
os.Setenv(client.TestAuthFileEnvKey, filepath.Join(c.MkDir(), "auth.json"))
cs.AddCleanup(func() { os.Unsetenv(client.TestAuthFileEnvKey) })
cs.cli = client.New(nil)
cs.cli.SetDoer(cs)
cs.err = nil
cs.req = nil
cs.reqs = nil
cs.rsp = ""
cs.rsps = nil
cs.req = nil
cs.header = nil
cs.status = 200
cs.doCalls = 0
cs.contentLength = 0
cs.countingCloser = nil
dirs.SetRootDir(c.MkDir())
cs.AddCleanup(func() { dirs.SetRootDir("") })
cs.AddCleanup(client.MockDoTimings(time.Millisecond, 100*time.Millisecond))
}
type countingCloser struct {
io.Reader
closeCalled int
}
func (n *countingCloser) Close() error {
n.closeCalled++
return nil
}
func (cs *clientSuite) Do(req *http.Request) (*http.Response, error) {
cs.req = req
cs.reqs = append(cs.reqs, req)
body := cs.rsp
if cs.doCalls < len(cs.rsps) {
body = cs.rsps[cs.doCalls]
}
cs.countingCloser = &countingCloser{Reader: strings.NewReader(body)}
rsp := &http.Response{
Body: cs.countingCloser,
Header: cs.header,
StatusCode: cs.status,
ContentLength: cs.contentLength,
}
cs.doCalls++
return rsp, cs.err
}
func (cs *clientSuite) TestNewPanics(c *C) {
c.Assert(func() {
client.New(&client.Config{BaseURL: ":"})
}, PanicMatches, `cannot parse server base URL: ":" \(parse \"?:\"?: missing protocol scheme\)`)
}
func (cs *clientSuite) TestClientDoReportsErrors(c *C) {
cs.err = errors.New("ouchie")
_, err := cs.cli.Do("GET", "/", nil, nil, nil, nil)
c.Check(err, ErrorMatches, "cannot communicate with server: ouchie")
if cs.doCalls < 2 {
c.Fatalf("do did not retry")
}
}
func (cs *clientSuite) TestClientWorks(c *C) {
var v []int
cs.rsp = `[1,2]`
reqBody := ioutil.NopCloser(strings.NewReader(""))
statusCode, err := cs.cli.Do("GET", "/this", nil, reqBody, &v, nil)
c.Check(err, IsNil)
c.Check(statusCode, Equals, 200)
c.Check(v, DeepEquals, []int{1, 2})
c.Assert(cs.req, NotNil)
c.Assert(cs.req.URL, NotNil)
c.Check(cs.req.Method, Equals, "GET")
c.Check(cs.req.Body, Equals, reqBody)
c.Check(cs.req.URL.Path, Equals, "/this")
}
func makeMaintenanceFile(c *C, b []byte) {
c.Assert(os.MkdirAll(filepath.Dir(dirs.SnapdMaintenanceFile), 0755), IsNil)
c.Assert(os.WriteFile(dirs.SnapdMaintenanceFile, b, 0644), IsNil)
}
func (cs *clientSuite) TestClientSetMaintenanceForMaintenanceJSON(c *C) {
// write a maintenance.json that says snapd is down for a restart
maintErr := &client.Error{
Kind: client.ErrorKindSystemRestart,
Message: "system is restarting",
}
b, err := json.Marshal(maintErr)
c.Assert(err, IsNil)
makeMaintenanceFile(c, b)
// now after a Do(), we will have maintenance set to what we wrote
// originally
_, err = cs.cli.Do("GET", "/this", nil, nil, nil, nil)
c.Check(err, IsNil)
returnedErr := cs.cli.Maintenance()
c.Assert(returnedErr, DeepEquals, maintErr)
}
func (cs *clientSuite) TestClientIgnoresGarbageMaintenanceJSON(c *C) {
// write a garbage maintenance.json that can't be unmarshalled
makeMaintenanceFile(c, []byte("blah blah blah not json"))
// after a Do(), no maintenance set and also no error returned from Do()
_, err := cs.cli.Do("GET", "/this", nil, nil, nil, nil)
c.Check(err, IsNil)
returnedErr := cs.cli.Maintenance()
c.Assert(returnedErr, IsNil)
}
func (cs *clientSuite) TestClientDoNoTimeoutIgnoresRetry(c *C) {
var v []int
cs.rsp = `[1,2]`
cs.err = fmt.Errorf("borken")
reqBody := ioutil.NopCloser(strings.NewReader(""))
doOpts := &client.DoOptions{
// Timeout is unset, thus 0, and thus we ignore the retry and only run
// once even though there is an error
Retry: time.Duration(time.Second),
}
_, err := cs.cli.Do("GET", "/this", nil, reqBody, &v, doOpts)
c.Check(err, ErrorMatches, "cannot communicate with server: borken")
c.Assert(cs.doCalls, Equals, 1)
}
func (cs *clientSuite) TestClientDoRetryValidation(c *C) {
var v []int
cs.rsp = `[1,2]`
reqBody := ioutil.NopCloser(strings.NewReader(""))
doOpts := &client.DoOptions{
Retry: time.Duration(-1),
Timeout: time.Duration(time.Minute),
}
_, err := cs.cli.Do("GET", "/this", nil, reqBody, &v, doOpts)
c.Check(err, ErrorMatches, "internal error: retry setting.*invalid")
c.Assert(cs.req, IsNil)
}
func (cs *clientSuite) TestClientDoRetryWorks(c *C) {
reqBody := ioutil.NopCloser(strings.NewReader(""))
cs.err = fmt.Errorf("borken")
doOpts := &client.DoOptions{
Retry: time.Duration(time.Millisecond),
Timeout: time.Duration(time.Second),
}
_, err := cs.cli.Do("GET", "/this", nil, reqBody, nil, doOpts)
c.Check(err, ErrorMatches, "cannot communicate with server: borken")
// best effort checking given that execution could be slow
// on some machines
c.Assert(cs.doCalls > 100, Equals, true, Commentf("got only %v calls", cs.doCalls))
c.Assert(cs.doCalls < 1100, Equals, true, Commentf("got %v calls", cs.doCalls))
}
func (cs *clientSuite) TestClientOnlyRetryAppropriateErrors(c *C) {
reqBody := ioutil.NopCloser(strings.NewReader(""))
doOpts := &client.DoOptions{
Retry: time.Millisecond,
Timeout: 1 * time.Minute,
}
for _, t := range []struct{ error }{
{client.InternalClientError{Err: fmt.Errorf("boom")}},
{client.AuthorizationError{Err: fmt.Errorf("boom")}},
} {
cs.doCalls = 0
cs.err = t.error
_, err := cs.cli.Do("GET", "/this", nil, reqBody, nil, doOpts)
c.Check(err, ErrorMatches, fmt.Sprintf(".*%s", t.error.Error()))
c.Assert(cs.doCalls, Equals, 1)
}
}
func (cs *clientSuite) TestClientUnderstandsStatusCode(c *C) {
var v []int
cs.status = 202
cs.rsp = `[1,2]`
reqBody := ioutil.NopCloser(strings.NewReader(""))
statusCode, err := cs.cli.Do("GET", "/this", nil, reqBody, &v, nil)
c.Check(err, IsNil)
c.Check(statusCode, Equals, 202)
c.Check(v, DeepEquals, []int{1, 2})
c.Assert(cs.req, NotNil)
c.Assert(cs.req.URL, NotNil)
c.Check(cs.req.Method, Equals, "GET")
c.Check(cs.req.Body, Equals, reqBody)
c.Check(cs.req.URL.Path, Equals, "/this")
}
func (cs *clientSuite) TestClientDefaultsToNoAuthorization(c *C) {
os.Setenv(client.TestAuthFileEnvKey, filepath.Join(c.MkDir(), "json"))
defer os.Unsetenv(client.TestAuthFileEnvKey)
var v string
_, _ = cs.cli.Do("GET", "/this", nil, nil, &v, nil)
c.Assert(cs.req, NotNil)
authorization := cs.req.Header.Get("Authorization")
c.Check(authorization, Equals, "")
}
func (cs *clientSuite) TestClientSetsAuthorization(c *C) {
os.Setenv(client.TestAuthFileEnvKey, filepath.Join(c.MkDir(), "json"))
defer os.Unsetenv(client.TestAuthFileEnvKey)
mockUserData := client.User{
Macaroon: "macaroon",
Discharges: []string{"discharge"},
}
err := client.TestWriteAuth(mockUserData)
c.Assert(err, IsNil)
var v string
_, _ = cs.cli.Do("GET", "/this", nil, nil, &v, nil)
authorization := cs.req.Header.Get("Authorization")
c.Check(authorization, Equals, `Macaroon root="macaroon", discharge="discharge"`)
}
func (cs *clientSuite) TestClientHonorsDisableAuth(c *C) {
os.Setenv(client.TestAuthFileEnvKey, filepath.Join(c.MkDir(), "json"))
defer os.Unsetenv(client.TestAuthFileEnvKey)
mockUserData := client.User{
Macaroon: "macaroon",
Discharges: []string{"discharge"},
}
err := client.TestWriteAuth(mockUserData)
c.Assert(err, IsNil)
var v string
cli := client.New(&client.Config{DisableAuth: true})
cli.SetDoer(cs)
_, _ = cli.Do("GET", "/this", nil, nil, &v, nil)
authorization := cs.req.Header.Get("Authorization")
c.Check(authorization, Equals, "")
}
func (cs *clientSuite) TestClientHonorsInteractive(c *C) {
var v string
cli := client.New(&client.Config{Interactive: false})
cli.SetDoer(cs)
_, _ = cli.Do("GET", "/this", nil, nil, &v, nil)
interactive := cs.req.Header.Get(client.AllowInteractionHeader)
c.Check(interactive, Equals, "")
cli = client.New(&client.Config{Interactive: true})
cli.SetDoer(cs)
_, _ = cli.Do("GET", "/this", nil, nil, &v, nil)
interactive = cs.req.Header.Get(client.AllowInteractionHeader)
c.Check(interactive, Equals, "true")
}
func (cs *clientSuite) TestClientWhoAmINobody(c *C) {
email, err := cs.cli.WhoAmI()
c.Assert(err, IsNil)
c.Check(email, Equals, "")
}
func (cs *clientSuite) TestClientWhoAmIRubbish(c *C) {
c.Assert(os.WriteFile(client.TestStoreAuthFilename(os.Getenv("HOME")), []byte("rubbish"), 0644), IsNil)
email, err := cs.cli.WhoAmI()
c.Check(err, NotNil)
c.Check(email, Equals, "")
}
func (cs *clientSuite) TestClientWhoAmISomebody(c *C) {
mockUserData := client.User{
Email: "foo@example.com",
}
c.Assert(client.TestWriteAuth(mockUserData), IsNil)
email, err := cs.cli.WhoAmI()
c.Check(err, IsNil)
c.Check(email, Equals, "foo@example.com")
}
func (cs *clientSuite) TestClientSysInfo(c *C) {
cs.rsp = `{
"type": "sync",
"result": {
"series": "16",
"version": "2",
"os-release": {"id": "ubuntu", "version-id": "16.04"},
"on-classic": true,
"build-id": "1234",
"confinement": "strict",
"architecture": "TI-99/4A",
"virtualization": "MESS",
"sandbox-features": {"backend": ["feature-1", "feature-2"]},
"features": {
"foo": {"supported": false, "unsupported-reason": "too foo", "enabled": false},
"bar": {"supported": false, "unsupported-reason": "not bar enough", "enabled": true},
"baz": {"supported": true, "enabled": false},
"buzz": {"supported": true, "enabled": true}
}
}
}`
sysInfo, err := cs.cli.SysInfo()
c.Check(err, IsNil)
c.Check(sysInfo, DeepEquals, &client.SysInfo{
Version: "2",
Series: "16",
OSRelease: client.OSRelease{
ID: "ubuntu",
VersionID: "16.04",
},
OnClassic: true,
Confinement: "strict",
SandboxFeatures: map[string][]string{
"backend": {"feature-1", "feature-2"},
},
BuildID: "1234",
Architecture: "TI-99/4A",
Virtualization: "MESS",
Features: map[string]features.FeatureInfo{
"foo": {Supported: false, UnsupportedReason: "too foo", Enabled: false},
"bar": {Supported: false, UnsupportedReason: "not bar enough", Enabled: true},
"baz": {Supported: true, Enabled: false},
"buzz": {Supported: true, Enabled: true},
},
})
}
func (cs *clientSuite) TestServerVersion(c *C) {
cs.rsp = `{"type": "sync", "result":
{"series": "16",
"version": "2",
"os-release": {"id": "zyggy", "version-id": "123"},
"architecture": "m32",
"virtualization": "qemu"
}}}`
version, err := cs.cli.ServerVersion()
c.Check(err, IsNil)
c.Check(version, DeepEquals, &client.ServerVersion{
Version: "2",
Series: "16",
OSID: "zyggy",
OSVersionID: "123",
Architecture: "m32",
Virtualization: "qemu",
})
}
func (cs *clientSuite) TestSnapdClientIntegration(c *C) {
c.Assert(os.MkdirAll(filepath.Dir(dirs.SnapdSocket), 0755), IsNil)
l, err := net.Listen("unix", dirs.SnapdSocket)
if err != nil {
c.Fatalf("unable to listen on %q: %v", dirs.SnapdSocket, err)
}
f := func(w http.ResponseWriter, r *http.Request) {
c.Check(r.URL.Path, Equals, "/v2/system-info")
c.Check(r.URL.RawQuery, Equals, "")
fmt.Fprintln(w, `{"type":"sync", "result":{"series":"42"}}`)
}
srv := &httptest.Server{
Listener: l,
Config: &http.Server{Handler: http.HandlerFunc(f)},
}
srv.Start()
defer srv.Close()
cli := client.New(nil)
si, err := cli.SysInfo()
c.Assert(err, IsNil)
c.Check(si.Series, Equals, "42")
}
func (cs *clientSuite) TestSnapClientIntegration(c *C) {
c.Assert(os.MkdirAll(filepath.Dir(dirs.SnapSocket), 0755), IsNil)
l, err := net.Listen("unix", dirs.SnapSocket)
if err != nil {
c.Fatalf("unable to listen on %q: %v", dirs.SnapSocket, err)
}
f := func(w http.ResponseWriter, r *http.Request) {
c.Check(r.URL.Path, Equals, "/v2/snapctl")
c.Check(r.URL.RawQuery, Equals, "")
fmt.Fprintln(w, `{"type":"sync", "result":{"stdout":"test stdout","stderr":"test stderr"}}`)
}
srv := &httptest.Server{
Listener: l,
Config: &http.Server{Handler: http.HandlerFunc(f)},
}
srv.Start()
defer srv.Close()
cli := client.New(&client.Config{
Socket: dirs.SnapSocket,
})
options := &client.SnapCtlOptions{
ContextID: "foo",
Args: []string{"bar", "--baz"},
}
stdout, stderr, err := cli.RunSnapctl(options, nil)
c.Check(err, IsNil)
c.Check(string(stdout), Equals, "test stdout")
c.Check(string(stderr), Equals, "test stderr")
}
func (cs *clientSuite) TestClientReportsOpError(c *C) {
cs.status = 500
cs.rsp = `{"type": "error"}`
_, err := cs.cli.SysInfo()
c.Check(err, ErrorMatches, `.*server error: "Internal Server Error"`)
}
func (cs *clientSuite) TestClientReportsOpErrorStr(c *C) {
cs.status = 400
cs.rsp = `{
"result": {},
"status": "Bad Request",
"status-code": 400,
"type": "error"
}`
_, err := cs.cli.SysInfo()
c.Check(err, ErrorMatches, `.*server error: "Bad Request"`)
}
func (cs *clientSuite) TestClientReportsBadType(c *C) {
cs.rsp = `{"type": "what"}`
_, err := cs.cli.SysInfo()
c.Check(err, ErrorMatches, `.*expected sync response, got "what"`)
}
func (cs *clientSuite) TestClientReportsOuterJSONError(c *C) {
cs.rsp = "this isn't really json is it"
_, err := cs.cli.SysInfo()
c.Check(err, ErrorMatches, `.*invalid character .*`)
}
func (cs *clientSuite) TestClientReportsInnerJSONError(c *C) {
cs.rsp = `{"type": "sync", "result": "this isn't really json is it"}`
_, err := cs.cli.SysInfo()
c.Check(err, ErrorMatches, `.*cannot unmarshal.*`)
}
func (cs *clientSuite) TestClientMaintenance(c *C) {
cs.rsp = `{"type":"sync", "result":{"series":"42"}, "maintenance": {"kind": "system-restart", "message": "system is restarting"}}`
_, err := cs.cli.SysInfo()
c.Assert(err, IsNil)
c.Check(cs.cli.Maintenance().(*client.Error), DeepEquals, &client.Error{
Kind: client.ErrorKindSystemRestart,
Message: "system is restarting",
})
cs.rsp = `{"type":"sync", "result":{"series":"42"}}`
_, err = cs.cli.SysInfo()
c.Assert(err, IsNil)
c.Check(cs.cli.Maintenance(), Equals, error(nil))
}
func (cs *clientSuite) TestClientAsyncOpMaintenance(c *C) {
cs.status = 202
cs.rsp = `{"type":"async", "status-code": 202, "change": "42", "maintenance": {"kind": "system-restart", "message": "system is restarting"}}`
_, err := cs.cli.Install("foo", nil)
c.Assert(err, IsNil)
c.Check(cs.cli.Maintenance().(*client.Error), DeepEquals, &client.Error{
Kind: client.ErrorKindSystemRestart,
Message: "system is restarting",
})
cs.rsp = `{"type":"async", "status-code": 202, "change": "42"}`
_, err = cs.cli.Install("foo", nil)
c.Assert(err, IsNil)
c.Check(cs.cli.Maintenance(), Equals, error(nil))
}
func (cs *clientSuite) TestParseError(c *C) {
resp := &http.Response{
Status: "404 Not Found",
}
err := client.ParseErrorInTest(resp)
c.Check(err, ErrorMatches, `server error: "404 Not Found"`)
h := http.Header{}
h.Add("Content-Type", "application/json")
resp = &http.Response{
Status: "400 Bad Request",
Header: h,
Body: ioutil.NopCloser(strings.NewReader(`{
"status-code": 400,
"type": "error",
"result": {
"message": "invalid"
}
}`)),
}
err = client.ParseErrorInTest(resp)
c.Check(err, ErrorMatches, "invalid")
resp = &http.Response{
Status: "400 Bad Request",
Header: h,
Body: ioutil.NopCloser(strings.NewReader("{}")),
}
err = client.ParseErrorInTest(resp)
c.Check(err, ErrorMatches, `server error: "400 Bad Request"`)
}
func (cs *clientSuite) TestIsTwoFactor(c *C) {
c.Check(client.IsTwoFactorError(&client.Error{Kind: client.ErrorKindTwoFactorRequired}), Equals, true)
c.Check(client.IsTwoFactorError(&client.Error{Kind: client.ErrorKindTwoFactorFailed}), Equals, true)
c.Check(client.IsTwoFactorError(&client.Error{Kind: "some other kind"}), Equals, false)
c.Check(client.IsTwoFactorError(errors.New("test")), Equals, false)
c.Check(client.IsTwoFactorError(nil), Equals, false)
c.Check(client.IsTwoFactorError((*client.Error)(nil)), Equals, false)
}
func (cs *clientSuite) TestIsRetryable(c *C) {
// unhappy
c.Check(client.IsRetryable(nil), Equals, false)
c.Check(client.IsRetryable(errors.New("some-error")), Equals, false)
c.Check(client.IsRetryable(&client.Error{Kind: "something-else"}), Equals, false)
// happy
c.Check(client.IsRetryable(&client.Error{Kind: client.ErrorKindSnapChangeConflict}), Equals, true)
}
func (cs *clientSuite) TestUserAgent(c *C) {
cli := client.New(&client.Config{UserAgent: "some-agent/9.87"})
cli.SetDoer(cs)
var v string
_, _ = cli.Do("GET", "/", nil, nil, &v, nil)
c.Assert(cs.req, NotNil)
c.Check(cs.req.Header.Get("User-Agent"), Equals, "some-agent/9.87")
}
func (cs *clientSuite) TestDebugEnsureStateSoon(c *C) {
cs.rsp = `{"type": "sync", "result":true}`
err := cs.cli.Debug("ensure-state-soon", nil, nil)
c.Check(err, IsNil)
c.Check(cs.reqs, HasLen, 1)
c.Check(cs.reqs[0].Method, Equals, "POST")
c.Check(cs.reqs[0].URL.Path, Equals, "/v2/debug")
data, err := ioutil.ReadAll(cs.reqs[0].Body)
c.Assert(err, IsNil)
c.Check(data, DeepEquals, []byte(`{"action":"ensure-state-soon"}`))
}
func (cs *clientSuite) TestDebugGeneric(c *C) {
cs.rsp = `{"type": "sync", "result":["res1","res2"]}`
var result []string
err := cs.cli.Debug("do-something", []string{"param1", "param2"}, &result)
c.Check(err, IsNil)
c.Check(result, DeepEquals, []string{"res1", "res2"})
c.Check(cs.reqs, HasLen, 1)
c.Check(cs.reqs[0].Method, Equals, "POST")
c.Check(cs.reqs[0].URL.Path, Equals, "/v2/debug")
data, err := ioutil.ReadAll(cs.reqs[0].Body)
c.Assert(err, IsNil)
c.Check(string(data), DeepEquals, `{"action":"do-something","params":["param1","param2"]}`)
}
func (cs *clientSuite) TestDebugGet(c *C) {
cs.rsp = `{"type": "sync", "result":["res1","res2"]}`
var result []string
err := cs.cli.DebugGet("do-something", &result, map[string]string{"foo": "bar"})
c.Check(err, IsNil)
c.Check(result, DeepEquals, []string{"res1", "res2"})
c.Check(cs.reqs, HasLen, 1)
c.Check(cs.reqs[0].Method, Equals, "GET")
c.Check(cs.reqs[0].URL.Path, Equals, "/v2/debug")
c.Check(cs.reqs[0].URL.Query(), DeepEquals, url.Values{"aspect": []string{"do-something"}, "foo": []string{"bar"}})
}
func (cs *clientSuite) TestDebugMigrateHome(c *C) {
cs.status = 202
cs.rsp = `{"type": "async", "status-code": 202, "change": "123"}`
snaps := []string{"foo", "bar"}
changeID, err := cs.cli.MigrateSnapHome(snaps)
c.Check(err, IsNil)
c.Check(changeID, Equals, "123")
c.Check(cs.reqs, HasLen, 1)
c.Check(cs.reqs[0].Method, Equals, "POST")
c.Check(cs.reqs[0].URL.Path, Equals, "/v2/debug")
data, err := ioutil.ReadAll(cs.reqs[0].Body)
c.Assert(err, IsNil)
c.Check(string(data), Equals, `{"action":"migrate-home","snaps":["foo","bar"]}`)
}
type integrationSuite struct{}
var _ = Suite(&integrationSuite{})
func (cs *integrationSuite) TestClientTimeoutLP1837804(c *C) {
restore := client.MockDoTimings(time.Millisecond, 5*time.Millisecond)
defer restore()
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
time.Sleep(25 * time.Millisecond)
}))
defer func() { testServer.Close() }()
cli := client.New(&client.Config{BaseURL: testServer.URL})
_, err := cli.Do("GET", "/", nil, nil, nil, nil)
c.Assert(err, ErrorMatches, `.* timeout exceeded while waiting for response`)
_, err = cli.Do("POST", "/", nil, nil, nil, nil)
c.Assert(err, ErrorMatches, `.* timeout exceeded while waiting for response`)
}
func (cs *clientSuite) TestClientSystemRecoveryKeys(c *C) {
cs.rsp = `{"type":"sync", "result":{"recovery-key":"42"}}`
var key client.SystemRecoveryKeysResponse
err := cs.cli.SystemRecoveryKeys(&key)
c.Assert(err, IsNil)
c.Check(cs.reqs, HasLen, 1)
c.Check(cs.reqs[0].Method, Equals, "GET")
c.Check(cs.reqs[0].URL.Path, Equals, "/v2/system-recovery-keys")
c.Check(key.RecoveryKey, Equals, "42")
}
func (cs *clientSuite) TestClientDebugEnvVar(c *C) {
buf, restore := logger.MockLogger()
defer restore()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, `bar`)
}))
defer srv.Close()
debugValue, ok := os.LookupEnv("SNAP_CLIENT_DEBUG_HTTP")
defer func() {
if ok {
os.Setenv("SNAP_CLIENT_DEBUG_HTTP", debugValue)
} else {
os.Unsetenv("SNAP_CLIENT_DEBUG_HTTP")
}
}()
os.Setenv("SNAP_CLIENT_DEBUG_HTTP", "7")
cli := client.New(&client.Config{BaseURL: srv.URL})
c.Assert(cli, NotNil)
_, err := cli.Do("GET", "/", nil, strings.NewReader("foo"), nil, nil)
c.Assert(err, IsNil)
// check request
c.Assert(buf.String(), testutil.Contains, `logger.go:67: DEBUG: > "GET`)
// check response
c.Assert(buf.String(), testutil.Contains, `logger.go:74: DEBUG: < "HTTP/1.1 200 OK`)
// check bodies
c.Assert(buf.String(), testutil.Contains, "foo")
c.Assert(buf.String(), testutil.Contains, "bar")
}