github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/daemon/api_find_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2020 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 daemon_test 21 22 import ( 23 "fmt" 24 "net/http" 25 26 "gopkg.in/check.v1" 27 28 "github.com/snapcore/snapd/client" 29 "github.com/snapcore/snapd/daemon" 30 "github.com/snapcore/snapd/overlord/auth" 31 "github.com/snapcore/snapd/overlord/snapstate" 32 "github.com/snapcore/snapd/snap" 33 "github.com/snapcore/snapd/store" 34 ) 35 36 var _ = check.Suite(&findSuite{}) 37 38 type findSuite struct { 39 apiBaseSuite 40 } 41 42 func (s *findSuite) TestFind(c *check.C) { 43 s.daemon(c) 44 45 s.suggestedCurrency = "EUR" 46 47 s.rsnaps = []*snap.Info{{ 48 SideInfo: snap.SideInfo{ 49 RealName: "store", 50 }, 51 Publisher: snap.StoreAccount{ 52 ID: "foo-id", 53 Username: "foo", 54 DisplayName: "Foo", 55 Validation: "unproven", 56 }, 57 }} 58 59 req, err := http.NewRequest("GET", "/v2/find?q=hi", nil) 60 c.Assert(err, check.IsNil) 61 62 rsp := s.req(c, req, nil).(*daemon.Resp) 63 64 snaps := snapList(rsp.Result) 65 c.Assert(snaps, check.HasLen, 1) 66 c.Assert(snaps[0]["name"], check.Equals, "store") 67 c.Check(snaps[0]["prices"], check.IsNil) 68 c.Check(snaps[0]["channels"], check.IsNil) 69 70 c.Check(rsp.SuggestedCurrency, check.Equals, "EUR") 71 72 c.Check(s.storeSearch, check.DeepEquals, store.Search{Query: "hi"}) 73 c.Check(s.currentSnaps, check.HasLen, 0) 74 c.Check(s.actions, check.HasLen, 0) 75 } 76 77 func (s *findSuite) TestFindRefreshes(c *check.C) { 78 s.daemon(c) 79 80 s.rsnaps = []*snap.Info{{ 81 SideInfo: snap.SideInfo{ 82 RealName: "store", 83 }, 84 Publisher: snap.StoreAccount{ 85 ID: "foo-id", 86 Username: "foo", 87 DisplayName: "Foo", 88 Validation: "unproven", 89 }, 90 }} 91 s.mockSnap(c, "name: store\nversion: 1.0") 92 93 req, err := http.NewRequest("GET", "/v2/find?select=refresh", nil) 94 c.Assert(err, check.IsNil) 95 96 rsp := s.req(c, req, nil).(*daemon.Resp) 97 98 snaps := snapList(rsp.Result) 99 c.Assert(snaps, check.HasLen, 1) 100 c.Assert(snaps[0]["name"], check.Equals, "store") 101 c.Check(s.currentSnaps, check.HasLen, 1) 102 c.Check(s.actions, check.HasLen, 1) 103 } 104 105 func (s *findSuite) TestFindRefreshSideloaded(c *check.C) { 106 d := s.daemon(c) 107 108 s.rsnaps = []*snap.Info{{ 109 SideInfo: snap.SideInfo{ 110 RealName: "store", 111 }, 112 Publisher: snap.StoreAccount{ 113 ID: "foo-id", 114 Username: "foo", 115 DisplayName: "Foo", 116 Validation: "unproven", 117 }, 118 }} 119 120 s.mockSnap(c, "name: store\nversion: 1.0") 121 122 var snapst snapstate.SnapState 123 st := d.Overlord().State() 124 st.Lock() 125 err := snapstate.Get(st, "store", &snapst) 126 st.Unlock() 127 c.Assert(err, check.IsNil) 128 c.Assert(snapst.Sequence, check.HasLen, 1) 129 130 // clear the snapid 131 snapst.Sequence[0].SnapID = "" 132 st.Lock() 133 snapstate.Set(st, "store", &snapst) 134 st.Unlock() 135 136 req, err := http.NewRequest("GET", "/v2/find?select=refresh", nil) 137 c.Assert(err, check.IsNil) 138 139 rsp := s.req(c, req, nil).(*daemon.Resp) 140 141 snaps := snapList(rsp.Result) 142 c.Assert(snaps, check.HasLen, 0) 143 c.Check(s.currentSnaps, check.HasLen, 0) 144 c.Check(s.actions, check.HasLen, 0) 145 } 146 147 func (s *findSuite) TestFindPrivate(c *check.C) { 148 s.daemon(c) 149 150 s.rsnaps = []*snap.Info{} 151 152 req, err := http.NewRequest("GET", "/v2/find?q=foo&select=private", nil) 153 c.Assert(err, check.IsNil) 154 155 _ = s.req(c, req, nil).(*daemon.Resp) 156 157 c.Check(s.storeSearch, check.DeepEquals, store.Search{ 158 Query: "foo", 159 Private: true, 160 }) 161 } 162 163 func (s *findSuite) TestFindUserAgentContextCreated(c *check.C) { 164 s.daemon(c) 165 166 req, err := http.NewRequest("GET", "/v2/find", nil) 167 c.Assert(err, check.IsNil) 168 req.Header.Add("User-Agent", "some-agent/1.0") 169 170 _ = s.req(c, req, nil).(*daemon.Resp) 171 172 c.Check(store.ClientUserAgent(s.ctx), check.Equals, "some-agent/1.0") 173 } 174 175 func (s *findSuite) TestFindOneUserAgentContextCreated(c *check.C) { 176 s.daemon(c) 177 178 s.rsnaps = []*snap.Info{{ 179 SnapType: snap.TypeApp, 180 Version: "v2", 181 SideInfo: snap.SideInfo{ 182 RealName: "banana", 183 }, 184 Publisher: snap.StoreAccount{ 185 ID: "foo-id", 186 Username: "foo", 187 DisplayName: "Foo", 188 Validation: "unproven", 189 }, 190 }} 191 req, err := http.NewRequest("GET", "/v2/find?name=foo", nil) 192 c.Assert(err, check.IsNil) 193 req.Header.Add("User-Agent", "some-agent/1.0") 194 195 _ = s.req(c, req, nil).(*daemon.Resp) 196 197 c.Check(store.ClientUserAgent(s.ctx), check.Equals, "some-agent/1.0") 198 } 199 200 func (s *findSuite) TestFindPrefix(c *check.C) { 201 s.daemon(c) 202 203 s.rsnaps = []*snap.Info{} 204 205 req, err := http.NewRequest("GET", "/v2/find?name=foo*", nil) 206 c.Assert(err, check.IsNil) 207 208 _ = s.req(c, req, nil).(*daemon.Resp) 209 210 c.Check(s.storeSearch, check.DeepEquals, store.Search{Query: "foo", Prefix: true}) 211 } 212 213 func (s *findSuite) TestFindSection(c *check.C) { 214 s.daemon(c) 215 216 s.rsnaps = []*snap.Info{} 217 218 req, err := http.NewRequest("GET", "/v2/find?q=foo§ion=bar", nil) 219 c.Assert(err, check.IsNil) 220 221 _ = s.req(c, req, nil).(*daemon.Resp) 222 223 c.Check(s.storeSearch, check.DeepEquals, store.Search{ 224 Query: "foo", 225 Category: "bar", 226 }) 227 } 228 229 func (s *findSuite) TestFindScope(c *check.C) { 230 s.daemon(c) 231 232 s.rsnaps = []*snap.Info{} 233 234 req, err := http.NewRequest("GET", "/v2/find?q=foo&scope=creep", nil) 235 c.Assert(err, check.IsNil) 236 237 _ = s.req(c, req, nil).(*daemon.Resp) 238 239 c.Check(s.storeSearch, check.DeepEquals, store.Search{ 240 Query: "foo", 241 Scope: "creep", 242 }) 243 } 244 245 func (s *findSuite) TestFindCommonID(c *check.C) { 246 s.daemon(c) 247 248 s.rsnaps = []*snap.Info{{ 249 SideInfo: snap.SideInfo{ 250 RealName: "store", 251 }, 252 Publisher: snap.StoreAccount{ 253 ID: "foo-id", 254 Username: "foo", 255 DisplayName: "Foo", 256 Validation: "unproven", 257 }, 258 CommonIDs: []string{"org.foo"}, 259 }} 260 s.mockSnap(c, "name: store\nversion: 1.0") 261 262 req, err := http.NewRequest("GET", "/v2/find?name=foo", nil) 263 c.Assert(err, check.IsNil) 264 265 rsp := s.req(c, req, nil).(*daemon.Resp) 266 267 snaps := snapList(rsp.Result) 268 c.Assert(snaps, check.HasLen, 1) 269 c.Check(snaps[0]["common-ids"], check.DeepEquals, []interface{}{"org.foo"}) 270 } 271 272 func (s *findSuite) TestFindByCommonID(c *check.C) { 273 s.daemon(c) 274 275 s.rsnaps = []*snap.Info{{ 276 SideInfo: snap.SideInfo{ 277 RealName: "store", 278 }, 279 Publisher: snap.StoreAccount{ 280 ID: "foo-id", 281 Username: "foo", 282 DisplayName: "Foo", 283 Validation: "unproven", 284 }, 285 CommonIDs: []string{"org.foo"}, 286 }} 287 s.mockSnap(c, "name: store\nversion: 1.0") 288 289 req, err := http.NewRequest("GET", "/v2/find?common-id=org.foo", nil) 290 c.Assert(err, check.IsNil) 291 292 rsp := s.req(c, req, nil).(*daemon.Resp) 293 294 snaps := snapList(rsp.Result) 295 c.Assert(snaps, check.HasLen, 1) 296 c.Check(s.storeSearch, check.DeepEquals, store.Search{CommonID: "org.foo"}) 297 } 298 299 func (s *findSuite) TestFindOne(c *check.C) { 300 s.daemon(c) 301 302 s.rsnaps = []*snap.Info{{ 303 SideInfo: snap.SideInfo{ 304 RealName: "store", 305 }, 306 Base: "base0", 307 Publisher: snap.StoreAccount{ 308 ID: "foo-id", 309 Username: "foo", 310 DisplayName: "Foo", 311 Validation: "verified", 312 }, 313 Channels: map[string]*snap.ChannelSnapInfo{ 314 "stable": { 315 Revision: snap.R(42), 316 }, 317 }, 318 }} 319 s.mockSnap(c, "name: store\nversion: 1.0") 320 321 req, err := http.NewRequest("GET", "/v2/find?name=foo", nil) 322 c.Assert(err, check.IsNil) 323 324 rsp := s.req(c, req, nil).(*daemon.Resp) 325 326 c.Check(s.storeSearch, check.DeepEquals, store.Search{}) 327 328 snaps := snapList(rsp.Result) 329 c.Assert(snaps, check.HasLen, 1) 330 c.Check(snaps[0]["name"], check.Equals, "store") 331 c.Check(snaps[0]["base"], check.Equals, "base0") 332 c.Check(snaps[0]["publisher"], check.DeepEquals, map[string]interface{}{ 333 "id": "foo-id", 334 "username": "foo", 335 "display-name": "Foo", 336 "validation": "verified", 337 }) 338 m := snaps[0]["channels"].(map[string]interface{})["stable"].(map[string]interface{}) 339 340 c.Check(m["revision"], check.Equals, "42") 341 } 342 343 func (s *findSuite) TestFindOneNotFound(c *check.C) { 344 s.daemon(c) 345 346 s.err = store.ErrSnapNotFound 347 s.mockSnap(c, "name: store\nversion: 1.0") 348 349 req, err := http.NewRequest("GET", "/v2/find?name=foo", nil) 350 c.Assert(err, check.IsNil) 351 352 rsp := s.req(c, req, nil).(*daemon.Resp) 353 354 c.Check(s.storeSearch, check.DeepEquals, store.Search{}) 355 c.Check(rsp.Status, check.Equals, 404) 356 } 357 358 func (s *findSuite) TestFindOneWithAuth(c *check.C) { 359 d := s.daemon(c) 360 361 state := d.Overlord().State() 362 state.Lock() 363 user, err := auth.NewUser(state, "username", "email@test.com", "macaroon", []string{"discharge"}) 364 state.Unlock() 365 c.Check(err, check.IsNil) 366 367 req, err := http.NewRequest("GET", "/v2/find?q=name:gfoo", nil) 368 c.Assert(err, check.IsNil) 369 370 c.Assert(s.user, check.IsNil) 371 372 _, ok := s.req(c, req, user).(*daemon.Resp) 373 c.Assert(ok, check.Equals, true) 374 // ensure user was set 375 c.Assert(s.user, check.DeepEquals, user) 376 } 377 378 func (s *findSuite) TestFindRefreshNotOther(c *check.C) { 379 s.daemon(c) 380 381 for _, other := range []string{"name", "q", "common-id"} { 382 req, err := http.NewRequest("GET", "/v2/find?select=refresh&"+other+"=foo*", nil) 383 c.Assert(err, check.IsNil) 384 385 rsp := s.req(c, req, nil).(*daemon.Resp) 386 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 387 c.Check(rsp.Status, check.Equals, 400) 388 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, "cannot use '"+other+"' with 'select=refresh'") 389 } 390 } 391 392 func (s *findSuite) TestFindNotTogether(c *check.C) { 393 s.daemon(c) 394 395 queries := map[string]string{"q": "foo", "name": "foo*", "common-id": "foo"} 396 for ki, vi := range queries { 397 for kj, vj := range queries { 398 if ki == kj { 399 continue 400 } 401 402 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/find?%s=%s&%s=%s", ki, vi, kj, vj), nil) 403 c.Assert(err, check.IsNil) 404 405 rsp := s.req(c, req, nil).(*daemon.Resp) 406 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 407 c.Check(rsp.Status, check.Equals, 400) 408 exp1 := "cannot use '" + ki + "' and '" + kj + "' together" 409 exp2 := "cannot use '" + kj + "' and '" + ki + "' together" 410 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, exp1+"|"+exp2) 411 } 412 } 413 } 414 415 func (s *findSuite) TestFindBadQueryReturnsCorrectErrorKind(c *check.C) { 416 s.daemon(c) 417 418 s.err = store.ErrBadQuery 419 req, err := http.NewRequest("GET", "/v2/find?q=return-bad-query-please", nil) 420 c.Assert(err, check.IsNil) 421 422 rsp := s.req(c, req, nil).(*daemon.Resp) 423 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 424 c.Check(rsp.Status, check.Equals, 400) 425 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, "bad query") 426 c.Check(rsp.Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindBadQuery) 427 } 428 429 func (s *findSuite) TestFindPriced(c *check.C) { 430 s.daemon(c) 431 432 s.suggestedCurrency = "GBP" 433 434 s.rsnaps = []*snap.Info{{ 435 SnapType: snap.TypeApp, 436 Version: "v2", 437 Prices: map[string]float64{ 438 "GBP": 1.23, 439 "EUR": 2.34, 440 }, 441 MustBuy: true, 442 SideInfo: snap.SideInfo{ 443 RealName: "banana", 444 }, 445 Publisher: snap.StoreAccount{ 446 ID: "foo-id", 447 Username: "foo", 448 DisplayName: "Foo", 449 Validation: "unproven", 450 }, 451 }} 452 453 req, err := http.NewRequest("GET", "/v2/find?q=banana&channel=stable", nil) 454 c.Assert(err, check.IsNil) 455 rsp, ok := s.req(c, req, nil).(*daemon.Resp) 456 c.Assert(ok, check.Equals, true) 457 458 snaps := snapList(rsp.Result) 459 c.Assert(snaps, check.HasLen, 1) 460 461 snap := snaps[0] 462 c.Check(snap["name"], check.Equals, "banana") 463 c.Check(snap["prices"], check.DeepEquals, map[string]interface{}{ 464 "EUR": 2.34, 465 "GBP": 1.23, 466 }) 467 c.Check(snap["status"], check.Equals, "priced") 468 469 c.Check(rsp.SuggestedCurrency, check.Equals, "GBP") 470 } 471 472 func (s *findSuite) TestFindScreenshotted(c *check.C) { 473 s.daemon(c) 474 475 s.rsnaps = []*snap.Info{{ 476 SnapType: snap.TypeApp, 477 Version: "v2", 478 Media: []snap.MediaInfo{ 479 { 480 Type: "screenshot", 481 URL: "http://example.com/screenshot.png", 482 Width: 800, 483 Height: 1280, 484 }, 485 { 486 Type: "screenshot", 487 URL: "http://example.com/screenshot2.png", 488 }, 489 }, 490 MustBuy: true, 491 SideInfo: snap.SideInfo{ 492 RealName: "test-screenshot", 493 }, 494 Publisher: snap.StoreAccount{ 495 ID: "foo-id", 496 Username: "foo", 497 DisplayName: "Foo", 498 Validation: "unproven", 499 }, 500 }} 501 502 req, err := http.NewRequest("GET", "/v2/find?q=test-screenshot", nil) 503 c.Assert(err, check.IsNil) 504 rsp, ok := s.req(c, req, nil).(*daemon.Resp) 505 c.Assert(ok, check.Equals, true) 506 507 snaps := snapList(rsp.Result) 508 c.Assert(snaps, check.HasLen, 1) 509 510 c.Check(snaps[0]["name"], check.Equals, "test-screenshot") 511 c.Check(snaps[0]["media"], check.DeepEquals, []interface{}{ 512 map[string]interface{}{ 513 "type": "screenshot", 514 "url": "http://example.com/screenshot.png", 515 "width": float64(800), 516 "height": float64(1280), 517 }, 518 map[string]interface{}{ 519 "type": "screenshot", 520 "url": "http://example.com/screenshot2.png", 521 }, 522 }) 523 } 524 525 func (s *findSuite) TestSnapsStoreConfinement(c *check.C) { 526 s.daemon(c) 527 528 s.rsnaps = []*snap.Info{ 529 { 530 // no explicit confinement in this one 531 SideInfo: snap.SideInfo{ 532 RealName: "foo", 533 }, 534 }, 535 { 536 Confinement: snap.StrictConfinement, 537 SideInfo: snap.SideInfo{ 538 RealName: "bar", 539 }, 540 }, 541 { 542 Confinement: snap.DevModeConfinement, 543 SideInfo: snap.SideInfo{ 544 RealName: "baz", 545 }, 546 }, 547 } 548 549 req, err := http.NewRequest("GET", "/v2/find", nil) 550 c.Assert(err, check.IsNil) 551 552 rsp := s.req(c, req, nil).(*daemon.Resp) 553 554 snaps := snapList(rsp.Result) 555 c.Assert(snaps, check.HasLen, 3) 556 557 for i, ss := range [][2]string{ 558 {"foo", string(snap.StrictConfinement)}, 559 {"bar", string(snap.StrictConfinement)}, 560 {"baz", string(snap.DevModeConfinement)}, 561 } { 562 name, mode := ss[0], ss[1] 563 c.Check(snaps[i]["name"], check.Equals, name, check.Commentf(name)) 564 c.Check(snaps[i]["confinement"], check.Equals, mode, check.Commentf(name)) 565 } 566 }