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 }