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 }