229 lines
7.7 KiB
Go
229 lines
7.7 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package notification
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/godbus/dbus/v5"
|
|
)
|
|
|
|
func TestMakoSyncAnnouncesOnlyNewNotifications(t *testing.T) {
|
|
presenter := NewPresenter()
|
|
announcer := &fakeAnnouncer{}
|
|
monitor := NewMakoMonitor(nil, presenter, announcer)
|
|
monitor.available = true
|
|
|
|
notifications := []map[string]dbus.Variant{makoNotification(7)}
|
|
monitor.sync(notifications, true)
|
|
if len(announcer.entries) != 1 {
|
|
t.Fatalf("announced %d notifications, want 1", len(announcer.entries))
|
|
}
|
|
if announcer.entries[0].Message != "Notification Mail You have one message" {
|
|
t.Fatalf("message = %q", announcer.entries[0].Message)
|
|
}
|
|
|
|
monitor.sync(notifications, true)
|
|
if len(announcer.entries) != 1 {
|
|
t.Fatalf("announced %d notifications after refresh, want 1", len(announcer.entries))
|
|
}
|
|
}
|
|
|
|
func TestMakoParseNormalizesMetadata(t *testing.T) {
|
|
metadata, id := parseMako(makoNotification(9))
|
|
if id == nil || *id != 9 {
|
|
t.Fatalf("id = %v, want 9", id)
|
|
}
|
|
if metadata.AppName != "Application" || metadata.Summary != "Mail" ||
|
|
metadata.Body != "You have one message" || metadata.DesktopEntry != "application" ||
|
|
metadata.Urgency != 2 || metadata.Actions["default"] != "View" {
|
|
t.Fatalf("metadata = %+v", metadata)
|
|
}
|
|
}
|
|
|
|
func TestMakoStopMarksLiveHistoryUnavailable(t *testing.T) {
|
|
presenter := NewPresenter()
|
|
notificationID := 12
|
|
presenter.Save("Notification Mail", SaveOptions{
|
|
Source: MakoSource, NotificationID: ¬ificationID, Live: true,
|
|
})
|
|
monitor := NewMakoMonitor(&fakeMakoConnection{}, presenter, &fakeAnnouncer{})
|
|
monitor.available = true
|
|
monitor.running = true
|
|
monitor.stop = make(chan struct{})
|
|
monitor.stopped = make(chan struct{})
|
|
close(monitor.stopped)
|
|
if err := monitor.Stop(context.Background()); err != nil {
|
|
t.Fatalf("stop mako: %v", err)
|
|
}
|
|
entries := presenter.Entries()
|
|
if len(entries) != 1 || entries[0].Live {
|
|
t.Fatalf("entries = %+v, want one unavailable entry", entries)
|
|
}
|
|
}
|
|
|
|
func TestMakoOwnerLossMarksLiveHistoryUnavailable(t *testing.T) {
|
|
presenter := NewPresenter()
|
|
notificationID := 12
|
|
presenter.Save("Notification Mail", SaveOptions{
|
|
Source: MakoSource, NotificationID: ¬ificationID, Live: true,
|
|
})
|
|
monitor := NewMakoMonitor(&fakeMakoConnection{}, presenter, &fakeAnnouncer{})
|
|
monitor.available = true
|
|
monitor.handleSignal(&dbus.Signal{
|
|
Name: "org.freedesktop.DBus.NameOwnerChanged",
|
|
Path: "/org/freedesktop/DBus",
|
|
Body: []interface{}{makoBusName, ":1.10", ""},
|
|
})
|
|
|
|
entries := presenter.Entries()
|
|
if monitor.available || len(entries) != 1 || entries[0].Live {
|
|
t.Fatalf("available = %v, entries = %+v", monitor.available, entries)
|
|
}
|
|
}
|
|
|
|
func TestMakoUnsupportedDaemonMarksHistoryUnavailable(t *testing.T) {
|
|
presenter := NewPresenter()
|
|
notificationID := 12
|
|
presenter.Save("Notification Mail", SaveOptions{
|
|
Source: MakoSource, NotificationID: ¬ificationID, Live: true,
|
|
})
|
|
monitor := NewMakoMonitor(&fakeMakoConnection{
|
|
object: &fakeMakoObject{err: errors.New("object does not exist")},
|
|
}, presenter, &fakeAnnouncer{})
|
|
monitor.available = true
|
|
|
|
if monitor.Refresh(false) {
|
|
t.Fatal("unsupported notification daemon reported as available")
|
|
}
|
|
entries := presenter.Entries()
|
|
if len(entries) != 1 || entries[0].Live {
|
|
t.Fatalf("entries = %+v, want one unavailable entry", entries)
|
|
}
|
|
if len(presenter.ActionsForEntry(entries[0])) != 0 {
|
|
t.Fatal("unsupported notification daemon retained live actions")
|
|
}
|
|
}
|
|
|
|
func TestPresenterRoutesLiveMakoActions(t *testing.T) {
|
|
presenter := NewPresenter()
|
|
controller := &fakeMakoController{}
|
|
presenter.SetMakoController(controller)
|
|
notificationID := 6
|
|
entry := presenter.Save("Notification Mail New message", SaveOptions{
|
|
Source: MakoSource, SourceGeneration: 1,
|
|
NotificationID: ¬ificationID, Live: true,
|
|
Actions: map[string]string{"default": "View"},
|
|
})
|
|
|
|
if !presenter.CanControlEntry(entry) {
|
|
t.Fatal("live mako entry was not controllable")
|
|
}
|
|
if actions := presenter.ActionsForEntry(entry); actions["default"] != "View" {
|
|
t.Fatalf("actions = %q, want View", actions["default"])
|
|
}
|
|
|
|
messages, dismissed := presenter.DismissEntry(entry)
|
|
if !dismissed || messages[0] != MessageDismissed {
|
|
t.Fatalf("dismiss = %v %q", dismissed, messages)
|
|
}
|
|
if len(presenter.Entries()) != 0 || controller.dismissed[0] != 6 {
|
|
t.Fatalf("entries = %+v dismissed = %v", presenter.Entries(), controller.dismissed)
|
|
}
|
|
|
|
actionMessages, invoked := presenter.InvokeActionEntry(entry, "default")
|
|
if !invoked || actionMessages[0] != MessageActionInvoked || controller.invoked[0] != (makoAction{6, "default"}) {
|
|
t.Fatalf("invoke = %v %q", invoked, actionMessages)
|
|
}
|
|
}
|
|
|
|
func makoNotification(id uint32) map[string]dbus.Variant {
|
|
return map[string]dbus.Variant{
|
|
"id": dbus.MakeVariant(id),
|
|
"app-name": dbus.MakeVariant("Application"),
|
|
"summary": dbus.MakeVariant("<b>Mail</b>"),
|
|
"body": dbus.MakeVariant("You have one message"),
|
|
"desktop-entry": dbus.MakeVariant("application"),
|
|
"urgency": dbus.MakeVariant(int32(2)),
|
|
"actions": dbus.MakeVariant(map[string]dbus.Variant{"default": dbus.MakeVariant("View")}),
|
|
}
|
|
}
|
|
|
|
type fakeAnnouncer struct{ entries []Entry }
|
|
|
|
func (announcer *fakeAnnouncer) Announce(entry Entry) {
|
|
announcer.entries = append(announcer.entries, entry)
|
|
}
|
|
|
|
type fakeMakoConnection struct {
|
|
object *fakeMakoObject
|
|
}
|
|
|
|
func (connection *fakeMakoConnection) Signal(chan<- *dbus.Signal) {}
|
|
|
|
func (connection *fakeMakoConnection) RemoveSignal(chan<- *dbus.Signal) {}
|
|
func (connection *fakeMakoConnection) Object(string, dbus.ObjectPath) dbus.BusObject {
|
|
return connection.object
|
|
}
|
|
func (connection *fakeMakoConnection) AddMatchSignal(...dbus.MatchOption) error { return nil }
|
|
func (connection *fakeMakoConnection) RemoveMatchSignal(...dbus.MatchOption) error { return nil }
|
|
func (connection *fakeMakoConnection) Close() error { return nil }
|
|
|
|
type fakeMakoObject struct {
|
|
err error
|
|
}
|
|
|
|
func (object *fakeMakoObject) Call(string, dbus.Flags, ...interface{}) *dbus.Call {
|
|
return &dbus.Call{Err: object.err}
|
|
}
|
|
func (object *fakeMakoObject) CallWithContext(context.Context, string, dbus.Flags, ...interface{}) *dbus.Call {
|
|
return &dbus.Call{Err: object.err}
|
|
}
|
|
func (object *fakeMakoObject) Go(string, dbus.Flags, chan *dbus.Call, ...interface{}) *dbus.Call {
|
|
return &dbus.Call{Err: object.err}
|
|
}
|
|
func (object *fakeMakoObject) GoWithContext(context.Context, string, dbus.Flags, chan *dbus.Call, ...interface{}) *dbus.Call {
|
|
return &dbus.Call{Err: object.err}
|
|
}
|
|
func (object *fakeMakoObject) AddMatchSignal(string, string, ...dbus.MatchOption) *dbus.Call {
|
|
return &dbus.Call{Err: object.err}
|
|
}
|
|
func (object *fakeMakoObject) RemoveMatchSignal(string, string, ...dbus.MatchOption) *dbus.Call {
|
|
return &dbus.Call{Err: object.err}
|
|
}
|
|
func (object *fakeMakoObject) GetProperty(string) (dbus.Variant, error) {
|
|
return dbus.Variant{}, object.err
|
|
}
|
|
func (object *fakeMakoObject) StoreProperty(string, interface{}) error {
|
|
return object.err
|
|
}
|
|
func (object *fakeMakoObject) SetProperty(string, interface{}) error {
|
|
return object.err
|
|
}
|
|
func (object *fakeMakoObject) Destination() string { return makoBusName }
|
|
func (object *fakeMakoObject) Path() dbus.ObjectPath { return makoObjectPath }
|
|
|
|
type makoAction struct {
|
|
id int
|
|
key string
|
|
}
|
|
|
|
type fakeMakoController struct {
|
|
dismissed []int
|
|
invoked []makoAction
|
|
}
|
|
|
|
func (controller *fakeMakoController) IsCurrent(Entry) bool { return true }
|
|
func (controller *fakeMakoController) Dismiss(id int) bool {
|
|
controller.dismissed = append(controller.dismissed, id)
|
|
return true
|
|
}
|
|
func (controller *fakeMakoController) InvokeAction(id int, key string) bool {
|
|
controller.invoked = append(controller.invoked, makoAction{id, key})
|
|
return true
|
|
}
|