package app import ( "context" "os" "path/filepath" "testing" "git.stormux.org/storm/cthulhu/internal/input" "git.stormux.org/storm/cthulhu/internal/preferences" "git.stormux.org/storm/cthulhu/internal/tentacle" ) func TestPreferencesTentacleSettingsDispatchAndMissingSettings(t *testing.T) { for _, mode := range []string{"settings-save", "settings-cancel", "settings-disable", "no-settings"} { t.Run(mode, func(t *testing.T) { directory := t.TempDir() path, marker := filepath.Join(directory, "tentacles.toml"), filepath.Join(directory, mode) runtime, _ := managed_tentacle_fixture(t, path, false) runtime.output = tentacleOutputFixture(t) keys := &uiTestKeys{} output := &ocrTestOutput{speech: make(chan speechRequest, 32)} ui := &internalUI{keys: keys, speech: output, gate: runtime.output.gate, events: make(chan input.CommandEvent, 16), requests: make(chan internalUIRequest, 1), notices: make(chan internalUINotice, 4), done: make(chan struct{})} runtime.forms = ui ctx, cancel := context.WithCancel(context.Background()) go ui.run(ctx) t.Cleanup(func() { cancel(); <-ui.done }) if err := runtime.Start(ctx); err != nil { t.Fatal(err) } executable, _ := os.Executable() entry := tentacle.Entry{ID: "probe", Enabled: true, Presentation: true, Interaction: true, Program: []string{executable, "-test.run=^TestTentacleInteractionPeer$", "--", "interaction-peer", marker}} manage_tentacle(t, runtime, "add", entry.ID, &entry) waitTentacle(t, func() bool { snapshot := runtime.Snapshot() return len(snapshot.Entries) == 1 && snapshot.Entries[0].State == "active" }) before, _ := os.ReadFile(path) snapshot := runtime.Snapshot() if len(snapshot.Entries[0].Shortcuts) != 1 || snapshot.Entries[0].Shortcuts[0] != "Desktop: F9 (Interactive probe)" { t.Fatal(snapshot) } snapshot.Entries[0].Shortcuts[0] = "modified copy" if runtime.Snapshot().Entries[0].Shortcuts[0] == "modified copy" { t.Fatal("snapshot shared shortcut storage") } worker := &preferencesWorker{tentacles: runtime} response := worker.manage_tentacle(ctx, preferences.Request{Tentacle: &tentacle.ManagementRequest{Action: "settings", ID: entry.ID}}) if mode == "no-settings" { if response.Accepted || response.Message != "No settings for this tentacle." || keys.InternalUISession() != 0 { t.Fatal(response) } return } if !response.Accepted || response.Message != "" { t.Fatal(response) } waitTentacle(t, func() bool { return keys.InternalUISession() != 0 }) sessionID := keys.InternalUISession() switch mode { case "settings-save": ui.Handle(input.CommandEvent{Command: input.InternalUIKey, UISession: sessionID, Text: "saved value"}) ui.Handle(input.CommandEvent{Command: input.InternalUIKey, UISession: sessionID, Chord: input.CommandChord{Symbol: 0xff0d}}) case "settings-cancel": ui.Handle(input.CommandEvent{Command: input.InternalUIKey, UISession: sessionID, Chord: input.CommandChord{Symbol: 0xff1b}}) case "settings-disable": manage_tentacle(t, runtime, "disable", entry.ID, nil) } waitTentacle(t, func() bool { return len(runtime.resultSlots) == 0 && keys.InternalUISession() == 0 }) if mode != "settings-disable" { after, _ := os.ReadFile(path) if string(before) != string(after) { t.Fatal("opening settings changed registry") } } if mode == "settings-save" { data, err := os.ReadFile(marker) if err != nil || len(data) == 0 { t.Fatal("settings submission was not delivered", err) } } }) } }