github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap/cmd_find_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 "fmt" 24 "io/ioutil" 25 "net/http" 26 "os" 27 "path" 28 29 "github.com/jessevdk/go-flags" 30 "gopkg.in/check.v1" 31 32 snap "github.com/snapcore/snapd/cmd/snap" 33 "github.com/snapcore/snapd/dirs" 34 ) 35 36 const findJSON = ` 37 { 38 "type": "sync", 39 "status-code": 200, 40 "status": "OK", 41 "result": [ 42 { 43 "channel": "stable", 44 "confinement": "strict", 45 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 46 "developer": "canonical", 47 "publisher": { 48 "id": "canonical", 49 "username": "canonical", 50 "display-name": "Canonical", 51 "validation": "verified" 52 }, 53 "download-size": 65536, 54 "icon": "", 55 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 56 "name": "hello", 57 "private": false, 58 "resource": "/v2/snaps/hello", 59 "revision": "1", 60 "status": "available", 61 "summary": "GNU Hello, the \"hello world\" snap", 62 "type": "app", 63 "version": "2.10" 64 }, 65 { 66 "channel": "stable", 67 "confinement": "strict", 68 "description": "This is a simple hello world example.", 69 "developer": "canonical", 70 "publisher": { 71 "id": "canonical", 72 "username": "canonical", 73 "display-name": "Canonical", 74 "validation": "verified" 75 }, 76 "download-size": 20480, 77 "icon": "", 78 "id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ", 79 "name": "hello-world", 80 "private": false, 81 "resource": "/v2/snaps/hello-world", 82 "revision": "26", 83 "status": "available", 84 "summary": "Hello world example", 85 "type": "app", 86 "version": "6.1" 87 }, 88 { 89 "channel": "stable", 90 "confinement": "strict", 91 "description": "1.0GB", 92 "developer": "noise", 93 "publisher": { 94 "id": "noise-id", 95 "username": "noise", 96 "display-name": "Bret", 97 "validation": "unproven" 98 }, 99 "download-size": 512004096, 100 "icon": "", 101 "id": "asXOGCreK66DIAdyXmucwspTMgqA4rne", 102 "name": "hello-huge", 103 "private": false, 104 "resource": "/v2/snaps/hello-huge", 105 "revision": "1", 106 "status": "available", 107 "summary": "a really big snap", 108 "type": "app", 109 "version": "1.0" 110 } 111 ], 112 "sources": [ 113 "store" 114 ], 115 "suggested-currency": "GBP" 116 } 117 ` 118 119 func (s *SnapSuite) TestFindSnapName(c *check.C) { 120 n := 0 121 122 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 123 switch n { 124 case 0: 125 c.Check(r.Method, check.Equals, "GET") 126 c.Check(r.URL.Path, check.Equals, "/v2/find") 127 q := r.URL.Query() 128 if q.Get("q") == "" { 129 v, ok := q["section"] 130 c.Check(ok, check.Equals, true) 131 c.Check(v, check.DeepEquals, []string{""}) 132 } 133 fmt.Fprintln(w, findJSON) 134 default: 135 c.Fatalf("expected to get 2 requests, now on %d", n+1) 136 } 137 n++ 138 }) 139 140 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"}) 141 142 c.Assert(err, check.IsNil) 143 c.Assert(rest, check.DeepEquals, []string{}) 144 145 c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary 146 hello +2.10 +canonical\* +- +GNU Hello, the "hello world" snap 147 hello-world +6.1 +canonical\* +- +Hello world example 148 hello-huge +1.0 +noise +- +a really big snap 149 `) 150 c.Check(s.Stderr(), check.Equals, "") 151 152 s.ResetStdStreams() 153 } 154 155 const findHelloJSON = ` 156 { 157 "type": "sync", 158 "status-code": 200, 159 "status": "OK", 160 "result": [ 161 { 162 "channel": "stable", 163 "confinement": "strict", 164 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 165 "developer": "canonical", 166 "publisher": { 167 "id": "canonical", 168 "username": "canonical", 169 "display-name": "Canonical", 170 "validation": "verified" 171 }, 172 "download-size": 65536, 173 "icon": "", 174 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 175 "name": "hello", 176 "private": false, 177 "resource": "/v2/snaps/hello", 178 "revision": "1", 179 "status": "available", 180 "summary": "GNU Hello, the \"hello world\" snap", 181 "type": "app", 182 "version": "2.10" 183 }, 184 { 185 "channel": "stable", 186 "confinement": "strict", 187 "description": "1.0GB", 188 "developer": "noise", 189 "publisher": { 190 "id": "noise-id", 191 "username": "noise", 192 "display-name": "Bret", 193 "validation": "unproven" 194 }, 195 "download-size": 512004096, 196 "icon": "", 197 "id": "asXOGCreK66DIAdyXmucwspTMgqA4rne", 198 "name": "hello-huge", 199 "private": false, 200 "resource": "/v2/snaps/hello-huge", 201 "revision": "1", 202 "status": "available", 203 "summary": "a really big snap", 204 "type": "app", 205 "version": "1.0" 206 } 207 ], 208 "sources": [ 209 "store" 210 ], 211 "suggested-currency": "GBP" 212 } 213 ` 214 215 func (s *SnapSuite) TestFindHello(c *check.C) { 216 n := 0 217 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 218 switch n { 219 case 0: 220 c.Check(r.Method, check.Equals, "GET") 221 c.Check(r.URL.Path, check.Equals, "/v2/find") 222 q := r.URL.Query() 223 c.Check(q, check.HasLen, 2) 224 c.Check(q.Get("q"), check.Equals, "hello") 225 c.Check(q.Get("scope"), check.Equals, "wide") 226 fmt.Fprintln(w, findHelloJSON) 227 default: 228 c.Fatalf("expected to get 1 requests, now on %d", n+1) 229 } 230 231 n++ 232 }) 233 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"}) 234 c.Assert(err, check.IsNil) 235 c.Assert(rest, check.DeepEquals, []string{}) 236 c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary 237 hello +2.10 +canonical\* +- +GNU Hello, the "hello world" snap 238 hello-huge +1.0 +noise +- +a really big snap 239 `) 240 c.Check(s.Stderr(), check.Equals, "") 241 } 242 243 func (s *SnapSuite) TestFindHelloNarrow(c *check.C) { 244 n := 0 245 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 246 switch n { 247 case 0: 248 c.Check(r.Method, check.Equals, "GET") 249 c.Check(r.URL.Path, check.Equals, "/v2/find") 250 q := r.URL.Query() 251 c.Check(q, check.HasLen, 1) 252 c.Check(q.Get("q"), check.Equals, "hello") 253 fmt.Fprintln(w, findHelloJSON) 254 default: 255 c.Fatalf("expected to get 1 requests, now on %d", n+1) 256 } 257 258 n++ 259 }) 260 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--narrow", "hello"}) 261 c.Assert(err, check.IsNil) 262 c.Assert(rest, check.DeepEquals, []string{}) 263 c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary 264 hello +2.10 +canonical\* +- +GNU Hello, the "hello world" snap 265 hello-huge +1.0 +noise +- +a really big snap 266 `) 267 c.Check(s.Stderr(), check.Equals, "") 268 } 269 270 const findPricedJSON = ` 271 { 272 "type": "sync", 273 "status-code": 200, 274 "status": "OK", 275 "result": [ 276 { 277 "channel": "stable", 278 "confinement": "strict", 279 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 280 "developer": "canonical", 281 "publisher": { 282 "id": "canonical", 283 "username": "canonical", 284 "display-name": "Canonical", 285 "validation": "verified" 286 }, 287 "download-size": 65536, 288 "icon": "", 289 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 290 "name": "hello", 291 "prices": {"GBP": 1.99, "USD": 2.99}, 292 "private": false, 293 "resource": "/v2/snaps/hello", 294 "revision": "1", 295 "status": "priced", 296 "summary": "GNU Hello, the \"hello world\" snap", 297 "type": "app", 298 "version": "2.10", 299 "license": "Proprietary" 300 } 301 ], 302 "sources": [ 303 "store" 304 ], 305 "suggested-currency": "GBP" 306 } 307 ` 308 309 func (s *SnapSuite) TestFindPriced(c *check.C) { 310 n := 0 311 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 312 switch n { 313 case 0: 314 c.Check(r.Method, check.Equals, "GET") 315 c.Check(r.URL.Path, check.Equals, "/v2/find") 316 fmt.Fprintln(w, findPricedJSON) 317 default: 318 c.Fatalf("expected to get 1 requests, now on %d", n+1) 319 } 320 321 n++ 322 }) 323 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"}) 324 c.Assert(err, check.IsNil) 325 c.Assert(rest, check.DeepEquals, []string{}) 326 c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary 327 hello +2.10 +canonical\* +1.99GBP +GNU Hello, the "hello world" snap 328 `) 329 c.Check(s.Stderr(), check.Equals, "") 330 } 331 332 const findPricedAndBoughtJSON = ` 333 { 334 "type": "sync", 335 "status-code": 200, 336 "status": "OK", 337 "result": [ 338 { 339 "channel": "stable", 340 "confinement": "strict", 341 "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", 342 "developer": "canonical", 343 "publisher": { 344 "id": "canonical", 345 "username": "canonical", 346 "display-name": "Canonical", 347 "validation": "verified" 348 }, 349 "download-size": 65536, 350 "icon": "", 351 "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", 352 "name": "hello", 353 "prices": {"GBP": 1.99, "USD": 2.99}, 354 "private": false, 355 "resource": "/v2/snaps/hello", 356 "revision": "1", 357 "status": "available", 358 "summary": "GNU Hello, the \"hello world\" snap", 359 "type": "app", 360 "version": "2.10" 361 } 362 ], 363 "sources": [ 364 "store" 365 ], 366 "suggested-currency": "GBP" 367 } 368 ` 369 370 func (s *SnapSuite) TestFindPricedAndBought(c *check.C) { 371 n := 0 372 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 373 switch n { 374 case 0: 375 c.Check(r.Method, check.Equals, "GET") 376 c.Check(r.URL.Path, check.Equals, "/v2/find") 377 fmt.Fprintln(w, findPricedAndBoughtJSON) 378 default: 379 c.Fatalf("expected to get 1 requests, now on %d", n+1) 380 } 381 382 n++ 383 }) 384 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"}) 385 c.Assert(err, check.IsNil) 386 c.Assert(rest, check.DeepEquals, []string{}) 387 c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary 388 hello +2.10 +canonical\* +bought +GNU Hello, the "hello world" snap 389 `) 390 c.Check(s.Stderr(), check.Equals, "") 391 } 392 393 func (s *SnapSuite) TestFindNothingMeansFeaturedSection(c *check.C) { 394 n := 0 395 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 396 switch n { 397 case 0: 398 c.Check(r.Method, check.Equals, "GET") 399 c.Check(r.URL.Path, check.Equals, "/v2/find") 400 c.Check(r.URL.Query().Get("section"), check.Equals, "featured") 401 fmt.Fprintln(w, findJSON) 402 default: 403 c.Fatalf("expected to get 1 request, now on %d", n+1) 404 } 405 n++ 406 }) 407 408 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"find"}) 409 c.Assert(err, check.IsNil) 410 c.Check(s.Stderr(), check.Equals, "") 411 c.Check(n, check.Equals, 1) 412 } 413 414 func (s *SnapSuite) TestSectionCompletion(c *check.C) { 415 n := 0 416 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 417 switch n { 418 case 0, 1: 419 c.Check(r.Method, check.Equals, "GET") 420 c.Check(r.URL.Path, check.Equals, "/v2/sections") 421 EncodeResponseBody(c, w, map[string]interface{}{ 422 "type": "sync", 423 "result": []string{"foo", "bar", "baz"}, 424 }) 425 default: 426 c.Fatalf("expected to get 2 requests, now on #%d", n+1) 427 } 428 n++ 429 }) 430 431 c.Check(snap.SectionName("").Complete(""), check.DeepEquals, []flags.Completion{ 432 {Item: "foo"}, 433 {Item: "bar"}, 434 {Item: "baz"}, 435 }) 436 437 c.Check(snap.SectionName("").Complete("f"), check.DeepEquals, []flags.Completion{ 438 {Item: "foo"}, 439 }) 440 } 441 442 const findNetworkTimeoutErrorJSON = ` 443 { 444 "type": "error", 445 "result": { 446 "message": "Get https://search.apps.ubuntu.com/api/v1/snaps/search?confinement=strict%2Cclassic&fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha3_384%2Csummary%2Cdescription%2Cdeltas%2Cbinary_filesize%2Cdownload_url%2Cepoch%2Cicon_url%2Clast_updated%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ccontact%2Ctitle%2Ccontent%2Cversion%2Corigin%2Cdeveloper_id%2Cprivate%2Cconfinement%2Cchannel_maps_list&q=test: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)", 447 "kind": "network-timeout" 448 }, 449 "status-code": 400 450 }` 451 452 func (s *SnapSuite) TestFindNetworkTimeoutError(c *check.C) { 453 n := 0 454 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 455 switch n { 456 case 0: 457 c.Check(r.Method, check.Equals, "GET") 458 c.Check(r.URL.Path, check.Equals, "/v2/find") 459 fmt.Fprintln(w, findNetworkTimeoutErrorJSON) 460 default: 461 c.Fatalf("expected to get 1 requests, now on %d", n+1) 462 } 463 464 n++ 465 }) 466 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"}) 467 c.Assert(err, check.ErrorMatches, `unable to contact snap store`) 468 c.Check(s.Stdout(), check.Equals, "") 469 } 470 471 func (s *SnapSuite) TestFindSnapSectionOverview(c *check.C) { 472 n := 0 473 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 474 switch n { 475 case 0, 1: 476 c.Check(r.Method, check.Equals, "GET") 477 c.Check(r.URL.Path, check.Equals, "/v2/sections") 478 EncodeResponseBody(c, w, map[string]interface{}{ 479 "type": "sync", 480 "result": []string{"sec2", "sec1"}, 481 }) 482 default: 483 c.Fatalf("expected to get 2 requests, now on #%d", n+1) 484 } 485 n++ 486 }) 487 488 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section"}) 489 490 c.Assert(err, check.IsNil) 491 c.Assert(rest, check.DeepEquals, []string{}) 492 493 c.Check(s.Stdout(), check.Equals, `No section specified. Available sections: 494 * sec1 495 * sec2 496 Please try 'snap find --section=<selected section>' 497 `) 498 c.Check(s.Stderr(), check.Equals, "") 499 500 s.ResetStdStreams() 501 } 502 503 func (s *SnapSuite) TestFindSnapInvalidSection(c *check.C) { 504 n := 0 505 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 506 switch n { 507 case 0: 508 c.Check(r.Method, check.Equals, "GET") 509 c.Check(r.URL.Path, check.Equals, "/v2/sections") 510 EncodeResponseBody(c, w, map[string]interface{}{ 511 "type": "sync", 512 "result": []string{"sec1"}, 513 }) 514 default: 515 c.Fatalf("expected to get 1 request, now on %d", n+1) 516 } 517 518 n++ 519 }) 520 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section=foobar", "hello"}) 521 c.Assert(err, check.ErrorMatches, `No matching section "foobar", use --section to list existing sections`) 522 } 523 524 func (s *SnapSuite) TestFindSnapNotFoundInSection(c *check.C) { 525 n := 0 526 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 527 switch n { 528 case 0: 529 c.Check(r.Method, check.Equals, "GET") 530 c.Check(r.URL.Path, check.Equals, "/v2/sections") 531 EncodeResponseBody(c, w, map[string]interface{}{ 532 "type": "sync", 533 "result": []string{"foobar"}, 534 }) 535 case 1: 536 c.Check(r.Method, check.Equals, "GET") 537 c.Check(r.URL.Path, check.Equals, "/v2/find") 538 v, ok := r.URL.Query()["section"] 539 c.Check(ok, check.Equals, true) 540 c.Check(v, check.DeepEquals, []string{"foobar"}) 541 EncodeResponseBody(c, w, map[string]interface{}{ 542 "type": "sync", 543 "result": []string{}, 544 }) 545 default: 546 c.Fatalf("expected to get 2 requests, now on #%d", n+1) 547 } 548 n++ 549 }) 550 551 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section=foobar", "hello"}) 552 c.Assert(err, check.IsNil) 553 c.Check(s.Stderr(), check.Equals, "No matching snaps for \"hello\" in section \"foobar\"\n") 554 c.Check(s.Stdout(), check.Equals, "") 555 556 s.ResetStdStreams() 557 } 558 559 func (s *SnapSuite) TestFindSnapCachedSection(c *check.C) { 560 numHits := 0 561 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 562 numHits++ 563 c.Check(numHits, check.Equals, 1) 564 c.Check(r.URL.Path, check.Equals, "/v2/sections") 565 EncodeResponseBody(c, w, map[string]interface{}{ 566 "type": "sync", 567 "result": []string{"sec1", "sec2", "sec3"}, 568 }) 569 }) 570 571 os.MkdirAll(path.Dir(dirs.SnapSectionsFile), 0755) 572 ioutil.WriteFile(dirs.SnapSectionsFile, []byte("sec1\nsec2\nsec3"), 0644) 573 574 _, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section=foobar", "hello"}) 575 c.Logf("stdout: %s", s.Stdout()) 576 c.Assert(err, check.ErrorMatches, `No matching section "foobar", use --section to list existing sections`) 577 578 s.ResetStdStreams() 579 580 rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section"}) 581 582 c.Assert(err, check.IsNil) 583 c.Assert(rest, check.DeepEquals, []string{}) 584 585 c.Check(s.Stdout(), check.Equals, `No section specified. Available sections: 586 * sec1 587 * sec2 588 * sec3 589 Please try 'snap find --section=<selected section>' 590 `) 591 592 s.ResetStdStreams() 593 c.Check(numHits, check.Equals, 1) 594 }