// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "errors" "os" "testing" "git.stormux.org/storm/fenrir/internal/config" "git.stormux.org/storm/fenrir/internal/control" "git.stormux.org/storm/fenrir/internal/speech" ) func TestRemoteApplicationSelectionAndDefault(t *testing.T) { for _, terminalMode := range []bool{true, false} { a, _ := persistence_application(t, terminalMode) override_read(t, a, override_screen(1, "ninjam")) for _, id := range []string{"ninjam", "networkmanager", "rogue", "codex", "default"} { if got := runtime_request(a, "COMMAND APPLICATION "+id); got != "" { t.Fatal(id, got) } selected := a.overrides[1] if id == "default" { if selected != nil { t.Fatal("Default retained selection") } } else if selected == nil || selected.id != id { t.Fatal("wrong application selected", id) } } if got := runtime_request(a, "command application NINJAM"); got != "" { t.Fatal(got) } override_read(t, a, override_screen(1, "ninjam")) if a.overrideCurrent == nil || a.engine.Tracking_mode() != "highlight" { t.Fatal("remote selection did not activate Ninjam") } if got := runtime_request(a, "command application default"); got != "" || a.overrideCurrent != nil { t.Fatal("Default did not restore settings", got) } } } func TestRemoteApplicationRejectsInvalidAndUnavailableRequests(t *testing.T) { a, _ := persistence_application(t, true) override_read(t, a, override_screen(1, "ninjam")) a.select_override("ninjam") selected := a.overrides[1] for _, parameter := range []string{"", "unknown", "ninjam extra", "default extra"} { if got := runtime_request(a, "COMMAND APPLICATION "+parameter); got == "" || a.overrides[1] != selected { t.Fatal("invalid request changed selection", parameter, got) } } for _, gate := range []string{"disabled", "save", "speech", "transition", "inactive", "no-terminal", "unauthorized"} { t.Run(gate, func(t *testing.T) { uid := os.Geteuid() switch gate { case "disabled": a.settings.live.Remote.EnableCommandRemote = false case "save": a.saver.busy = true case "speech": a.speechPending = true case "transition": a.overrideTransition = true case "inactive": a.lastActive = false case "no-terminal": a.lastTerminal = 0 case "unauthorized": uid++ } reply := make(chan string, 1) a.remote_request(control.Request{UID: uid, Text: "COMMAND APPLICATION default", Reply: reply}) if got := <-reply; got == "" || a.overrides[1] != selected { t.Fatal("gated request changed selection", got) } a.settings.live.Remote.EnableCommandRemote = true a.saver.busy, a.speechPending, a.overrideTransition = false, false, false a.lastActive, a.lastTerminal = true, 1 }) } } func TestRemoteApplicationWaitsForVoiceRestoration(t *testing.T) { for _, fail := range []bool{false, true} { a, _, _ := selection_application(t, true) a.prepareSpeech = func(config.Speech) (speech.Transport, error) { return &preparedVoice{}, nil } override_read(t, a, override_screen(1, "ninjam")) a.select_override("ninjam") override_read(t, a, override_screen(1, "ninjam")) if got := request_selection(t, a, "SET speech#module=fixture;speech#voice=Temporary"); got != "" { t.Fatal(got) } if fail { a.prepareSpeech = func(config.Speech) (speech.Transport, error) { return nil, errors.New("fixture unavailable") } } reply := make(chan string, 1) a.remote_request(control.Request{UID: os.Geteuid(), Text: "COMMAND APPLICATION rogue", Reply: reply}) select { case got := <-reply: t.Fatal("selection replied before restoration", got) default: } finish_selection(t, a) got := <-reply if fail { if got == "" || a.overrides[1].id != "ninjam" { t.Fatal("restoration failure lost", got) } } else if got != "" || a.overrides[1].id != "rogue" { t.Fatal("selection failed after restoration", got) } } }