github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/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.syncReq(c, req, nil) 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.syncReq(c, req, nil) 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.syncReq(c, req, nil) 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.syncReq(c, req, nil) 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.syncReq(c, req, nil) 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.syncReq(c, req, nil) 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.syncReq(c, req, nil) 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.syncReq(c, req, nil) 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.syncReq(c, req, nil) 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.syncReq(c, req, nil) 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.syncReq(c, req, nil) 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.syncReq(c, req, nil) 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.errorReq(c, req, nil) 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 _ = s.syncReq(c, req, user) 373 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.errorReq(c, req, nil) 386 c.Check(rsp.Status, check.Equals, 400) 387 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, "cannot use '"+other+"' with 'select=refresh'") 388 } 389 } 390 391 func (s *findSuite) TestFindNotTogether(c *check.C) { 392 s.daemon(c) 393 394 queries := map[string]string{"q": "foo", "name": "foo*", "common-id": "foo"} 395 for ki, vi := range queries { 396 for kj, vj := range queries { 397 if ki == kj { 398 continue 399 } 400 401 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/find?%s=%s&%s=%s", ki, vi, kj, vj), nil) 402 c.Assert(err, check.IsNil) 403 404 rsp := s.errorReq(c, req, nil) 405 c.Check(rsp.Status, check.Equals, 400) 406 exp1 := "cannot use '" + ki + "' and '" + kj + "' together" 407 exp2 := "cannot use '" + kj + "' and '" + ki + "' together" 408 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, exp1+"|"+exp2) 409 } 410 } 411 } 412 413 func (s *findSuite) TestFindBadQueryReturnsCorrectErrorKind(c *check.C) { 414 s.daemon(c) 415 416 s.err = store.ErrBadQuery 417 req, err := http.NewRequest("GET", "/v2/find?q=return-bad-query-please", nil) 418 c.Assert(err, check.IsNil) 419 420 rsp := s.errorReq(c, req, nil) 421 c.Check(rsp.Status, check.Equals, 400) 422 c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, "bad query") 423 c.Check(rsp.Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindBadQuery) 424 } 425 426 func (s *findSuite) TestFindPriced(c *check.C) { 427 s.daemon(c) 428 429 s.suggestedCurrency = "GBP" 430 431 s.rsnaps = []*snap.Info{{ 432 SnapType: snap.TypeApp, 433 Version: "v2", 434 Prices: map[string]float64{ 435 "GBP": 1.23, 436 "EUR": 2.34, 437 }, 438 MustBuy: true, 439 SideInfo: snap.SideInfo{ 440 RealName: "banana", 441 }, 442 Publisher: snap.StoreAccount{ 443 ID: "foo-id", 444 Username: "foo", 445 DisplayName: "Foo", 446 Validation: "unproven", 447 }, 448 }} 449 450 req, err := http.NewRequest("GET", "/v2/find?q=banana&channel=stable", nil) 451 c.Assert(err, check.IsNil) 452 rsp := s.syncReq(c, req, nil) 453 454 snaps := snapList(rsp.Result) 455 c.Assert(snaps, check.HasLen, 1) 456 457 snap := snaps[0] 458 c.Check(snap["name"], check.Equals, "banana") 459 c.Check(snap["prices"], check.DeepEquals, map[string]interface{}{ 460 "EUR": 2.34, 461 "GBP": 1.23, 462 }) 463 c.Check(snap["status"], check.Equals, "priced") 464 465 c.Check(rsp.SuggestedCurrency, check.Equals, "GBP") 466 } 467 468 func (s *findSuite) TestFindScreenshotted(c *check.C) { 469 s.daemon(c) 470 471 s.rsnaps = []*snap.Info{{ 472 SnapType: snap.TypeApp, 473 Version: "v2", 474 Media: []snap.MediaInfo{ 475 { 476 Type: "screenshot", 477 URL: "http://example.com/screenshot.png", 478 Width: 800, 479 Height: 1280, 480 }, 481 { 482 Type: "screenshot", 483 URL: "http://example.com/screenshot2.png", 484 }, 485 }, 486 MustBuy: true, 487 SideInfo: snap.SideInfo{ 488 RealName: "test-screenshot", 489 }, 490 Publisher: snap.StoreAccount{ 491 ID: "foo-id", 492 Username: "foo", 493 DisplayName: "Foo", 494 Validation: "unproven", 495 }, 496 }} 497 498 req, err := http.NewRequest("GET", "/v2/find?q=test-screenshot", nil) 499 c.Assert(err, check.IsNil) 500 rsp := s.syncReq(c, req, nil) 501 502 snaps := snapList(rsp.Result) 503 c.Assert(snaps, check.HasLen, 1) 504 505 c.Check(snaps[0]["name"], check.Equals, "test-screenshot") 506 c.Check(snaps[0]["media"], check.DeepEquals, []interface{}{ 507 map[string]interface{}{ 508 "type": "screenshot", 509 "url": "http://example.com/screenshot.png", 510 "width": float64(800), 511 "height": float64(1280), 512 }, 513 map[string]interface{}{ 514 "type": "screenshot", 515 "url": "http://example.com/screenshot2.png", 516 }, 517 }) 518 } 519 520 func (s *findSuite) TestSnapsStoreConfinement(c *check.C) { 521 s.daemon(c) 522 523 s.rsnaps = []*snap.Info{ 524 { 525 // no explicit confinement in this one 526 SideInfo: snap.SideInfo{ 527 RealName: "foo", 528 }, 529 }, 530 { 531 Confinement: snap.StrictConfinement, 532 SideInfo: snap.SideInfo{ 533 RealName: "bar", 534 }, 535 }, 536 { 537 Confinement: snap.DevModeConfinement, 538 SideInfo: snap.SideInfo{ 539 RealName: "baz", 540 }, 541 }, 542 } 543 544 req, err := http.NewRequest("GET", "/v2/find", nil) 545 c.Assert(err, check.IsNil) 546 547 rsp := s.syncReq(c, req, nil) 548 549 snaps := snapList(rsp.Result) 550 c.Assert(snaps, check.HasLen, 3) 551 552 for i, ss := range [][2]string{ 553 {"foo", string(snap.StrictConfinement)}, 554 {"bar", string(snap.StrictConfinement)}, 555 {"baz", string(snap.DevModeConfinement)}, 556 } { 557 name, mode := ss[0], ss[1] 558 c.Check(snaps[i]["name"], check.Equals, name, check.Commentf(name)) 559 c.Check(snaps[i]["confinement"], check.Equals, mode, check.Commentf(name)) 560 } 561 }