github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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 }