// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "context" "errors" "testing" "time" "git.stormux.org/storm/cthulhu/internal/braille" "git.stormux.org/storm/cthulhu/internal/input" "git.stormux.org/storm/cthulhu/internal/interaction" ) type secretTestKeys struct { uiTestKeys failClose bool } func (keys *secretTestKeys) EndInternalUI(ctx context.Context, id uint64) error { keys.uiTestKeys.EndInternalUI(ctx, id) if keys.failClose { return errors.New("fixture capture close failed") } return nil } func TestInternalUISecretOutputAndLifetime(t *testing.T) { for _, ending := range []string{"submit", "escape", "revoke", "cancel", "close-error"} { t.Run(ending, func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() keys := &secretTestKeys{failClose: ending == "close-error"} output := &ocrTestOutput{speech: make(chan speechRequest, 16)} display := &brailleWorker{wake: make(chan struct{}, 1), cancel: func() {}, connected: true} display.EnqueueFrame(braille.Frame{Text: "Application"}) ui := &internalUI{keys: keys, speech: output, braille: display, gate: &focusGate{}, events: make(chan input.CommandEvent, 16), requests: make(chan internalUIRequest, 1), done: make(chan struct{})} go ui.run(ctx) defer func() { cancel(); <-ui.done }() formCtx, closeForm := context.WithCancel(ctx) defer closeForm() result, err := ui.Open(formCtx, interaction.Form{Title: "Connection", Elements: []interaction.Element{ {ID: "key", Label: "Channel key", Kind: interaction.Entry, Secret: true}, }}) if err != nil { t.Fatal(err) } waitSpeech := func(want string) { t.Helper() select { case got := <-output.speech: if got.Text != want || got.Cause != "internal-ui" { t.Fatal("unexpected secret field output") } case <-ctx.Done(): t.Fatal("secret field output timed out") } } waitSpeech("Connection. Channel key, password edit, 0 characters") ui.Handle(input.CommandEvent{Command: input.InternalUIKey, UISession: 1, Text: "fixture猫"}) waitSpeech("Inserted 8 characters") waitTentacle(t, func() bool { return display.current().Text == "Channel key, password edit: ********" }) switch ending { case "submit", "close-error": ui.Handle(input.CommandEvent{Command: input.InternalUIKey, UISession: 1, Chord: input.CommandChord{Symbol: 0xff0d}}) case "escape": ui.Handle(input.CommandEvent{Command: input.InternalUIKey, UISession: 1, Chord: input.CommandChord{Symbol: 0xff1b}}) case "revoke": keys.session.Store(0) case "cancel": closeForm() } select { case got := <-result: if ending == "submit" { if got.Err != nil || got.Cancelled || got.Activated != "key" || got.Values["key"].Text != "fixture猫" { t.Fatal("secret not delivered on successful submission") } } else if got.Values != nil || got.Activated != "" || !got.Cancelled && got.Err == nil { t.Fatal("failed or cancelled form delivered secret values") } case <-ctx.Done(): t.Fatal("secret form did not close") } if keys.InternalUISession() != 0 || display.current().Text != "Application" { t.Fatal("secret form did not restore local input and Braille") } }) } }