github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/usersession/agent/rest_api_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 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 agent_test 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "fmt" 26 "io/ioutil" 27 "net/http/httptest" 28 "os" 29 "path/filepath" 30 "time" 31 32 . "gopkg.in/check.v1" 33 34 "github.com/godbus/dbus" 35 "github.com/snapcore/snapd/desktop/notification" 36 "github.com/snapcore/snapd/desktop/notification/notificationtest" 37 "github.com/snapcore/snapd/dirs" 38 "github.com/snapcore/snapd/systemd" 39 "github.com/snapcore/snapd/testutil" 40 "github.com/snapcore/snapd/usersession/agent" 41 "github.com/snapcore/snapd/usersession/client" 42 ) 43 44 type restSuite struct { 45 testutil.BaseTest 46 testutil.DBusTest 47 sysdLog [][]string 48 agent *agent.SessionAgent 49 notify *notificationtest.FdoServer 50 } 51 52 var _ = Suite(&restSuite{}) 53 54 func (s *restSuite) SetUpTest(c *C) { 55 s.BaseTest.SetUpTest(c) 56 s.DBusTest.SetUpTest(c) 57 dirs.SetRootDir(c.MkDir()) 58 xdgRuntimeDir := fmt.Sprintf("%s/%d", dirs.XdgRuntimeDirBase, os.Getuid()) 59 c.Assert(os.MkdirAll(xdgRuntimeDir, 0700), IsNil) 60 61 s.sysdLog = nil 62 restore := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 63 s.sysdLog = append(s.sysdLog, cmd) 64 return []byte("ActiveState=inactive\n"), nil 65 }) 66 s.AddCleanup(restore) 67 restore = systemd.MockStopDelays(time.Millisecond, 25*time.Second) 68 s.AddCleanup(restore) 69 restore = agent.MockStopTimeouts(20*time.Millisecond, time.Millisecond) 70 s.AddCleanup(restore) 71 72 var err error 73 s.notify, err = notificationtest.NewFdoServer() 74 c.Assert(err, IsNil) 75 s.AddCleanup(func() { s.notify.Stop() }) 76 77 s.agent, err = agent.New() 78 c.Assert(err, IsNil) 79 s.agent.Start() 80 s.AddCleanup(func() { s.agent.Stop() }) 81 } 82 83 func (s *restSuite) TearDownTest(c *C) { 84 dirs.SetRootDir("") 85 s.DBusTest.TearDownTest(c) 86 s.BaseTest.TearDownTest(c) 87 } 88 89 type resp struct { 90 Type agent.ResponseType `json:"type"` 91 Result interface{} `json:"result"` 92 } 93 94 func (s *restSuite) TestSessionInfo(c *C) { 95 // the agent.SessionInfo end point only supports GET requests 96 c.Check(agent.SessionInfoCmd.PUT, IsNil) 97 c.Check(agent.SessionInfoCmd.POST, IsNil) 98 c.Check(agent.SessionInfoCmd.DELETE, IsNil) 99 c.Assert(agent.SessionInfoCmd.GET, NotNil) 100 101 c.Check(agent.SessionInfoCmd.Path, Equals, "/v1/session-info") 102 103 s.agent.Version = "42b1" 104 rec := httptest.NewRecorder() 105 agent.SessionInfoCmd.GET(agent.SessionInfoCmd, nil).ServeHTTP(rec, nil) 106 c.Check(rec.Code, Equals, 200) 107 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 108 109 var rsp resp 110 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 111 c.Check(rsp.Type, Equals, agent.ResponseTypeSync) 112 c.Check(rsp.Result, DeepEquals, map[string]interface{}{ 113 "version": "42b1", 114 }) 115 } 116 117 func (s *restSuite) TestServiceControl(c *C) { 118 // the agent.Services end point only supports POST requests 119 c.Assert(agent.ServiceControlCmd.GET, IsNil) 120 c.Check(agent.ServiceControlCmd.PUT, IsNil) 121 c.Check(agent.ServiceControlCmd.POST, NotNil) 122 c.Check(agent.ServiceControlCmd.DELETE, IsNil) 123 124 c.Check(agent.ServiceControlCmd.Path, Equals, "/v1/service-control") 125 } 126 127 func (s *restSuite) TestServiceControlDaemonReload(c *C) { 128 s.testServiceControlDaemonReload(c, "application/json") 129 } 130 131 func (s *restSuite) TestServiceControlDaemonReloadComplexerContentType(c *C) { 132 s.testServiceControlDaemonReload(c, "application/json; charset=utf-8") 133 } 134 135 func (s *restSuite) TestServiceControlDaemonReloadInvalidCharset(c *C) { 136 req := httptest.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"daemon-reload"}`)) 137 req.Header.Set("Content-Type", "application/json; charset=iso-8859-1") 138 rec := httptest.NewRecorder() 139 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 140 c.Check(rec.Code, Equals, 400) 141 c.Check(rec.Body.String(), testutil.Contains, 142 "unknown charset in content type") 143 } 144 145 func (s *restSuite) testServiceControlDaemonReload(c *C, contentType string) { 146 req := httptest.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"daemon-reload"}`)) 147 req.Header.Set("Content-Type", contentType) 148 rec := httptest.NewRecorder() 149 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 150 c.Check(rec.Code, Equals, 200) 151 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 152 153 var rsp resp 154 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 155 c.Check(rsp.Type, Equals, agent.ResponseTypeSync) 156 c.Check(rsp.Result, IsNil) 157 158 c.Check(s.sysdLog, DeepEquals, [][]string{ 159 {"--user", "daemon-reload"}, 160 }) 161 } 162 163 func (s *restSuite) TestServiceControlStart(c *C) { 164 req := httptest.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"start","services":["snap.foo.service", "snap.bar.service"]}`)) 165 req.Header.Set("Content-Type", "application/json") 166 rec := httptest.NewRecorder() 167 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 168 c.Check(rec.Code, Equals, 200) 169 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 170 171 var rsp resp 172 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 173 c.Check(rsp.Type, Equals, agent.ResponseTypeSync) 174 c.Check(rsp.Result, Equals, nil) 175 176 c.Check(s.sysdLog, DeepEquals, [][]string{ 177 {"--user", "start", "snap.foo.service"}, 178 {"--user", "start", "snap.bar.service"}, 179 }) 180 } 181 182 func (s *restSuite) TestServicesStartNonSnap(c *C) { 183 req := httptest.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"start","services":["snap.foo.service", "not-snap.bar.service"]}`)) 184 req.Header.Set("Content-Type", "application/json") 185 rec := httptest.NewRecorder() 186 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 187 c.Check(rec.Code, Equals, 500) 188 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 189 190 var rsp resp 191 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 192 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 193 c.Check(rsp.Result, DeepEquals, map[string]interface{}{ 194 "message": "cannot start non-snap service not-snap.bar.service", 195 }) 196 197 // No services were started on the error. 198 c.Check(s.sysdLog, HasLen, 0) 199 } 200 201 func (s *restSuite) TestServicesStartFailureStopsServices(c *C) { 202 var sysdLog [][]string 203 restore := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 204 sysdLog = append(sysdLog, cmd) 205 if cmd[0] == "--user" && cmd[1] == "start" && cmd[2] == "snap.bar.service" { 206 return nil, fmt.Errorf("start failure") 207 } 208 return []byte("ActiveState=inactive\n"), nil 209 }) 210 defer restore() 211 212 req := httptest.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"start","services":["snap.foo.service", "snap.bar.service"]}`)) 213 req.Header.Set("Content-Type", "application/json") 214 rec := httptest.NewRecorder() 215 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 216 c.Check(rec.Code, Equals, 500) 217 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 218 219 var rsp resp 220 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 221 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 222 c.Check(rsp.Result, DeepEquals, map[string]interface{}{ 223 "message": "some user services failed to start", 224 "kind": "service-control", 225 "value": map[string]interface{}{ 226 "start-errors": map[string]interface{}{ 227 "snap.bar.service": "start failure", 228 }, 229 "stop-errors": map[string]interface{}{}, 230 }, 231 }) 232 233 c.Check(sysdLog, DeepEquals, [][]string{ 234 {"--user", "start", "snap.foo.service"}, 235 {"--user", "start", "snap.bar.service"}, 236 {"--user", "stop", "snap.foo.service"}, 237 {"--user", "show", "--property=ActiveState", "snap.foo.service"}, 238 }) 239 } 240 241 func (s *restSuite) TestServicesStartFailureReportsStopFailures(c *C) { 242 var sysdLog [][]string 243 restore := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 244 sysdLog = append(sysdLog, cmd) 245 if cmd[0] == "--user" && cmd[1] == "start" && cmd[2] == "snap.bar.service" { 246 return nil, fmt.Errorf("start failure") 247 } 248 if cmd[0] == "--user" && cmd[1] == "stop" && cmd[2] == "snap.foo.service" { 249 return nil, fmt.Errorf("stop failure") 250 } 251 return []byte("ActiveState=inactive\n"), nil 252 }) 253 defer restore() 254 255 req := httptest.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"start","services":["snap.foo.service", "snap.bar.service"]}`)) 256 req.Header.Set("Content-Type", "application/json") 257 rec := httptest.NewRecorder() 258 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 259 c.Check(rec.Code, Equals, 500) 260 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 261 262 var rsp resp 263 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 264 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 265 c.Check(rsp.Result, DeepEquals, map[string]interface{}{ 266 "message": "some user services failed to start", 267 "kind": "service-control", 268 "value": map[string]interface{}{ 269 "start-errors": map[string]interface{}{ 270 "snap.bar.service": "start failure", 271 }, 272 "stop-errors": map[string]interface{}{ 273 "snap.foo.service": "stop failure", 274 }, 275 }, 276 }) 277 278 c.Check(sysdLog, DeepEquals, [][]string{ 279 {"--user", "start", "snap.foo.service"}, 280 {"--user", "start", "snap.bar.service"}, 281 {"--user", "stop", "snap.foo.service"}, 282 }) 283 } 284 285 func (s *restSuite) TestServicesStop(c *C) { 286 req := httptest.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"stop","services":["snap.foo.service", "snap.bar.service"]}`)) 287 req.Header.Set("Content-Type", "application/json") 288 rec := httptest.NewRecorder() 289 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 290 c.Check(rec.Code, Equals, 200) 291 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 292 293 var rsp resp 294 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 295 c.Check(rsp.Type, Equals, agent.ResponseTypeSync) 296 c.Check(rsp.Result, Equals, nil) 297 298 c.Check(s.sysdLog, DeepEquals, [][]string{ 299 {"--user", "stop", "snap.foo.service"}, 300 {"--user", "show", "--property=ActiveState", "snap.foo.service"}, 301 {"--user", "stop", "snap.bar.service"}, 302 {"--user", "show", "--property=ActiveState", "snap.bar.service"}, 303 }) 304 } 305 306 func (s *restSuite) TestServicesStopNonSnap(c *C) { 307 req := httptest.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"stop","services":["snap.foo.service", "not-snap.bar.service"]}`)) 308 req.Header.Set("Content-Type", "application/json") 309 rec := httptest.NewRecorder() 310 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 311 c.Check(rec.Code, Equals, 500) 312 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 313 314 var rsp resp 315 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 316 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 317 c.Check(rsp.Result, DeepEquals, map[string]interface{}{ 318 "message": "cannot stop non-snap service not-snap.bar.service", 319 }) 320 321 // No services were started on the error. 322 c.Check(s.sysdLog, HasLen, 0) 323 } 324 325 func (s *restSuite) TestServicesStopReportsTimeout(c *C) { 326 var sysdLog [][]string 327 restore := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 328 // Ignore "show" spam 329 if cmd[1] != "show" { 330 sysdLog = append(sysdLog, cmd) 331 } 332 if cmd[len(cmd)-1] == "snap.bar.service" { 333 return []byte("ActiveState=active\n"), nil 334 } 335 return []byte("ActiveState=inactive\n"), nil 336 }) 337 defer restore() 338 339 req := httptest.NewRequest("POST", "/v1/service-control", bytes.NewBufferString(`{"action":"stop","services":["snap.foo.service", "snap.bar.service"]}`)) 340 req.Header.Set("Content-Type", "application/json") 341 rec := httptest.NewRecorder() 342 agent.ServiceControlCmd.POST(agent.ServiceControlCmd, req).ServeHTTP(rec, req) 343 c.Check(rec.Code, Equals, 500) 344 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 345 346 var rsp resp 347 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 348 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 349 c.Check(rsp.Result, DeepEquals, map[string]interface{}{ 350 "message": "some user services failed to stop", 351 "kind": "service-control", 352 "value": map[string]interface{}{ 353 "stop-errors": map[string]interface{}{ 354 "snap.bar.service": "snap.bar.service failed to stop: timeout", 355 }, 356 }, 357 }) 358 359 c.Check(sysdLog, DeepEquals, [][]string{ 360 {"--user", "stop", "snap.foo.service"}, 361 {"--user", "stop", "snap.bar.service"}, 362 }) 363 } 364 365 func (s *restSuite) TestPostPendingRefreshNotificationMalformedContentType(c *C) { 366 req := httptest.NewRequest("POST", "/v1/notifications/pending-refresh", bytes.NewBufferString("")) 367 req.Header.Set("Content-Type", "text/plain/joke") 368 rec := httptest.NewRecorder() 369 agent.PendingRefreshNotificationCmd.POST(agent.PendingRefreshNotificationCmd, req).ServeHTTP(rec, req) 370 c.Check(rec.Code, Equals, 400) 371 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 372 373 var rsp resp 374 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 375 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 376 c.Check(rsp.Result, DeepEquals, map[string]interface{}{"message": "cannot parse content type: mime: unexpected content after media subtype"}) 377 } 378 379 func (s *restSuite) TestPostPendingRefreshNotificationUnsupportedContentType(c *C) { 380 req := httptest.NewRequest("POST", "/v1/notifications/pending-refresh", bytes.NewBufferString("")) 381 req.Header.Set("Content-Type", "text/plain") 382 rec := httptest.NewRecorder() 383 agent.PendingRefreshNotificationCmd.POST(agent.PendingRefreshNotificationCmd, req).ServeHTTP(rec, req) 384 c.Check(rec.Code, Equals, 400) 385 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 386 387 var rsp resp 388 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 389 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 390 c.Check(rsp.Result, DeepEquals, map[string]interface{}{"message": "unknown content type: text/plain"}) 391 } 392 393 func (s *restSuite) TestPostPendingRefreshNotificationUnsupportedContentEncoding(c *C) { 394 req := httptest.NewRequest("POST", "/v1/notifications/pending-refresh", bytes.NewBufferString("")) 395 req.Header.Set("Content-Type", "application/json; charset=EBCDIC") 396 rec := httptest.NewRecorder() 397 agent.PendingRefreshNotificationCmd.POST(agent.PendingRefreshNotificationCmd, req).ServeHTTP(rec, req) 398 c.Check(rec.Code, Equals, 400) 399 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 400 401 var rsp resp 402 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 403 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 404 c.Check(rsp.Result, DeepEquals, map[string]interface{}{"message": "unknown charset in content type: application/json; charset=EBCDIC"}) 405 } 406 407 func (s *restSuite) TestPostPendingRefreshNotificationMalformedRequestBody(c *C) { 408 req := httptest.NewRequest("POST", "/v1/notifications/pending-refresh", 409 bytes.NewBufferString(`{"instance-name":syntaxerror}`)) 410 req.Header.Set("Content-Type", "application/json") 411 rec := httptest.NewRecorder() 412 agent.PendingRefreshNotificationCmd.POST(agent.PendingRefreshNotificationCmd, req).ServeHTTP(rec, req) 413 c.Check(rec.Code, Equals, 400) 414 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 415 416 var rsp resp 417 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 418 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 419 c.Check(rsp.Result, DeepEquals, map[string]interface{}{"message": "cannot decode request body into pending snap refresh info: invalid character 's' looking for beginning of value"}) 420 } 421 422 func (s *restSuite) TestPostPendingRefreshNotificationNoSessionBus(c *C) { 423 restore := agent.MockNoBus(s.agent) 424 defer restore() 425 426 req := httptest.NewRequest("POST", "/v1/notifications/pending-refresh", 427 bytes.NewBufferString(`{"instance-name":"pkg"}`)) 428 req.Header.Set("Content-Type", "application/json") 429 rec := httptest.NewRecorder() 430 agent.PendingRefreshNotificationCmd.POST(agent.PendingRefreshNotificationCmd, req).ServeHTTP(rec, req) 431 c.Check(rec.Code, Equals, 500) 432 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 433 434 var rsp resp 435 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 436 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 437 c.Check(rsp.Result, DeepEquals, map[string]interface{}{"message": "cannot connect to the session bus"}) 438 } 439 440 func (s *restSuite) testPostPendingRefreshNotificationBody(c *C, refreshInfo *client.PendingSnapRefreshInfo) { 441 reqBody, err := json.Marshal(refreshInfo) 442 c.Assert(err, IsNil) 443 req := httptest.NewRequest("POST", "/v1/notifications/pending-refresh", bytes.NewBuffer(reqBody)) 444 req.Header.Set("Content-Type", "application/json") 445 rec := httptest.NewRecorder() 446 agent.PendingRefreshNotificationCmd.POST(agent.PendingRefreshNotificationCmd, req).ServeHTTP(rec, req) 447 c.Check(rec.Code, Equals, 200) 448 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 449 450 var rsp resp 451 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 452 c.Check(rsp.Type, Equals, agent.ResponseTypeSync) 453 c.Check(rsp.Result, IsNil) 454 } 455 456 func (s *restSuite) TestPostPendingRefreshNotificationHappeningNow(c *C) { 457 refreshInfo := &client.PendingSnapRefreshInfo{InstanceName: "pkg"} 458 s.testPostPendingRefreshNotificationBody(c, refreshInfo) 459 notifications := s.notify.GetAll() 460 c.Assert(notifications, HasLen, 1) 461 n := notifications[0] 462 c.Check(n.AppName, Equals, "") 463 c.Check(n.Icon, Equals, "") 464 c.Check(n.Summary, Equals, `Snap "pkg" is refreshing now!`) 465 c.Check(n.Body, Equals, "") 466 c.Check(n.Actions, DeepEquals, []string{}) 467 c.Check(n.Hints, DeepEquals, map[string]dbus.Variant{ 468 "urgency": dbus.MakeVariant(byte(notification.CriticalUrgency)), 469 "desktop-entry": dbus.MakeVariant("io.snapcraft.SessionAgent"), 470 }) 471 c.Check(n.Expires, Equals, int32(0)) 472 } 473 474 func (s *restSuite) TestPostPendingRefreshNotificationFewDays(c *C) { 475 refreshInfo := &client.PendingSnapRefreshInfo{ 476 InstanceName: "pkg", 477 TimeRemaining: time.Hour * 72, 478 } 479 s.testPostPendingRefreshNotificationBody(c, refreshInfo) 480 notifications := s.notify.GetAll() 481 c.Assert(notifications, HasLen, 1) 482 n := notifications[0] 483 // boring stuff is checked above 484 c.Check(n.Summary, Equals, `Pending update of "pkg" snap`) 485 c.Check(n.Body, Equals, "Close the app to avoid disruptions (3 days left)") 486 c.Check(n.Hints, DeepEquals, map[string]dbus.Variant{ 487 "urgency": dbus.MakeVariant(byte(notification.LowUrgency)), 488 "desktop-entry": dbus.MakeVariant("io.snapcraft.SessionAgent"), 489 }) 490 } 491 492 func (s *restSuite) TestPostPendingRefreshNotificationFewHours(c *C) { 493 refreshInfo := &client.PendingSnapRefreshInfo{ 494 InstanceName: "pkg", 495 TimeRemaining: time.Hour * 7, 496 } 497 s.testPostPendingRefreshNotificationBody(c, refreshInfo) 498 notifications := s.notify.GetAll() 499 c.Assert(notifications, HasLen, 1) 500 n := notifications[0] 501 // boring stuff is checked above 502 c.Check(n.Summary, Equals, `Pending update of "pkg" snap`) 503 c.Check(n.Body, Equals, "Close the app to avoid disruptions (7 hours left)") 504 c.Check(n.Hints, DeepEquals, map[string]dbus.Variant{ 505 "urgency": dbus.MakeVariant(byte(notification.NormalUrgency)), 506 "desktop-entry": dbus.MakeVariant("io.snapcraft.SessionAgent"), 507 }) 508 } 509 510 func (s *restSuite) TestPostPendingRefreshNotificationFewMinutes(c *C) { 511 refreshInfo := &client.PendingSnapRefreshInfo{ 512 InstanceName: "pkg", 513 TimeRemaining: time.Minute * 15, 514 } 515 s.testPostPendingRefreshNotificationBody(c, refreshInfo) 516 notifications := s.notify.GetAll() 517 c.Assert(notifications, HasLen, 1) 518 n := notifications[0] 519 // boring stuff is checked above 520 c.Check(n.Summary, Equals, `Pending update of "pkg" snap`) 521 c.Check(n.Body, Equals, "Close the app to avoid disruptions (15 minutes left)") 522 c.Check(n.Hints, DeepEquals, map[string]dbus.Variant{ 523 "urgency": dbus.MakeVariant(byte(notification.CriticalUrgency)), 524 "desktop-entry": dbus.MakeVariant("io.snapcraft.SessionAgent"), 525 }) 526 } 527 528 func (s *restSuite) TestPostPendingRefreshNotificationBusyAppDesktopFile(c *C) { 529 refreshInfo := &client.PendingSnapRefreshInfo{ 530 InstanceName: "pkg", 531 BusyAppName: "app", 532 BusyAppDesktopEntry: "pkg_app", 533 } 534 err := os.MkdirAll(dirs.SnapDesktopFilesDir, 0755) 535 c.Assert(err, IsNil) 536 desktopFilePath := filepath.Join(dirs.SnapDesktopFilesDir, "pkg_app.desktop") 537 err = ioutil.WriteFile(desktopFilePath, []byte(` 538 [Desktop Entry] 539 Icon=app.png 540 `), 0644) 541 c.Assert(err, IsNil) 542 543 s.testPostPendingRefreshNotificationBody(c, refreshInfo) 544 notifications := s.notify.GetAll() 545 c.Assert(notifications, HasLen, 1) 546 n := notifications[0] 547 // boring stuff is checked above 548 c.Check(n.Icon, Equals, "app.png") 549 c.Check(n.Hints, DeepEquals, map[string]dbus.Variant{ 550 "urgency": dbus.MakeVariant(byte(notification.CriticalUrgency)), 551 "desktop-entry": dbus.MakeVariant("io.snapcraft.SessionAgent"), 552 }) 553 } 554 555 func (s *restSuite) TestPostPendingRefreshNotificationBusyAppMalformedDesktopFile(c *C) { 556 refreshInfo := &client.PendingSnapRefreshInfo{ 557 InstanceName: "pkg", 558 BusyAppName: "app", 559 BusyAppDesktopEntry: "pkg_app", 560 } 561 err := os.MkdirAll(dirs.SnapDesktopFilesDir, 0755) 562 c.Assert(err, IsNil) 563 desktopFilePath := filepath.Join(dirs.SnapDesktopFilesDir, "pkg_app.desktop") 564 err = ioutil.WriteFile(desktopFilePath, []byte(`garbage!`), 0644) 565 c.Assert(err, IsNil) 566 567 s.testPostPendingRefreshNotificationBody(c, refreshInfo) 568 notifications := s.notify.GetAll() 569 c.Assert(notifications, HasLen, 1) 570 n := notifications[0] 571 // boring stuff is checked above 572 c.Check(n.Icon, Equals, "") // Icon is not provided 573 c.Check(n.Hints, DeepEquals, map[string]dbus.Variant{ 574 "urgency": dbus.MakeVariant(byte(notification.CriticalUrgency)), 575 "desktop-entry": dbus.MakeVariant("io.snapcraft.SessionAgent"), 576 }) 577 } 578 579 func (s *restSuite) TestPostPendingRefreshNotificationNotificationServerFailure(c *C) { 580 s.notify.SetError(&dbus.Error{Name: "org.freedesktop.DBus.Error.Failed"}) 581 582 refreshInfo := &client.PendingSnapRefreshInfo{ 583 InstanceName: "pkg", 584 } 585 reqBody, err := json.Marshal(refreshInfo) 586 c.Assert(err, IsNil) 587 req := httptest.NewRequest("POST", "/v1/notifications/pending-refresh", bytes.NewBuffer(reqBody)) 588 req.Header.Set("Content-Type", "application/json") 589 rec := httptest.NewRecorder() 590 agent.PendingRefreshNotificationCmd.POST(agent.PendingRefreshNotificationCmd, req).ServeHTTP(rec, req) 591 c.Check(rec.Code, Equals, 500) 592 c.Check(rec.HeaderMap.Get("Content-Type"), Equals, "application/json") 593 594 var rsp resp 595 c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), IsNil) 596 c.Check(rsp.Type, Equals, agent.ResponseTypeError) 597 c.Check(rsp.Result, DeepEquals, map[string]interface{}{"message": "cannot send notification message: org.freedesktop.DBus.Error.Failed"}) 598 }