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