github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/client/apps_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 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  	"fmt"
    25  	"strconv"
    26  	"strings"
    27  
    28  	"gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/client"
    31  )
    32  
    33  func mksvc(snap, app string) *client.AppInfo {
    34  	return &client.AppInfo{
    35  		Snap:    snap,
    36  		Name:    app,
    37  		Daemon:  "simple",
    38  		Active:  true,
    39  		Enabled: true,
    40  	}
    41  
    42  }
    43  
    44  func testClientApps(cs *clientSuite, c *check.C) ([]*client.AppInfo, error) {
    45  	services, err := cs.cli.Apps([]string{"foo", "bar"}, client.AppOptions{})
    46  	c.Check(cs.req.URL.Path, check.Equals, "/v2/apps")
    47  	c.Check(cs.req.Method, check.Equals, "GET")
    48  	query := cs.req.URL.Query()
    49  	c.Check(query, check.HasLen, 1)
    50  	c.Check(query.Get("names"), check.Equals, "foo,bar")
    51  
    52  	return services, err
    53  }
    54  
    55  func testClientAppsService(cs *clientSuite, c *check.C) ([]*client.AppInfo, error) {
    56  	services, err := cs.cli.Apps([]string{"foo", "bar"}, client.AppOptions{Service: true})
    57  	c.Check(cs.req.URL.Path, check.Equals, "/v2/apps")
    58  	c.Check(cs.req.Method, check.Equals, "GET")
    59  	query := cs.req.URL.Query()
    60  	c.Check(query, check.HasLen, 2)
    61  	c.Check(query.Get("names"), check.Equals, "foo,bar")
    62  	c.Check(query.Get("select"), check.Equals, "service")
    63  
    64  	return services, err
    65  }
    66  
    67  var appcheckers = []func(*clientSuite, *check.C) ([]*client.AppInfo, error){testClientApps, testClientAppsService}
    68  
    69  func (cs *clientSuite) TestClientServiceGetHappy(c *check.C) {
    70  	expected := []*client.AppInfo{mksvc("foo", "foo"), mksvc("bar", "bar1")}
    71  	buf, err := json.Marshal(expected)
    72  	c.Assert(err, check.IsNil)
    73  	cs.rsp = fmt.Sprintf(`{"type": "sync", "result": %s}`, buf)
    74  	for _, chkr := range appcheckers {
    75  		actual, err := chkr(cs, c)
    76  		c.Assert(err, check.IsNil)
    77  		c.Check(actual, check.DeepEquals, expected)
    78  	}
    79  }
    80  
    81  func (cs *clientSuite) TestClientServiceGetSad(c *check.C) {
    82  	cs.err = fmt.Errorf("xyzzy")
    83  	for _, chkr := range appcheckers {
    84  		actual, err := chkr(cs, c)
    85  		c.Assert(err, check.ErrorMatches, ".* xyzzy")
    86  		c.Check(actual, check.HasLen, 0)
    87  	}
    88  }
    89  
    90  func (cs *clientSuite) TestClientAppCommonID(c *check.C) {
    91  	expected := []*client.AppInfo{{
    92  		Snap:     "foo",
    93  		Name:     "foo",
    94  		CommonID: "org.foo",
    95  	}}
    96  	buf, err := json.Marshal(expected)
    97  	c.Assert(err, check.IsNil)
    98  	cs.rsp = fmt.Sprintf(`{"type": "sync", "result": %s}`, buf)
    99  	for _, chkr := range appcheckers {
   100  		actual, err := chkr(cs, c)
   101  		c.Assert(err, check.IsNil)
   102  		c.Check(actual, check.DeepEquals, expected)
   103  	}
   104  }
   105  
   106  func testClientLogs(cs *clientSuite, c *check.C) ([]client.Log, error) {
   107  	ch, err := cs.cli.Logs([]string{"foo", "bar"}, client.LogOptions{N: -1, Follow: false})
   108  	c.Check(cs.req.URL.Path, check.Equals, "/v2/logs")
   109  	c.Check(cs.req.Method, check.Equals, "GET")
   110  
   111  	// logs cannot have a deadline because of "-f"
   112  	_, ok := cs.req.Context().Deadline()
   113  	c.Check(ok, check.Equals, false)
   114  
   115  	query := cs.req.URL.Query()
   116  	c.Check(query, check.HasLen, 2)
   117  	c.Check(query.Get("names"), check.Equals, "foo,bar")
   118  	c.Check(query.Get("n"), check.Equals, "-1")
   119  
   120  	var logs []client.Log
   121  	if ch != nil {
   122  		for log := range ch {
   123  			logs = append(logs, log)
   124  		}
   125  	}
   126  
   127  	return logs, err
   128  }
   129  
   130  func (cs *clientSuite) TestClientLogsHappy(c *check.C) {
   131  	cs.rsp = `
   132  {"message":"hello"}
   133  {"message":"bye"}
   134  `[1:] // remove the first \n
   135  
   136  	logs, err := testClientLogs(cs, c)
   137  	c.Assert(err, check.IsNil)
   138  	c.Check(logs, check.DeepEquals, []client.Log{{Message: "hello"}, {Message: "bye"}})
   139  }
   140  
   141  func (cs *clientSuite) TestClientLogsDealsWithIt(c *check.C) {
   142  	cs.rsp = `this is a line with no RS on it
   143  this is a line with a RS after some junk{"message": "hello"}
   144  {"message": "bye"}
   145  and that was a regular line. The next one is empty, despite having a RS (and the one after is entirely empty):
   146  
   147  
   148  `
   149  	logs, err := testClientLogs(cs, c)
   150  	c.Assert(err, check.IsNil)
   151  	c.Check(logs, check.DeepEquals, []client.Log{{Message: "hello"}, {Message: "bye"}})
   152  }
   153  
   154  func (cs *clientSuite) TestClientLogsSad(c *check.C) {
   155  	cs.err = fmt.Errorf("xyzzy")
   156  	actual, err := testClientLogs(cs, c)
   157  	c.Assert(err, check.ErrorMatches, ".* xyzzy")
   158  	c.Check(actual, check.HasLen, 0)
   159  }
   160  
   161  func (cs *clientSuite) TestClientLogsOpts(c *check.C) {
   162  	const (
   163  		maxint = int((^uint(0)) >> 1)
   164  		minint = -maxint - 1
   165  	)
   166  	for _, names := range [][]string{nil, {}, {"foo"}, {"foo", "bar"}} {
   167  		for _, n := range []int{-1, 0, 1, minint, maxint} {
   168  			for _, follow := range []bool{true, false} {
   169  				iterdesc := check.Commentf("names: %v, n: %v, follow: %v", names, n, follow)
   170  
   171  				ch, err := cs.cli.Logs(names, client.LogOptions{N: n, Follow: follow})
   172  				c.Check(err, check.IsNil, iterdesc)
   173  				c.Check(cs.req.URL.Path, check.Equals, "/v2/logs", iterdesc)
   174  				c.Check(cs.req.Method, check.Equals, "GET", iterdesc)
   175  				query := cs.req.URL.Query()
   176  				numQ := 0
   177  
   178  				var namesout []string
   179  				if ns := query.Get("names"); ns != "" {
   180  					namesout = strings.Split(ns, ",")
   181  				}
   182  
   183  				c.Check(len(namesout), check.Equals, len(names), iterdesc)
   184  				if len(names) != 0 {
   185  					c.Check(namesout, check.DeepEquals, names, iterdesc)
   186  					numQ++
   187  				}
   188  
   189  				nout, nerr := strconv.Atoi(query.Get("n"))
   190  				c.Check(nerr, check.IsNil, iterdesc)
   191  				c.Check(nout, check.Equals, n, iterdesc)
   192  				numQ++
   193  
   194  				if follow {
   195  					fout, ferr := strconv.ParseBool(query.Get("follow"))
   196  					c.Check(fout, check.Equals, true, iterdesc)
   197  					c.Check(ferr, check.IsNil, iterdesc)
   198  					numQ++
   199  				}
   200  
   201  				c.Check(query, check.HasLen, numQ, iterdesc)
   202  
   203  				for x := range ch {
   204  					c.Logf("expecting empty channel, got %v during %s", x, iterdesc)
   205  					c.Fail()
   206  				}
   207  			}
   208  		}
   209  	}
   210  }
   211  
   212  func (cs *clientSuite) TestClientLogsNotFound(c *check.C) {
   213  	cs.rsp = `{"type":"error","status-code":404,"status":"Not Found","result":{"message":"snap \"foo\" not found","kind":"snap-not-found","value":"foo"}}`
   214  	cs.status = 404
   215  	actual, err := testClientLogs(cs, c)
   216  	c.Assert(err, check.ErrorMatches, `snap "foo" not found`)
   217  	c.Check(actual, check.HasLen, 0)
   218  }
   219  
   220  func (cs *clientSuite) TestClientServiceStart(c *check.C) {
   221  	cs.status = 202
   222  	cs.rsp = `{"type": "async", "status-code": 202, "change": "24"}`
   223  
   224  	type scenario struct {
   225  		names   []string
   226  		opts    client.StartOptions
   227  		comment check.CommentInterface
   228  	}
   229  
   230  	var scenarios []scenario
   231  
   232  	for _, names := range [][]string{
   233  		nil, {},
   234  		{"foo"},
   235  		{"foo", "bar", "baz"},
   236  	} {
   237  		for _, opts := range []client.StartOptions{
   238  			{Enable: true},
   239  			{Enable: false},
   240  		} {
   241  			scenarios = append(scenarios, scenario{
   242  				names:   names,
   243  				opts:    opts,
   244  				comment: check.Commentf("{%q; %#v}", names, opts),
   245  			})
   246  		}
   247  	}
   248  
   249  	for _, sc := range scenarios {
   250  		id, err := cs.cli.Start(sc.names, sc.opts)
   251  		if len(sc.names) == 0 {
   252  			c.Check(id, check.Equals, "", sc.comment)
   253  			c.Check(err, check.Equals, client.ErrNoNames, sc.comment)
   254  			c.Check(cs.req, check.IsNil, sc.comment) // i.e. the request was never done
   255  		} else {
   256  			c.Assert(err, check.IsNil, sc.comment)
   257  			c.Check(id, check.Equals, "24", sc.comment)
   258  			c.Check(cs.req.URL.Path, check.Equals, "/v2/apps", sc.comment)
   259  			c.Check(cs.req.Method, check.Equals, "POST", sc.comment)
   260  			c.Check(cs.req.URL.Query(), check.HasLen, 0, sc.comment)
   261  
   262  			inames := make([]interface{}, len(sc.names))
   263  			for i, name := range sc.names {
   264  				inames[i] = interface{}(name)
   265  			}
   266  
   267  			var reqOp map[string]interface{}
   268  			c.Assert(json.NewDecoder(cs.req.Body).Decode(&reqOp), check.IsNil, sc.comment)
   269  			if sc.opts.Enable {
   270  				c.Check(len(reqOp), check.Equals, 3, sc.comment)
   271  				c.Check(reqOp["enable"], check.Equals, true, sc.comment)
   272  			} else {
   273  				c.Check(len(reqOp), check.Equals, 2, sc.comment)
   274  				c.Check(reqOp["enable"], check.IsNil, sc.comment)
   275  			}
   276  			c.Check(reqOp["action"], check.Equals, "start", sc.comment)
   277  			c.Check(reqOp["names"], check.DeepEquals, inames, sc.comment)
   278  		}
   279  	}
   280  }
   281  
   282  func (cs *clientSuite) TestClientServiceStop(c *check.C) {
   283  	cs.status = 202
   284  	cs.rsp = `{"type": "async", "status-code": 202, "change": "24"}`
   285  
   286  	type tT struct {
   287  		names   []string
   288  		opts    client.StopOptions
   289  		comment check.CommentInterface
   290  	}
   291  
   292  	var scs []tT
   293  
   294  	for _, names := range [][]string{
   295  		nil, {},
   296  		{"foo"},
   297  		{"foo", "bar", "baz"},
   298  	} {
   299  		for _, opts := range []client.StopOptions{
   300  			{Disable: true},
   301  			{Disable: false},
   302  		} {
   303  			scs = append(scs, tT{
   304  				names:   names,
   305  				opts:    opts,
   306  				comment: check.Commentf("{%q; %#v}", names, opts),
   307  			})
   308  		}
   309  	}
   310  
   311  	for _, sc := range scs {
   312  		id, err := cs.cli.Stop(sc.names, sc.opts)
   313  		if len(sc.names) == 0 {
   314  			c.Check(id, check.Equals, "", sc.comment)
   315  			c.Check(err, check.Equals, client.ErrNoNames, sc.comment)
   316  			c.Check(cs.req, check.IsNil, sc.comment) // i.e. the request was never done
   317  		} else {
   318  			c.Assert(err, check.IsNil, sc.comment)
   319  			c.Check(id, check.Equals, "24", sc.comment)
   320  			c.Check(cs.req.URL.Path, check.Equals, "/v2/apps", sc.comment)
   321  			c.Check(cs.req.Method, check.Equals, "POST", sc.comment)
   322  			c.Check(cs.req.URL.Query(), check.HasLen, 0, sc.comment)
   323  
   324  			inames := make([]interface{}, len(sc.names))
   325  			for i, name := range sc.names {
   326  				inames[i] = interface{}(name)
   327  			}
   328  
   329  			var reqOp map[string]interface{}
   330  			c.Assert(json.NewDecoder(cs.req.Body).Decode(&reqOp), check.IsNil, sc.comment)
   331  			if sc.opts.Disable {
   332  				c.Check(len(reqOp), check.Equals, 3, sc.comment)
   333  				c.Check(reqOp["disable"], check.Equals, true, sc.comment)
   334  			} else {
   335  				c.Check(len(reqOp), check.Equals, 2, sc.comment)
   336  				c.Check(reqOp["disable"], check.IsNil, sc.comment)
   337  			}
   338  			c.Check(reqOp["action"], check.Equals, "stop", sc.comment)
   339  			c.Check(reqOp["names"], check.DeepEquals, inames, sc.comment)
   340  		}
   341  	}
   342  }
   343  
   344  func (cs *clientSuite) TestClientServiceRestart(c *check.C) {
   345  	cs.status = 202
   346  	cs.rsp = `{"type": "async", "status-code": 202, "change": "24"}`
   347  
   348  	type tT struct {
   349  		names   []string
   350  		opts    client.RestartOptions
   351  		comment check.CommentInterface
   352  	}
   353  
   354  	var scs []tT
   355  
   356  	for _, names := range [][]string{
   357  		nil, {},
   358  		{"foo"},
   359  		{"foo", "bar", "baz"},
   360  	} {
   361  		for _, opts := range []client.RestartOptions{
   362  			{Reload: true},
   363  			{Reload: false},
   364  		} {
   365  			scs = append(scs, tT{
   366  				names:   names,
   367  				opts:    opts,
   368  				comment: check.Commentf("{%q; %#v}", names, opts),
   369  			})
   370  		}
   371  	}
   372  
   373  	for _, sc := range scs {
   374  		id, err := cs.cli.Restart(sc.names, sc.opts)
   375  		if len(sc.names) == 0 {
   376  			c.Check(id, check.Equals, "", sc.comment)
   377  			c.Check(err, check.Equals, client.ErrNoNames, sc.comment)
   378  			c.Check(cs.req, check.IsNil, sc.comment) // i.e. the request was never done
   379  		} else {
   380  			c.Assert(err, check.IsNil, sc.comment)
   381  			c.Check(id, check.Equals, "24", sc.comment)
   382  			c.Check(cs.req.URL.Path, check.Equals, "/v2/apps", sc.comment)
   383  			c.Check(cs.req.Method, check.Equals, "POST", sc.comment)
   384  			c.Check(cs.req.URL.Query(), check.HasLen, 0, sc.comment)
   385  
   386  			inames := make([]interface{}, len(sc.names))
   387  			for i, name := range sc.names {
   388  				inames[i] = interface{}(name)
   389  			}
   390  
   391  			var reqOp map[string]interface{}
   392  			c.Assert(json.NewDecoder(cs.req.Body).Decode(&reqOp), check.IsNil, sc.comment)
   393  			if sc.opts.Reload {
   394  				c.Check(len(reqOp), check.Equals, 3, sc.comment)
   395  				c.Check(reqOp["reload"], check.Equals, true, sc.comment)
   396  			} else {
   397  				c.Check(len(reqOp), check.Equals, 2, sc.comment)
   398  				c.Check(reqOp["reload"], check.IsNil, sc.comment)
   399  			}
   400  			c.Check(reqOp["action"], check.Equals, "restart", sc.comment)
   401  			c.Check(reqOp["names"], check.DeepEquals, inames, sc.comment)
   402  		}
   403  	}
   404  }