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