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