github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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, nil), 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, nil), 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 "mailto:joe@example.com": "contact:\tjoe@example.com\n", 341 // gofmt 1.9 being silly 342 "foo": "contact:\tfoo\n", 343 "": "", 344 } { 345 buf.Reset() 346 snap.SetupDiskSnap(iw, "", &client.Snap{Contact: contact}) 347 snap.MaybePrintContact(iw) 348 c.Check(buf.String(), check.Equals, expected, check.Commentf("%q", contact)) 349 } 350 } 351 352 func (s *infoSuite) TestMaybePrintBase(c *check.C) { 353 var buf flushBuffer 354 iw := snap.NewInfoWriter(&buf) 355 dSnap := &client.Snap{} 356 snap.SetupDiskSnap(iw, "", dSnap) 357 358 // no verbose -> no base 359 snap.SetVerbose(iw, false) 360 snap.MaybePrintBase(iw) 361 c.Check(buf.String(), check.Equals, "") 362 buf.Reset() 363 364 // no base -> no base :) 365 snap.SetVerbose(iw, true) 366 snap.MaybePrintBase(iw) 367 c.Check(buf.String(), check.Equals, "") 368 buf.Reset() 369 370 // base + verbose -> base 371 dSnap.Base = "xyzzy" 372 snap.MaybePrintBase(iw) 373 c.Check(buf.String(), check.Equals, "base:\txyzzy\n") 374 buf.Reset() 375 } 376 377 func (s *infoSuite) TestMaybePrintPath(c *check.C) { 378 var buf flushBuffer 379 iw := snap.NewInfoWriter(&buf) 380 dSnap := &client.Snap{} 381 382 // no path -> no path 383 snap.SetupDiskSnap(iw, "", dSnap) 384 snap.MaybePrintPath(iw) 385 c.Check(buf.String(), check.Equals, "") 386 buf.Reset() 387 388 // path -> path (quoted!) 389 snap.SetupDiskSnap(iw, "xyzzy", dSnap) 390 snap.MaybePrintPath(iw) 391 c.Check(buf.String(), check.Equals, "path:\t\"xyzzy\"\n") 392 buf.Reset() 393 } 394 395 func (s *infoSuite) TestClientSnapFromPath(c *check.C) { 396 // minimal sanity check 397 fn := snaptest.MakeTestSnapWithFiles(c, ` 398 name: some-snap 399 version: 9 400 `, nil) 401 dSnap, err := snap.ClientSnapFromPath(fn) 402 c.Assert(err, check.IsNil) 403 c.Check(dSnap.Version, check.Equals, "9") 404 } 405 406 func (s *infoSuite) TestInfoPricedNarrowTerminal(c *check.C) { 407 defer snap.MockTermSize(func() (int, int) { return 44, 25 })() 408 409 n := 0 410 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 411 switch n { 412 case 0: 413 c.Check(r.Method, check.Equals, "GET") 414 c.Check(r.URL.Path, check.Equals, "/v2/find") 415 fmt.Fprintln(w, findPricedJSON) 416 case 1: 417 c.Check(r.Method, check.Equals, "GET") 418 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 419 fmt.Fprintln(w, "{}") 420 default: 421 c.Fatalf("expected to get 1 requests, now on %d (%v)", n+1, r) 422 } 423 424 n++ 425 }) 426 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) 427 c.Assert(err, check.IsNil) 428 c.Assert(rest, check.DeepEquals, []string{}) 429 c.Check(s.Stdout(), check.Equals, ` 430 name: hello 431 summary: GNU Hello, the "hello world" 432 snap 433 publisher: Canonical* 434 license: Proprietary 435 price: 1.99GBP 436 description: | 437 GNU hello prints a friendly greeting. 438 This is part of the snapcraft tour at 439 https://snapcraft.io/ 440 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 441 `[1:]) 442 c.Check(s.Stderr(), check.Equals, "") 443 } 444 445 func (s *infoSuite) TestInfoPriced(c *check.C) { 446 n := 0 447 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 448 switch n { 449 case 0: 450 c.Check(r.Method, check.Equals, "GET") 451 c.Check(r.URL.Path, check.Equals, "/v2/find") 452 fmt.Fprintln(w, findPricedJSON) 453 case 1: 454 c.Check(r.Method, check.Equals, "GET") 455 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 456 fmt.Fprintln(w, "{}") 457 default: 458 c.Fatalf("expected to get 1 requests, now on %d (%v)", n+1, r) 459 } 460 461 n++ 462 }) 463 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) 464 c.Assert(err, check.IsNil) 465 c.Assert(rest, check.DeepEquals, []string{}) 466 c.Check(s.Stdout(), check.Equals, `name: hello 467 summary: GNU Hello, the "hello world" snap 468 publisher: Canonical* 469 license: Proprietary 470 price: 1.99GBP 471 description: | 472 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 473 https://snapcraft.io/ 474 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 475 `) 476 c.Check(s.Stderr(), check.Equals, "") 477 } 478 479 // only used for results on /v2/find 480 const mockInfoJSON = ` 481 { 482 "type": "sync", 483 "status-code": 200, 484 "status": "OK", 485 "result": [ 486 { 487 "channel": "stable", 488 "confinement": "strict", 489 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 490 "developer": "canonical", 491 "publisher": { 492 "id": "canonical", 493 "username": "canonical", 494 "display-name": "Canonical", 495 "validation": "verified" 496 }, 497 "download-size": 65536, 498 "icon": "", 499 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 500 "name": "hello", 501 "private": false, 502 "resource": "/v2/snaps/hello", 503 "revision": "1", 504 "status": "available", 505 "summary": "The GNU Hello snap", 506 "type": "app", 507 "version": "2.10", 508 "license": "MIT" 509 } 510 ], 511 "sources": [ 512 "store" 513 ], 514 "suggested-currency": "GBP" 515 } 516 ` 517 518 const mockInfoJSONWithChannels = ` 519 { 520 "type": "sync", 521 "status-code": 200, 522 "status": "OK", 523 "result": [ 524 { 525 "channel": "stable", 526 "confinement": "strict", 527 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 528 "developer": "canonical", 529 "publisher": { 530 "id": "canonical", 531 "username": "canonical", 532 "display-name": "Canonical", 533 "validation": "verified" 534 }, 535 "download-size": 65536, 536 "icon": "", 537 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 538 "name": "hello", 539 "private": false, 540 "resource": "/v2/snaps/hello", 541 "revision": "1", 542 "status": "available", 543 "summary": "The GNU Hello snap", 544 "store-url": "https://snapcraft.io/hello", 545 "type": "app", 546 "version": "2.10", 547 "license": "MIT", 548 "channels": { 549 "1/stable": { 550 "revision": "1", 551 "version": "2.10", 552 "channel": "1/stable", 553 "size": 65536, 554 "released-at": "2018-12-18T15:16:56.723501Z" 555 } 556 }, 557 "tracks": ["1"] 558 } 559 ], 560 "sources": [ 561 "store" 562 ], 563 "suggested-currency": "GBP" 564 } 565 ` 566 567 func (s *infoSuite) TestInfoUnquoted(c *check.C) { 568 n := 0 569 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 570 switch n { 571 case 0: 572 c.Check(r.Method, check.Equals, "GET") 573 c.Check(r.URL.Path, check.Equals, "/v2/find") 574 fmt.Fprintln(w, mockInfoJSON) 575 case 1: 576 c.Check(r.Method, check.Equals, "GET") 577 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 578 fmt.Fprintln(w, "{}") 579 default: 580 c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r) 581 } 582 583 n++ 584 }) 585 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) 586 c.Assert(err, check.IsNil) 587 c.Assert(rest, check.DeepEquals, []string{}) 588 c.Check(s.Stdout(), check.Equals, `name: hello 589 summary: The GNU Hello snap 590 publisher: Canonical* 591 license: MIT 592 description: | 593 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 594 https://snapcraft.io/ 595 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 596 `) 597 c.Check(s.Stderr(), check.Equals, "") 598 } 599 600 // only used for /v2/snaps/hello 601 const mockInfoJSONOtherLicense = ` 602 { 603 "type": "sync", 604 "status-code": 200, 605 "status": "OK", 606 "result": { 607 "channel": "stable", 608 "confinement": "strict", 609 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 610 "developer": "canonical", 611 "publisher": { 612 "id": "canonical", 613 "username": "canonical", 614 "display-name": "Canonical", 615 "validation": "verified" 616 }, 617 "health": {"revision": "1", "status": "blocked", "message": "please configure the grawflit", "timestamp": "2019-05-13T16:27:01.475851677+01:00"}, 618 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 619 "install-date": "2006-01-02T22:04:07.123456789Z", 620 "installed-size": 1024, 621 "name": "hello", 622 "private": false, 623 "resource": "/v2/snaps/hello", 624 "revision": "1", 625 "status": "available", 626 "summary": "The GNU Hello snap", 627 "type": "app", 628 "version": "2.10", 629 "license": "BSD-3", 630 "tracking-channel": "beta" 631 } 632 } 633 ` 634 const mockInfoJSONNoLicense = ` 635 { 636 "type": "sync", 637 "status-code": 200, 638 "status": "OK", 639 "result": { 640 "channel": "stable", 641 "confinement": "strict", 642 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 643 "developer": "canonical", 644 "publisher": { 645 "id": "canonical", 646 "username": "canonical", 647 "display-name": "Canonical", 648 "validation": "verified" 649 }, 650 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 651 "install-date": "2006-01-02T22:04:07.123456789Z", 652 "installed-size": 1024, 653 "name": "hello", 654 "private": false, 655 "resource": "/v2/snaps/hello", 656 "revision": "100", 657 "status": "available", 658 "summary": "The GNU Hello snap", 659 "type": "app", 660 "version": "2.10", 661 "license": "", 662 "tracking-channel": "beta" 663 } 664 } 665 ` 666 667 func (s *infoSuite) TestInfoWithLocalDifferentLicense(c *check.C) { 668 n := 0 669 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 670 switch n { 671 case 0: 672 c.Check(r.Method, check.Equals, "GET") 673 c.Check(r.URL.Path, check.Equals, "/v2/find") 674 fmt.Fprintln(w, mockInfoJSON) 675 case 1: 676 c.Check(r.Method, check.Equals, "GET") 677 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 678 fmt.Fprintln(w, mockInfoJSONOtherLicense) 679 default: 680 c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r) 681 } 682 683 n++ 684 }) 685 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"}) 686 c.Assert(err, check.IsNil) 687 c.Assert(rest, check.DeepEquals, []string{}) 688 c.Check(s.Stdout(), check.Equals, ` 689 name: hello 690 summary: The GNU Hello snap 691 health: 692 status: blocked 693 message: please configure the grawflit 694 checked: 2019-05-13T16:27:01+01:00 695 revision: 1 696 publisher: Canonical* 697 license: BSD-3 698 description: | 699 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 700 https://snapcraft.io/ 701 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 702 tracking: beta 703 refresh-date: 2006-01-02T22:04:07Z 704 installed: 2.10 (1) 1kB disabled,blocked 705 `[1:]) 706 c.Check(s.Stderr(), check.Equals, "") 707 } 708 709 func (s *infoSuite) TestInfoNotFound(c *check.C) { 710 n := 0 711 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 712 switch n % 2 { 713 case 0: 714 c.Check(r.Method, check.Equals, "GET") 715 c.Check(r.URL.Path, check.Equals, "/v2/find") 716 case 1: 717 c.Check(r.Method, check.Equals, "GET") 718 c.Check(r.URL.Path, check.Equals, "/v2/snaps/x") 719 } 720 w.WriteHeader(404) 721 fmt.Fprintln(w, `{"type":"error","status-code":404,"status":"Not Found","result":{"message":"No.","kind":"snap-not-found","value":"x"}}`) 722 723 n++ 724 }) 725 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--verbose", "/x"}) 726 c.Check(err, check.ErrorMatches, `no snap found for "/x"`) 727 c.Check(s.Stdout(), check.Equals, "") 728 c.Check(s.Stderr(), check.Equals, "") 729 } 730 731 func (s *infoSuite) TestInfoWithLocalNoLicense(c *check.C) { 732 n := 0 733 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 734 switch n { 735 case 0: 736 c.Check(r.Method, check.Equals, "GET") 737 c.Check(r.URL.Path, check.Equals, "/v2/find") 738 fmt.Fprintln(w, mockInfoJSON) 739 case 1: 740 c.Check(r.Method, check.Equals, "GET") 741 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 742 fmt.Fprintln(w, mockInfoJSONNoLicense) 743 default: 744 c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r) 745 } 746 747 n++ 748 }) 749 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"}) 750 c.Assert(err, check.IsNil) 751 c.Assert(rest, check.DeepEquals, []string{}) 752 c.Check(s.Stdout(), check.Equals, `name: hello 753 summary: The GNU Hello snap 754 publisher: Canonical* 755 license: unset 756 description: | 757 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 758 https://snapcraft.io/ 759 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 760 tracking: beta 761 refresh-date: 2006-01-02T22:04:07Z 762 installed: 2.10 (100) 1kB disabled 763 `) 764 c.Check(s.Stderr(), check.Equals, "") 765 } 766 767 func (s *infoSuite) TestInfoWithChannelsAndLocal(c *check.C) { 768 n := 0 769 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 770 switch n { 771 case 0, 2, 4: 772 c.Check(r.Method, check.Equals, "GET") 773 c.Check(r.URL.Path, check.Equals, "/v2/find") 774 fmt.Fprintln(w, mockInfoJSONWithChannels) 775 case 1, 3, 5: 776 c.Check(r.Method, check.Equals, "GET") 777 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 778 fmt.Fprintln(w, mockInfoJSONNoLicense) 779 default: 780 c.Fatalf("expected to get 6 requests, now on %d (%v)", n+1, r) 781 } 782 783 n++ 784 }) 785 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"}) 786 c.Assert(err, check.IsNil) 787 c.Assert(rest, check.DeepEquals, []string{}) 788 c.Check(s.Stdout(), check.Equals, `name: hello 789 summary: The GNU Hello snap 790 publisher: Canonical* 791 store-url: https://snapcraft.io/hello 792 license: unset 793 description: | 794 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 795 https://snapcraft.io/ 796 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 797 tracking: beta 798 refresh-date: 2006-01-02T22:04:07Z 799 channels: 800 1/stable: 2.10 2018-12-18T15:16:56Z (1) 65kB - 801 1/candidate: ^ 802 1/beta: ^ 803 1/edge: ^ 804 installed: 2.10 (100) 1kB disabled 805 `) 806 c.Check(s.Stderr(), check.Equals, "") 807 c.Check(n, check.Equals, 2) 808 809 // now the same but without abs-time 810 s.ResetStdStreams() 811 rest, err = snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) 812 c.Assert(err, check.IsNil) 813 c.Assert(rest, check.DeepEquals, []string{}) 814 c.Check(s.Stdout(), check.Equals, `name: hello 815 summary: The GNU Hello snap 816 publisher: Canonical* 817 store-url: https://snapcraft.io/hello 818 license: unset 819 description: | 820 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 821 https://snapcraft.io/ 822 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 823 tracking: beta 824 refresh-date: 2006-01-02 825 channels: 826 1/stable: 2.10 2018-12-18 (1) 65kB - 827 1/candidate: ^ 828 1/beta: ^ 829 1/edge: ^ 830 installed: 2.10 (100) 1kB disabled 831 `) 832 c.Check(s.Stderr(), check.Equals, "") 833 c.Check(n, check.Equals, 4) 834 835 // now the same but with unicode on 836 s.ResetStdStreams() 837 rest, err = snap.Parser(snap.Client()).ParseArgs([]string{"info", "--unicode=always", "hello"}) 838 c.Assert(err, check.IsNil) 839 c.Assert(rest, check.DeepEquals, []string{}) 840 c.Check(s.Stdout(), check.Equals, `name: hello 841 summary: The GNU Hello snap 842 publisher: Canonical✓ 843 store-url: https://snapcraft.io/hello 844 license: unset 845 description: | 846 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 847 https://snapcraft.io/ 848 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 849 tracking: beta 850 refresh-date: 2006-01-02 851 channels: 852 1/stable: 2.10 2018-12-18 (1) 65kB - 853 1/candidate: ↑ 854 1/beta: ↑ 855 1/edge: ↑ 856 installed: 2.10 (100) 1kB disabled 857 `) 858 c.Check(s.Stderr(), check.Equals, "") 859 c.Check(n, check.Equals, 6) 860 } 861 862 func (s *infoSuite) TestInfoHumanTimes(c *check.C) { 863 // checks that tiemutil.Human is called when no --abs-time is given 864 restore := snap.MockTimeutilHuman(func(time.Time) string { return "TOTALLY NOT A ROBOT" }) 865 defer restore() 866 867 n := 0 868 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 869 switch n { 870 case 0: 871 c.Check(r.Method, check.Equals, "GET") 872 c.Check(r.URL.Path, check.Equals, "/v2/find") 873 fmt.Fprintln(w, "{}") 874 case 1: 875 c.Check(r.Method, check.Equals, "GET") 876 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 877 fmt.Fprintln(w, mockInfoJSONNoLicense) 878 default: 879 c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r) 880 } 881 882 n++ 883 }) 884 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) 885 c.Assert(err, check.IsNil) 886 c.Assert(rest, check.DeepEquals, []string{}) 887 c.Check(s.Stdout(), check.Equals, `name: hello 888 summary: The GNU Hello snap 889 publisher: Canonical* 890 license: unset 891 description: | 892 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 893 https://snapcraft.io/ 894 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 895 tracking: beta 896 refresh-date: TOTALLY NOT A ROBOT 897 installed: 2.10 (100) 1kB disabled 898 `) 899 c.Check(s.Stderr(), check.Equals, "") 900 } 901 902 func (infoSuite) TestDescr(c *check.C) { 903 for k, v := range map[string]string{ 904 "": " \n", 905 `one: 906 * two three four five six 907 * seven height nine ten 908 `: ` one: 909 * two three four 910 five six 911 * seven height 912 nine ten 913 `, 914 "abcdefghijklm nopqrstuvwxyz ABCDEFGHIJKLMNOPQR STUVWXYZ": ` 915 abcdefghijklm 916 nopqrstuvwxyz 917 ABCDEFGHIJKLMNOPQR 918 STUVWXYZ 919 `[1:], 920 // not much we can do when it won't fit 921 "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ": ` 922 abcdefghijklmnopqr 923 stuvwxyz 924 ABCDEFGHIJKLMNOPQR 925 STUVWXYZ 926 `[1:], 927 } { 928 var buf bytes.Buffer 929 snap.PrintDescr(&buf, k, 20) 930 c.Check(buf.String(), check.Equals, v, check.Commentf("%q", k)) 931 } 932 } 933 934 func (infoSuite) TestMaybePrintCohortKey(c *check.C) { 935 type T struct { 936 snap *client.Snap 937 verbose bool 938 expected string 939 } 940 941 tests := []T{ 942 {snap: nil, verbose: false, expected: ""}, 943 {snap: nil, verbose: true, expected: ""}, 944 {snap: &client.Snap{}, verbose: false, expected: ""}, 945 {snap: &client.Snap{}, verbose: true, expected: ""}, 946 {snap: &client.Snap{CohortKey: "some-cohort-key"}, verbose: false, expected: ""}, 947 {snap: &client.Snap{CohortKey: "some-cohort-key"}, verbose: true, expected: "cohort:\t…-key\n"}, 948 } 949 950 var buf flushBuffer 951 iw := snap.NewInfoWriter(&buf) 952 defer snap.MockIsStdoutTTY(true)() 953 954 for i, t := range tests { 955 buf.Reset() 956 snap.SetupSnap(iw, t.snap, nil, nil) 957 snap.SetVerbose(iw, t.verbose) 958 snap.MaybePrintCohortKey(iw) 959 c.Check(buf.String(), check.Equals, t.expected, check.Commentf("tty:true/%d", i)) 960 } 961 // now the same but without a tty -> the last test should no longer ellipt 962 tests[len(tests)-1].expected = "cohort:\tsome-cohort-key\n" 963 snap.MockIsStdoutTTY(false) 964 for i, t := range tests { 965 buf.Reset() 966 snap.SetupSnap(iw, t.snap, nil, nil) 967 snap.SetVerbose(iw, t.verbose) 968 snap.MaybePrintCohortKey(iw) 969 c.Check(buf.String(), check.Equals, t.expected, check.Commentf("tty:false/%d", i)) 970 } 971 } 972 973 func (infoSuite) TestMaybePrintHealth(c *check.C) { 974 type T struct { 975 snap *client.Snap 976 verbose bool 977 expected string 978 } 979 980 goodHealth := &client.SnapHealth{Status: "okay"} 981 t0 := time.Date(1970, 1, 1, 10, 24, 0, 0, time.UTC) 982 badHealth := &client.SnapHealth{ 983 Status: "waiting", 984 Message: "godot should be here any moment now", 985 Code: "godot-is-a-lie", 986 Revision: snaplib.R("42"), 987 Timestamp: t0, 988 } 989 990 tests := []T{ 991 {snap: nil, verbose: false, expected: ""}, 992 {snap: nil, verbose: true, expected: ""}, 993 {snap: &client.Snap{}, verbose: false, expected: ""}, 994 {snap: &client.Snap{}, verbose: true, expected: `health: 995 status: unknown 996 message: health 997 has not been set 998 `}, 999 {snap: &client.Snap{Health: goodHealth}, verbose: false, expected: ``}, 1000 {snap: &client.Snap{Health: goodHealth}, verbose: true, expected: `health: 1001 status: okay 1002 `}, 1003 {snap: &client.Snap{Health: badHealth}, verbose: false, expected: `health: 1004 status: waiting 1005 message: godot 1006 should be here 1007 any moment now 1008 code: godot-is-a-lie 1009 checked: 10:24AM 1010 revision: 42 1011 `}, 1012 {snap: &client.Snap{Health: badHealth}, verbose: true, expected: `health: 1013 status: waiting 1014 message: godot 1015 should be here 1016 any moment now 1017 code: godot-is-a-lie 1018 checked: 10:24AM 1019 revision: 42 1020 `}, 1021 } 1022 1023 var buf flushBuffer 1024 iw := snap.NewInfoWriter(&buf) 1025 defer snap.MockIsStdoutTTY(false)() 1026 1027 for i, t := range tests { 1028 buf.Reset() 1029 snap.SetupSnap(iw, t.snap, nil, nil) 1030 snap.SetVerbose(iw, t.verbose) 1031 snap.MaybePrintHealth(iw) 1032 c.Check(buf.String(), check.Equals, t.expected, check.Commentf("%d", i)) 1033 } 1034 } 1035 1036 func (infoSuite) TestWrapCornerCase(c *check.C) { 1037 // this particular corner case isn't currently reachable from 1038 // printDescr nor printSummary, but best to have it covered 1039 var buf bytes.Buffer 1040 const s = "This is a paragraph indented with leading spaces that are encoded as multiple bytes. All hail EN SPACE." 1041 snap.WrapFlow(&buf, []rune(s), "\u2002\u2002", 30) 1042 c.Check(buf.String(), check.Equals, ` 1043 This is a paragraph indented 1044 with leading spaces that are 1045 encoded as multiple bytes. 1046 All hail EN SPACE. 1047 `[1:]) 1048 } 1049 1050 func (infoSuite) TestBug1828425(c *check.C) { 1051 const s = `This is a description 1052 that has 1053 lines 1054 too deeply 1055 indented. 1056 ` 1057 var buf bytes.Buffer 1058 err := snap.PrintDescr(&buf, s, 30) 1059 c.Assert(err, check.IsNil) 1060 c.Check(buf.String(), check.Equals, ` This is a description 1061 that has 1062 lines 1063 too deeply 1064 indented. 1065 `) 1066 } 1067 1068 const mockInfoJSONParallelInstance = ` 1069 { 1070 "type": "sync", 1071 "status-code": 200, 1072 "status": "OK", 1073 "result": { 1074 "channel": "stable", 1075 "confinement": "strict", 1076 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 1077 "developer": "canonical", 1078 "publisher": { 1079 "id": "canonical", 1080 "username": "canonical", 1081 "display-name": "Canonical", 1082 "validation": "verified" 1083 }, 1084 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 1085 "install-date": "2006-01-02T22:04:07.123456789Z", 1086 "installed-size": 1024, 1087 "name": "hello_foo", 1088 "private": false, 1089 "revision": "100", 1090 "status": "available", 1091 "summary": "The GNU Hello snap", 1092 "type": "app", 1093 "version": "2.10", 1094 "license": "", 1095 "tracking-channel": "beta" 1096 } 1097 } 1098 ` 1099 1100 func (s *infoSuite) TestInfoParllelInstance(c *check.C) { 1101 n := 0 1102 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1103 switch n { 1104 case 0: 1105 c.Check(r.Method, check.Equals, "GET") 1106 c.Check(r.URL.Path, check.Equals, "/v2/find") 1107 q := r.URL.Query() 1108 // asks for the instance snap 1109 c.Check(q.Get("name"), check.Equals, "hello") 1110 fmt.Fprintln(w, mockInfoJSONWithChannels) 1111 case 1: 1112 c.Check(r.Method, check.Equals, "GET") 1113 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello_foo") 1114 fmt.Fprintln(w, mockInfoJSONParallelInstance) 1115 default: 1116 c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r) 1117 } 1118 1119 n++ 1120 }) 1121 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello_foo"}) 1122 c.Assert(err, check.IsNil) 1123 c.Assert(rest, check.DeepEquals, []string{}) 1124 // make sure local and remote info is combined in the output 1125 c.Check(s.Stdout(), check.Equals, `name: hello_foo 1126 summary: The GNU Hello snap 1127 publisher: Canonical* 1128 store-url: https://snapcraft.io/hello 1129 license: unset 1130 description: | 1131 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 1132 https://snapcraft.io/ 1133 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 1134 tracking: beta 1135 refresh-date: 2006-01-02 1136 channels: 1137 1/stable: 2.10 2018-12-18 (1) 65kB - 1138 1/candidate: ^ 1139 1/beta: ^ 1140 1/edge: ^ 1141 installed: 2.10 (100) 1kB disabled 1142 `) 1143 c.Check(s.Stderr(), check.Equals, "") 1144 } 1145 1146 const mockInfoJSONWithStoreURL = ` 1147 { 1148 "type": "sync", 1149 "status-code": 200, 1150 "status": "OK", 1151 "result": { 1152 "channel": "stable", 1153 "confinement": "strict", 1154 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 1155 "developer": "canonical", 1156 "publisher": { 1157 "id": "canonical", 1158 "username": "canonical", 1159 "display-name": "Canonical", 1160 "validation": "verified" 1161 }, 1162 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 1163 "install-date": "2006-01-02T22:04:07.123456789Z", 1164 "installed-size": 1024, 1165 "name": "hello", 1166 "private": false, 1167 "revision": "100", 1168 "status": "available", 1169 "store-url": "https://snapcraft.io/hello", 1170 "summary": "The GNU Hello snap", 1171 "type": "app", 1172 "version": "2.10", 1173 "license": "", 1174 "tracking-channel": "beta" 1175 } 1176 } 1177 ` 1178 1179 func (s *infoSuite) TestInfoStoreURL(c *check.C) { 1180 n := 0 1181 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1182 switch n { 1183 case 0: 1184 c.Check(r.Method, check.Equals, "GET") 1185 c.Check(r.URL.Path, check.Equals, "/v2/find") 1186 q := r.URL.Query() 1187 // asks for the instance snap 1188 c.Check(q.Get("name"), check.Equals, "hello") 1189 fmt.Fprintln(w, mockInfoJSONWithChannels) 1190 case 1: 1191 c.Check(r.Method, check.Equals, "GET") 1192 c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") 1193 fmt.Fprintln(w, mockInfoJSONWithStoreURL) 1194 default: 1195 c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r) 1196 } 1197 1198 n++ 1199 }) 1200 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) 1201 c.Assert(err, check.IsNil) 1202 c.Assert(rest, check.DeepEquals, []string{}) 1203 // make sure local and remote info is combined in the output 1204 c.Check(s.Stdout(), check.Equals, `name: hello 1205 summary: The GNU Hello snap 1206 publisher: Canonical* 1207 store-url: https://snapcraft.io/hello 1208 license: unset 1209 description: | 1210 GNU hello prints a friendly greeting. This is part of the snapcraft tour at 1211 https://snapcraft.io/ 1212 snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 1213 tracking: beta 1214 refresh-date: 2006-01-02 1215 channels: 1216 1/stable: 2.10 2018-12-18 (1) 65kB - 1217 1/candidate: ^ 1218 1/beta: ^ 1219 1/edge: ^ 1220 installed: 2.10 (100) 1kB disabled 1221 `) 1222 c.Check(s.Stderr(), check.Equals, "") 1223 }