// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "fmt" "os" "reflect" "testing" "time" ) func shared_notification_application(t *testing.T, namespace string) (*application, *fakeVoice, *fakeSounds) { t.Helper() a, voice := persistence_application(t, true) voice.messages = nil sounds := &fakeSounds{} a.cues = sounds a.options.SharedNotifications = true a.options.NotificationNamespace = namespace a.share_notification_history(1, true) t.Cleanup(func() { a.notificationHistory.Close() a.notificationClosers.Wait() }) return a, voice, sounds } func wait_shared_notifications(t *testing.T, a *application, count int) { t.Helper() deadline := time.NewTimer(3 * time.Second) defer deadline.Stop() for { a.refresh_notification_history() if len(a.notifications) == count { return } select { case <-a.notificationHistory.Updates(): case <-deadline.C: t.Fatalf("notification history has %d entries, want %d", len(a.notifications), count) } } } func TestSharedNotificationHistoryNeverSpeaksOrPlaysOnOtherReaders(t *testing.T) { namespace := fmt.Sprintf(".app.%d.%d", os.Getpid(), time.Now().UnixNano()) var readers []*application var voices []*fakeVoice var sounds []*fakeSounds for range 4 { a, voice, cues := shared_notification_application(t, namespace) readers, voices, sounds = append(readers, a), append(voices, voice), append(sounds, cues) } if response := runtime_request(readers[0], "command notify First target"); response != "" { t.Fatal(response) } for index, a := range readers { wait_shared_notifications(t, a, 1) wantSpeech, wantCues := 0, 0 if index == 0 { wantSpeech, wantCues = 1, 1 } if len(voices[index].messages) != wantSpeech || len(sounds[index].cues) != wantCues { t.Fatal("silent history update triggered speech or sound", index, voices[index].messages, sounds[index].cues) } } runtime_request(readers[1], "command notify Second target") for index, a := range readers { wait_shared_notifications(t, a, 2) if !reflect.DeepEqual(a.notifications, readers[0].notifications) { t.Fatal("readers disagree about history", index) } } if !reflect.DeepEqual(voices[0].messages, []string{"First target"}) || !reflect.DeepEqual(voices[1].messages, []string{"Second target"}) { t.Fatal("targeted speech was duplicated", voices[0].messages, voices[1].messages) } for index := 1; index < len(readers); index++ { if len(sounds[index].cues) != 0 { t.Fatal("target switch reset the shared sound cooldown", index, sounds[index].cues) } } // History recall is an explicit user action and still belongs to that reader. readers[2].last_notification() if !reflect.DeepEqual(voices[2].messages, []string{"Second target"}) || len(sounds[2].cues) != 0 { t.Fatal("history recall was missing or replayed the cue") } readers[2].muted, voices[2].muted = true, true runtime_request(readers[2], "command notify Muted target") readers[3].temporarySilence = true runtime_request(readers[3], "command notify Silenced target") for index, a := range readers { wait_shared_notifications(t, a, 4) if index >= 2 && len(sounds[index].cues) != 0 { t.Fatal("muted or silenced target replayed the cue") } } joining, voice, cues := shared_notification_application(t, namespace) wait_shared_notifications(t, joining, 4) if len(voice.messages) != 0 || len(cues.cues) != 0 || !reflect.DeepEqual(joining.notifications, readers[0].notifications) { t.Fatal("joining reader announced existing history or loaded different messages") } } func TestNotificationUserSwitchClearsHistoryAndClosesOldDialog(t *testing.T) { namespace := fmt.Sprintf(".app.%d.%d", os.Getpid(), time.Now().UnixNano()) a, voice, _ := shared_notification_application(t, namespace) peer, _, _ := shared_notification_application(t, namespace) a.notify("Original account", time.Now()) wait_shared_notifications(t, peer, 1) a.open_notifications() if !a.ui.active { t.Fatal("notification dialog did not open") } a.select_notification_user(os.Geteuid() + 1) voice.messages = nil a.refresh_notification_history() if len(a.notifications) != 0 || a.ui.active { t.Fatal("switching accounts exposed the previous history or dialog") } a.select_notification_user(os.Geteuid()) wait_shared_notifications(t, a, 1) if len(voice.messages) != 0 { t.Fatal("restoring an account spoke its history", voice.messages) } }