github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap/cmd_services_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-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 main_test
    21  
    22  import (
    23  	"encoding/json"
    24  	"fmt"
    25  	"net/http"
    26  	"sort"
    27  	"strings"
    28  	"time"
    29  
    30  	"gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/client"
    33  	snap "github.com/snapcore/snapd/cmd/snap"
    34  )
    35  
    36  type appOpSuite struct {
    37  	BaseSnapSuite
    38  }
    39  
    40  var _ = check.Suite(&appOpSuite{})
    41  
    42  func (s *appOpSuite) SetUpTest(c *check.C) {
    43  	s.BaseSnapSuite.SetUpTest(c)
    44  
    45  	restoreClientRetry := client.MockDoTimings(time.Millisecond, 100*time.Millisecond)
    46  	restorePollTime := snap.MockPollTime(time.Millisecond)
    47  	s.AddCleanup(restoreClientRetry)
    48  	s.AddCleanup(restorePollTime)
    49  }
    50  
    51  func (s *appOpSuite) TearDownTest(c *check.C) {
    52  	s.BaseSnapSuite.TearDownTest(c)
    53  }
    54  
    55  func (s *appOpSuite) expectedBody(op string, names []string, extra []string) map[string]interface{} {
    56  	inames := make([]interface{}, len(names))
    57  	for i, name := range names {
    58  		inames[i] = name
    59  	}
    60  	expectedBody := map[string]interface{}{
    61  		"action": op,
    62  		"names":  inames,
    63  	}
    64  	for _, x := range extra {
    65  		expectedBody[x] = true
    66  	}
    67  	return expectedBody
    68  }
    69  
    70  func (s *appOpSuite) args(op string, names []string, extra []string, noWait bool) []string {
    71  	args := []string{op}
    72  	if noWait {
    73  		args = append(args, "--no-wait")
    74  	}
    75  	for _, x := range extra {
    76  		args = append(args, "--"+x)
    77  	}
    78  	args = append(args, names...)
    79  	return args
    80  }
    81  
    82  func (s *appOpSuite) testOpNoArgs(c *check.C, op string) {
    83  	s.RedirectClientToTestServer(nil)
    84  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{op})
    85  	c.Assert(err, check.ErrorMatches, `.* required argument .* not provided`)
    86  }
    87  
    88  func (s *appOpSuite) testOpErrorResponse(c *check.C, op string, names []string, extra []string, noWait bool) {
    89  	n := 0
    90  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
    91  		switch n {
    92  		case 0:
    93  			c.Check(r.Method, check.Equals, "POST")
    94  			c.Check(r.URL.Path, check.Equals, "/v2/apps")
    95  			c.Check(r.URL.Query(), check.HasLen, 0)
    96  			c.Check(DecodedRequestBody(c, r), check.DeepEquals, s.expectedBody(op, names, extra))
    97  			w.WriteHeader(400)
    98  			fmt.Fprintln(w, `{"type": "error", "result": {"message": "error"}, "status-code": 400}`)
    99  		default:
   100  			c.Fatalf("expected to get 1 requests, now on %d", n+1)
   101  		}
   102  
   103  		n++
   104  	})
   105  
   106  	_, err := snap.Parser(snap.Client()).ParseArgs(s.args(op, names, extra, noWait))
   107  	c.Assert(err, check.ErrorMatches, "error")
   108  	c.Check(n, check.Equals, 1)
   109  }
   110  
   111  func (s *appOpSuite) testOp(c *check.C, op, summary string, names []string, extra []string, noWait bool) {
   112  	n := 0
   113  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   114  		switch n {
   115  		case 0:
   116  			c.Check(r.URL.Path, check.Equals, "/v2/apps")
   117  			c.Check(r.URL.Query(), check.HasLen, 0)
   118  			c.Check(DecodedRequestBody(c, r), check.DeepEquals, s.expectedBody(op, names, extra))
   119  			c.Check(r.Method, check.Equals, "POST")
   120  			w.WriteHeader(202)
   121  			fmt.Fprintln(w, `{"type":"async", "change": "42", "status-code": 202}`)
   122  		case 1:
   123  			c.Check(r.Method, check.Equals, "GET")
   124  			c.Check(r.URL.Path, check.Equals, "/v2/changes/42")
   125  			fmt.Fprintln(w, `{"type": "sync", "result": {"status": "Doing"}}`)
   126  		case 2:
   127  			c.Check(r.Method, check.Equals, "GET")
   128  			c.Check(r.URL.Path, check.Equals, "/v2/changes/42")
   129  			fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done"}}`)
   130  		default:
   131  			c.Fatalf("expected to get 2 requests, now on %d", n+1)
   132  		}
   133  
   134  		n++
   135  	})
   136  	rest, err := snap.Parser(snap.Client()).ParseArgs(s.args(op, names, extra, noWait))
   137  	c.Assert(err, check.IsNil)
   138  	c.Assert(rest, check.HasLen, 0)
   139  	c.Check(s.Stderr(), check.Equals, "")
   140  	expectedN := 3
   141  	if noWait {
   142  		summary = "42"
   143  		expectedN = 1
   144  	}
   145  	c.Check(s.Stdout(), check.Equals, summary+"\n")
   146  	// ensure that the fake server api was actually hit
   147  	c.Check(n, check.Equals, expectedN)
   148  }
   149  
   150  func (s *appOpSuite) TestAppOps(c *check.C) {
   151  	extras := []string{"enable", "disable", "reload"}
   152  	summaries := []string{"Started.", "Stopped.", "Restarted."}
   153  	for i, op := range []string{"start", "stop", "restart"} {
   154  		s.testOpNoArgs(c, op)
   155  		for _, extra := range [][]string{nil, {extras[i]}} {
   156  			for _, noWait := range []bool{false, true} {
   157  				for _, names := range [][]string{
   158  					{"foo"},
   159  					{"foo", "bar"},
   160  					{"foo", "bar.baz"},
   161  				} {
   162  					s.testOpErrorResponse(c, op, names, extra, noWait)
   163  					s.testOp(c, op, summaries[i], names, extra, noWait)
   164  					s.stdout.Reset()
   165  				}
   166  			}
   167  		}
   168  	}
   169  }
   170  
   171  func (s *appOpSuite) TestAppStatus(c *check.C) {
   172  	n := 0
   173  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   174  		switch n {
   175  		case 0:
   176  			c.Check(r.URL.Path, check.Equals, "/v2/apps")
   177  			c.Check(r.URL.Query(), check.HasLen, 1)
   178  			c.Check(r.URL.Query().Get("select"), check.Equals, "service")
   179  			c.Check(r.Method, check.Equals, "GET")
   180  			w.WriteHeader(200)
   181  			enc := json.NewEncoder(w)
   182  			enc.Encode(map[string]interface{}{
   183  				"type": "sync",
   184  				"result": []map[string]interface{}{
   185  					{"snap": "foo", "name": "bar", "daemon": "oneshot",
   186  						"active": false, "enabled": true,
   187  						"activators": []map[string]interface{}{
   188  							{"name": "bar", "type": "timer", "active": true, "enabled": true},
   189  						},
   190  					}, {"snap": "foo", "name": "baz", "daemon": "oneshot",
   191  						"active": false, "enabled": true,
   192  						"activators": []map[string]interface{}{
   193  							{"name": "baz-sock1", "type": "socket", "active": true, "enabled": true},
   194  							{"name": "baz-sock2", "type": "socket", "active": false, "enabled": true},
   195  						},
   196  					}, {"snap": "foo", "name": "zed",
   197  						"active": true, "enabled": true,
   198  					},
   199  				},
   200  				"status":      "OK",
   201  				"status-code": 200,
   202  			})
   203  		default:
   204  			c.Fatalf("expected to get 1 requests, now on %d", n+1)
   205  		}
   206  
   207  		n++
   208  	})
   209  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"services"})
   210  	c.Assert(err, check.IsNil)
   211  	c.Assert(rest, check.HasLen, 0)
   212  	c.Check(s.Stderr(), check.Equals, "")
   213  	c.Check(s.Stdout(), check.Equals, `Service  Startup  Current   Notes
   214  foo.bar  enabled  inactive  timer-activated
   215  foo.baz  enabled  inactive  socket-activated
   216  foo.zed  enabled  active    -
   217  `)
   218  	// ensure that the fake server api was actually hit
   219  	c.Check(n, check.Equals, 1)
   220  }
   221  
   222  func (s *appOpSuite) TestServiceCompletion(c *check.C) {
   223  	n := 0
   224  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   225  		c.Check(r.URL.Path, check.Equals, "/v2/apps")
   226  		c.Check(r.URL.Query(), check.HasLen, 1)
   227  		c.Check(r.URL.Query().Get("select"), check.Equals, "service")
   228  		c.Check(r.Method, check.Equals, "GET")
   229  		w.WriteHeader(200)
   230  		enc := json.NewEncoder(w)
   231  		enc.Encode(map[string]interface{}{
   232  			"type": "sync",
   233  			"result": []map[string]interface{}{
   234  				{"snap": "a-snap", "name": "foo", "daemon": "simple"},
   235  				{"snap": "a-snap", "name": "bar", "daemon": "simple"},
   236  				{"snap": "b-snap", "name": "baz", "daemon": "simple"},
   237  			},
   238  			"status":      "OK",
   239  			"status-code": 200,
   240  		})
   241  
   242  		n++
   243  	})
   244  
   245  	var comp = func(s string) string {
   246  		comps := snap.ServiceName("").Complete(s)
   247  		as := make([]string, len(comps))
   248  		for i := range comps {
   249  			as[i] = comps[i].Item
   250  		}
   251  		sort.Strings(as)
   252  		return strings.Join(as, "  ")
   253  	}
   254  
   255  	c.Check(comp(""), check.Equals, "a-snap  a-snap.bar  a-snap.foo  b-snap.baz")
   256  	c.Check(comp("a"), check.Equals, "a-snap  a-snap.bar  a-snap.foo")
   257  	c.Check(comp("a-snap"), check.Equals, "a-snap  a-snap.bar  a-snap.foo")
   258  	c.Check(comp("a-snap."), check.Equals, "a-snap.bar  a-snap.foo")
   259  	c.Check(comp("a-snap.b"), check.Equals, "a-snap.bar")
   260  	c.Check(comp("b"), check.Equals, "b-snap.baz")
   261  	c.Check(comp("c"), check.Equals, "")
   262  
   263  	// ensure that the fake server api was actually hit
   264  	c.Check(n, check.Equals, 7)
   265  }
   266  
   267  func (s *appOpSuite) TestAppStatusNoServices(c *check.C) {
   268  	n := 0
   269  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   270  		switch n {
   271  		case 0:
   272  			c.Check(r.URL.Path, check.Equals, "/v2/apps")
   273  			c.Check(r.URL.Query(), check.HasLen, 1)
   274  			c.Check(r.URL.Query().Get("select"), check.Equals, "service")
   275  			c.Check(r.Method, check.Equals, "GET")
   276  			w.WriteHeader(200)
   277  			enc := json.NewEncoder(w)
   278  			enc.Encode(map[string]interface{}{
   279  				"type":        "sync",
   280  				"result":      []map[string]interface{}{},
   281  				"status":      "OK",
   282  				"status-code": 200,
   283  			})
   284  		default:
   285  			c.Fatalf("expected to get 1 requests, now on %d", n+1)
   286  		}
   287  		n++
   288  	})
   289  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"services"})
   290  	c.Assert(err, check.IsNil)
   291  	c.Assert(rest, check.HasLen, 0)
   292  	c.Check(s.Stdout(), check.Equals, "")
   293  	c.Check(s.Stderr(), check.Equals, "There are no services provided by installed snaps.\n")
   294  	// ensure that the fake server api was actually hit
   295  	c.Check(n, check.Equals, 1)
   296  }