github.com/rigado/snapd@v2.42.5-go-mod+incompatible/cmd/snap/cmd_info_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 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 "bytes" 24 "fmt" 25 "io/ioutil" 26 "net/http" 27 "path/filepath" 28 "time" 29 30 "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/client" 33 snap "github.com/snapcore/snapd/cmd/snap" 34 snaplib "github.com/snapcore/snapd/snap" 35 "github.com/snapcore/snapd/snap/snaptest" 36 "github.com/snapcore/snapd/snap/squashfs" 37 ) 38 39 var cmdAppInfos = []client.AppInfo{{Name: "app1"}, {Name: "app2"}} 40 var svcAppInfos = []client.AppInfo{ 41 { 42 Name: "svc1", 43 Daemon: "simple", 44 Enabled: false, 45 Active: true, 46 }, 47 { 48 Name: "svc2", 49 Daemon: "simple", 50 Enabled: true, 51 Active: false, 52 }, 53 } 54 55 var mixedAppInfos = append(append([]client.AppInfo(nil), cmdAppInfos...), svcAppInfos...) 56 57 type infoSuite struct { 58 BaseSnapSuite 59 } 60 61 var _ = check.Suite(&infoSuite{}) 62 63 type flushBuffer struct{ bytes.Buffer } 64 65 func (*flushBuffer) Flush() error { return nil } 66 67 func (s *infoSuite) TestMaybePrintServices(c *check.C) { 68 var buf flushBuffer 69 iw := snap.NewInfoWriter(&buf) 70 for _, infos := range [][]client.AppInfo{svcAppInfos, mixedAppInfos} { 71 buf.Reset() 72 snap.SetupDiskSnap(iw, "", &client.Snap{Name: "foo", Apps: infos}) 73 snap.MaybePrintServices(iw) 74 75 c.Check(buf.String(), check.Equals, `services: 76 foo.svc1: simple, disabled, active 77 foo.svc2: simple, enabled, inactive 78 `) 79 } 80 } 81 82 func (s *infoSuite) TestMaybePrintServicesNoServices(c *check.C) { 83 var buf flushBuffer 84 iw := snap.NewInfoWriter(&buf) 85 for _, infos := range [][]client.AppInfo{cmdAppInfos, nil} { 86 buf.Reset() 87 snap.SetupDiskSnap(iw, "", &client.Snap{Name: "foo", Apps: infos}) 88 snap.MaybePrintServices(iw) 89 c.Check(buf.String(), check.Equals, "") 90 } 91 } 92 93 func (s *infoSuite) TestMaybePrintCommands(c *check.C) { 94 var buf flushBuffer 95 iw := snap.NewInfoWriter(&buf) 96 for _, infos := range [][]client.AppInfo{cmdAppInfos, mixedAppInfos} { 97 buf.Reset() 98 snap.SetupDiskSnap(iw, "", &client.Snap{Name: "foo", Apps: infos}) 99 snap.MaybePrintCommands(iw) 100 101 c.Check(buf.String(), check.Equals, `commands: 102 - foo.app1 103 - foo.app2 104 `) 105 } 106 } 107 108 func (s *infoSuite) TestMaybePrintCommandsNoCommands(c *check.C) { 109 var buf flushBuffer 110 iw := snap.NewInfoWriter(&buf) 111 for _, infos := range [][]client.AppInfo{svcAppInfos, nil} { 112 buf.Reset() 113 snap.SetupDiskSnap(iw, "", &client.Snap{Name: "foo", Apps: infos}) 114 snap.MaybePrintCommands(iw) 115 116 c.Check(buf.String(), check.Equals, "") 117 } 118 } 119 120 func (infoSuite) TestPrintType(c *check.C) { 121 var buf flushBuffer 122 iw := snap.NewInfoWriter(&buf) 123 for from, to := range map[string]string{ 124 "": "", 125 "app": "", 126 "application": "", 127 "gadget": "type:\tgadget\n", 128 "core": "type:\tcore\n", 129 "os": "type:\tcore\n", 130 } { 131 buf.Reset() 132 snap.SetupDiskSnap(iw, "", &client.Snap{Type: from}) 133 snap.MaybePrintType(iw) 134 c.Check(buf.String(), check.Equals, to, check.Commentf("%q", from)) 135 } 136 } 137 138 func (infoSuite) TestPrintSummary(c *check.C) { 139 var buf flushBuffer 140 iw := snap.NewInfoWriter(&buf) 141 for from, to := range map[string]string{ 142 "": `""`, // empty results in quoted empty 143 "foo": "foo", // plain text results in unquoted 144 "two words": "two words", // ...even when multi-word 145 "{": `"{"`, // but yaml-breaking is quoted 146 "very long text": "very long\n text", // too-long text gets split (TODO: split with tabbed indent to preserve alignment) 147 } { 148 buf.Reset() 149 snap.SetupDiskSnap(iw, "", &client.Snap{Summary: from}) 150 snap.PrintSummary(iw) 151 c.Check(buf.String(), check.Equals, "summary:\t"+to+"\n", check.Commentf("%q", from)) 152 } 153 } 154 155 func (s *infoSuite) TestMaybePrintPublisher(c *check.C) { 156 acct := &snaplib.StoreAccount{ 157 Validation: "verified", 158 Username: "team-potato", 159 DisplayName: "Team Potato", 160 } 161 162 type T struct { 163 diskSnap, localSnap *client.Snap 164 expected string 165 } 166 167 var buf flushBuffer 168 iw := snap.NewInfoWriter(&buf) 169 for i, t := range []T{ 170 {&client.Snap{}, nil, ""}, // nothing output for on-disk snap 171 {nil, &client.Snap{}, "publisher:\t--\n"}, // from-snapd snap with no publisher is explicit 172 {nil, &client.Snap{Publisher: acct}, "publisher:\tTeam Potato*\n"}, 173 } { 174 buf.Reset() 175 if t.diskSnap == nil { 176 snap.SetupSnap(iw, t.localSnap, nil, nil) 177 } else { 178 snap.SetupDiskSnap(iw, "", t.diskSnap) 179 } 180 snap.MaybePrintPublisher(iw) 181 c.Check(buf.String(), check.Equals, t.expected, check.Commentf("%d", i)) 182 } 183 } 184 185 func (s *infoSuite) TestMaybePrintNotes(c *check.C) { 186 type T struct { 187 localSnap, diskSnap *client.Snap 188 expected string 189 } 190 191 var buf flushBuffer 192 iw := snap.NewInfoWriter(&buf) 193 for i, t := range []T{ 194 { 195 nil, 196 &client.Snap{Private: true, Confinement: "devmode"}, 197 "notes:\t\n" + 198 " private:\ttrue\n" + 199 " confinement:\tdevmode\n", 200 }, { 201 &client.Snap{Private: true, Confinement: "devmode"}, 202 nil, 203 "notes:\t\n" + 204 " private:\ttrue\n" + 205 " confinement:\tdevmode\n" + 206 " devmode:\tfalse\n" + 207 " jailmode:\ttrue\n" + 208 " trymode:\tfalse\n" + 209 " enabled:\tfalse\n" + 210 " broken:\tfalse\n" + 211 " ignore-validation:\tfalse\n", 212 }, { 213 &client.Snap{Private: true, Confinement: "devmode", Broken: "ouch"}, 214 nil, 215 "notes:\t\n" + 216 " private:\ttrue\n" + 217 " confinement:\tdevmode\n" + 218 " devmode:\tfalse\n" + 219 " jailmode:\ttrue\n" + 220 " trymode:\tfalse\n" + 221 " enabled:\tfalse\n" + 222 " broken:\ttrue (ouch)\n" + 223 " ignore-validation:\tfalse\n", 224 }, 225 } { 226 buf.Reset() 227 snap.SetVerbose(iw, false) 228 if t.diskSnap == nil { 229 snap.SetupSnap(iw, t.localSnap, nil, nil) 230 } else { 231 snap.SetupDiskSnap(iw, "", t.diskSnap) 232 } 233 snap.MaybePrintNotes(iw) 234 c.Check(buf.String(), check.Equals, "", check.Commentf("%d/false", i)) 235 236 buf.Reset() 237 snap.SetVerbose(iw, true) 238 snap.MaybePrintNotes(iw) 239 c.Check(buf.String(), check.Equals, t.expected, check.Commentf("%d/true", i)) 240 } 241 } 242 243 func (s *infoSuite) TestMaybePrintStandaloneVersion(c *check.C) { 244 var buf flushBuffer 245 iw := snap.NewInfoWriter(&buf) 246 247 // no disk snap -> no version 248 snap.MaybePrintStandaloneVersion(iw) 249 c.Check(buf.String(), check.Equals, "") 250 251 for version, expected := range map[string]string{ 252 "": "--", 253 "4.2": "4.2", 254 } { 255 buf.Reset() 256 snap.SetupDiskSnap(iw, "", &client.Snap{Version: version}) 257 snap.MaybePrintStandaloneVersion(iw) 258 c.Check(buf.String(), check.Equals, "version:\t"+expected+" -\n", check.Commentf("%q", version)) 259 260 buf.Reset() 261 snap.SetupDiskSnap(iw, "", &client.Snap{Version: version, Confinement: "devmode"}) 262 snap.MaybePrintStandaloneVersion(iw) 263 c.Check(buf.String(), check.Equals, "version:\t"+expected+" devmode\n", check.Commentf("%q", version)) 264 } 265 } 266 267 func (s *infoSuite) TestMaybePrintBuildDate(c *check.C) { 268 var buf flushBuffer 269 iw := snap.NewInfoWriter(&buf) 270 // some prep 271 dir := c.MkDir() 272 arbfile := filepath.Join(dir, "arb") 273 c.Assert(ioutil.WriteFile(arbfile, nil, 0600), check.IsNil) 274 filename := filepath.Join(c.MkDir(), "foo.snap") 275 diskSnap := squashfs.New(filename) 276 c.Assert(diskSnap.Build(dir, "app"), check.IsNil) 277 buildDate := diskSnap.BuildDate().Format(time.Kitchen) 278 279 // no disk snap -> no build date 280 snap.MaybePrintBuildDate(iw) 281 c.Check(buf.String(), check.Equals, "") 282 283 // path is directory -> no build date 284 buf.Reset() 285 snap.SetupDiskSnap(iw, dir, &client.Snap{}) 286 snap.MaybePrintBuildDate(iw) 287 c.Check(buf.String(), check.Equals, "") 288 289 // not actually a snap -> no build date 290 buf.Reset() 291 snap.SetupDiskSnap(iw, arbfile, &client.Snap{}) 292 snap.MaybePrintBuildDate(iw) 293 c.Check(buf.String(), check.Equals, "") 294 295 // disk snap -> get build date 296 buf.Reset() 297 snap.SetupDiskSnap(iw, filename, &client.Snap{}) 298 snap.MaybePrintBuildDate(iw) 299 c.Check(buf.String(), check.Equals, "build-date:\t"+buildDate+"\n") 300 } 301 302 func (s *infoSuite) TestMaybePrintSum(c *check.C) { 303 var buf flushBuffer 304 // some prep 305 dir := c.MkDir() 306 filename := filepath.Join(c.MkDir(), "foo.snap") 307 diskSnap := squashfs.New(filename) 308 c.Assert(diskSnap.Build(dir, "app"), check.IsNil) 309 iw := snap.NewInfoWriter(&buf) 310 snap.SetVerbose(iw, true) 311 312 // no disk snap -> no checksum 313 snap.MaybePrintSum(iw) 314 c.Check(buf.String(), check.Equals, "") 315 316 // path is directory -> no checksum 317 buf.Reset() 318 snap.SetupDiskSnap(iw, dir, &client.Snap{}) 319 snap.MaybePrintSum(iw) 320 c.Check(buf.String(), check.Equals, "") 321 322 // disk snap and verbose -> get checksum 323 buf.Reset() 324 snap.SetupDiskSnap(iw, filename, &client.Snap{}) 325 snap.MaybePrintSum(iw) 326 c.Check(buf.String(), check.Matches, "sha3-384:\t\\S+\n") 327 328 // disk snap but not verbose -> no checksum 329 buf.Reset() 330 snap.SetVerbose(iw, false) 331 snap.MaybePrintSum(iw) 332 c.Check(buf.String(), check.Equals, "") 333 } 334 335 func (s *infoSuite) TestMaybePrintContact(c *check.C) { 336 var buf flushBuffer 337 iw := snap.NewInfoWriter(&buf) 338 339 for contact, expected := range map[string]string{ 340 "": "", 341 "mailto:joe@example.com": "contact:\tjoe@example.com\n", 342 "foo": "contact:\tfoo\n", 343 } { 344 buf.Reset() 345 snap.SetupDiskSnap(iw, "", &client.Snap{Contact: contact}) 346 snap.MaybePrintContact(iw) 347 c.Check(buf.String(), check.Equals, expected, check.Commentf("%q", contact)) 348 } 349 } 350 351 func (s *infoSuite) TestMaybePrintBase(c *check.C) { 352 var buf flushBuffer 353 iw := snap.NewInfoWriter(&buf) 354 dSnap := &client.Snap{} 355 snap.SetupDiskSnap(iw, "", dSnap) 356 357 // no verbose -> no base 358 snap.SetVerbose(iw, false) 359 snap.MaybePrintBase(iw) 360 c.Check(buf.String(), check.Equals, "") 361 buf.Reset() 362 363 // no base -> no base :) 364 snap.SetVerbose(iw, true) 365 snap.MaybePrintBase(iw) 366 c.Check(buf.String(), check.Equals, "") 367 buf.Reset() 368 369 // base + verbose -> base 370 dSnap.Base = "xyzzy" 371 snap.MaybePrintBase(iw) 372 c.Check(buf.String(), check.Equals, "base:\txyzzy\n") 373 buf.Reset() 374 } 375 376 func (s *infoSuite) TestMaybePrintPath(c *check.C) { 377 var buf flushBuffer 378 iw := snap.NewInfoWriter(&buf) 379 dSnap := &client.Snap{} 380 381 // no path -> no path 382 snap.SetupDiskSnap(iw, "", dSnap) 383 snap.MaybePrintPath(iw) 384 c.Check(buf.String(), check.Equals, "") 385 buf.Reset() 386 387 // path -> path (quoted!) 388 snap.SetupDiskSnap(iw, "xyzzy", dSnap) 389 snap.MaybePrintPath(iw) 390 c.Check(buf.String(), check.Equals, "path:\t\"xyzzy\"\n") 391 buf.Reset() 392 } 393 394 func (s *infoSuite) TestClientSnapFromPath(c *check.C) { 395 // minimal sanity check 396 fn := snaptest.MakeTestSnapWithFiles(c, ` 397 name: some-snap 398 version: 9 399 `, nil) 400 dSnap, err := snap.ClientSnapFromPath(fn) 401 c.Assert(err, check.IsNil) 402 c.Check(dSnap.Version, check.Equals, "9") 403 } 404 405 func (s *infoSuite) TestInfoPricedNarrowTerminal(c *check.C) { 406 defer snap.MockTermSize(func() (int, int) { return 44, 25 })() 407 408 n := 0 409 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 410 switch n { 411 case 0: 412 c.Check(r.Method, check.Equals, "GET") 413 c.Check(r.URL.Path, check.Equals, "/v2/find") 414 fmt.Fprintln(w, findPricedJSON) 415 case 1: 416 c.Check(r.Method, check.Equals, "GET") 417 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 418 fmt.Fprintln(w, "{}") 419 default: 420 c.Fatalf("expected to get 1 requests, now on %d (%v)", n+1, r) 421 } 422 423 n++ 424 }) 425 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) 426 c.Assert(err, check.IsNil) 427 c.Assert(rest, check.DeepEquals, []string{}) 428 c.Check(s.Stdout(), check.Equals, ` 429 name: hello 430 summary: GNU Hello, the "hello world" 431 snap 432 publisher: Canonical* 433 license: Proprietary 434 price: 1.99GBP 435 description: | 436 GNU hello prints a friendly greeting. 437 This is part of the snapcraft tour at 438 https://snapcraft.io/ 439 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 440 `[1:]) 441 c.Check(s.Stderr(), check.Equals, "") 442 } 443 444 func (s *infoSuite) TestInfoPriced(c *check.C) { 445 n := 0 446 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 447 switch n { 448 case 0: 449 c.Check(r.Method, check.Equals, "GET") 450 c.Check(r.URL.Path, check.Equals, "/v2/find") 451 fmt.Fprintln(w, findPricedJSON) 452 case 1: 453 c.Check(r.Method, check.Equals, "GET") 454 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 455 fmt.Fprintln(w, "{}") 456 default: 457 c.Fatalf("expected to get 1 requests, now on %d (%v)", n+1, r) 458 } 459 460 n++ 461 }) 462 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) 463 c.Assert(err, check.IsNil) 464 c.Assert(rest, check.DeepEquals, []string{}) 465 c.Check(s.Stdout(), check.Equals, `name: hello 466 summary: GNU Hello, the "hello world" snap 467 publisher: Canonical* 468 license: Proprietary 469 price: 1.99GBP 470 description: | 471 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 472 https://snapcraft.io/ 473 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 474 `) 475 c.Check(s.Stderr(), check.Equals, "") 476 } 477 478 // only used for results on /v2/find 479 const mockInfoJSON = ` 480 { 481 "type": "sync", 482 "status-code": 200, 483 "status": "OK", 484 "result": [ 485 { 486 "channel": "stable", 487 "confinement": "strict", 488 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 489 "developer": "canonical", 490 "publisher": { 491 "id": "canonical", 492 "username": "canonical", 493 "display-name": "Canonical", 494 "validation": "verified" 495 }, 496 "download-size": 65536, 497 "icon": "", 498 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 499 "name": "hello", 500 "private": false, 501 "resource": "/v2/snaps/hello", 502 "revision": "1", 503 "status": "available", 504 "summary": "The GNU Hello snap", 505 "type": "app", 506 "version": "2.10", 507 "license": "MIT" 508 } 509 ], 510 "sources": [ 511 "store" 512 ], 513 "suggested-currency": "GBP" 514 } 515 ` 516 517 const mockInfoJSONWithChannels = ` 518 { 519 "type": "sync", 520 "status-code": 200, 521 "status": "OK", 522 "result": [ 523 { 524 "channel": "stable", 525 "confinement": "strict", 526 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 527 "developer": "canonical", 528 "publisher": { 529 "id": "canonical", 530 "username": "canonical", 531 "display-name": "Canonical", 532 "validation": "verified" 533 }, 534 "download-size": 65536, 535 "icon": "", 536 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 537 "name": "hello", 538 "private": false, 539 "resource": "/v2/snaps/hello", 540 "revision": "1", 541 "status": "available", 542 "summary": "The GNU Hello snap", 543 "type": "app", 544 "version": "2.10", 545 "license": "MIT", 546 "channels": { 547 "1/stable": { 548 "revision": "1", 549 "version": "2.10", 550 "channel": "1/stable", 551 "size": 65536, 552 "released-at": "2018-12-18T15:16:56.723501Z" 553 } 554 }, 555 "tracks": ["1"] 556 } 557 ], 558 "sources": [ 559 "store" 560 ], 561 "suggested-currency": "GBP" 562 } 563 ` 564 565 func (s *infoSuite) TestInfoUnquoted(c *check.C) { 566 n := 0 567 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 568 switch n { 569 case 0: 570 c.Check(r.Method, check.Equals, "GET") 571 c.Check(r.URL.Path, check.Equals, "/v2/find") 572 fmt.Fprintln(w, mockInfoJSON) 573 case 1: 574 c.Check(r.Method, check.Equals, "GET") 575 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 576 fmt.Fprintln(w, "{}") 577 default: 578 c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r) 579 } 580 581 n++ 582 }) 583 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) 584 c.Assert(err, check.IsNil) 585 c.Assert(rest, check.DeepEquals, []string{}) 586 c.Check(s.Stdout(), check.Equals, `name: hello 587 summary: The GNU Hello snap 588 publisher: Canonical* 589 license: MIT 590 description: | 591 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 592 https://snapcraft.io/ 593 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 594 `) 595 c.Check(s.Stderr(), check.Equals, "") 596 } 597 598 // only used for /v2/snaps/hello 599 const mockInfoJSONOtherLicense = ` 600 { 601 "type": "sync", 602 "status-code": 200, 603 "status": "OK", 604 "result": { 605 "channel": "stable", 606 "confinement": "strict", 607 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 608 "developer": "canonical", 609 "publisher": { 610 "id": "canonical", 611 "username": "canonical", 612 "display-name": "Canonical", 613 "validation": "verified" 614 }, 615 "health": {"revision": "1", "status": "blocked", "message": "please configure the grawflit", "timestamp": "2019-05-13T16:27:01.475851677+01:00"}, 616 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 617 "install-date": "2006-01-02T22:04:07.123456789Z", 618 "installed-size": 1024, 619 "name": "hello", 620 "private": false, 621 "resource": "/v2/snaps/hello", 622 "revision": "1", 623 "status": "available", 624 "summary": "The GNU Hello snap", 625 "type": "app", 626 "version": "2.10", 627 "license": "BSD-3", 628 "tracking-channel": "beta" 629 } 630 } 631 ` 632 const mockInfoJSONNoLicense = ` 633 { 634 "type": "sync", 635 "status-code": 200, 636 "status": "OK", 637 "result": { 638 "channel": "stable", 639 "confinement": "strict", 640 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 641 "developer": "canonical", 642 "publisher": { 643 "id": "canonical", 644 "username": "canonical", 645 "display-name": "Canonical", 646 "validation": "verified" 647 }, 648 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 649 "install-date": "2006-01-02T22:04:07.123456789Z", 650 "installed-size": 1024, 651 "name": "hello", 652 "private": false, 653 "resource": "/v2/snaps/hello", 654 "revision": "100", 655 "status": "available", 656 "summary": "The GNU Hello snap", 657 "type": "app", 658 "version": "2.10", 659 "license": "", 660 "tracking-channel": "beta" 661 } 662 } 663 ` 664 665 func (s *infoSuite) TestInfoWithLocalDifferentLicense(c *check.C) { 666 n := 0 667 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 668 switch n { 669 case 0: 670 c.Check(r.Method, check.Equals, "GET") 671 c.Check(r.URL.Path, check.Equals, "/v2/find") 672 fmt.Fprintln(w, mockInfoJSON) 673 case 1: 674 c.Check(r.Method, check.Equals, "GET") 675 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 676 fmt.Fprintln(w, mockInfoJSONOtherLicense) 677 default: 678 c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r) 679 } 680 681 n++ 682 }) 683 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"}) 684 c.Assert(err, check.IsNil) 685 c.Assert(rest, check.DeepEquals, []string{}) 686 c.Check(s.Stdout(), check.Equals, ` 687 name: hello 688 summary: The GNU Hello snap 689 health: 690 status: blocked 691 message: please configure the grawflit 692 checked: 2019-05-13T16:27:01+01:00 693 revision: 1 694 publisher: Canonical* 695 license: BSD-3 696 description: | 697 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 698 https://snapcraft.io/ 699 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 700 tracking: beta 701 refresh-date: 2006-01-02T22:04:07Z 702 installed: 2.10 (1) 1kB disabled,blocked 703 `[1:]) 704 c.Check(s.Stderr(), check.Equals, "") 705 } 706 707 func (s *infoSuite) TestInfoNotFound(c *check.C) { 708 n := 0 709 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 710 switch n % 2 { 711 case 0: 712 c.Check(r.Method, check.Equals, "GET") 713 c.Check(r.URL.Path, check.Equals, "/v2/find") 714 case 1: 715 c.Check(r.Method, check.Equals, "GET") 716 c.Check(r.URL.Path, check.Equals, "/v2/snaps/x") 717 } 718 w.WriteHeader(404) 719 fmt.Fprintln(w, `{"type":"error","status-code":404,"status":"Not Found","result":{"message":"No.","kind":"snap-not-found","value":"x"}}`) 720 721 n++ 722 }) 723 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--verbose", "/x"}) 724 c.Check(err, check.ErrorMatches, `no snap found for "/x"`) 725 c.Check(s.Stdout(), check.Equals, "") 726 c.Check(s.Stderr(), check.Equals, "") 727 } 728 729 func (s *infoSuite) TestInfoWithLocalNoLicense(c *check.C) { 730 n := 0 731 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 732 switch n { 733 case 0: 734 c.Check(r.Method, check.Equals, "GET") 735 c.Check(r.URL.Path, check.Equals, "/v2/find") 736 fmt.Fprintln(w, mockInfoJSON) 737 case 1: 738 c.Check(r.Method, check.Equals, "GET") 739 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 740 fmt.Fprintln(w, mockInfoJSONNoLicense) 741 default: 742 c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r) 743 } 744 745 n++ 746 }) 747 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"}) 748 c.Assert(err, check.IsNil) 749 c.Assert(rest, check.DeepEquals, []string{}) 750 c.Check(s.Stdout(), check.Equals, `name: hello 751 summary: The GNU Hello snap 752 publisher: Canonical* 753 license: unset 754 description: | 755 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 756 https://snapcraft.io/ 757 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 758 tracking: beta 759 refresh-date: 2006-01-02T22:04:07Z 760 installed: 2.10 (100) 1kB disabled 761 `) 762 c.Check(s.Stderr(), check.Equals, "") 763 } 764 765 func (s *infoSuite) TestInfoWithChannelsAndLocal(c *check.C) { 766 n := 0 767 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 768 switch n { 769 case 0, 2, 4: 770 c.Check(r.Method, check.Equals, "GET") 771 c.Check(r.URL.Path, check.Equals, "/v2/find") 772 fmt.Fprintln(w, mockInfoJSONWithChannels) 773 case 1, 3, 5: 774 c.Check(r.Method, check.Equals, "GET") 775 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 776 fmt.Fprintln(w, mockInfoJSONNoLicense) 777 default: 778 c.Fatalf("expected to get 6 requests, now on %d (%v)", n+1, r) 779 } 780 781 n++ 782 }) 783 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"}) 784 c.Assert(err, check.IsNil) 785 c.Assert(rest, check.DeepEquals, []string{}) 786 c.Check(s.Stdout(), check.Equals, `name: hello 787 summary: The GNU Hello snap 788 publisher: Canonical* 789 license: unset 790 description: | 791 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 792 https://snapcraft.io/ 793 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 794 tracking: beta 795 refresh-date: 2006-01-02T22:04:07Z 796 channels: 797 1/stable: 2.10 2018-12-18T15:16:56Z (1) 65kB - 798 1/candidate: ^ 799 1/beta: ^ 800 1/edge: ^ 801 installed: 2.10 (100) 1kB disabled 802 `) 803 c.Check(s.Stderr(), check.Equals, "") 804 c.Check(n, check.Equals, 2) 805 806 // now the same but without abs-time 807 s.ResetStdStreams() 808 rest, err = snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) 809 c.Assert(err, check.IsNil) 810 c.Assert(rest, check.DeepEquals, []string{}) 811 c.Check(s.Stdout(), check.Equals, `name: hello 812 summary: The GNU Hello snap 813 publisher: Canonical* 814 license: unset 815 description: | 816 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 817 https://snapcraft.io/ 818 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 819 tracking: beta 820 refresh-date: 2006-01-02 821 channels: 822 1/stable: 2.10 2018-12-18 (1) 65kB - 823 1/candidate: ^ 824 1/beta: ^ 825 1/edge: ^ 826 installed: 2.10 (100) 1kB disabled 827 `) 828 c.Check(s.Stderr(), check.Equals, "") 829 c.Check(n, check.Equals, 4) 830 831 // now the same but with unicode on 832 s.ResetStdStreams() 833 rest, err = snap.Parser(snap.Client()).ParseArgs([]string{"info", "--unicode=always", "hello"}) 834 c.Assert(err, check.IsNil) 835 c.Assert(rest, check.DeepEquals, []string{}) 836 c.Check(s.Stdout(), check.Equals, `name: hello 837 summary: The GNU Hello snap 838 publisher: Canonical✓ 839 license: unset 840 description: | 841 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 842 https://snapcraft.io/ 843 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 844 tracking: beta 845 refresh-date: 2006-01-02 846 channels: 847 1/stable: 2.10 2018-12-18 (1) 65kB - 848 1/candidate: ↑ 849 1/beta: ↑ 850 1/edge: ↑ 851 installed: 2.10 (100) 1kB disabled 852 `) 853 c.Check(s.Stderr(), check.Equals, "") 854 c.Check(n, check.Equals, 6) 855 } 856 857 func (s *infoSuite) TestInfoHumanTimes(c *check.C) { 858 // checks that tiemutil.Human is called when no --abs-time is given 859 restore := snap.MockTimeutilHuman(func(time.Time) string { return "TOTALLY NOT A ROBOT" }) 860 defer restore() 861 862 n := 0 863 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 864 switch n { 865 case 0: 866 c.Check(r.Method, check.Equals, "GET") 867 c.Check(r.URL.Path, check.Equals, "/v2/find") 868 fmt.Fprintln(w, "{}") 869 case 1: 870 c.Check(r.Method, check.Equals, "GET") 871 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 872 fmt.Fprintln(w, mockInfoJSONNoLicense) 873 default: 874 c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r) 875 } 876 877 n++ 878 }) 879 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) 880 c.Assert(err, check.IsNil) 881 c.Assert(rest, check.DeepEquals, []string{}) 882 c.Check(s.Stdout(), check.Equals, `name: hello 883 summary: The GNU Hello snap 884 publisher: Canonical* 885 license: unset 886 description: | 887 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 888 https://snapcraft.io/ 889 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 890 tracking: beta 891 refresh-date: TOTALLY NOT A ROBOT 892 installed: 2.10 (100) 1kB disabled 893 `) 894 c.Check(s.Stderr(), check.Equals, "") 895 } 896 897 func (infoSuite) TestDescr(c *check.C) { 898 for k, v := range map[string]string{ 899 "": " \n", 900 `one: 901 * two three four five six 902 * seven height nine ten 903 `: ` one: 904 * two three four 905 five six 906 * seven height 907 nine ten 908 `, 909 "abcdefghijklm nopqrstuvwxyz ABCDEFGHIJKLMNOPQR STUVWXYZ": ` 910 abcdefghijklm 911 nopqrstuvwxyz 912 ABCDEFGHIJKLMNOPQR 913 STUVWXYZ 914 `[1:], 915 // not much we can do when it won't fit 916 "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ": ` 917 abcdefghijklmnopqr 918 stuvwxyz 919 ABCDEFGHIJKLMNOPQR 920 STUVWXYZ 921 `[1:], 922 } { 923 var buf bytes.Buffer 924 snap.PrintDescr(&buf, k, 20) 925 c.Check(buf.String(), check.Equals, v, check.Commentf("%q", k)) 926 } 927 } 928 929 func (infoSuite) TestMaybePrintCohortKey(c *check.C) { 930 type T struct { 931 snap *client.Snap 932 verbose bool 933 expected string 934 } 935 936 tests := []T{ 937 {snap: nil, verbose: false, expected: ""}, 938 {snap: nil, verbose: true, expected: ""}, 939 {snap: &client.Snap{}, verbose: false, expected: ""}, 940 {snap: &client.Snap{}, verbose: true, expected: ""}, 941 {snap: &client.Snap{CohortKey: "some-cohort-key"}, verbose: false, expected: ""}, 942 {snap: &client.Snap{CohortKey: "some-cohort-key"}, verbose: true, expected: "cohort:\t…-key\n"}, 943 } 944 945 var buf flushBuffer 946 iw := snap.NewInfoWriter(&buf) 947 defer snap.MockIsStdoutTTY(true)() 948 949 for i, t := range tests { 950 buf.Reset() 951 snap.SetupSnap(iw, t.snap, nil, nil) 952 snap.SetVerbose(iw, t.verbose) 953 snap.MaybePrintCohortKey(iw) 954 c.Check(buf.String(), check.Equals, t.expected, check.Commentf("tty:true/%d", i)) 955 } 956 // now the same but without a tty -> the last test should no longer ellipt 957 tests[len(tests)-1].expected = "cohort:\tsome-cohort-key\n" 958 snap.MockIsStdoutTTY(false) 959 for i, t := range tests { 960 buf.Reset() 961 snap.SetupSnap(iw, t.snap, nil, nil) 962 snap.SetVerbose(iw, t.verbose) 963 snap.MaybePrintCohortKey(iw) 964 c.Check(buf.String(), check.Equals, t.expected, check.Commentf("tty:false/%d", i)) 965 } 966 } 967 968 func (infoSuite) TestMaybePrintHealth(c *check.C) { 969 type T struct { 970 snap *client.Snap 971 verbose bool 972 expected string 973 } 974 975 goodHealth := &client.SnapHealth{Status: "okay"} 976 t0 := time.Date(1970, 1, 1, 10, 24, 0, 0, time.UTC) 977 badHealth := &client.SnapHealth{ 978 Status: "waiting", 979 Message: "godot should be here any moment now", 980 Code: "godot-is-a-lie", 981 Revision: snaplib.R("42"), 982 Timestamp: t0, 983 } 984 985 tests := []T{ 986 {snap: nil, verbose: false, expected: ""}, 987 {snap: nil, verbose: true, expected: ""}, 988 {snap: &client.Snap{}, verbose: false, expected: ""}, 989 {snap: &client.Snap{}, verbose: true, expected: `health: 990 status: unknown 991 message: health 992 has not been set 993 `}, 994 {snap: &client.Snap{Health: goodHealth}, verbose: false, expected: ``}, 995 {snap: &client.Snap{Health: goodHealth}, verbose: true, expected: `health: 996 status: okay 997 `}, 998 {snap: &client.Snap{Health: badHealth}, verbose: false, expected: `health: 999 status: waiting 1000 message: godot 1001 should be here 1002 any moment now 1003 code: godot-is-a-lie 1004 checked: 10:24AM 1005 revision: 42 1006 `}, 1007 {snap: &client.Snap{Health: badHealth}, verbose: true, expected: `health: 1008 status: waiting 1009 message: godot 1010 should be here 1011 any moment now 1012 code: godot-is-a-lie 1013 checked: 10:24AM 1014 revision: 42 1015 `}, 1016 } 1017 1018 var buf flushBuffer 1019 iw := snap.NewInfoWriter(&buf) 1020 defer snap.MockIsStdoutTTY(false)() 1021 1022 for i, t := range tests { 1023 buf.Reset() 1024 snap.SetupSnap(iw, t.snap, nil, nil) 1025 snap.SetVerbose(iw, t.verbose) 1026 snap.MaybePrintHealth(iw) 1027 c.Check(buf.String(), check.Equals, t.expected, check.Commentf("%d", i)) 1028 } 1029 } 1030 1031 func (infoSuite) TestWrapCornerCase(c *check.C) { 1032 // this particular corner case isn't currently reachable from 1033 // printDescr nor printSummary, but best to have it covered 1034 var buf bytes.Buffer 1035 const s = "This is a paragraph indented with leading spaces that are encoded as multiple bytes. All hail EN SPACE." 1036 snap.WrapFlow(&buf, []rune(s), "\u2002\u2002", 30) 1037 c.Check(buf.String(), check.Equals, ` 1038 This is a paragraph indented 1039 with leading spaces that are 1040 encoded as multiple bytes. 1041 All hail EN SPACE. 1042 `[1:]) 1043 } 1044 1045 func (infoSuite) TestBug1828425(c *check.C) { 1046 const s = `This is a description 1047 that has 1048 lines 1049 too deeply 1050 indented. 1051 ` 1052 var buf bytes.Buffer 1053 err := snap.PrintDescr(&buf, s, 30) 1054 c.Assert(err, check.IsNil) 1055 c.Check(buf.String(), check.Equals, ` This is a description 1056 that has 1057 lines 1058 too deeply 1059 indented. 1060 `) 1061 } 1062 1063 const mockInfoJSONParallelInstance = ` 1064 { 1065 "type": "sync", 1066 "status-code": 200, 1067 "status": "OK", 1068 "result": { 1069 "channel": "stable", 1070 "confinement": "strict", 1071 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 1072 "developer": "canonical", 1073 "publisher": { 1074 "id": "canonical", 1075 "username": "canonical", 1076 "display-name": "Canonical", 1077 "validation": "verified" 1078 }, 1079 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 1080 "install-date": "2006-01-02T22:04:07.123456789Z", 1081 "installed-size": 1024, 1082 "name": "hello_foo", 1083 "private": false, 1084 "revision": "100", 1085 "status": "available", 1086 "summary": "The GNU Hello snap", 1087 "type": "app", 1088 "version": "2.10", 1089 "license": "", 1090 "tracking-channel": "beta" 1091 } 1092 } 1093 ` 1094 1095 func (s *infoSuite) TestInfoParllelInstance(c *check.C) { 1096 n := 0 1097 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1098 switch n { 1099 case 0: 1100 c.Check(r.Method, check.Equals, "GET") 1101 c.Check(r.URL.Path, check.Equals, "/v2/find") 1102 q := r.URL.Query() 1103 // asks for the instance snap 1104 c.Check(q.Get("name"), check.Equals, "hello") 1105 fmt.Fprintln(w, mockInfoJSONWithChannels) 1106 case 1: 1107 c.Check(r.Method, check.Equals, "GET") 1108 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello_foo") 1109 fmt.Fprintln(w, mockInfoJSONParallelInstance) 1110 default: 1111 c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r) 1112 } 1113 1114 n++ 1115 }) 1116 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello_foo"}) 1117 c.Assert(err, check.IsNil) 1118 c.Assert(rest, check.DeepEquals, []string{}) 1119 // make sure local and remote info is combined in the output 1120 c.Check(s.Stdout(), check.Equals, `name: hello_foo 1121 summary: The GNU Hello snap 1122 publisher: Canonical* 1123 license: unset 1124 description: | 1125 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 1126 https://snapcraft.io/ 1127 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 1128 tracking: beta 1129 refresh-date: 2006-01-02 1130 channels: 1131 1/stable: 2.10 2018-12-18 (1) 65kB - 1132 1/candidate: ^ 1133 1/beta: ^ 1134 1/edge: ^ 1135 installed: 2.10 (100) 1kB disabled 1136 `) 1137 c.Check(s.Stderr(), check.Equals, "") 1138 }