github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/daemon/api_snaps_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 "bytes" 24 "context" 25 "encoding/json" 26 "errors" 27 "fmt" 28 //"io/ioutil" 29 //"mime/multipart" 30 "net/http" 31 //"net/http/httptest" 32 "net/url" 33 "os" 34 "path/filepath" 35 "strings" 36 37 "gopkg.in/check.v1" 38 39 //"github.com/snapcore/snapd/arch" 40 //"github.com/snapcore/snapd/asserts" 41 //"github.com/snapcore/snapd/asserts/assertstest" 42 //"github.com/snapcore/snapd/client" 43 "github.com/snapcore/snapd/daemon" 44 //"github.com/snapcore/snapd/dirs" 45 "github.com/snapcore/snapd/overlord/assertstate" 46 //"github.com/snapcore/snapd/overlord/assertstate/assertstatetest" 47 "github.com/snapcore/snapd/overlord/auth" 48 "github.com/snapcore/snapd/overlord/healthstate" 49 "github.com/snapcore/snapd/overlord/snapstate" 50 "github.com/snapcore/snapd/overlord/state" 51 //"github.com/snapcore/snapd/sandbox" 52 "github.com/snapcore/snapd/snap" 53 //"github.com/snapcore/snapd/snap/channel" 54 //"github.com/snapcore/snapd/snap/snaptest" 55 "github.com/snapcore/snapd/store" 56 "github.com/snapcore/snapd/testutil" 57 ) 58 59 type snapsSuite struct { 60 apiBaseSuite 61 } 62 63 var _ = check.Suite(&snapsSuite{}) 64 65 func (s *snapsSuite) TestSnapsInfoIntegration(c *check.C) { 66 s.checkSnapsInfoIntegration(c, false, nil) 67 } 68 69 func (s *snapsSuite) TestSnapsInfoIntegrationSome(c *check.C) { 70 s.checkSnapsInfoIntegration(c, false, []string{"foo", "baz"}) 71 } 72 73 func (s *snapsSuite) TestSnapsInfoIntegrationAll(c *check.C) { 74 s.checkSnapsInfoIntegration(c, true, nil) 75 } 76 77 func (s *snapsSuite) TestSnapsInfoIntegrationAllSome(c *check.C) { 78 s.checkSnapsInfoIntegration(c, true, []string{"foo", "baz"}) 79 } 80 81 func snapList(rawSnaps interface{}) []map[string]interface{} { 82 snaps := make([]map[string]interface{}, len(rawSnaps.([]*json.RawMessage))) 83 for i, raw := range rawSnaps.([]*json.RawMessage) { 84 err := json.Unmarshal([]byte(*raw), &snaps[i]) 85 if err != nil { 86 panic(err) 87 } 88 } 89 return snaps 90 } 91 92 func (s *snapsSuite) checkSnapsInfoIntegration(c *check.C, all bool, names []string) { 93 d := s.daemon(c) 94 95 type tsnap struct { 96 name string 97 dev string 98 ver string 99 rev int 100 active bool 101 102 wanted bool 103 } 104 105 tsnaps := []tsnap{ 106 {name: "foo", dev: "bar", ver: "v0.9", rev: 1}, 107 {name: "foo", dev: "bar", ver: "v1", rev: 5, active: true}, 108 {name: "bar", dev: "baz", ver: "v2", rev: 10, active: true}, 109 {name: "baz", dev: "qux", ver: "v3", rev: 15, active: true}, 110 {name: "qux", dev: "mip", ver: "v4", rev: 20, active: true}, 111 } 112 numExpected := 0 113 114 for _, snp := range tsnaps { 115 if all || snp.active { 116 if len(names) == 0 { 117 numExpected++ 118 snp.wanted = true 119 } 120 for _, n := range names { 121 if snp.name == n { 122 numExpected++ 123 snp.wanted = true 124 break 125 } 126 } 127 } 128 s.mkInstalledInState(c, d, snp.name, snp.dev, snp.ver, snap.R(snp.rev), snp.active, "") 129 } 130 131 q := url.Values{} 132 if all { 133 q.Set("select", "all") 134 } 135 if len(names) > 0 { 136 q.Set("snaps", strings.Join(names, ",")) 137 } 138 req, err := http.NewRequest("GET", "/v2/snaps?"+q.Encode(), nil) 139 c.Assert(err, check.IsNil) 140 141 rsp, ok := s.req(c, req, nil).(*daemon.Resp) 142 c.Assert(ok, check.Equals, true) 143 144 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync) 145 c.Check(rsp.Status, check.Equals, 200) 146 c.Check(rsp.Result, check.NotNil) 147 148 snaps := snapList(rsp.Result) 149 c.Check(snaps, check.HasLen, numExpected) 150 151 for _, s := range tsnaps { 152 if !((all || s.active) && s.wanted) { 153 continue 154 } 155 var got map[string]interface{} 156 for _, got = range snaps { 157 if got["name"].(string) == s.name && got["revision"].(string) == snap.R(s.rev).String() { 158 break 159 } 160 } 161 c.Check(got["name"], check.Equals, s.name) 162 c.Check(got["version"], check.Equals, s.ver) 163 c.Check(got["revision"], check.Equals, snap.R(s.rev).String()) 164 c.Check(got["developer"], check.Equals, s.dev) 165 c.Check(got["confinement"], check.Equals, "strict") 166 } 167 } 168 169 func (s *snapsSuite) TestSnapsInfoOnlyLocal(c *check.C) { 170 d := s.daemon(c) 171 172 s.rsnaps = []*snap.Info{{ 173 SideInfo: snap.SideInfo{ 174 RealName: "store", 175 }, 176 Publisher: snap.StoreAccount{ 177 ID: "foo-id", 178 Username: "foo", 179 DisplayName: "Foo", 180 Validation: "unproven", 181 }, 182 }} 183 s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "") 184 st := d.Overlord().State() 185 st.Lock() 186 st.Set("health", map[string]healthstate.HealthState{ 187 "local": {Status: healthstate.OkayStatus}, 188 }) 189 st.Unlock() 190 191 req, err := http.NewRequest("GET", "/v2/snaps?sources=local", nil) 192 c.Assert(err, check.IsNil) 193 194 rsp := s.req(c, req, nil).(*daemon.Resp) 195 196 c.Assert(rsp.Sources, check.DeepEquals, []string{"local"}) 197 198 snaps := snapList(rsp.Result) 199 c.Assert(snaps, check.HasLen, 1) 200 c.Assert(snaps[0]["name"], check.Equals, "local") 201 c.Check(snaps[0]["health"], check.DeepEquals, map[string]interface{}{ 202 "status": "okay", 203 "revision": "unset", 204 "timestamp": "0001-01-01T00:00:00Z", 205 }) 206 } 207 208 func (s *snapsSuite) TestSnapsInfoAllMixedPublishers(c *check.C) { 209 d := s.daemon(c) 210 211 // the first 'local' is from a 'local' snap 212 s.mkInstalledInState(c, d, "local", "", "v1", snap.R(-1), false, "") 213 s.mkInstalledInState(c, d, "local", "foo", "v2", snap.R(1), false, "") 214 s.mkInstalledInState(c, d, "local", "foo", "v3", snap.R(2), true, "") 215 216 req, err := http.NewRequest("GET", "/v2/snaps?select=all", nil) 217 c.Assert(err, check.IsNil) 218 rsp := s.req(c, req, nil).(*daemon.Resp) 219 c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeSync) 220 221 snaps := snapList(rsp.Result) 222 c.Assert(snaps, check.HasLen, 3) 223 224 publisher := map[string]interface{}{ 225 "id": "foo-id", 226 "username": "foo", 227 "display-name": "Foo", 228 "validation": "unproven", 229 } 230 231 c.Check(snaps[0]["publisher"], check.IsNil) 232 c.Check(snaps[1]["publisher"], check.DeepEquals, publisher) 233 c.Check(snaps[2]["publisher"], check.DeepEquals, publisher) 234 } 235 236 func (s *snapsSuite) TestSnapsInfoAll(c *check.C) { 237 d := s.daemon(c) 238 239 s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(1), false, "") 240 s.mkInstalledInState(c, d, "local", "foo", "v2", snap.R(2), false, "") 241 s.mkInstalledInState(c, d, "local", "foo", "v3", snap.R(3), true, "") 242 s.mkInstalledInState(c, d, "local_foo", "foo", "v4", snap.R(4), true, "") 243 brokenInfo := s.mkInstalledInState(c, d, "local_bar", "foo", "v5", snap.R(5), true, "") 244 // make sure local_bar is 'broken' 245 err := os.Remove(filepath.Join(brokenInfo.MountDir(), "meta", "snap.yaml")) 246 c.Assert(err, check.IsNil) 247 248 expectedHappy := map[string]bool{ 249 "local": true, 250 "local_foo": true, 251 "local_bar": true, 252 } 253 for _, t := range []struct { 254 q string 255 numSnaps int 256 typ daemon.ResponseType 257 }{ 258 {"?select=enabled", 3, "sync"}, 259 {`?select=`, 3, "sync"}, 260 {"", 3, "sync"}, 261 {"?select=all", 5, "sync"}, 262 {"?select=invalid-field", 0, "error"}, 263 } { 264 c.Logf("trying: %v", t) 265 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/snaps%s", t.q), nil) 266 c.Assert(err, check.IsNil) 267 rsp := s.req(c, req, nil).(*daemon.Resp) 268 c.Assert(rsp.Type, check.Equals, t.typ) 269 270 if rsp.Type != "error" { 271 snaps := snapList(rsp.Result) 272 c.Assert(snaps, check.HasLen, t.numSnaps) 273 seen := map[string]bool{} 274 for _, s := range snaps { 275 seen[s["name"].(string)] = true 276 } 277 c.Assert(seen, check.DeepEquals, expectedHappy) 278 } 279 } 280 } 281 282 func (s *snapsSuite) TestSnapsInfoOnlyStore(c *check.C) { 283 d := s.daemon(c) 284 285 s.suggestedCurrency = "EUR" 286 287 s.rsnaps = []*snap.Info{{ 288 SideInfo: snap.SideInfo{ 289 RealName: "store", 290 }, 291 Publisher: snap.StoreAccount{ 292 ID: "foo-id", 293 Username: "foo", 294 DisplayName: "Foo", 295 Validation: "unproven", 296 }, 297 }} 298 s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "") 299 300 req, err := http.NewRequest("GET", "/v2/snaps?sources=store", nil) 301 c.Assert(err, check.IsNil) 302 303 rsp := s.req(c, req, nil).(*daemon.Resp) 304 305 c.Assert(rsp.Sources, check.DeepEquals, []string{"store"}) 306 307 snaps := snapList(rsp.Result) 308 c.Assert(snaps, check.HasLen, 1) 309 c.Assert(snaps[0]["name"], check.Equals, "store") 310 c.Check(snaps[0]["prices"], check.IsNil) 311 312 c.Check(rsp.SuggestedCurrency, check.Equals, "EUR") 313 } 314 315 func (s *snapsSuite) TestSnapsInfoStoreWithAuth(c *check.C) { 316 d := s.daemon(c) 317 318 state := d.Overlord().State() 319 state.Lock() 320 user, err := auth.NewUser(state, "username", "email@test.com", "macaroon", []string{"discharge"}) 321 state.Unlock() 322 c.Check(err, check.IsNil) 323 324 req, err := http.NewRequest("GET", "/v2/snaps?sources=store", nil) 325 c.Assert(err, check.IsNil) 326 327 c.Assert(s.user, check.IsNil) 328 329 _ = s.req(c, req, user).(*daemon.Resp) 330 331 // ensure user was set 332 c.Assert(s.user, check.DeepEquals, user) 333 } 334 335 func (s *snapsSuite) TestSnapsInfoLocalAndStore(c *check.C) { 336 d := s.daemon(c) 337 338 s.rsnaps = []*snap.Info{{ 339 Version: "v42", 340 SideInfo: snap.SideInfo{ 341 RealName: "remote", 342 }, 343 Publisher: snap.StoreAccount{ 344 ID: "foo-id", 345 Username: "foo", 346 DisplayName: "Foo", 347 Validation: "unproven", 348 }, 349 }} 350 s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "") 351 352 req, err := http.NewRequest("GET", "/v2/snaps?sources=local,store", nil) 353 c.Assert(err, check.IsNil) 354 355 rsp := s.req(c, req, nil).(*daemon.Resp) 356 357 // presence of 'store' in sources bounces request over to /find 358 c.Assert(rsp.Sources, check.DeepEquals, []string{"store"}) 359 360 snaps := snapList(rsp.Result) 361 c.Assert(snaps, check.HasLen, 1) 362 c.Check(snaps[0]["version"], check.Equals, "v42") 363 364 // as does a 'q' 365 req, err = http.NewRequest("GET", "/v2/snaps?q=what", nil) 366 c.Assert(err, check.IsNil) 367 rsp = s.req(c, req, nil).(*daemon.Resp) 368 snaps = snapList(rsp.Result) 369 c.Assert(snaps, check.HasLen, 1) 370 c.Check(snaps[0]["version"], check.Equals, "v42") 371 372 // otherwise, local only 373 req, err = http.NewRequest("GET", "/v2/snaps", nil) 374 c.Assert(err, check.IsNil) 375 rsp = s.req(c, req, nil).(*daemon.Resp) 376 snaps = snapList(rsp.Result) 377 c.Assert(snaps, check.HasLen, 1) 378 c.Check(snaps[0]["version"], check.Equals, "v1") 379 } 380 381 func (s *snapsSuite) TestSnapsInfoDefaultSources(c *check.C) { 382 d := s.daemon(c) 383 384 s.rsnaps = []*snap.Info{{ 385 SideInfo: snap.SideInfo{ 386 RealName: "remote", 387 }, 388 Publisher: snap.StoreAccount{ 389 ID: "foo-id", 390 Username: "foo", 391 DisplayName: "Foo", 392 Validation: "unproven", 393 }, 394 }} 395 s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "") 396 397 req, err := http.NewRequest("GET", "/v2/snaps", nil) 398 c.Assert(err, check.IsNil) 399 400 rsp := s.req(c, req, nil).(*daemon.Resp) 401 402 c.Assert(rsp.Sources, check.DeepEquals, []string{"local"}) 403 snaps := snapList(rsp.Result) 404 c.Assert(snaps, check.HasLen, 1) 405 } 406 407 func (s *snapsSuite) TestSnapsInfoFilterRemote(c *check.C) { 408 s.daemon(c) 409 410 s.rsnaps = nil 411 412 req, err := http.NewRequest("GET", "/v2/snaps?q=foo&sources=store", nil) 413 c.Assert(err, check.IsNil) 414 415 rsp := s.req(c, req, nil).(*daemon.Resp) 416 417 c.Check(s.storeSearch, check.DeepEquals, store.Search{Query: "foo"}) 418 419 c.Assert(rsp.Result, check.NotNil) 420 } 421 422 func (s *snapsSuite) TestPostSnapsVerifyMultiSnapInstruction(c *check.C) { 423 s.daemonWithOverlordMockAndStore(c) 424 425 buf := strings.NewReader(`{"action": "install","snaps":["ubuntu-core"]}`) 426 req, err := http.NewRequest("POST", "/v2/snaps", buf) 427 c.Assert(err, check.IsNil) 428 req.Header.Set("Content-Type", "application/json") 429 430 rsp := s.req(c, req, nil).(*daemon.Resp) 431 432 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 433 c.Check(rsp.Status, check.Equals, 400) 434 c.Check(rsp.Result.(*daemon.ErrorResult).Message, testutil.Contains, `cannot install "ubuntu-core", please use "core" instead`) 435 } 436 437 func (s *snapsSuite) TestPostSnapsUnsupportedMultiOp(c *check.C) { 438 s.daemonWithOverlordMockAndStore(c) 439 440 buf := strings.NewReader(`{"action": "switch","snaps":["foo"]}`) 441 req, err := http.NewRequest("POST", "/v2/snaps", buf) 442 c.Assert(err, check.IsNil) 443 req.Header.Set("Content-Type", "application/json") 444 445 rsp := s.req(c, req, nil).(*daemon.Resp) 446 447 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 448 c.Check(rsp.Status, check.Equals, 400) 449 c.Check(rsp.Result.(*daemon.ErrorResult).Message, testutil.Contains, `unsupported multi-snap operation "switch"`) 450 } 451 452 func (s *snapsSuite) TestPostSnapsNoWeirdses(c *check.C) { 453 s.daemonWithOverlordMockAndStore(c) 454 455 // one could add more actions here ... 🤷 456 for _, action := range []string{"install", "refresh", "remove"} { 457 for weird, v := range map[string]string{ 458 "channel": `"beta"`, 459 "revision": `"1"`, 460 "devmode": "true", 461 "jailmode": "true", 462 "cohort-key": `"what"`, 463 "leave-cohort": "true", 464 "purge": "true", 465 } { 466 buf := strings.NewReader(fmt.Sprintf(`{"action": "%s","snaps":["foo","bar"], "%s": %s}`, action, weird, v)) 467 req, err := http.NewRequest("POST", "/v2/snaps", buf) 468 c.Assert(err, check.IsNil) 469 req.Header.Set("Content-Type", "application/json") 470 471 rsp := s.req(c, req, nil).(*daemon.Resp) 472 473 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError) 474 c.Check(rsp.Status, check.Equals, 400) 475 c.Check(rsp.Result.(*daemon.ErrorResult).Message, testutil.Contains, `unsupported option provided for multi-snap operation`) 476 } 477 } 478 } 479 480 func (s *snapsSuite) TestPostSnapsOp(c *check.C) { 481 s.testPostSnapsOp(c, "application/json") 482 } 483 484 func (s *snapsSuite) TestPostSnapsOpMoreComplexContentType(c *check.C) { 485 s.testPostSnapsOp(c, "application/json; charset=utf-8") 486 } 487 488 func (s *snapsSuite) testPostSnapsOp(c *check.C, contentType string) { 489 defer daemon.MockAssertstateRefreshSnapDeclarations(func(*state.State, int) error { return nil })() 490 defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) { 491 c.Check(names, check.HasLen, 0) 492 t := s.NewTask("fake-refresh-all", "Refreshing everything") 493 return []string{"fake1", "fake2"}, []*state.TaskSet{state.NewTaskSet(t)}, nil 494 })() 495 496 d := s.daemonWithOverlordMockAndStore(c) 497 498 buf := bytes.NewBufferString(`{"action": "refresh"}`) 499 req, err := http.NewRequest("POST", "/v2/snaps", buf) 500 c.Assert(err, check.IsNil) 501 req.Header.Set("Content-Type", contentType) 502 503 rsp, ok := s.req(c, req, nil).(*daemon.Resp) 504 c.Assert(ok, check.Equals, true) 505 c.Check(rsp.Type, check.Equals, daemon.ResponseTypeAsync) 506 507 st := d.Overlord().State() 508 st.Lock() 509 defer st.Unlock() 510 chg := st.Change(rsp.Change) 511 c.Check(chg.Summary(), check.Equals, `Refresh snaps "fake1", "fake2"`) 512 var apiData map[string]interface{} 513 c.Check(chg.Get("api-data", &apiData), check.IsNil) 514 c.Check(apiData["snap-names"], check.DeepEquals, []interface{}{"fake1", "fake2"}) 515 } 516 517 func (s *snapsSuite) TestPostSnapsOpInvalidCharset(c *check.C) { 518 s.daemon(c) 519 520 buf := bytes.NewBufferString(`{"action": "refresh"}`) 521 req, err := http.NewRequest("POST", "/v2/snaps", buf) 522 c.Assert(err, check.IsNil) 523 req.Header.Set("Content-Type", "application/json; charset=iso-8859-1") 524 525 rsp := s.req(c, req, nil).(*daemon.Resp) 526 c.Check(rsp.Status, check.Equals, 400) 527 c.Check(rsp.Result.(*daemon.ErrorResult).Message, testutil.Contains, "unknown charset in content type") 528 } 529 530 func (s *snapsSuite) TestRefreshAll(c *check.C) { 531 refreshSnapDecls := false 532 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 533 refreshSnapDecls = true 534 return assertstate.RefreshSnapDeclarations(s, userID) 535 })() 536 537 d := s.daemon(c) 538 539 for _, tst := range []struct { 540 snaps []string 541 msg string 542 }{ 543 {nil, "Refresh all snaps: no updates"}, 544 {[]string{"fake"}, `Refresh snap "fake"`}, 545 {[]string{"fake1", "fake2"}, `Refresh snaps "fake1", "fake2"`}, 546 } { 547 refreshSnapDecls = false 548 549 defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) { 550 c.Check(names, check.HasLen, 0) 551 t := s.NewTask("fake-refresh-all", "Refreshing everything") 552 return tst.snaps, []*state.TaskSet{state.NewTaskSet(t)}, nil 553 })() 554 555 inst := &daemon.SnapInstruction{Action: "refresh"} 556 st := d.Overlord().State() 557 st.Lock() 558 res, err := inst.DispatchForMany()(inst, st) 559 st.Unlock() 560 c.Assert(err, check.IsNil) 561 c.Check(res.Summary, check.Equals, tst.msg) 562 c.Check(refreshSnapDecls, check.Equals, true) 563 } 564 } 565 566 func (s *snapsSuite) TestRefreshAllNoChanges(c *check.C) { 567 refreshSnapDecls := false 568 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 569 refreshSnapDecls = true 570 return assertstate.RefreshSnapDeclarations(s, userID) 571 })() 572 573 defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) { 574 c.Check(names, check.HasLen, 0) 575 return nil, nil, nil 576 })() 577 578 d := s.daemon(c) 579 inst := &daemon.SnapInstruction{Action: "refresh"} 580 st := d.Overlord().State() 581 st.Lock() 582 res, err := inst.DispatchForMany()(inst, st) 583 st.Unlock() 584 c.Assert(err, check.IsNil) 585 c.Check(res.Summary, check.Equals, `Refresh all snaps: no updates`) 586 c.Check(refreshSnapDecls, check.Equals, true) 587 } 588 589 func (s *snapsSuite) TestRefreshMany(c *check.C) { 590 refreshSnapDecls := false 591 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 592 refreshSnapDecls = true 593 return nil 594 })() 595 596 defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) { 597 c.Check(names, check.HasLen, 2) 598 t := s.NewTask("fake-refresh-2", "Refreshing two") 599 return names, []*state.TaskSet{state.NewTaskSet(t)}, nil 600 })() 601 602 d := s.daemon(c) 603 inst := &daemon.SnapInstruction{Action: "refresh", Snaps: []string{"foo", "bar"}} 604 st := d.Overlord().State() 605 st.Lock() 606 res, err := inst.DispatchForMany()(inst, st) 607 st.Unlock() 608 c.Assert(err, check.IsNil) 609 c.Check(res.Summary, check.Equals, `Refresh snaps "foo", "bar"`) 610 c.Check(res.Affected, check.DeepEquals, inst.Snaps) 611 c.Check(refreshSnapDecls, check.Equals, true) 612 } 613 614 func (s *snapsSuite) TestRefreshMany1(c *check.C) { 615 refreshSnapDecls := false 616 defer daemon.MockAssertstateRefreshSnapDeclarations(func(s *state.State, userID int) error { 617 refreshSnapDecls = true 618 return nil 619 })() 620 621 defer daemon.MockSnapstateUpdateMany(func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) { 622 c.Check(names, check.HasLen, 1) 623 t := s.NewTask("fake-refresh-1", "Refreshing one") 624 return names, []*state.TaskSet{state.NewTaskSet(t)}, nil 625 })() 626 627 d := s.daemon(c) 628 inst := &daemon.SnapInstruction{Action: "refresh", Snaps: []string{"foo"}} 629 st := d.Overlord().State() 630 st.Lock() 631 res, err := inst.DispatchForMany()(inst, st) 632 st.Unlock() 633 c.Assert(err, check.IsNil) 634 c.Check(res.Summary, check.Equals, `Refresh snap "foo"`) 635 c.Check(res.Affected, check.DeepEquals, inst.Snaps) 636 c.Check(refreshSnapDecls, check.Equals, true) 637 } 638 639 func (s *snapsSuite) TestInstallMany(c *check.C) { 640 defer daemon.MockSnapstateInstallMany(func(s *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) { 641 c.Check(names, check.HasLen, 2) 642 t := s.NewTask("fake-install-2", "Install two") 643 return names, []*state.TaskSet{state.NewTaskSet(t)}, nil 644 })() 645 646 d := s.daemon(c) 647 inst := &daemon.SnapInstruction{Action: "install", Snaps: []string{"foo", "bar"}} 648 st := d.Overlord().State() 649 st.Lock() 650 res, err := inst.DispatchForMany()(inst, st) 651 st.Unlock() 652 c.Assert(err, check.IsNil) 653 c.Check(res.Summary, check.Equals, `Install snaps "foo", "bar"`) 654 c.Check(res.Affected, check.DeepEquals, inst.Snaps) 655 } 656 657 func (s *snapsSuite) TestInstallManyEmptyName(c *check.C) { 658 defer daemon.MockSnapstateInstallMany(func(_ *state.State, _ []string, _ int) ([]string, []*state.TaskSet, error) { 659 return nil, nil, errors.New("should not be called") 660 })() 661 d := s.daemon(c) 662 inst := &daemon.SnapInstruction{Action: "install", Snaps: []string{"", "bar"}} 663 st := d.Overlord().State() 664 st.Lock() 665 res, err := inst.DispatchForMany()(inst, st) 666 st.Unlock() 667 c.Assert(res, check.IsNil) 668 c.Assert(err, check.ErrorMatches, "cannot install snap with empty name") 669 } 670 671 func (s *snapsSuite) TestRemoveMany(c *check.C) { 672 defer daemon.MockSnapstateRemoveMany(func(s *state.State, names []string) ([]string, []*state.TaskSet, error) { 673 c.Check(names, check.HasLen, 2) 674 t := s.NewTask("fake-remove-2", "Remove two") 675 return names, []*state.TaskSet{state.NewTaskSet(t)}, nil 676 })() 677 678 d := s.daemon(c) 679 inst := &daemon.SnapInstruction{Action: "remove", Snaps: []string{"foo", "bar"}} 680 st := d.Overlord().State() 681 st.Lock() 682 res, err := inst.DispatchForMany()(inst, st) 683 st.Unlock() 684 c.Assert(err, check.IsNil) 685 c.Check(res.Summary, check.Equals, `Remove snaps "foo", "bar"`) 686 c.Check(res.Affected, check.DeepEquals, inst.Snaps) 687 }