github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/desktop/notification/fdo_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 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 notification_test 21 22 import ( 23 "context" 24 "fmt" 25 "sync" 26 "time" 27 28 "github.com/godbus/dbus" 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/dbusutil" 32 "github.com/snapcore/snapd/dbusutil/dbustest" 33 "github.com/snapcore/snapd/desktop/notification" 34 "github.com/snapcore/snapd/logger" 35 "github.com/snapcore/snapd/testutil" 36 ) 37 38 type fdoSuite struct { 39 testutil.BaseTest 40 } 41 42 var _ = Suite(&fdoSuite{}) 43 44 func (s *fdoSuite) connectWithHandler(c *C, handler dbustest.DBusHandlerFunc) *notification.Server { 45 conn, err := dbustest.Connection(handler) 46 c.Assert(err, IsNil) 47 restore := dbusutil.MockOnlySessionBusAvailable(conn) 48 s.AddCleanup(restore) 49 return notification.New(conn) 50 } 51 52 func (s *fdoSuite) checkGetServerInformationRequest(c *C, msg *dbus.Message) { 53 c.Assert(msg.Type, Equals, dbus.TypeMethodCall) 54 c.Check(msg.Flags, Equals, dbus.Flags(0)) 55 c.Check(msg.Headers, DeepEquals, map[dbus.HeaderField]dbus.Variant{ 56 dbus.FieldDestination: dbus.MakeVariant("org.freedesktop.Notifications"), 57 dbus.FieldPath: dbus.MakeVariant(dbus.ObjectPath("/org/freedesktop/Notifications")), 58 dbus.FieldInterface: dbus.MakeVariant("org.freedesktop.Notifications"), 59 dbus.FieldMember: dbus.MakeVariant("GetServerInformation"), 60 }) 61 c.Check(msg.Body, HasLen, 0) 62 } 63 64 func (s *fdoSuite) checkGetCapabilitiesRequest(c *C, msg *dbus.Message) { 65 c.Assert(msg.Type, Equals, dbus.TypeMethodCall) 66 c.Check(msg.Flags, Equals, dbus.Flags(0)) 67 c.Check(msg.Headers, DeepEquals, map[dbus.HeaderField]dbus.Variant{ 68 dbus.FieldDestination: dbus.MakeVariant("org.freedesktop.Notifications"), 69 dbus.FieldPath: dbus.MakeVariant(dbus.ObjectPath("/org/freedesktop/Notifications")), 70 dbus.FieldInterface: dbus.MakeVariant("org.freedesktop.Notifications"), 71 dbus.FieldMember: dbus.MakeVariant("GetCapabilities"), 72 }) 73 c.Check(msg.Body, HasLen, 0) 74 } 75 76 func (s *fdoSuite) checkNotifyRequest(c *C, msg *dbus.Message) { 77 c.Assert(msg.Type, Equals, dbus.TypeMethodCall) 78 c.Check(msg.Flags, Equals, dbus.Flags(0)) 79 c.Check(msg.Headers, DeepEquals, map[dbus.HeaderField]dbus.Variant{ 80 dbus.FieldDestination: dbus.MakeVariant("org.freedesktop.Notifications"), 81 dbus.FieldPath: dbus.MakeVariant(dbus.ObjectPath("/org/freedesktop/Notifications")), 82 dbus.FieldInterface: dbus.MakeVariant("org.freedesktop.Notifications"), 83 dbus.FieldMember: dbus.MakeVariant("Notify"), 84 dbus.FieldSignature: dbus.MakeVariant(dbus.SignatureOf( 85 "", uint32(0), "", "", "", []string{}, map[string]dbus.Variant{}, int32(0), 86 )), 87 }) 88 c.Check(msg.Body, HasLen, 8) 89 } 90 91 func (s *fdoSuite) checkCloseNotificationRequest(c *C, msg *dbus.Message) { 92 c.Assert(msg.Type, Equals, dbus.TypeMethodCall) 93 c.Check(msg.Flags, Equals, dbus.Flags(0)) 94 c.Check(msg.Headers, DeepEquals, map[dbus.HeaderField]dbus.Variant{ 95 dbus.FieldDestination: dbus.MakeVariant("org.freedesktop.Notifications"), 96 dbus.FieldPath: dbus.MakeVariant(dbus.ObjectPath("/org/freedesktop/Notifications")), 97 dbus.FieldInterface: dbus.MakeVariant("org.freedesktop.Notifications"), 98 dbus.FieldMember: dbus.MakeVariant("CloseNotification"), 99 dbus.FieldSignature: dbus.MakeVariant(dbus.SignatureOf(uint32(0))), 100 }) 101 c.Check(msg.Body, HasLen, 1) 102 } 103 104 func (s *fdoSuite) nameHasNoOwnerResponse(c *C, msg *dbus.Message) *dbus.Message { 105 return &dbus.Message{ 106 Type: dbus.TypeError, 107 Headers: map[dbus.HeaderField]dbus.Variant{ 108 dbus.FieldReplySerial: dbus.MakeVariant(msg.Serial()), 109 dbus.FieldSender: dbus.MakeVariant(":1"), // This does not matter. 110 // dbus.FieldDestination is provided automatically by DBus test helper. 111 dbus.FieldErrorName: dbus.MakeVariant("org.freedesktop.DBus.Error.NameHasNoOwner"), 112 }, 113 } 114 } 115 116 func (s *fdoSuite) TestServerInformationSuccess(c *C) { 117 srv := s.connectWithHandler(c, func(msg *dbus.Message, n int) ([]*dbus.Message, error) { 118 switch n { 119 case 0: 120 s.checkGetServerInformationRequest(c, msg) 121 responseSig := dbus.SignatureOf("", "", "", "") 122 response := &dbus.Message{ 123 Type: dbus.TypeMethodReply, 124 Headers: map[dbus.HeaderField]dbus.Variant{ 125 dbus.FieldReplySerial: dbus.MakeVariant(msg.Serial()), 126 dbus.FieldSender: dbus.MakeVariant(":1"), // This does not matter. 127 // dbus.FieldDestination is provided automatically by DBus test helper. 128 dbus.FieldSignature: dbus.MakeVariant(responseSig), 129 }, 130 Body: []interface{}{"name", "vendor", "version", "specVersion"}, 131 } 132 return []*dbus.Message{response}, nil 133 } 134 return nil, fmt.Errorf("unexpected message #%d: %s", n, msg) 135 }) 136 name, vendor, version, specVersion, err := srv.ServerInformation() 137 c.Assert(err, IsNil) 138 c.Check(name, Equals, "name") 139 c.Check(vendor, Equals, "vendor") 140 c.Check(version, Equals, "version") 141 c.Check(specVersion, Equals, "specVersion") 142 } 143 144 func (s *fdoSuite) TestServerInformationError(c *C) { 145 srv := s.connectWithHandler(c, func(msg *dbus.Message, n int) ([]*dbus.Message, error) { 146 switch n { 147 case 0: 148 s.checkGetServerInformationRequest(c, msg) 149 response := s.nameHasNoOwnerResponse(c, msg) 150 return []*dbus.Message{response}, nil 151 } 152 return nil, fmt.Errorf("unexpected message #%d: %s", n, msg) 153 }) 154 _, _, _, _, err := srv.ServerInformation() 155 c.Assert(err, ErrorMatches, "org.freedesktop.DBus.Error.NameHasNoOwner") 156 } 157 158 func (s *fdoSuite) TestServerCapabilitiesSuccess(c *C) { 159 srv := s.connectWithHandler(c, func(msg *dbus.Message, n int) ([]*dbus.Message, error) { 160 switch n { 161 case 0: 162 s.checkGetCapabilitiesRequest(c, msg) 163 responseSig := dbus.SignatureOf([]string{}) 164 response := &dbus.Message{ 165 Type: dbus.TypeMethodReply, 166 Headers: map[dbus.HeaderField]dbus.Variant{ 167 dbus.FieldReplySerial: dbus.MakeVariant(msg.Serial()), 168 dbus.FieldSender: dbus.MakeVariant(":1"), // This does not matter. 169 // dbus.FieldDestination is provided automatically by DBus test helper. 170 dbus.FieldSignature: dbus.MakeVariant(responseSig), 171 }, 172 Body: []interface{}{ 173 []string{"cap-foo", "cap-bar"}, 174 }, 175 } 176 return []*dbus.Message{response}, nil 177 } 178 return nil, fmt.Errorf("unexpected message #%d: %s", n, msg) 179 }) 180 caps, err := srv.ServerCapabilities() 181 c.Assert(err, IsNil) 182 c.Check(caps, DeepEquals, []notification.ServerCapability{"cap-foo", "cap-bar"}) 183 } 184 185 func (s *fdoSuite) TestServerCapabilitiesError(c *C) { 186 srv := s.connectWithHandler(c, func(msg *dbus.Message, n int) ([]*dbus.Message, error) { 187 switch n { 188 case 0: 189 s.checkGetCapabilitiesRequest(c, msg) 190 response := s.nameHasNoOwnerResponse(c, msg) 191 return []*dbus.Message{response}, nil 192 } 193 return nil, fmt.Errorf("unexpected message #%d: %s", n, msg) 194 }) 195 _, err := srv.ServerCapabilities() 196 c.Assert(err, ErrorMatches, "org.freedesktop.DBus.Error.NameHasNoOwner") 197 } 198 199 func (s *fdoSuite) TestSendNotificationSuccess(c *C) { 200 srv := s.connectWithHandler(c, func(msg *dbus.Message, n int) ([]*dbus.Message, error) { 201 switch n { 202 case 0: 203 s.checkNotifyRequest(c, msg) 204 c.Check(msg.Body[0], Equals, "app-name") 205 c.Check(msg.Body[1], Equals, uint32(42)) 206 c.Check(msg.Body[2], Equals, "icon") 207 c.Check(msg.Body[3], Equals, "summary") 208 c.Check(msg.Body[4], Equals, "body") 209 c.Check(msg.Body[5], DeepEquals, []string{"key-1", "text-1", "key-2", "text-2"}) 210 c.Check(msg.Body[6], DeepEquals, map[string]dbus.Variant{ 211 "hint-str": dbus.MakeVariant("str"), 212 "hint-bool": dbus.MakeVariant(true), 213 }) 214 c.Check(msg.Body[7], Equals, int32(1000)) 215 responseSig := dbus.SignatureOf(uint32(0)) 216 response := &dbus.Message{ 217 Type: dbus.TypeMethodReply, 218 Headers: map[dbus.HeaderField]dbus.Variant{ 219 dbus.FieldReplySerial: dbus.MakeVariant(msg.Serial()), 220 dbus.FieldSender: dbus.MakeVariant(":1"), // This does not matter. 221 // dbus.FieldDestination is provided automatically by DBus test helper. 222 dbus.FieldSignature: dbus.MakeVariant(responseSig), 223 }, 224 Body: []interface{}{uint32(7)}, 225 } 226 return []*dbus.Message{response}, nil 227 } 228 return nil, fmt.Errorf("unexpected message #%d: %s", n, msg) 229 }) 230 id, err := srv.SendNotification(¬ification.Message{ 231 AppName: "app-name", 232 Icon: "icon", 233 Summary: "summary", 234 Body: "body", 235 ExpireTimeout: time.Second * 1, 236 ReplacesID: notification.ID(42), 237 Actions: []notification.Action{ 238 {ActionKey: "key-1", LocalizedText: "text-1"}, 239 {ActionKey: "key-2", LocalizedText: "text-2"}, 240 }, 241 Hints: []notification.Hint{ 242 {Name: "hint-str", Value: "str"}, 243 {Name: "hint-bool", Value: true}, 244 }, 245 }) 246 c.Assert(err, IsNil) 247 c.Check(id, Equals, notification.ID(7)) 248 } 249 250 func (s *fdoSuite) TestSendNotificationWithServerDecitedExpireTimeout(c *C) { 251 srv := s.connectWithHandler(c, func(msg *dbus.Message, n int) ([]*dbus.Message, error) { 252 switch n { 253 case 0: 254 s.checkNotifyRequest(c, msg) 255 c.Check(msg.Body[7], Equals, int32(-1)) 256 responseSig := dbus.SignatureOf(uint32(0)) 257 response := &dbus.Message{ 258 Type: dbus.TypeMethodReply, 259 Headers: map[dbus.HeaderField]dbus.Variant{ 260 dbus.FieldReplySerial: dbus.MakeVariant(msg.Serial()), 261 dbus.FieldSender: dbus.MakeVariant(":1"), // This does not matter. 262 // dbus.FieldDestination is provided automatically by DBus test helper. 263 dbus.FieldSignature: dbus.MakeVariant(responseSig), 264 }, 265 Body: []interface{}{uint32(7)}, 266 } 267 return []*dbus.Message{response}, nil 268 } 269 return nil, fmt.Errorf("unexpected message #%d: %s", n, msg) 270 }) 271 id, err := srv.SendNotification(¬ification.Message{ 272 ExpireTimeout: notification.ServerSelectedExpireTimeout, 273 }) 274 c.Assert(err, IsNil) 275 c.Check(id, Equals, notification.ID(7)) 276 } 277 278 func (s *fdoSuite) TestSendNotificationError(c *C) { 279 srv := s.connectWithHandler(c, func(msg *dbus.Message, n int) ([]*dbus.Message, error) { 280 switch n { 281 case 0: 282 s.checkNotifyRequest(c, msg) 283 response := s.nameHasNoOwnerResponse(c, msg) 284 return []*dbus.Message{response}, nil 285 } 286 return nil, fmt.Errorf("unexpected message #%d: %s", n, msg) 287 }) 288 _, err := srv.SendNotification(¬ification.Message{}) 289 c.Assert(err, ErrorMatches, "org.freedesktop.DBus.Error.NameHasNoOwner") 290 } 291 292 func (s *fdoSuite) TestCloseNotificationSuccess(c *C) { 293 srv := s.connectWithHandler(c, func(msg *dbus.Message, n int) ([]*dbus.Message, error) { 294 switch n { 295 case 0: 296 s.checkCloseNotificationRequest(c, msg) 297 c.Check(msg.Body[0], Equals, uint32(42)) 298 response := &dbus.Message{ 299 Type: dbus.TypeMethodReply, 300 Headers: map[dbus.HeaderField]dbus.Variant{ 301 dbus.FieldReplySerial: dbus.MakeVariant(msg.Serial()), 302 dbus.FieldSender: dbus.MakeVariant(":1"), // This does not matter. 303 // dbus.FieldDestination is provided automatically by DBus test helper. 304 }, 305 } 306 return []*dbus.Message{response}, nil 307 } 308 return nil, fmt.Errorf("unexpected message #%d: %s", n, msg) 309 }) 310 err := srv.CloseNotification(notification.ID(42)) 311 c.Assert(err, IsNil) 312 } 313 314 func (s *fdoSuite) TestCloseNotificationError(c *C) { 315 srv := s.connectWithHandler(c, func(msg *dbus.Message, n int) ([]*dbus.Message, error) { 316 switch n { 317 case 0: 318 s.checkCloseNotificationRequest(c, msg) 319 response := s.nameHasNoOwnerResponse(c, msg) 320 return []*dbus.Message{response}, nil 321 } 322 return nil, fmt.Errorf("unexpected message #%d: %s", n, msg) 323 }) 324 err := srv.CloseNotification(notification.ID(42)) 325 c.Assert(err, ErrorMatches, "org.freedesktop.DBus.Error.NameHasNoOwner") 326 } 327 328 type testObserver struct { 329 notificationClosed func(notification.ID, notification.CloseReason) error 330 actionInvoked func(notification.ID, string) error 331 } 332 333 func (o *testObserver) NotificationClosed(id notification.ID, reason notification.CloseReason) error { 334 if o.notificationClosed != nil { 335 return o.notificationClosed(id, reason) 336 } 337 return nil 338 } 339 340 func (o *testObserver) ActionInvoked(id notification.ID, actionKey string) error { 341 if o.actionInvoked != nil { 342 return o.actionInvoked(id, actionKey) 343 } 344 return nil 345 } 346 347 func (s *fdoSuite) checkAddMatchRequest(c *C, msg *dbus.Message) { 348 c.Assert(msg.Type, Equals, dbus.TypeMethodCall) 349 c.Check(msg.Flags, Equals, dbus.Flags(0)) 350 c.Check(msg.Headers, DeepEquals, map[dbus.HeaderField]dbus.Variant{ 351 dbus.FieldDestination: dbus.MakeVariant("org.freedesktop.DBus"), 352 dbus.FieldPath: dbus.MakeVariant(dbus.ObjectPath("/org/freedesktop/DBus")), 353 dbus.FieldInterface: dbus.MakeVariant("org.freedesktop.DBus"), 354 dbus.FieldMember: dbus.MakeVariant("AddMatch"), 355 dbus.FieldSignature: dbus.MakeVariant(dbus.SignatureOf("")), 356 }) 357 } 358 359 func (s *fdoSuite) checkRemoveMatchRequest(c *C, msg *dbus.Message) { 360 c.Assert(msg.Type, Equals, dbus.TypeMethodCall) 361 c.Check(msg.Flags, Equals, dbus.Flags(0)) 362 c.Check(msg.Headers, DeepEquals, map[dbus.HeaderField]dbus.Variant{ 363 dbus.FieldDestination: dbus.MakeVariant("org.freedesktop.DBus"), 364 dbus.FieldPath: dbus.MakeVariant(dbus.ObjectPath("/org/freedesktop/DBus")), 365 dbus.FieldInterface: dbus.MakeVariant("org.freedesktop.DBus"), 366 dbus.FieldMember: dbus.MakeVariant("RemoveMatch"), 367 dbus.FieldSignature: dbus.MakeVariant(dbus.SignatureOf("")), 368 }) 369 } 370 371 func (s *fdoSuite) addMatchResponse(c *C, msg *dbus.Message) *dbus.Message { 372 return &dbus.Message{ 373 Type: dbus.TypeMethodReply, 374 Headers: map[dbus.HeaderField]dbus.Variant{ 375 dbus.FieldReplySerial: dbus.MakeVariant(msg.Serial()), 376 dbus.FieldSender: dbus.MakeVariant(":1"), // This does not matter. 377 // dbus.FieldDestination is provided automatically by DBus test helper. 378 }, 379 } 380 } 381 382 func (s *fdoSuite) removeMatchResponse(c *C, msg *dbus.Message) *dbus.Message { 383 return &dbus.Message{ 384 Type: dbus.TypeMethodReply, 385 Headers: map[dbus.HeaderField]dbus.Variant{ 386 dbus.FieldReplySerial: dbus.MakeVariant(msg.Serial()), 387 dbus.FieldSender: dbus.MakeVariant(":1"), // This does not matter. 388 // dbus.FieldDestination is provided automatically by DBus test helper. 389 }, 390 } 391 } 392 393 func (s *fdoSuite) TestObserveNotificationsContextAndSignalWatch(c *C) { 394 ctx, cancel := context.WithCancel(context.TODO()) 395 msgsSeen := 0 396 addMatchSeen := make(chan struct{}, 1) 397 defer close(addMatchSeen) 398 srv := s.connectWithHandler(c, func(msg *dbus.Message, n int) ([]*dbus.Message, error) { 399 msgsSeen++ 400 switch n { 401 case 0: 402 s.checkAddMatchRequest(c, msg) 403 c.Check(msg.Body, HasLen, 1) 404 c.Check(msg.Body[0], Equals, "type='signal',sender='org.freedesktop.Notifications',path='/org/freedesktop/Notifications',interface='org.freedesktop.Notifications'") 405 response := s.addMatchResponse(c, msg) 406 addMatchSeen <- struct{}{} 407 return []*dbus.Message{response}, nil 408 case 1: 409 s.checkRemoveMatchRequest(c, msg) 410 c.Check(msg.Body, HasLen, 1) 411 c.Check(msg.Body[0], Equals, "type='signal',sender='org.freedesktop.Notifications',path='/org/freedesktop/Notifications',interface='org.freedesktop.Notifications'") 412 response := s.removeMatchResponse(c, msg) 413 return []*dbus.Message{response}, nil 414 default: 415 return nil, fmt.Errorf("unexpected message #%d: %s", n, msg) 416 } 417 }) 418 419 var wg sync.WaitGroup 420 wg.Add(1) 421 go func() { 422 err := srv.ObserveNotifications(ctx, &testObserver{}) 423 c.Assert(err, ErrorMatches, "context canceled") 424 wg.Done() 425 }() 426 // Wait for the signal that we saw the AddMatch message and then stop. 427 <-addMatchSeen 428 cancel() 429 // Wait for ObserveNotifications to return 430 wg.Wait() 431 c.Check(msgsSeen, Equals, 2) 432 } 433 434 func (s *fdoSuite) TestObserveNotificationsAddWatchError(c *C) { 435 srv := s.connectWithHandler(c, func(msg *dbus.Message, n int) ([]*dbus.Message, error) { 436 switch n { 437 case 0: 438 s.checkAddMatchRequest(c, msg) 439 response := s.nameHasNoOwnerResponse(c, msg) 440 return []*dbus.Message{response}, nil 441 default: 442 return nil, fmt.Errorf("unexpected message #%d: %s", n, msg) 443 } 444 }) 445 err := srv.ObserveNotifications(context.TODO(), &testObserver{}) 446 c.Assert(err, ErrorMatches, "org.freedesktop.DBus.Error.NameHasNoOwner") 447 } 448 449 func (s *fdoSuite) TestObserveNotificationsRemoveWatchError(c *C) { 450 logBuffer, restore := logger.MockLogger() 451 defer restore() 452 453 ctx, cancel := context.WithCancel(context.TODO()) 454 msgsSeen := 0 455 addMatchSeen := make(chan struct{}, 1) 456 defer close(addMatchSeen) 457 srv := s.connectWithHandler(c, func(msg *dbus.Message, n int) ([]*dbus.Message, error) { 458 msgsSeen++ 459 switch n { 460 case 0: 461 s.checkAddMatchRequest(c, msg) 462 response := s.addMatchResponse(c, msg) 463 addMatchSeen <- struct{}{} 464 return []*dbus.Message{response}, nil 465 case 1: 466 s.checkRemoveMatchRequest(c, msg) 467 response := s.nameHasNoOwnerResponse(c, msg) 468 return []*dbus.Message{response}, nil 469 default: 470 return nil, fmt.Errorf("unexpected message #%d: %s", n, msg) 471 } 472 }) 473 474 var wg sync.WaitGroup 475 wg.Add(1) 476 go func() { 477 err := srv.ObserveNotifications(ctx, &testObserver{}) 478 // The error from RemoveWatch is not clobbering the return value of ObserveNotifications. 479 c.Assert(err, ErrorMatches, "context canceled") 480 c.Check(logBuffer.String(), testutil.Contains, "Cannot remove D-Bus signal matcher: org.freedesktop.DBus.Error.NameHasNoOwner\n") 481 wg.Done() 482 }() 483 // Wait for the signal that we saw the AddMatch message and then stop. 484 <-addMatchSeen 485 cancel() 486 // Wait for ObserveNotifications to return 487 wg.Wait() 488 c.Check(msgsSeen, Equals, 2) 489 } 490 491 func (s *fdoSuite) TestObserveNotificationsProcessingError(c *C) { 492 msgsSeen := 0 493 srv := s.connectWithHandler(c, func(msg *dbus.Message, n int) ([]*dbus.Message, error) { 494 msgsSeen++ 495 switch n { 496 case 0: 497 s.checkAddMatchRequest(c, msg) 498 response := s.addMatchResponse(c, msg) 499 sig := &dbus.Message{ 500 Type: dbus.TypeSignal, 501 Headers: map[dbus.HeaderField]dbus.Variant{ 502 dbus.FieldPath: dbus.MakeVariant(dbus.ObjectPath("/org/freedesktop/Notifications")), 503 dbus.FieldInterface: dbus.MakeVariant("org.freedesktop.Notifications"), 504 dbus.FieldMember: dbus.MakeVariant("ActionInvoked"), 505 dbus.FieldSender: dbus.MakeVariant("org.freedesktop.Notifications"), 506 dbus.FieldSignature: dbus.MakeVariant(dbus.SignatureOf(uint32(0), "")), 507 }, 508 Body: []interface{}{uint32(42), "action-key"}, 509 } 510 // Send the DBus response for the method call and an additional signal. 511 return []*dbus.Message{response, sig}, nil 512 case 1: 513 s.checkRemoveMatchRequest(c, msg) 514 response := s.removeMatchResponse(c, msg) 515 return []*dbus.Message{response}, nil 516 default: 517 return nil, fmt.Errorf("unexpected message #%d: %s", n, msg) 518 } 519 }) 520 err := srv.ObserveNotifications(context.TODO(), &testObserver{ 521 actionInvoked: func(id notification.ID, actionKey string) error { 522 c.Check(id, Equals, notification.ID(42)) 523 c.Check(actionKey, Equals, "action-key") 524 return fmt.Errorf("boom") 525 }, 526 }) 527 c.Assert(err, ErrorMatches, "cannot process ActionInvoked signal: boom") 528 c.Check(msgsSeen, Equals, 2) 529 } 530 531 func (s *fdoSuite) TestProcessActionInvokedSignalSuccess(c *C) { 532 called := false 533 err := notification.ProcessSignal(&dbus.Signal{ 534 // Sender and Path are not used 535 Name: "org.freedesktop.Notifications.ActionInvoked", 536 Body: []interface{}{uint32(42), "action-key"}, 537 }, &testObserver{ 538 actionInvoked: func(id notification.ID, actionKey string) error { 539 called = true 540 c.Check(id, Equals, notification.ID(42)) 541 c.Check(actionKey, Equals, "action-key") 542 return nil 543 }, 544 }) 545 c.Assert(err, IsNil) 546 c.Assert(called, Equals, true) 547 } 548 549 func (s *fdoSuite) TestProcessActionInvokedSignalError(c *C) { 550 err := notification.ProcessSignal(&dbus.Signal{ 551 Name: "org.freedesktop.Notifications.ActionInvoked", 552 Body: []interface{}{uint32(42), "action-key"}, 553 }, &testObserver{ 554 actionInvoked: func(id notification.ID, actionKey string) error { 555 return fmt.Errorf("boom") 556 }, 557 }) 558 c.Assert(err, ErrorMatches, "cannot process ActionInvoked signal: boom") 559 } 560 561 func (s *fdoSuite) TestProcessActionInvokedSignalBodyParseErrors(c *C) { 562 err := notification.ProcessSignal(&dbus.Signal{ 563 Name: "org.freedesktop.Notifications.ActionInvoked", 564 Body: []interface{}{uint32(42), "action-key", "unexpected"}, 565 }, &testObserver{}) 566 c.Assert(err, ErrorMatches, "cannot process ActionInvoked signal: unexpected number of body elements: 3") 567 568 err = notification.ProcessSignal(&dbus.Signal{ 569 Name: "org.freedesktop.Notifications.ActionInvoked", 570 Body: []interface{}{uint32(42)}, 571 }, &testObserver{}) 572 c.Assert(err, ErrorMatches, "cannot process ActionInvoked signal: unexpected number of body elements: 1") 573 574 err = notification.ProcessSignal(&dbus.Signal{ 575 Name: "org.freedesktop.Notifications.ActionInvoked", 576 Body: []interface{}{uint32(42), true}, 577 }, &testObserver{}) 578 c.Assert(err, ErrorMatches, "cannot process ActionInvoked signal: expected second body element to be string, got bool") 579 580 err = notification.ProcessSignal(&dbus.Signal{ 581 Name: "org.freedesktop.Notifications.ActionInvoked", 582 Body: []interface{}{true, "action-key"}, 583 }, &testObserver{}) 584 c.Assert(err, ErrorMatches, "cannot process ActionInvoked signal: expected first body element to be uint32, got bool") 585 } 586 587 func (s *fdoSuite) TestProcessNotificationClosedSignalSuccess(c *C) { 588 called := false 589 err := notification.ProcessSignal(&dbus.Signal{ 590 Name: "org.freedesktop.Notifications.NotificationClosed", 591 Body: []interface{}{uint32(42), uint32(2)}, 592 }, &testObserver{ 593 notificationClosed: func(id notification.ID, reason notification.CloseReason) error { 594 called = true 595 c.Check(id, Equals, notification.ID(42)) 596 c.Check(reason, Equals, notification.CloseReason(2)) 597 return nil 598 }, 599 }) 600 c.Assert(err, IsNil) 601 c.Assert(called, Equals, true) 602 } 603 604 func (s *fdoSuite) TestProcessNotificationClosedSignalError(c *C) { 605 err := notification.ProcessSignal(&dbus.Signal{ 606 Name: "org.freedesktop.Notifications.NotificationClosed", 607 Body: []interface{}{uint32(42), uint32(2)}, 608 }, &testObserver{ 609 notificationClosed: func(id notification.ID, reason notification.CloseReason) error { 610 return fmt.Errorf("boom") 611 }, 612 }) 613 c.Assert(err, ErrorMatches, "cannot process NotificationClosed signal: boom") 614 } 615 616 func (s *fdoSuite) TestProcessNotificationClosedSignalBodyParseErrors(c *C) { 617 err := notification.ProcessSignal(&dbus.Signal{ 618 Name: "org.freedesktop.Notifications.NotificationClosed", 619 Body: []interface{}{uint32(42), uint32(2), "unexpected"}, 620 }, &testObserver{}) 621 c.Assert(err, ErrorMatches, "cannot process NotificationClosed signal: unexpected number of body elements: 3") 622 623 err = notification.ProcessSignal(&dbus.Signal{ 624 Name: "org.freedesktop.Notifications.NotificationClosed", 625 Body: []interface{}{uint32(42)}, 626 }, &testObserver{}) 627 c.Assert(err, ErrorMatches, "cannot process NotificationClosed signal: unexpected number of body elements: 1") 628 629 err = notification.ProcessSignal(&dbus.Signal{ 630 Name: "org.freedesktop.Notifications.NotificationClosed", 631 Body: []interface{}{uint32(42), true}, 632 }, &testObserver{}) 633 c.Assert(err, ErrorMatches, "cannot process NotificationClosed signal: expected second body element to be uint32, got bool") 634 635 err = notification.ProcessSignal(&dbus.Signal{ 636 Name: "org.freedesktop.Notifications.NotificationClosed", 637 Body: []interface{}{true, uint32(2)}, 638 }, &testObserver{}) 639 c.Assert(err, ErrorMatches, "cannot process NotificationClosed signal: expected first body element to be uint32, got bool") 640 }