github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/cmd/snap/cmd_snap_op_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2018 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 "fmt" 24 "io/ioutil" 25 "mime" 26 "mime/multipart" 27 "net/http" 28 "net/http/httptest" 29 "os" 30 "path/filepath" 31 "regexp" 32 "strings" 33 "time" 34 35 "gopkg.in/check.v1" 36 37 "github.com/snapcore/snapd/client" 38 snap "github.com/snapcore/snapd/cmd/snap" 39 "github.com/snapcore/snapd/progress" 40 "github.com/snapcore/snapd/progress/progresstest" 41 "github.com/snapcore/snapd/release" 42 "github.com/snapcore/snapd/testutil" 43 ) 44 45 type snapOpTestServer struct { 46 c *check.C 47 48 checker func(r *http.Request) 49 n int 50 total int 51 channel string 52 trackingChannel string 53 confinement string 54 restart string 55 snap string 56 } 57 58 var _ = check.Suite(&SnapOpSuite{}) 59 60 func (t *snapOpTestServer) handle(w http.ResponseWriter, r *http.Request) { 61 switch t.n { 62 case 0: 63 t.checker(r) 64 method := "POST" 65 if strings.HasSuffix(r.URL.Path, "/conf") { 66 method = "PUT" 67 } 68 t.c.Check(r.Method, check.Equals, method) 69 w.WriteHeader(202) 70 fmt.Fprintln(w, `{"type":"async", "change": "42", "status-code": 202}`) 71 case 1: 72 t.c.Check(r.Method, check.Equals, "GET") 73 t.c.Check(r.URL.Path, check.Equals, "/v2/changes/42") 74 if t.restart == "" { 75 fmt.Fprintln(w, `{"type": "sync", "result": {"status": "Doing"}}`) 76 } else { 77 fmt.Fprintln(w, fmt.Sprintf(`{"type": "sync", "result": {"status": "Doing"}, "maintenance": {"kind": "system-restart", "message": "system is %sing", "value": {"op": %q}}}}`, t.restart, t.restart)) 78 } 79 case 2: 80 t.c.Check(r.Method, check.Equals, "GET") 81 t.c.Check(r.URL.Path, check.Equals, "/v2/changes/42") 82 fmt.Fprintf(w, `{"type": "sync", "result": {"ready": true, "status": "Done", "data": {"snap-name": "%s"}}}\n`, t.snap) 83 case 3: 84 t.c.Check(r.Method, check.Equals, "GET") 85 t.c.Check(r.URL.Path, check.Equals, "/v2/snaps") 86 fmt.Fprintf(w, `{"type": "sync", "result": [{"name": "%s", "status": "active", "version": "1.0", "developer": "bar", "publisher": {"id": "bar-id", "username": "bar", "display-name": "Bar", "validation": "unproven"}, "revision":42, "channel":"%s", "tracking-channel": "%s", "confinement": "%s"}]}\n`, t.snap, t.channel, t.trackingChannel, t.confinement) 87 default: 88 t.c.Fatalf("expected to get %d requests, now on %d", t.total, t.n+1) 89 } 90 91 t.n++ 92 } 93 94 type SnapOpSuite struct { 95 BaseSnapSuite 96 97 restoreAll func() 98 srv snapOpTestServer 99 } 100 101 func (s *SnapOpSuite) SetUpTest(c *check.C) { 102 s.BaseSnapSuite.SetUpTest(c) 103 104 restoreClientRetry := client.MockDoTimings(time.Millisecond, time.Second) 105 restorePollTime := snap.MockPollTime(time.Millisecond) 106 s.restoreAll = func() { 107 restoreClientRetry() 108 restorePollTime() 109 } 110 111 s.srv = snapOpTestServer{ 112 c: c, 113 total: 4, 114 snap: "foo", 115 } 116 } 117 118 func (s *SnapOpSuite) TearDownTest(c *check.C) { 119 s.restoreAll() 120 s.BaseSnapSuite.TearDownTest(c) 121 } 122 123 func (s *SnapOpSuite) TestWait(c *check.C) { 124 meter := &progresstest.Meter{} 125 defer progress.MockMeter(meter)() 126 restore := snap.MockMaxGoneTime(time.Millisecond) 127 defer restore() 128 129 // lazy way of getting a URL that won't work nor break stuff 130 server := httptest.NewServer(nil) 131 snap.ClientConfig.BaseURL = server.URL 132 server.Close() 133 134 cli := snap.Client() 135 chg, err := snap.Wait(cli, "x") 136 c.Assert(chg, check.IsNil) 137 c.Assert(err, check.NotNil) 138 c.Check(meter.Labels, testutil.Contains, "Waiting for server to restart") 139 } 140 141 func (s *SnapOpSuite) TestWaitRecovers(c *check.C) { 142 meter := &progresstest.Meter{} 143 defer progress.MockMeter(meter)() 144 145 nah := true 146 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 147 if nah { 148 nah = false 149 return 150 } 151 fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done"}}`) 152 }) 153 154 cli := snap.Client() 155 chg, err := snap.Wait(cli, "x") 156 // we got the change 157 c.Check(chg, check.NotNil) 158 c.Assert(err, check.IsNil) 159 160 // but only after recovering 161 c.Check(meter.Labels, testutil.Contains, "Waiting for server to restart") 162 } 163 164 func (s *SnapOpSuite) TestWaitRebooting(c *check.C) { 165 meter := &progresstest.Meter{} 166 defer progress.MockMeter(meter)() 167 restore := snap.MockMaxGoneTime(time.Millisecond) 168 defer restore() 169 170 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 171 fmt.Fprintln(w, `{"type": "sync", 172 "result": { 173 "ready": false, 174 "status": "Doing", 175 "tasks": [{"kind": "bar", "summary": "...", "status": "Doing", "progress": {"done": 1, "total": 1}, "log": ["INFO: info"]}] 176 }, 177 "maintenance": {"kind": "system-restart", "message": "system is restarting"}}`) 178 }) 179 180 cli := snap.Client() 181 chg, err := snap.Wait(cli, "x") 182 c.Assert(chg, check.IsNil) 183 c.Assert(err, check.DeepEquals, &client.Error{Kind: client.ErrorKindSystemRestart, Message: "system is restarting"}) 184 185 // last available info is still displayed 186 c.Check(meter.Notices, testutil.Contains, "INFO: info") 187 } 188 189 func (s *SnapOpSuite) TestInstall(c *check.C) { 190 s.srv.checker = func(r *http.Request) { 191 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 192 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 193 "action": "install", 194 "channel": "candidate", 195 "cohort-key": "what", 196 }) 197 s.srv.channel = "candidate" 198 } 199 200 s.RedirectClientToTestServer(s.srv.handle) 201 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "candidate", "--cohort", "what", "foo"}) 202 c.Assert(err, check.IsNil) 203 c.Assert(rest, check.DeepEquals, []string{}) 204 c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(candidate\) 1.0 from Bar installed`) 205 c.Check(s.Stderr(), check.Equals, "") 206 // ensure that the fake server api was actually hit 207 c.Check(s.srv.n, check.Equals, s.srv.total) 208 } 209 210 func (s *SnapOpSuite) TestInstallIgnoreRunning(c *check.C) { 211 s.srv.checker = func(r *http.Request) { 212 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 213 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 214 "action": "install", 215 "ignore-running": true, 216 }) 217 } 218 219 s.RedirectClientToTestServer(s.srv.handle) 220 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--ignore-running", "foo"}) 221 c.Assert(err, check.IsNil) 222 c.Assert(rest, check.DeepEquals, []string{}) 223 c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`) 224 c.Check(s.Stderr(), check.Equals, "") 225 // ensure that the fake server api was actually hit 226 c.Check(s.srv.n, check.Equals, s.srv.total) 227 } 228 229 func (s *SnapOpSuite) TestInstallNoPATH(c *check.C) { 230 // PATH restored by test tear down 231 os.Setenv("PATH", "/bin:/usr/bin:/sbin:/usr/sbin") 232 s.srv.checker = func(r *http.Request) { 233 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 234 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 235 "action": "install", 236 "channel": "candidate", 237 }) 238 s.srv.channel = "candidate" 239 } 240 241 s.RedirectClientToTestServer(s.srv.handle) 242 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "candidate", "foo"}) 243 c.Assert(err, check.IsNil) 244 c.Assert(rest, check.DeepEquals, []string{}) 245 c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(candidate\) 1.0 from Bar installed`) 246 c.Check(s.Stderr(), testutil.MatchesWrapped, `Warning: \S+/bin was not found in your \$PATH.*`) 247 // ensure that the fake server api was actually hit 248 c.Check(s.srv.n, check.Equals, s.srv.total) 249 } 250 251 func (s *SnapOpSuite) TestInstallNoPATHMaybeResetBySudo(c *check.C) { 252 // PATH restored by test tear down 253 os.Setenv("PATH", "/bin:/usr/bin:/sbin:/usr/sbin") 254 if old, isset := os.LookupEnv("SUDO_UID"); isset { 255 defer os.Setenv("SUDO_UID", old) 256 } 257 os.Setenv("SUDO_UID", "1234") 258 restore := release.MockReleaseInfo(&release.OS{ID: "fedora"}) 259 defer restore() 260 s.srv.checker = func(r *http.Request) { 261 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 262 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 263 "action": "install", 264 "channel": "candidate", 265 }) 266 s.srv.channel = "candidate" 267 } 268 269 s.RedirectClientToTestServer(s.srv.handle) 270 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "candidate", "foo"}) 271 c.Assert(err, check.IsNil) 272 c.Assert(rest, check.DeepEquals, []string{}) 273 c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(candidate\) 1.0 from Bar installed`) 274 c.Check(s.Stderr(), check.HasLen, 0) 275 // ensure that the fake server api was actually hit 276 c.Check(s.srv.n, check.Equals, s.srv.total) 277 } 278 279 func (s *SnapOpSuite) TestInstallFromTrack(c *check.C) { 280 s.srv.checker = func(r *http.Request) { 281 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 282 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 283 "action": "install", 284 "channel": "3.4", 285 }) 286 s.srv.channel = "3.4/stable" 287 } 288 289 s.RedirectClientToTestServer(s.srv.handle) 290 // snap install --channel=3.4 means 3.4/stable, this is what we test here 291 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "3.4", "foo"}) 292 c.Assert(err, check.IsNil) 293 c.Assert(rest, check.DeepEquals, []string{}) 294 c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(3.4/stable\) 1.0 from Bar installed`) 295 c.Check(s.Stderr(), check.Equals, "") 296 // ensure that the fake server api was actually hit 297 c.Check(s.srv.n, check.Equals, s.srv.total) 298 } 299 300 func (s *SnapOpSuite) TestInstallFromBranch(c *check.C) { 301 s.srv.checker = func(r *http.Request) { 302 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 303 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 304 "action": "install", 305 "channel": "3.4/stable/hotfix-1", 306 }) 307 s.srv.channel = "3.4/stable/hotfix-1" 308 } 309 310 s.RedirectClientToTestServer(s.srv.handle) 311 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "3.4/stable/hotfix-1", "foo"}) 312 c.Assert(err, check.IsNil) 313 c.Assert(rest, check.DeepEquals, []string{}) 314 c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(3.4/stable/hotfix-1\) 1.0 from Bar installed`) 315 c.Check(s.Stderr(), check.Equals, "") 316 // ensure that the fake server api was actually hit 317 c.Check(s.srv.n, check.Equals, s.srv.total) 318 } 319 320 func (s *SnapOpSuite) TestInstallSameRiskInTrack(c *check.C) { 321 s.srv.checker = func(r *http.Request) { 322 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 323 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 324 "action": "install", 325 "channel": "latest/stable", 326 }) 327 s.srv.channel = "stable" 328 s.srv.trackingChannel = "latest/stable" 329 } 330 331 s.RedirectClientToTestServer(s.srv.handle) 332 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "latest/stable", "foo"}) 333 c.Assert(err, check.IsNil) 334 c.Assert(rest, check.DeepEquals, []string{}) 335 c.Check(s.Stdout(), check.Equals, "foo 1.0 from Bar installed\n") 336 c.Check(s.Stderr(), check.Equals, "") 337 // ensure that the fake server api was actually hit 338 c.Check(s.srv.n, check.Equals, s.srv.total) 339 } 340 341 func (s *SnapOpSuite) TestInstallSameRiskInDefaultTrack(c *check.C) { 342 s.srv.checker = func(r *http.Request) { 343 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 344 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 345 "action": "install", 346 "channel": "stable", 347 }) 348 s.srv.channel = "18/stable" 349 s.srv.trackingChannel = "18/stable" 350 } 351 352 s.RedirectClientToTestServer(s.srv.handle) 353 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--stable", "foo"}) 354 c.Assert(err, check.IsNil) 355 c.Assert(rest, check.DeepEquals, []string{}) 356 c.Check(s.Stdout(), check.Equals, "foo (18/stable) 1.0 from Bar installed\n") 357 c.Check(s.Stderr(), check.Equals, "") 358 // ensure that the fake server api was actually hit 359 c.Check(s.srv.n, check.Equals, s.srv.total) 360 } 361 362 func (s *SnapOpSuite) TestInstallRiskChannelClosed(c *check.C) { 363 s.srv.checker = func(r *http.Request) { 364 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 365 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 366 "action": "install", 367 "channel": "edge", 368 }) 369 s.srv.channel = "stable" 370 s.srv.trackingChannel = "edge" 371 } 372 373 s.RedirectClientToTestServer(s.srv.handle) 374 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "edge", "foo"}) 375 c.Assert(err, check.IsNil) 376 c.Assert(rest, check.DeepEquals, []string{}) 377 c.Check(s.Stdout(), check.Equals, `foo 1.0 from Bar installed 378 Channel edge for foo is closed; temporarily forwarding to stable. 379 `) 380 c.Check(s.Stderr(), check.Equals, "") 381 // ensure that the fake server api was actually hit 382 c.Check(s.srv.n, check.Equals, s.srv.total) 383 } 384 385 func (s *SnapOpSuite) TestInstallDevMode(c *check.C) { 386 s.srv.checker = func(r *http.Request) { 387 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 388 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 389 "action": "install", 390 "devmode": true, 391 "channel": "beta", 392 }) 393 s.srv.channel = "beta" 394 } 395 396 s.RedirectClientToTestServer(s.srv.handle) 397 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "beta", "--devmode", "foo"}) 398 c.Assert(err, check.IsNil) 399 c.Assert(rest, check.DeepEquals, []string{}) 400 c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(beta\) 1.0 from Bar installed`) 401 c.Check(s.Stderr(), check.Equals, "") 402 // ensure that the fake server api was actually hit 403 c.Check(s.srv.n, check.Equals, s.srv.total) 404 } 405 406 func (s *SnapOpSuite) TestInstallClassic(c *check.C) { 407 s.srv.checker = func(r *http.Request) { 408 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 409 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 410 "action": "install", 411 "classic": true, 412 }) 413 s.srv.confinement = "classic" 414 } 415 416 s.RedirectClientToTestServer(s.srv.handle) 417 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--classic", "foo"}) 418 c.Assert(err, check.IsNil) 419 c.Assert(rest, check.DeepEquals, []string{}) 420 c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`) 421 c.Check(s.Stderr(), check.Equals, "") 422 // ensure that the fake server api was actually hit 423 c.Check(s.srv.n, check.Equals, s.srv.total) 424 } 425 426 func (s *SnapOpSuite) TestInstallStrictWithClassicFlag(c *check.C) { 427 s.srv.checker = func(r *http.Request) { 428 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 429 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 430 "action": "install", 431 "classic": true, 432 }) 433 s.srv.confinement = "strict" 434 } 435 436 s.RedirectClientToTestServer(s.srv.handle) 437 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--classic", "foo"}) 438 c.Assert(err, check.IsNil) 439 c.Assert(rest, check.DeepEquals, []string{}) 440 c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`) 441 c.Check(s.Stderr(), testutil.MatchesWrapped, `Warning:\s+flag --classic ignored for strictly confined snap foo.*`) 442 // ensure that the fake server api was actually hit 443 c.Check(s.srv.n, check.Equals, s.srv.total) 444 } 445 446 func (s *SnapOpSuite) TestInstallUnaliased(c *check.C) { 447 s.srv.checker = func(r *http.Request) { 448 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 449 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 450 "action": "install", 451 "unaliased": true, 452 }) 453 } 454 455 s.RedirectClientToTestServer(s.srv.handle) 456 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--unaliased", "foo"}) 457 c.Assert(err, check.IsNil) 458 c.Assert(rest, check.DeepEquals, []string{}) 459 c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`) 460 c.Check(s.Stderr(), check.Equals, "") 461 // ensure that the fake server api was actually hit 462 c.Check(s.srv.n, check.Equals, s.srv.total) 463 } 464 465 func (s *SnapOpSuite) TestInstallSnapNotFound(c *check.C) { 466 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 467 fmt.Fprintln(w, `{"type": "error", "result": {"message": "snap not found", "value": "foo", "kind": "snap-not-found"}, "status-code": 404}`) 468 }) 469 470 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"}) 471 c.Assert(err, check.NotNil) 472 c.Check(fmt.Sprintf("error: %v\n", err), check.Equals, `error: snap "foo" not found 473 `) 474 475 c.Check(s.Stdout(), check.Equals, "") 476 c.Check(s.Stderr(), check.Equals, "") 477 } 478 479 func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailable(c *check.C) { 480 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 481 fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision available as specified", "value": "foo", "kind": "snap-revision-not-available"}, "status-code": 404}`) 482 }) 483 484 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"}) 485 c.Assert(err, check.NotNil) 486 c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` 487 error: snap "foo" not available as specified (see 'snap info foo') 488 `) 489 490 c.Check(s.Stdout(), check.Equals, "") 491 c.Check(s.Stderr(), check.Equals, "") 492 } 493 494 func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableOnChannel(c *check.C) { 495 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 496 fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision available as specified", "value": "foo", "kind": "snap-revision-not-available"}, "status-code": 404}`) 497 }) 498 499 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=mytrack", "foo"}) 500 c.Check(err, check.ErrorMatches, `snap "foo" not available on channel "mytrack" \(see 'snap info foo'\)`) 501 502 c.Check(s.Stdout(), check.Equals, "") 503 c.Check(s.Stderr(), check.Equals, "") 504 } 505 506 func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableAtRevision(c *check.C) { 507 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 508 fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision available as specified", "value": "foo", "kind": "snap-revision-not-available"}, "status-code": 404}`) 509 }) 510 511 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--revision=2", "foo"}) 512 c.Assert(err, check.NotNil) 513 c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` 514 error: snap "foo" revision 2 not available (see 'snap info foo') 515 `) 516 517 c.Check(s.Stdout(), check.Equals, "") 518 c.Check(s.Stderr(), check.Equals, "") 519 } 520 521 func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForChannelTrackOK(c *check.C) { 522 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 523 fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": { 524 "snap-name": "foo", 525 "action": "install", 526 "architecture": "amd64", 527 "channel": "stable", 528 "releases": [{"architecture": "amd64", "channel": "beta"}, 529 {"architecture": "amd64", "channel": "edge"}] 530 }, "kind": "snap-channel-not-available"}, "status-code": 404}`) 531 }) 532 533 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"}) 534 c.Assert(err, check.NotNil) 535 c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` 536 error: snap "foo" is not available on stable but is available to install on the 537 following channels: 538 539 beta snap install --beta foo 540 edge snap install --edge foo 541 542 Please be mindful pre-release channels may include features not 543 completely tested or implemented. Get more information with 'snap info 544 foo'. 545 `) 546 547 c.Check(s.Stdout(), check.Equals, "") 548 c.Check(s.Stderr(), check.Equals, "") 549 } 550 551 func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForChannelTrackOKPrerelOK(c *check.C) { 552 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 553 fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": { 554 "snap-name": "foo", 555 "action": "install", 556 "architecture": "amd64", 557 "channel": "candidate", 558 "releases": [{"architecture": "amd64", "channel": "beta"}, 559 {"architecture": "amd64", "channel": "edge"}] 560 }, "kind": "snap-channel-not-available"}, "status-code": 404}`) 561 }) 562 563 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--candidate", "foo"}) 564 c.Assert(err, check.NotNil) 565 c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` 566 error: snap "foo" is not available on candidate but is available to install on 567 the following channels: 568 569 beta snap install --beta foo 570 edge snap install --edge foo 571 572 Get more information with 'snap info foo'. 573 `) 574 575 c.Check(s.Stdout(), check.Equals, "") 576 c.Check(s.Stderr(), check.Equals, "") 577 } 578 579 func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForChannelTrackOther(c *check.C) { 580 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 581 fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": { 582 "snap-name": "foo", 583 "action": "install", 584 "architecture": "amd64", 585 "channel": "stable", 586 "releases": [{"architecture": "amd64", "channel": "1.0/stable"}, 587 {"architecture": "amd64", "channel": "2.0/stable"}] 588 }, "kind": "snap-channel-not-available"}, "status-code": 404}`) 589 }) 590 591 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"}) 592 c.Assert(err, check.NotNil) 593 c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` 594 error: snap "foo" is not available on latest/stable but is available to install 595 on the following tracks: 596 597 1.0/stable snap install --channel=1.0 foo 598 2.0/stable snap install --channel=2.0 foo 599 600 Please be mindful that different tracks may include different features. 601 Get more information with 'snap info foo'. 602 `) 603 604 c.Check(s.Stdout(), check.Equals, "") 605 c.Check(s.Stderr(), check.Equals, "") 606 } 607 608 func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForChannelTrackLatestStable(c *check.C) { 609 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 610 fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": { 611 "snap-name": "foo", 612 "action": "install", 613 "architecture": "amd64", 614 "channel": "2.0/stable", 615 "releases": [{"architecture": "amd64", "channel": "stable"}] 616 }, "kind": "snap-channel-not-available"}, "status-code": 404}`) 617 }) 618 619 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=2.0/stable", "foo"}) 620 c.Assert(err, check.NotNil) 621 c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` 622 error: snap "foo" is not available on 2.0/stable but is available to install on 623 the following tracks: 624 625 latest/stable snap install --stable foo 626 627 Please be mindful that different tracks may include different features. 628 Get more information with 'snap info foo'. 629 `) 630 631 c.Check(s.Stdout(), check.Equals, "") 632 c.Check(s.Stderr(), check.Equals, "") 633 } 634 635 func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForChannelTrackAndRiskOther(c *check.C) { 636 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 637 fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": { 638 "snap-name": "foo", 639 "action": "install", 640 "architecture": "amd64", 641 "channel": "2.0/stable", 642 "releases": [{"architecture": "amd64", "channel": "1.0/edge"}] 643 }, "kind": "snap-channel-not-available"}, "status-code": 404}`) 644 }) 645 646 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=2.0/stable", "foo"}) 647 c.Assert(err, check.NotNil) 648 c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` 649 error: snap "foo" is not available on 2.0/stable but other tracks exist. 650 651 Please be mindful that different tracks may include different features. 652 Get more information with 'snap info foo'. 653 `) 654 655 c.Check(s.Stdout(), check.Equals, "") 656 c.Check(s.Stderr(), check.Equals, "") 657 } 658 659 func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForArchitectureTrackAndRiskOK(c *check.C) { 660 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 661 fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified architecture", "value": { 662 "snap-name": "foo", 663 "action": "install", 664 "architecture": "arm64", 665 "channel": "stable", 666 "releases": [{"architecture": "amd64", "channel": "stable"}, 667 {"architecture": "s390x", "channel": "stable"}] 668 }, "kind": "snap-architecture-not-available"}, "status-code": 404}`) 669 }) 670 671 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"}) 672 c.Assert(err, check.NotNil) 673 c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` 674 error: snap "foo" is not available on stable for this architecture (arm64) but 675 exists on other architectures (amd64, s390x). 676 `) 677 678 c.Check(s.Stdout(), check.Equals, "") 679 c.Check(s.Stderr(), check.Equals, "") 680 } 681 682 func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForArchitectureTrackAndRiskOther(c *check.C) { 683 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 684 fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified architecture", "value": { 685 "snap-name": "foo", 686 "action": "install", 687 "architecture": "arm64", 688 "channel": "1.0/stable", 689 "releases": [{"architecture": "amd64", "channel": "stable"}, 690 {"architecture": "s390x", "channel": "stable"}] 691 }, "kind": "snap-architecture-not-available"}, "status-code": 404}`) 692 }) 693 694 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=1.0/stable", "foo"}) 695 c.Assert(err, check.NotNil) 696 c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` 697 error: snap "foo" is not available on this architecture (arm64) but exists on 698 other architectures (amd64, s390x). 699 `) 700 701 c.Check(s.Stdout(), check.Equals, "") 702 c.Check(s.Stderr(), check.Equals, "") 703 } 704 705 func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableInvalidChannel(c *check.C) { 706 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 707 c.Fatal("unexpected call to server") 708 }) 709 710 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=a/b/c/d", "foo"}) 711 c.Assert(err, check.ErrorMatches, "channel name has too many components: a/b/c/d") 712 713 c.Check(s.Stdout(), check.Equals, "") 714 c.Check(s.Stderr(), check.Equals, "") 715 } 716 717 func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForChannelNonExistingBranchOnMainChannel(c *check.C) { 718 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 719 fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": { 720 "snap-name": "foo", 721 "action": "install", 722 "architecture": "amd64", 723 "channel": "stable/baz", 724 "releases": [{"architecture": "amd64", "channel": "stable"}] 725 }, "kind": "snap-channel-not-available"}, "status-code": 404}`) 726 }) 727 728 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=stable/baz", "foo"}) 729 c.Assert(err, check.NotNil) 730 c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` 731 error: requested a non-existing branch on latest/stable for snap "foo": baz 732 `) 733 734 c.Check(s.Stdout(), check.Equals, "") 735 c.Check(s.Stderr(), check.Equals, "") 736 } 737 738 func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForChannelNonExistingBranch(c *check.C) { 739 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 740 fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": { 741 "snap-name": "foo", 742 "action": "install", 743 "architecture": "amd64", 744 "channel": "stable/baz", 745 "releases": [{"architecture": "amd64", "channel": "edge"}] 746 }, "kind": "snap-channel-not-available"}, "status-code": 404}`) 747 }) 748 749 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=stable/baz", "foo"}) 750 c.Assert(err, check.NotNil) 751 c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` 752 error: requested a non-existing branch for snap "foo": latest/stable/baz 753 `) 754 755 c.Check(s.Stdout(), check.Equals, "") 756 c.Check(s.Stderr(), check.Equals, "") 757 } 758 759 func testForm(r *http.Request, c *check.C) *multipart.Form { 760 contentType := r.Header.Get("Content-Type") 761 mediaType, params, err := mime.ParseMediaType(contentType) 762 c.Assert(err, check.IsNil) 763 c.Assert(params["boundary"], check.Matches, ".{10,}") 764 c.Check(mediaType, check.Equals, "multipart/form-data") 765 766 form, err := multipart.NewReader(r.Body, params["boundary"]).ReadForm(1 << 20) 767 c.Assert(err, check.IsNil) 768 769 return form 770 } 771 772 func formFile(form *multipart.Form, c *check.C) (name, filename string, content []byte) { 773 c.Assert(form.File, check.HasLen, 1) 774 775 for name, fheaders := range form.File { 776 c.Assert(fheaders, check.HasLen, 1) 777 body, err := fheaders[0].Open() 778 c.Assert(err, check.IsNil) 779 defer body.Close() 780 filename = fheaders[0].Filename 781 content, err = ioutil.ReadAll(body) 782 c.Assert(err, check.IsNil) 783 784 return name, filename, content 785 } 786 787 return "", "", nil 788 } 789 790 func (s *SnapOpSuite) TestInstallPath(c *check.C) { 791 s.srv.checker = func(r *http.Request) { 792 c.Check(r.URL.Path, check.Equals, "/v2/snaps") 793 794 form := testForm(r, c) 795 defer form.RemoveAll() 796 797 c.Check(form.Value["action"], check.DeepEquals, []string{"install"}) 798 c.Check(form.Value["devmode"], check.IsNil) 799 c.Check(form.Value["snap-path"], check.NotNil) 800 c.Check(form.Value, check.HasLen, 2) 801 802 name, _, body := formFile(form, c) 803 c.Check(name, check.Equals, "snap") 804 c.Check(string(body), check.Equals, "snap-data") 805 } 806 807 snapBody := []byte("snap-data") 808 s.RedirectClientToTestServer(s.srv.handle) 809 snapPath := filepath.Join(c.MkDir(), "foo.snap") 810 err := ioutil.WriteFile(snapPath, snapBody, 0644) 811 c.Assert(err, check.IsNil) 812 813 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", snapPath}) 814 c.Assert(err, check.IsNil) 815 c.Assert(rest, check.DeepEquals, []string{}) 816 c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`) 817 c.Check(s.Stderr(), check.Equals, "") 818 // ensure that the fake server api was actually hit 819 c.Check(s.srv.n, check.Equals, s.srv.total) 820 } 821 822 func (s *SnapOpSuite) TestInstallPathDevMode(c *check.C) { 823 s.srv.checker = func(r *http.Request) { 824 c.Check(r.URL.Path, check.Equals, "/v2/snaps") 825 form := testForm(r, c) 826 defer form.RemoveAll() 827 828 c.Check(form.Value["action"], check.DeepEquals, []string{"install"}) 829 c.Check(form.Value["devmode"], check.DeepEquals, []string{"true"}) 830 c.Check(form.Value["snap-path"], check.NotNil) 831 c.Check(form.Value, check.HasLen, 3) 832 833 name, _, body := formFile(form, c) 834 c.Check(name, check.Equals, "snap") 835 c.Check(string(body), check.Equals, "snap-data") 836 } 837 838 snapBody := []byte("snap-data") 839 s.RedirectClientToTestServer(s.srv.handle) 840 snapPath := filepath.Join(c.MkDir(), "foo.snap") 841 err := ioutil.WriteFile(snapPath, snapBody, 0644) 842 c.Assert(err, check.IsNil) 843 844 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--devmode", snapPath}) 845 c.Assert(err, check.IsNil) 846 c.Assert(rest, check.DeepEquals, []string{}) 847 c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`) 848 c.Check(s.Stderr(), check.Equals, "") 849 // ensure that the fake server api was actually hit 850 c.Check(s.srv.n, check.Equals, s.srv.total) 851 } 852 853 func (s *SnapOpSuite) TestInstallPathClassic(c *check.C) { 854 s.srv.checker = func(r *http.Request) { 855 c.Check(r.URL.Path, check.Equals, "/v2/snaps") 856 form := testForm(r, c) 857 defer form.RemoveAll() 858 859 c.Check(form.Value["action"], check.DeepEquals, []string{"install"}) 860 c.Check(form.Value["classic"], check.DeepEquals, []string{"true"}) 861 c.Check(form.Value["snap-path"], check.NotNil) 862 c.Check(form.Value, check.HasLen, 3) 863 864 name, _, body := formFile(form, c) 865 c.Check(name, check.Equals, "snap") 866 c.Check(string(body), check.Equals, "snap-data") 867 868 s.srv.confinement = "classic" 869 } 870 871 snapBody := []byte("snap-data") 872 s.RedirectClientToTestServer(s.srv.handle) 873 snapPath := filepath.Join(c.MkDir(), "foo.snap") 874 err := ioutil.WriteFile(snapPath, snapBody, 0644) 875 c.Assert(err, check.IsNil) 876 877 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--classic", snapPath}) 878 c.Assert(err, check.IsNil) 879 c.Assert(rest, check.DeepEquals, []string{}) 880 c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`) 881 c.Check(s.Stderr(), check.Equals, "") 882 // ensure that the fake server api was actually hit 883 c.Check(s.srv.n, check.Equals, s.srv.total) 884 } 885 886 func (s *SnapOpSuite) TestInstallPathDangerous(c *check.C) { 887 s.srv.checker = func(r *http.Request) { 888 c.Check(r.URL.Path, check.Equals, "/v2/snaps") 889 form := testForm(r, c) 890 defer form.RemoveAll() 891 892 c.Check(form.Value["action"], check.DeepEquals, []string{"install"}) 893 c.Check(form.Value["dangerous"], check.DeepEquals, []string{"true"}) 894 c.Check(form.Value["snap-path"], check.NotNil) 895 c.Check(form.Value, check.HasLen, 3) 896 897 name, _, body := formFile(form, c) 898 c.Check(name, check.Equals, "snap") 899 c.Check(string(body), check.Equals, "snap-data") 900 } 901 902 snapBody := []byte("snap-data") 903 s.RedirectClientToTestServer(s.srv.handle) 904 snapPath := filepath.Join(c.MkDir(), "foo.snap") 905 err := ioutil.WriteFile(snapPath, snapBody, 0644) 906 c.Assert(err, check.IsNil) 907 908 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--dangerous", snapPath}) 909 c.Assert(err, check.IsNil) 910 c.Assert(rest, check.DeepEquals, []string{}) 911 c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`) 912 c.Check(s.Stderr(), check.Equals, "") 913 // ensure that the fake server api was actually hit 914 c.Check(s.srv.n, check.Equals, s.srv.total) 915 } 916 917 func (s *SnapOpSuite) TestInstallPathInstance(c *check.C) { 918 s.srv.checker = func(r *http.Request) { 919 c.Check(r.URL.Path, check.Equals, "/v2/snaps") 920 921 form := testForm(r, c) 922 defer form.RemoveAll() 923 924 c.Check(form.Value["action"], check.DeepEquals, []string{"install"}) 925 c.Check(form.Value["name"], check.DeepEquals, []string{"foo_bar"}) 926 c.Check(form.Value["devmode"], check.IsNil) 927 c.Check(form.Value["snap-path"], check.NotNil) 928 c.Check(form.Value, check.HasLen, 3) 929 930 name, _, body := formFile(form, c) 931 c.Check(name, check.Equals, "snap") 932 c.Check(string(body), check.Equals, "snap-data") 933 } 934 935 snapBody := []byte("snap-data") 936 s.RedirectClientToTestServer(s.srv.handle) 937 // instance is named foo_bar 938 s.srv.snap = "foo_bar" 939 snapPath := filepath.Join(c.MkDir(), "foo.snap") 940 err := ioutil.WriteFile(snapPath, snapBody, 0644) 941 c.Assert(err, check.IsNil) 942 943 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", snapPath, "--name", "foo_bar"}) 944 c.Assert(rest, check.DeepEquals, []string{}) 945 c.Assert(err, check.IsNil) 946 c.Check(s.Stdout(), check.Matches, `(?sm).*foo_bar 1.0 from Bar installed`) 947 c.Check(s.Stderr(), check.Equals, "") 948 // ensure that the fake server api was actually hit 949 c.Check(s.srv.n, check.Equals, s.srv.total) 950 } 951 952 func (s *SnapSuite) TestInstallWithInstanceNoPath(c *check.C) { 953 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--name", "foo_bar", "some-snap"}) 954 c.Assert(err, check.ErrorMatches, "cannot use explicit name when installing from store") 955 } 956 957 func (s *SnapSuite) TestInstallManyWithInstance(c *check.C) { 958 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--name", "foo_bar", "some-snap-1", "some-snap-2"}) 959 c.Assert(err, check.ErrorMatches, "cannot use instance name when installing multiple snaps") 960 } 961 962 func (s *SnapOpSuite) TestRevertRunthrough(c *check.C) { 963 s.srv.total = 4 964 s.srv.channel = "potato" 965 s.srv.checker = func(r *http.Request) { 966 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 967 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 968 "action": "revert", 969 }) 970 } 971 972 s.RedirectClientToTestServer(s.srv.handle) 973 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"revert", "foo"}) 974 c.Assert(err, check.IsNil) 975 c.Assert(rest, check.DeepEquals, []string{}) 976 // tracking channel is "" in the test server 977 c.Check(s.Stdout(), check.Equals, `foo reverted to 1.0 978 `) 979 c.Check(s.Stderr(), check.Equals, "") 980 // ensure that the fake server api was actually hit 981 c.Check(s.srv.n, check.Equals, s.srv.total) 982 } 983 984 func (s *SnapOpSuite) runRevertTest(c *check.C, opts *client.SnapOptions) { 985 modes := []struct { 986 enabled bool 987 name string 988 }{ 989 {opts.DevMode, "devmode"}, 990 {opts.JailMode, "jailmode"}, 991 {opts.Classic, "classic"}, 992 } 993 994 s.srv.checker = func(r *http.Request) { 995 996 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 997 d := DecodedRequestBody(c, r) 998 999 n := 1 1000 c.Check(d["action"], check.Equals, "revert") 1001 1002 for _, mode := range modes { 1003 if mode.enabled { 1004 n++ 1005 c.Check(d[mode.name], check.Equals, true) 1006 } else { 1007 c.Check(d[mode.name], check.IsNil) 1008 } 1009 } 1010 c.Check(d, check.HasLen, n) 1011 } 1012 1013 s.RedirectClientToTestServer(s.srv.handle) 1014 1015 cmd := []string{"revert", "foo"} 1016 for _, mode := range modes { 1017 if mode.enabled { 1018 cmd = append(cmd, "--"+mode.name) 1019 } 1020 } 1021 1022 rest, err := snap.Parser(snap.Client()).ParseArgs(cmd) 1023 c.Assert(err, check.IsNil) 1024 c.Assert(rest, check.DeepEquals, []string{}) 1025 c.Check(s.Stdout(), check.Equals, "foo reverted to 1.0\n") 1026 c.Check(s.Stderr(), check.Equals, "") 1027 // ensure that the fake server api was actually hit 1028 c.Check(s.srv.n, check.Equals, s.srv.total) 1029 } 1030 1031 func (s *SnapOpSuite) TestRevertNoMode(c *check.C) { 1032 s.runRevertTest(c, &client.SnapOptions{}) 1033 } 1034 1035 func (s *SnapOpSuite) TestRevertDevMode(c *check.C) { 1036 s.runRevertTest(c, &client.SnapOptions{DevMode: true}) 1037 } 1038 1039 func (s *SnapOpSuite) TestRevertJailMode(c *check.C) { 1040 s.runRevertTest(c, &client.SnapOptions{JailMode: true}) 1041 } 1042 1043 func (s *SnapOpSuite) TestRevertClassic(c *check.C) { 1044 s.runRevertTest(c, &client.SnapOptions{Classic: true}) 1045 } 1046 1047 func (s *SnapOpSuite) TestRevertMissingName(c *check.C) { 1048 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"revert"}) 1049 c.Assert(err, check.NotNil) 1050 c.Assert(err, check.ErrorMatches, "the required argument `<snap>` was not provided") 1051 } 1052 1053 func (s *SnapSuite) TestRefreshListLessOptions(c *check.C) { 1054 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1055 c.Fatal("expected to get 0 requests") 1056 }) 1057 1058 for _, flag := range []string{"--beta", "--channel=potato", "--classic"} { 1059 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--list", flag}) 1060 c.Assert(err, check.ErrorMatches, "--list does not accept additional arguments") 1061 1062 _, err = snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--list", flag, "some-snap"}) 1063 c.Assert(err, check.ErrorMatches, "--list does not accept additional arguments") 1064 } 1065 } 1066 1067 func (s *SnapSuite) TestRefreshList(c *check.C) { 1068 n := 0 1069 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1070 switch n { 1071 case 0: 1072 c.Check(r.Method, check.Equals, "GET") 1073 c.Check(r.URL.Path, check.Equals, "/v2/find") 1074 c.Check(r.URL.Query().Get("select"), check.Equals, "refresh") 1075 fmt.Fprintln(w, `{"type": "sync", "result": [{"name": "foo", "status": "active", "version": "4.2update1", "developer": "bar", "publisher": {"id": "bar-id", "username": "bar", "display-name": "Bar", "validation": "unproven"}, "revision":17,"summary":"some summary"}]}`) 1076 default: 1077 c.Fatalf("expected to get 1 requests, now on %d", n+1) 1078 } 1079 1080 n++ 1081 }) 1082 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--list"}) 1083 c.Assert(err, check.IsNil) 1084 c.Assert(rest, check.DeepEquals, []string{}) 1085 c.Check(s.Stdout(), check.Matches, `Name +Version +Rev +Publisher +Notes 1086 foo +4.2update1 +17 +bar +-.* 1087 `) 1088 c.Check(s.Stderr(), check.Equals, "") 1089 // ensure that the fake server api was actually hit 1090 c.Check(n, check.Equals, 1) 1091 } 1092 1093 func (s *SnapSuite) TestRefreshLegacyTime(c *check.C) { 1094 n := 0 1095 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1096 switch n { 1097 case 0: 1098 c.Check(r.Method, check.Equals, "GET") 1099 c.Check(r.URL.Path, check.Equals, "/v2/system-info") 1100 fmt.Fprintln(w, `{"type": "sync", "status-code": 200, "result": {"refresh": {"schedule": "00:00-04:59/5:00-10:59/11:00-16:59/17:00-23:59", "last": "2017-04-25T17:35:00+02:00", "next": "2017-04-26T00:58:00+02:00"}}}`) 1101 default: 1102 c.Fatalf("expected to get 1 requests, now on %d", n+1) 1103 } 1104 1105 n++ 1106 }) 1107 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--time", "--abs-time"}) 1108 c.Assert(err, check.IsNil) 1109 c.Assert(rest, check.DeepEquals, []string{}) 1110 c.Check(s.Stdout(), check.Equals, `schedule: 00:00-04:59/5:00-10:59/11:00-16:59/17:00-23:59 1111 last: 2017-04-25T17:35:00+02:00 1112 next: 2017-04-26T00:58:00+02:00 1113 `) 1114 c.Check(s.Stderr(), check.Equals, "") 1115 // ensure that the fake server api was actually hit 1116 c.Check(n, check.Equals, 1) 1117 } 1118 1119 func (s *SnapSuite) TestRefreshTimer(c *check.C) { 1120 n := 0 1121 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1122 switch n { 1123 case 0: 1124 c.Check(r.Method, check.Equals, "GET") 1125 c.Check(r.URL.Path, check.Equals, "/v2/system-info") 1126 fmt.Fprintln(w, `{"type": "sync", "status-code": 200, "result": {"refresh": {"timer": "0:00-24:00/4", "last": "2017-04-25T17:35:00+02:00", "next": "2017-04-26T00:58:00+02:00"}}}`) 1127 default: 1128 c.Fatalf("expected to get 1 requests, now on %d", n+1) 1129 } 1130 1131 n++ 1132 }) 1133 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--time", "--abs-time"}) 1134 c.Assert(err, check.IsNil) 1135 c.Assert(rest, check.DeepEquals, []string{}) 1136 c.Check(s.Stdout(), check.Equals, `timer: 0:00-24:00/4 1137 last: 2017-04-25T17:35:00+02:00 1138 next: 2017-04-26T00:58:00+02:00 1139 `) 1140 c.Check(s.Stderr(), check.Equals, "") 1141 // ensure that the fake server api was actually hit 1142 c.Check(n, check.Equals, 1) 1143 } 1144 1145 func (s *SnapSuite) TestRefreshHold(c *check.C) { 1146 n := 0 1147 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1148 switch n { 1149 case 0: 1150 c.Check(r.Method, check.Equals, "GET") 1151 c.Check(r.URL.Path, check.Equals, "/v2/system-info") 1152 fmt.Fprintln(w, `{"type": "sync", "status-code": 200, "result": {"refresh": {"timer": "0:00-24:00/4", "last": "2017-04-25T17:35:00+02:00", "next": "2017-04-26T00:58:00+02:00", "hold": "2017-04-28T00:00:00+02:00"}}}`) 1153 default: 1154 c.Fatalf("expected to get 1 requests, now on %d", n+1) 1155 } 1156 1157 n++ 1158 }) 1159 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--time", "--abs-time"}) 1160 c.Assert(err, check.IsNil) 1161 c.Assert(rest, check.DeepEquals, []string{}) 1162 c.Check(s.Stdout(), check.Equals, `timer: 0:00-24:00/4 1163 last: 2017-04-25T17:35:00+02:00 1164 hold: 2017-04-28T00:00:00+02:00 1165 next: 2017-04-26T00:58:00+02:00 (but held) 1166 `) 1167 c.Check(s.Stderr(), check.Equals, "") 1168 // ensure that the fake server api was actually hit 1169 c.Check(n, check.Equals, 1) 1170 } 1171 1172 func (s *SnapSuite) TestRefreshNoTimerNoSchedule(c *check.C) { 1173 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1174 c.Check(r.Method, check.Equals, "GET") 1175 c.Check(r.URL.Path, check.Equals, "/v2/system-info") 1176 fmt.Fprintln(w, `{"type": "sync", "status-code": 200, "result": {"refresh": {"last": "2017-04-25T17:35:00+0200", "next": "2017-04-26T00:58:00+0200"}}}`) 1177 }) 1178 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--time"}) 1179 c.Assert(err, check.ErrorMatches, `internal error: both refresh.timer and refresh.schedule are empty`) 1180 } 1181 1182 func (s *SnapOpSuite) TestRefreshOne(c *check.C) { 1183 s.RedirectClientToTestServer(s.srv.handle) 1184 s.srv.checker = func(r *http.Request) { 1185 c.Check(r.Method, check.Equals, "POST") 1186 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 1187 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1188 "action": "refresh", 1189 }) 1190 } 1191 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "foo"}) 1192 c.Assert(err, check.IsNil) 1193 c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar refreshed`) 1194 1195 } 1196 1197 func (s *SnapOpSuite) TestRefreshOneSwitchChannel(c *check.C) { 1198 s.RedirectClientToTestServer(s.srv.handle) 1199 s.srv.checker = func(r *http.Request) { 1200 c.Check(r.Method, check.Equals, "POST") 1201 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 1202 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1203 "action": "refresh", 1204 "channel": "beta", 1205 }) 1206 s.srv.channel = "beta" 1207 } 1208 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta", "foo"}) 1209 c.Assert(err, check.IsNil) 1210 c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(beta\) 1.0 from Bar refreshed`) 1211 } 1212 1213 func (s *SnapOpSuite) TestRefreshOneSwitchCohort(c *check.C) { 1214 s.RedirectClientToTestServer(s.srv.handle) 1215 s.srv.checker = func(r *http.Request) { 1216 c.Check(r.Method, check.Equals, "POST") 1217 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 1218 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1219 "action": "refresh", 1220 "cohort-key": "what", 1221 }) 1222 } 1223 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--cohort=what", "foo"}) 1224 c.Assert(err, check.IsNil) 1225 c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar refreshed`) 1226 } 1227 1228 func (s *SnapOpSuite) TestRefreshOneLeaveCohort(c *check.C) { 1229 s.RedirectClientToTestServer(s.srv.handle) 1230 s.srv.checker = func(r *http.Request) { 1231 c.Check(r.Method, check.Equals, "POST") 1232 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 1233 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1234 "action": "refresh", 1235 "leave-cohort": true, 1236 }) 1237 } 1238 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--leave-cohort", "foo"}) 1239 c.Assert(err, check.IsNil) 1240 c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar refreshed`) 1241 } 1242 1243 func (s *SnapOpSuite) TestRefreshOneWithPinnedTrack(c *check.C) { 1244 s.RedirectClientToTestServer(s.srv.handle) 1245 s.srv.checker = func(r *http.Request) { 1246 c.Check(r.Method, check.Equals, "POST") 1247 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 1248 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1249 "action": "refresh", 1250 "channel": "stable", 1251 }) 1252 s.srv.channel = "18/stable" 1253 s.srv.trackingChannel = "18/stable" 1254 } 1255 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--stable", "foo"}) 1256 c.Assert(err, check.IsNil) 1257 c.Check(s.Stdout(), check.Equals, "foo (18/stable) 1.0 from Bar refreshed\n") 1258 } 1259 1260 func (s *SnapOpSuite) TestRefreshOneClassic(c *check.C) { 1261 s.RedirectClientToTestServer(s.srv.handle) 1262 s.srv.checker = func(r *http.Request) { 1263 c.Check(r.Method, check.Equals, "POST") 1264 c.Check(r.URL.Path, check.Equals, "/v2/snaps/one") 1265 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1266 "action": "refresh", 1267 "classic": true, 1268 }) 1269 } 1270 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--classic", "one"}) 1271 c.Assert(err, check.IsNil) 1272 } 1273 1274 func (s *SnapOpSuite) TestRefreshOneDevmode(c *check.C) { 1275 s.RedirectClientToTestServer(s.srv.handle) 1276 s.srv.checker = func(r *http.Request) { 1277 c.Check(r.Method, check.Equals, "POST") 1278 c.Check(r.URL.Path, check.Equals, "/v2/snaps/one") 1279 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1280 "action": "refresh", 1281 "devmode": true, 1282 }) 1283 } 1284 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--devmode", "one"}) 1285 c.Assert(err, check.IsNil) 1286 } 1287 1288 func (s *SnapOpSuite) TestRefreshOneJailmode(c *check.C) { 1289 s.RedirectClientToTestServer(s.srv.handle) 1290 s.srv.checker = func(r *http.Request) { 1291 c.Check(r.Method, check.Equals, "POST") 1292 c.Check(r.URL.Path, check.Equals, "/v2/snaps/one") 1293 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1294 "action": "refresh", 1295 "jailmode": true, 1296 }) 1297 } 1298 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--jailmode", "one"}) 1299 c.Assert(err, check.IsNil) 1300 } 1301 1302 func (s *SnapOpSuite) TestRefreshOneIgnoreValidation(c *check.C) { 1303 s.RedirectClientToTestServer(s.srv.handle) 1304 s.srv.checker = func(r *http.Request) { 1305 c.Check(r.Method, check.Equals, "POST") 1306 c.Check(r.URL.Path, check.Equals, "/v2/snaps/one") 1307 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1308 "action": "refresh", 1309 "ignore-validation": true, 1310 }) 1311 } 1312 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--ignore-validation", "one"}) 1313 c.Assert(err, check.IsNil) 1314 } 1315 1316 func (s *SnapOpSuite) TestRefreshOneRebooting(c *check.C) { 1317 s.RedirectClientToTestServer(s.srv.handle) 1318 s.srv.checker = func(r *http.Request) { 1319 c.Check(r.Method, check.Equals, "POST") 1320 c.Check(r.URL.Path, check.Equals, "/v2/snaps/core") 1321 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1322 "action": "refresh", 1323 }) 1324 } 1325 s.srv.restart = "reboot" 1326 1327 restore := mockArgs("snap", "refresh", "core") 1328 defer restore() 1329 1330 err := snap.RunMain() 1331 c.Check(err, check.IsNil) 1332 c.Check(s.Stderr(), check.Equals, "snapd is about to reboot the system\n") 1333 } 1334 1335 func (s *SnapOpSuite) TestRefreshOneHalting(c *check.C) { 1336 s.RedirectClientToTestServer(s.srv.handle) 1337 s.srv.checker = func(r *http.Request) { 1338 c.Check(r.Method, check.Equals, "POST") 1339 c.Check(r.URL.Path, check.Equals, "/v2/snaps/core") 1340 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1341 "action": "refresh", 1342 }) 1343 } 1344 s.srv.restart = "halt" 1345 1346 restore := mockArgs("snap", "refresh", "core") 1347 defer restore() 1348 1349 err := snap.RunMain() 1350 c.Check(err, check.IsNil) 1351 c.Check(s.Stderr(), check.Equals, "snapd is about to halt the system\n") 1352 } 1353 1354 func (s *SnapOpSuite) TestRefreshOnePoweringOff(c *check.C) { 1355 s.RedirectClientToTestServer(s.srv.handle) 1356 s.srv.checker = func(r *http.Request) { 1357 c.Check(r.Method, check.Equals, "POST") 1358 c.Check(r.URL.Path, check.Equals, "/v2/snaps/core") 1359 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1360 "action": "refresh", 1361 }) 1362 } 1363 s.srv.restart = "poweroff" 1364 1365 restore := mockArgs("snap", "refresh", "core") 1366 defer restore() 1367 1368 err := snap.RunMain() 1369 c.Check(err, check.IsNil) 1370 c.Check(s.Stderr(), check.Equals, "snapd is about to power off the system\n") 1371 } 1372 1373 func (s *SnapOpSuite) TestRefreshOneChanDeprecated(c *check.C) { 1374 var in, out string 1375 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1376 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{"action": "refresh", "channel": out}) 1377 fmt.Fprintln(w, `{"type": "error", "result": {"message": "snap not found", "value": "foo", "kind": "snap-not-found"}, "status-code": 404}`) 1378 }) 1379 1380 for in, out = range map[string]string{ 1381 "/foo": "foo/stable", 1382 "/stable": "latest/stable", 1383 "///foo/stable//": "foo/stable", 1384 } { 1385 s.stderr.Reset() 1386 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--channel=" + in, "one"}) 1387 c.Assert(err, check.ErrorMatches, "snap \"one\" not found") 1388 c.Check(s.Stderr(), testutil.EqualsWrapped, `Warning: Specifying a channel "`+in+`" is relying on undefined behaviour. Interpreting it as "`+out+`" for now, but this will be an error later.`) 1389 } 1390 } 1391 1392 func (s *SnapOpSuite) TestRefreshOneModeErr(c *check.C) { 1393 s.RedirectClientToTestServer(nil) 1394 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--jailmode", "--devmode", "one"}) 1395 c.Assert(err, check.ErrorMatches, `cannot use devmode and jailmode flags together`) 1396 } 1397 1398 func (s *SnapOpSuite) TestRefreshOneChanErr(c *check.C) { 1399 s.RedirectClientToTestServer(nil) 1400 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta", "--channel=foo", "one"}) 1401 c.Assert(err, check.ErrorMatches, `Please specify a single channel`) 1402 } 1403 1404 func (s *SnapOpSuite) TestRefreshAllChannel(c *check.C) { 1405 s.RedirectClientToTestServer(nil) 1406 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta"}) 1407 c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`) 1408 } 1409 1410 func (s *SnapOpSuite) TestRefreshManyChannel(c *check.C) { 1411 s.RedirectClientToTestServer(nil) 1412 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta", "one", "two"}) 1413 c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`) 1414 } 1415 1416 func (s *SnapOpSuite) TestRefreshManyIgnoreValidation(c *check.C) { 1417 s.RedirectClientToTestServer(nil) 1418 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--ignore-validation", "one", "two"}) 1419 c.Assert(err, check.ErrorMatches, `a single snap name must be specified when ignoring validation`) 1420 } 1421 1422 func (s *SnapOpSuite) TestRefreshAllModeFlags(c *check.C) { 1423 s.RedirectClientToTestServer(nil) 1424 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--devmode"}) 1425 c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`) 1426 } 1427 1428 func (s *SnapOpSuite) TestRefreshOneAmend(c *check.C) { 1429 s.RedirectClientToTestServer(s.srv.handle) 1430 s.srv.checker = func(r *http.Request) { 1431 c.Check(r.Method, check.Equals, "POST") 1432 c.Check(r.URL.Path, check.Equals, "/v2/snaps/one") 1433 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1434 "action": "refresh", 1435 "amend": true, 1436 }) 1437 } 1438 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--amend", "one"}) 1439 c.Assert(err, check.IsNil) 1440 } 1441 1442 func (s *SnapOpSuite) runTryTest(c *check.C, opts *client.SnapOptions) { 1443 // pass relative path to cmd 1444 tryDir := "some-dir" 1445 1446 modes := []struct { 1447 enabled bool 1448 name string 1449 }{ 1450 {opts.DevMode, "devmode"}, 1451 {opts.JailMode, "jailmode"}, 1452 {opts.Classic, "classic"}, 1453 } 1454 1455 s.srv.checker = func(r *http.Request) { 1456 // ensure the client always sends the absolute path 1457 fullTryDir, err := filepath.Abs(tryDir) 1458 c.Assert(err, check.IsNil) 1459 1460 c.Check(r.URL.Path, check.Equals, "/v2/snaps") 1461 form := testForm(r, c) 1462 defer form.RemoveAll() 1463 1464 c.Assert(form.Value["action"], check.HasLen, 1) 1465 c.Assert(form.Value["snap-path"], check.HasLen, 1) 1466 c.Check(form.File, check.HasLen, 0) 1467 c.Check(form.Value["action"][0], check.Equals, "try") 1468 c.Check(form.Value["snap-path"][0], check.Matches, regexp.QuoteMeta(fullTryDir)) 1469 1470 for _, mode := range modes { 1471 if mode.enabled { 1472 c.Assert(form.Value[mode.name], check.HasLen, 1) 1473 c.Check(form.Value[mode.name][0], check.Equals, "true") 1474 } else { 1475 c.Check(form.Value[mode.name], check.IsNil) 1476 } 1477 } 1478 } 1479 1480 s.RedirectClientToTestServer(s.srv.handle) 1481 1482 cmd := []string{"try", tryDir} 1483 for _, mode := range modes { 1484 if mode.enabled { 1485 cmd = append(cmd, "--"+mode.name) 1486 } 1487 } 1488 1489 rest, err := snap.Parser(snap.Client()).ParseArgs(cmd) 1490 c.Assert(err, check.IsNil) 1491 c.Assert(rest, check.DeepEquals, []string{}) 1492 c.Check(s.Stdout(), check.Matches, fmt.Sprintf(`(?sm).*foo 1.0 mounted from .*%s`, tryDir)) 1493 c.Check(s.Stderr(), check.Equals, "") 1494 // ensure that the fake server api was actually hit 1495 c.Check(s.srv.n, check.Equals, s.srv.total) 1496 } 1497 1498 func (s *SnapOpSuite) TestTryNoMode(c *check.C) { 1499 s.runTryTest(c, &client.SnapOptions{}) 1500 } 1501 1502 func (s *SnapOpSuite) TestTryDevMode(c *check.C) { 1503 s.runTryTest(c, &client.SnapOptions{DevMode: true}) 1504 } 1505 1506 func (s *SnapOpSuite) TestTryJailMode(c *check.C) { 1507 s.runTryTest(c, &client.SnapOptions{JailMode: true}) 1508 } 1509 1510 func (s *SnapOpSuite) TestTryClassic(c *check.C) { 1511 s.runTryTest(c, &client.SnapOptions{Classic: true}) 1512 } 1513 1514 func (s *SnapOpSuite) TestTryNoSnapDirErrors(c *check.C) { 1515 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1516 c.Check(r.Method, check.Equals, "POST") 1517 w.WriteHeader(202) 1518 fmt.Fprintln(w, ` 1519 { 1520 "type": "error", 1521 "result": { 1522 "message":"error from server", 1523 "kind":"snap-not-a-snap" 1524 }, 1525 "status-code": 400 1526 }`) 1527 }) 1528 1529 cmd := []string{"try", "/"} 1530 _, err := snap.Parser(snap.Client()).ParseArgs(cmd) 1531 c.Assert(err, testutil.EqualsWrapped, ` 1532 "/" does not contain an unpacked snap. 1533 1534 Try 'snapcraft prime' in your project directory, then 'snap try' again.`) 1535 } 1536 1537 func (s *SnapOpSuite) TestTryMissingOpt(c *check.C) { 1538 oldArgs := os.Args 1539 defer func() { 1540 os.Args = oldArgs 1541 }() 1542 os.Args = []string{"snap", "try", "./"} 1543 var kind string 1544 1545 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1546 c.Check(r.Method, check.Equals, "POST", check.Commentf("%q", kind)) 1547 w.WriteHeader(400) 1548 fmt.Fprintf(w, ` 1549 { 1550 "type": "error", 1551 "result": { 1552 "message":"error from server", 1553 "value": "some-snap", 1554 "kind": %q 1555 }, 1556 "status-code": 400 1557 }`, kind) 1558 }) 1559 1560 type table struct { 1561 kind, expected string 1562 } 1563 1564 tests := []table{ 1565 {"snap-needs-classic", "published using classic confinement"}, 1566 {"snap-needs-devmode", "only meant for development"}, 1567 } 1568 1569 for _, test := range tests { 1570 kind = test.kind 1571 c.Check(snap.RunMain(), testutil.ContainsWrapped, test.expected, check.Commentf("%q", kind)) 1572 } 1573 } 1574 1575 func (s *SnapOpSuite) TestInstallConfinedAsClassic(c *check.C) { 1576 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1577 c.Check(r.Method, check.Equals, "POST") 1578 w.WriteHeader(400) 1579 fmt.Fprintf(w, `{ 1580 "type": "error", 1581 "result": { 1582 "message":"error from server", 1583 "value": "some-snap", 1584 "kind": "snap-not-classic" 1585 }, 1586 "status-code": 400 1587 }`) 1588 }) 1589 1590 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--classic", "some-snap"}) 1591 c.Assert(err, check.ErrorMatches, `snap "some-snap" is not compatible with --classic`) 1592 } 1593 1594 func (s *SnapSuite) TestInstallChannelDuplicationError(c *check.C) { 1595 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--edge", "--beta", "some-snap"}) 1596 c.Assert(err, check.ErrorMatches, "Please specify a single channel") 1597 } 1598 1599 func (s *SnapSuite) TestRefreshChannelDuplicationError(c *check.C) { 1600 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--edge", "--beta", "some-snap"}) 1601 c.Assert(err, check.ErrorMatches, "Please specify a single channel") 1602 } 1603 1604 func (s *SnapOpSuite) TestInstallFromChannel(c *check.C) { 1605 s.srv.checker = func(r *http.Request) { 1606 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 1607 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1608 "action": "install", 1609 "channel": "edge", 1610 }) 1611 s.srv.channel = "edge" 1612 } 1613 1614 s.RedirectClientToTestServer(s.srv.handle) 1615 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--edge", "foo"}) 1616 c.Assert(err, check.IsNil) 1617 c.Assert(rest, check.DeepEquals, []string{}) 1618 c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(edge\) 1.0 from Bar installed`) 1619 c.Check(s.Stderr(), check.Equals, "") 1620 // ensure that the fake server api was actually hit 1621 c.Check(s.srv.n, check.Equals, s.srv.total) 1622 } 1623 1624 func (s *SnapOpSuite) TestEnable(c *check.C) { 1625 s.srv.total = 3 1626 s.srv.checker = func(r *http.Request) { 1627 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 1628 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1629 "action": "enable", 1630 }) 1631 } 1632 1633 s.RedirectClientToTestServer(s.srv.handle) 1634 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"enable", "foo"}) 1635 c.Assert(err, check.IsNil) 1636 c.Assert(rest, check.DeepEquals, []string{}) 1637 c.Check(s.Stdout(), check.Matches, `(?sm).*foo enabled`) 1638 c.Check(s.Stderr(), check.Equals, "") 1639 // ensure that the fake server api was actually hit 1640 c.Check(s.srv.n, check.Equals, s.srv.total) 1641 } 1642 1643 func (s *SnapOpSuite) TestDisable(c *check.C) { 1644 s.srv.total = 3 1645 s.srv.checker = func(r *http.Request) { 1646 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 1647 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1648 "action": "disable", 1649 }) 1650 } 1651 1652 s.RedirectClientToTestServer(s.srv.handle) 1653 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"disable", "foo"}) 1654 c.Assert(err, check.IsNil) 1655 c.Assert(rest, check.DeepEquals, []string{}) 1656 c.Check(s.Stdout(), check.Matches, `(?sm).*foo disabled`) 1657 c.Check(s.Stderr(), check.Equals, "") 1658 // ensure that the fake server api was actually hit 1659 c.Check(s.srv.n, check.Equals, s.srv.total) 1660 } 1661 1662 func (s *SnapOpSuite) TestRemove(c *check.C) { 1663 s.srv.total = 3 1664 s.srv.checker = func(r *http.Request) { 1665 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 1666 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1667 "action": "remove", 1668 }) 1669 } 1670 1671 s.RedirectClientToTestServer(s.srv.handle) 1672 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "foo"}) 1673 c.Assert(err, check.IsNil) 1674 c.Assert(rest, check.DeepEquals, []string{}) 1675 c.Check(s.Stdout(), check.Matches, `(?sm).*foo removed`) 1676 c.Check(s.Stderr(), check.Equals, "") 1677 // ensure that the fake server api was actually hit 1678 c.Check(s.srv.n, check.Equals, s.srv.total) 1679 } 1680 1681 func (s *SnapOpSuite) TestRemoveWithPurge(c *check.C) { 1682 s.srv.total = 3 1683 s.srv.checker = func(r *http.Request) { 1684 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 1685 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1686 "action": "remove", 1687 "purge": true, 1688 }) 1689 } 1690 1691 s.RedirectClientToTestServer(s.srv.handle) 1692 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "--purge", "foo"}) 1693 c.Assert(err, check.IsNil) 1694 c.Assert(rest, check.DeepEquals, []string{}) 1695 c.Check(s.Stdout(), check.Matches, `(?sm).*foo removed`) 1696 c.Check(s.Stderr(), check.Equals, "") 1697 // ensure that the fake server api was actually hit 1698 c.Check(s.srv.n, check.Equals, s.srv.total) 1699 } 1700 1701 func (s *SnapOpSuite) TestRemoveInsufficientDiskSpace(c *check.C) { 1702 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1703 fmt.Fprintln(w, `{ 1704 "type": "error", 1705 "result": { 1706 "message": "disk space error", 1707 "kind": "insufficient-disk-space", 1708 "value": { 1709 "snap-names": ["foo", "bar"], 1710 "change-kind": "remove" 1711 }, 1712 "status-code": 507 1713 }}`) 1714 }) 1715 1716 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "foo"}) 1717 c.Check(err, check.ErrorMatches, `(?sm)cannot remove "foo", "bar" due to low disk space for automatic snapshot,.*use --purge to avoid creating a snapshot`) 1718 c.Check(s.Stdout(), check.Equals, "") 1719 c.Check(s.Stderr(), check.Equals, "") 1720 } 1721 1722 func (s *SnapOpSuite) TestInstallInsufficientDiskSpace(c *check.C) { 1723 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1724 fmt.Fprintln(w, `{ 1725 "type": "error", 1726 "result": { 1727 "message": "disk space error", 1728 "kind": "insufficient-disk-space", 1729 "value": { 1730 "snap-names": ["foo"], 1731 "change-kind": "install" 1732 }, 1733 "status-code": 507 1734 }}`) 1735 }) 1736 1737 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"}) 1738 c.Check(err, check.ErrorMatches, `cannot install "foo" due to low disk space`) 1739 c.Check(s.Stdout(), check.Equals, "") 1740 c.Check(s.Stderr(), check.Equals, "") 1741 } 1742 1743 func (s *SnapOpSuite) TestRefreshInsufficientDiskSpace(c *check.C) { 1744 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1745 fmt.Fprintln(w, `{ 1746 "type": "error", 1747 "result": { 1748 "message": "disk space error", 1749 "kind": "insufficient-disk-space", 1750 "value": { 1751 "snap-names": ["foo"], 1752 "change-kind": "refresh" 1753 }, 1754 "status-code": 507 1755 }}`) 1756 }) 1757 1758 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "foo"}) 1759 c.Check(err, check.ErrorMatches, `cannot refresh "foo" due to low disk space`) 1760 c.Check(s.Stdout(), check.Equals, "") 1761 c.Check(s.Stderr(), check.Equals, "") 1762 } 1763 1764 func (s *SnapOpSuite) TestRemoveRevision(c *check.C) { 1765 s.srv.total = 3 1766 s.srv.checker = func(r *http.Request) { 1767 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 1768 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1769 "action": "remove", 1770 "revision": "17", 1771 }) 1772 } 1773 1774 s.RedirectClientToTestServer(s.srv.handle) 1775 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "--revision=17", "foo"}) 1776 c.Assert(err, check.IsNil) 1777 c.Assert(rest, check.DeepEquals, []string{}) 1778 c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(revision 17\) removed`) 1779 c.Check(s.Stderr(), check.Equals, "") 1780 // ensure that the fake server api was actually hit 1781 c.Check(s.srv.n, check.Equals, s.srv.total) 1782 } 1783 1784 func (s *SnapOpSuite) TestRemoveManyOptions(c *check.C) { 1785 s.RedirectClientToTestServer(nil) 1786 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "--revision=17", "one", "two"}) 1787 c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify options`) 1788 _, err = snap.Parser(snap.Client()).ParseArgs([]string{"remove", "--purge", "one", "two"}) 1789 c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify options`) 1790 } 1791 1792 func (s *SnapOpSuite) TestRemoveMany(c *check.C) { 1793 total := 3 1794 n := 0 1795 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1796 switch n { 1797 case 0: 1798 c.Check(r.URL.Path, check.Equals, "/v2/snaps") 1799 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1800 "action": "remove", 1801 "snaps": []interface{}{"one", "two"}, 1802 }) 1803 1804 c.Check(r.Method, check.Equals, "POST") 1805 w.WriteHeader(202) 1806 fmt.Fprintln(w, `{"type":"async", "change": "42", "status-code": 202}`) 1807 case 1: 1808 c.Check(r.Method, check.Equals, "GET") 1809 c.Check(r.URL.Path, check.Equals, "/v2/changes/42") 1810 fmt.Fprintln(w, `{"type": "sync", "result": {"status": "Doing"}}`) 1811 case 2: 1812 c.Check(r.Method, check.Equals, "GET") 1813 c.Check(r.URL.Path, check.Equals, "/v2/changes/42") 1814 fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done", "data": {"snap-names": ["one","two"]}}}`) 1815 default: 1816 c.Fatalf("expected to get %d requests, now on %d", total, n+1) 1817 } 1818 1819 n++ 1820 }) 1821 1822 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "one", "two"}) 1823 c.Assert(err, check.IsNil) 1824 c.Assert(rest, check.DeepEquals, []string{}) 1825 c.Check(s.Stdout(), check.Matches, `(?sm).*one removed`) 1826 c.Check(s.Stdout(), check.Matches, `(?sm).*two removed`) 1827 c.Check(s.Stderr(), check.Equals, "") 1828 // ensure that the fake server api was actually hit 1829 c.Check(n, check.Equals, total) 1830 } 1831 1832 func (s *SnapOpSuite) TestInstallManyChannel(c *check.C) { 1833 s.RedirectClientToTestServer(nil) 1834 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--beta", "one", "two"}) 1835 c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`) 1836 } 1837 1838 func (s *SnapOpSuite) TestInstallManyMixFileAndStore(c *check.C) { 1839 s.RedirectClientToTestServer(nil) 1840 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "store-snap", "./local.snap"}) 1841 c.Assert(err, check.ErrorMatches, `only one snap file can be installed at a time`) 1842 } 1843 1844 func (s *SnapOpSuite) TestInstallMany(c *check.C) { 1845 total := 4 1846 n := 0 1847 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1848 switch n { 1849 case 0: 1850 c.Check(r.URL.Path, check.Equals, "/v2/snaps") 1851 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 1852 "action": "install", 1853 "snaps": []interface{}{"one", "two"}, 1854 }) 1855 1856 c.Check(r.Method, check.Equals, "POST") 1857 w.WriteHeader(202) 1858 fmt.Fprintln(w, `{"type":"async", "change": "42", "status-code": 202}`) 1859 case 1: 1860 c.Check(r.Method, check.Equals, "GET") 1861 c.Check(r.URL.Path, check.Equals, "/v2/changes/42") 1862 fmt.Fprintln(w, `{"type": "sync", "result": {"status": "Doing"}}`) 1863 case 2: 1864 c.Check(r.Method, check.Equals, "GET") 1865 c.Check(r.URL.Path, check.Equals, "/v2/changes/42") 1866 fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done", "data": {"snap-names": ["one","two"]}}}`) 1867 case 3: 1868 c.Check(r.Method, check.Equals, "GET") 1869 c.Check(r.URL.Path, check.Equals, "/v2/snaps") 1870 fmt.Fprintf(w, `{"type": "sync", "result": [{"name": "one", "status": "active", "version": "1.0", "developer": "bar", "publisher": {"id": "bar-id", "username": "bar", "display-name": "Bar", "validation": "unproven"}, "revision":42, "channel":"stable"},{"name": "two", "status": "active", "version": "2.0", "developer": "baz", "publisher": {"id": "baz-id", "username": "baz", "display-name": "Baz", "validation": "unproven"}, "revision":42, "channel":"edge"}]}\n`) 1871 1872 default: 1873 c.Fatalf("expected to get %d requests, now on %d", total, n+1) 1874 } 1875 1876 n++ 1877 }) 1878 1879 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "one", "two"}) 1880 c.Assert(err, check.IsNil) 1881 c.Assert(rest, check.DeepEquals, []string{}) 1882 // note that (stable) is omitted 1883 c.Check(s.Stdout(), check.Matches, `(?sm).*one 1.0 from Bar installed`) 1884 c.Check(s.Stdout(), check.Matches, `(?sm).*two \(edge\) 2.0 from Baz installed`) 1885 c.Check(s.Stderr(), check.Equals, "") 1886 // ensure that the fake server api was actually hit 1887 c.Check(n, check.Equals, total) 1888 } 1889 1890 func (s *SnapOpSuite) TestInstallZeroEmpty(c *check.C) { 1891 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install"}) 1892 c.Assert(err, check.ErrorMatches, "cannot install zero snaps") 1893 _, err = snap.Parser(snap.Client()).ParseArgs([]string{"install", ""}) 1894 c.Assert(err, check.ErrorMatches, "cannot install snap with empty name") 1895 _, err = snap.Parser(snap.Client()).ParseArgs([]string{"install", "", "bar"}) 1896 c.Assert(err, check.ErrorMatches, "cannot install snap with empty name") 1897 } 1898 1899 func (s *SnapOpSuite) TestNoWait(c *check.C) { 1900 s.srv.checker = func(r *http.Request) {} 1901 1902 cmds := [][]string{ 1903 {"remove", "--no-wait", "foo"}, 1904 {"remove", "--no-wait", "foo", "bar"}, 1905 {"install", "--no-wait", "foo"}, 1906 {"install", "--no-wait", "foo", "bar"}, 1907 {"revert", "--no-wait", "foo"}, 1908 {"refresh", "--no-wait", "foo"}, 1909 {"refresh", "--no-wait", "foo", "bar"}, 1910 {"refresh", "--no-wait"}, 1911 {"enable", "--no-wait", "foo"}, 1912 {"disable", "--no-wait", "foo"}, 1913 {"try", "--no-wait", "."}, 1914 {"switch", "--no-wait", "--channel=foo", "bar"}, 1915 // commands that use waitMixin from elsewhere 1916 {"start", "--no-wait", "foo"}, 1917 {"stop", "--no-wait", "foo"}, 1918 {"restart", "--no-wait", "foo"}, 1919 {"alias", "--no-wait", "foo", "bar"}, 1920 {"unalias", "--no-wait", "foo"}, 1921 {"prefer", "--no-wait", "foo"}, 1922 {"set", "--no-wait", "foo", "bar=baz"}, 1923 {"disconnect", "--no-wait", "foo:bar"}, 1924 {"connect", "--no-wait", "foo:bar"}, 1925 } 1926 1927 s.RedirectClientToTestServer(s.srv.handle) 1928 for _, cmd := range cmds { 1929 rest, err := snap.Parser(snap.Client()).ParseArgs(cmd) 1930 c.Assert(err, check.IsNil, check.Commentf("%v", cmd)) 1931 c.Assert(rest, check.DeepEquals, []string{}) 1932 c.Check(s.Stdout(), check.Matches, "(?sm)42\n") 1933 c.Check(s.Stderr(), check.Equals, "") 1934 c.Check(s.srv.n, check.Equals, 1) 1935 // reset 1936 s.srv.n = 0 1937 s.stdout.Reset() 1938 } 1939 } 1940 1941 func (s *SnapOpSuite) TestNoWaitImmediateError(c *check.C) { 1942 1943 cmds := [][]string{ 1944 {"remove", "--no-wait", "foo"}, 1945 {"remove", "--no-wait", "foo", "bar"}, 1946 {"install", "--no-wait", "foo"}, 1947 {"install", "--no-wait", "foo", "bar"}, 1948 {"revert", "--no-wait", "foo"}, 1949 {"refresh", "--no-wait", "foo"}, 1950 {"refresh", "--no-wait", "foo", "bar"}, 1951 {"refresh", "--no-wait"}, 1952 {"enable", "--no-wait", "foo"}, 1953 {"disable", "--no-wait", "foo"}, 1954 {"try", "--no-wait", "."}, 1955 {"switch", "--no-wait", "--channel=foo", "bar"}, 1956 // commands that use waitMixin from elsewhere 1957 {"start", "--no-wait", "foo"}, 1958 {"stop", "--no-wait", "foo"}, 1959 {"restart", "--no-wait", "foo"}, 1960 {"alias", "--no-wait", "foo", "bar"}, 1961 {"unalias", "--no-wait", "foo"}, 1962 {"prefer", "--no-wait", "foo"}, 1963 {"set", "--no-wait", "foo", "bar=baz"}, 1964 {"disconnect", "--no-wait", "foo:bar"}, 1965 {"connect", "--no-wait", "foo:bar"}, 1966 } 1967 1968 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1969 fmt.Fprintln(w, `{"type": "error", "result": {"message": "failure"}}`) 1970 }) 1971 1972 for _, cmd := range cmds { 1973 _, err := snap.Parser(snap.Client()).ParseArgs(cmd) 1974 c.Assert(err, check.ErrorMatches, "failure", check.Commentf("%v", cmd)) 1975 } 1976 } 1977 1978 func (s *SnapOpSuite) TestWaitServerError(c *check.C) { 1979 r := snap.MockMaxGoneTime(0) 1980 defer r() 1981 1982 cmds := [][]string{ 1983 {"remove", "foo"}, 1984 {"remove", "foo", "bar"}, 1985 {"install", "foo"}, 1986 {"install", "foo", "bar"}, 1987 {"revert", "foo"}, 1988 {"refresh", "foo"}, 1989 {"refresh", "foo", "bar"}, 1990 {"refresh"}, 1991 {"enable", "foo"}, 1992 {"disable", "foo"}, 1993 {"try", "."}, 1994 {"switch", "--channel=foo", "bar"}, 1995 // commands that use waitMixin from elsewhere 1996 {"start", "foo"}, 1997 {"stop", "foo"}, 1998 {"restart", "foo"}, 1999 {"alias", "foo", "bar"}, 2000 {"unalias", "foo"}, 2001 {"prefer", "foo"}, 2002 {"set", "foo", "bar=baz"}, 2003 {"disconnect", "foo:bar"}, 2004 {"connect", "foo:bar"}, 2005 } 2006 2007 n := 0 2008 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 2009 n++ 2010 if n == 1 { 2011 w.WriteHeader(202) 2012 fmt.Fprintln(w, `{"type":"async", "change": "42", "status-code": 202}`) 2013 return 2014 } 2015 if n == 3 { 2016 fmt.Fprintln(w, `{"type": "error", "result": {"message": "unexpected request"}}`) 2017 return 2018 } 2019 fmt.Fprintln(w, `{"type": "error", "result": {"message": "server error"}}`) 2020 }) 2021 2022 for _, cmd := range cmds { 2023 _, err := snap.Parser(snap.Client()).ParseArgs(cmd) 2024 c.Assert(err, check.ErrorMatches, "server error", check.Commentf("%v", cmd)) 2025 // reset 2026 n = 0 2027 } 2028 } 2029 2030 func (s *SnapOpSuite) TestSwitchHappy(c *check.C) { 2031 s.srv.total = 4 2032 s.srv.checker = func(r *http.Request) { 2033 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 2034 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 2035 "action": "switch", 2036 "channel": "beta", 2037 }) 2038 s.srv.trackingChannel = "beta" 2039 } 2040 2041 s.RedirectClientToTestServer(s.srv.handle) 2042 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--beta", "foo"}) 2043 c.Assert(err, check.IsNil) 2044 c.Assert(rest, check.DeepEquals, []string{}) 2045 c.Check(s.Stdout(), check.Equals, `"foo" switched to the "beta" channel 2046 2047 `) 2048 c.Check(s.Stderr(), check.Equals, "") 2049 // ensure that the fake server api was actually hit 2050 c.Check(s.srv.n, check.Equals, s.srv.total) 2051 } 2052 2053 func (s *SnapOpSuite) TestSwitchHappyCohort(c *check.C) { 2054 s.srv.total = 4 2055 s.srv.checker = func(r *http.Request) { 2056 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 2057 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 2058 "action": "switch", 2059 "cohort-key": "what", 2060 }) 2061 } 2062 2063 s.RedirectClientToTestServer(s.srv.handle) 2064 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--cohort=what", "foo"}) 2065 c.Assert(err, check.IsNil) 2066 c.Assert(rest, check.DeepEquals, []string{}) 2067 c.Check(s.Stdout(), check.Matches, `(?sm).*"foo" switched to the "what" cohort`) 2068 c.Check(s.Stderr(), check.Equals, "") 2069 // ensure that the fake server api was actually hit 2070 c.Check(s.srv.n, check.Equals, s.srv.total) 2071 } 2072 2073 func (s *SnapOpSuite) TestSwitchHappyLeaveCohort(c *check.C) { 2074 s.srv.total = 4 2075 s.srv.checker = func(r *http.Request) { 2076 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 2077 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 2078 "action": "switch", 2079 "leave-cohort": true, 2080 }) 2081 } 2082 2083 s.RedirectClientToTestServer(s.srv.handle) 2084 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--leave-cohort", "foo"}) 2085 c.Assert(err, check.IsNil) 2086 c.Assert(rest, check.DeepEquals, []string{}) 2087 c.Check(s.Stdout(), check.Matches, `(?sm).*"foo" left the cohort`) 2088 c.Check(s.Stderr(), check.Equals, "") 2089 // ensure that the fake server api was actually hit 2090 c.Check(s.srv.n, check.Equals, s.srv.total) 2091 } 2092 2093 func (s *SnapOpSuite) TestSwitchHappyChannelAndCohort(c *check.C) { 2094 s.srv.total = 4 2095 s.srv.checker = func(r *http.Request) { 2096 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 2097 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 2098 "action": "switch", 2099 "cohort-key": "what", 2100 "channel": "edge", 2101 }) 2102 s.srv.trackingChannel = "edge" 2103 } 2104 2105 s.RedirectClientToTestServer(s.srv.handle) 2106 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--cohort=what", "--edge", "foo"}) 2107 c.Assert(err, check.IsNil) 2108 c.Assert(rest, check.DeepEquals, []string{}) 2109 c.Check(s.Stdout(), check.Matches, `(?sm).*"foo" switched to the "edge" channel and the "what" cohort`) 2110 c.Check(s.Stderr(), check.Equals, "") 2111 // ensure that the fake server api was actually hit 2112 c.Check(s.srv.n, check.Equals, s.srv.total) 2113 } 2114 2115 func (s *SnapOpSuite) TestSwitchHappyChannelAndLeaveCohort(c *check.C) { 2116 s.srv.total = 4 2117 s.srv.checker = func(r *http.Request) { 2118 c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") 2119 c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ 2120 "action": "switch", 2121 "leave-cohort": true, 2122 "channel": "edge", 2123 }) 2124 s.srv.trackingChannel = "edge" 2125 } 2126 2127 s.RedirectClientToTestServer(s.srv.handle) 2128 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--leave-cohort", "--edge", "foo"}) 2129 c.Assert(err, check.IsNil) 2130 c.Assert(rest, check.DeepEquals, []string{}) 2131 c.Check(s.Stdout(), check.Matches, `(?sm).*"foo" left the cohort, and switched to the "edge" channel`) 2132 c.Check(s.Stderr(), check.Equals, "") 2133 // ensure that the fake server api was actually hit 2134 c.Check(s.srv.n, check.Equals, s.srv.total) 2135 } 2136 2137 func (s *SnapOpSuite) TestSwitchUnhappy(c *check.C) { 2138 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch"}) 2139 c.Assert(err, check.ErrorMatches, "the required argument `<snap>` was not provided") 2140 } 2141 2142 func (s *SnapOpSuite) TestSwitchAlsoUnhappy(c *check.C) { 2143 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "foo"}) 2144 c.Assert(err, check.ErrorMatches, `nothing to switch.*`) 2145 } 2146 2147 func (s *SnapOpSuite) TestSwitchMoreUnhappy(c *check.C) { 2148 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "foo", "--cohort=what", "--leave-cohort"}) 2149 c.Assert(err, check.ErrorMatches, `cannot specify both --cohort and --leave-cohort`) 2150 } 2151 2152 func (s *SnapOpSuite) TestSnapOpNetworkTimeoutError(c *check.C) { 2153 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 2154 c.Check(r.Method, check.Equals, "POST") 2155 w.WriteHeader(202) 2156 w.Write([]byte(` 2157 { 2158 "type": "error", 2159 "result": { 2160 "message":"Get https://api.snapcraft.io/api/v1/snaps/details/hello?channel=stable&fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha3_384%2Csummary%2Cdescription%2Cdeltas%2Cbinary_filesize%2Cdownload_url%2Cepoch%2Cicon_url%2Clast_updated%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Clicense%2Cbase%2Csupport_url%2Ccontact%2Ctitle%2Ccontent%2Cversion%2Corigin%2Cdeveloper_id%2Cprivate%2Cconfinement%2Cchannel_maps_list: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)", 2161 "kind":"network-timeout" 2162 }, 2163 "status-code": 400 2164 } 2165 `)) 2166 2167 }) 2168 2169 cmd := []string{"install", "hello"} 2170 _, err := snap.Parser(snap.Client()).ParseArgs(cmd) 2171 c.Assert(err, check.ErrorMatches, `unable to contact snap store`) 2172 }