// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "context" "encoding/json" "errors" "log/slog" "path/filepath" "strconv" "strings" "testing" "time" "git.stormux.org/storm/cthulhu/internal/tentacle" "git.stormux.org/storm/cthulhu/internal/tentaclewire" ) func keyboard_attach_runtime(policy *tentacle.RemotePolicy, helper string) *tentacleRuntime { return &tentacleRuntime{logger: slog.New(slog.DiscardHandler), remotePolicy: policy, remoteInputHelper: helper, output: &tentacleOutput{gate: &focusGate{}}, announcements: make(chan string, 1)} } func keyboard_attach_key(t *testing.T, session *tentacle.Session, lease tentaclewire.RemoteLease, sequence uint64, name string, pressed bool) error { t.Helper() ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() request := tentaclewire.RemoteKeyRequest{RemoteOperation: tentaclewire.RemoteOperation{Lease: lease.Lease, Epoch: lease.Epoch, Sequence: sequence}, Key: tentaclewire.RemoteKey{Name: name, Pressed: pressed}} data, _ := json.Marshal(map[string]any{"Method": "remote.key", "Params": request}) pending, err := session.Invoke(ctx, "rpc", data) if err != nil { t.Fatal(err) } select { case result := <-pending: return result.Err case <-ctx.Done(): t.Fatal("keyboard peer call timed out") return nil } } func keyboard_attach_open(t *testing.T, session *tentacle.Session) tentaclewire.RemoteLease { t.Helper() approval, err := session.BeginRemoteApproval("controlled", []string{"keyboard-inject"}) if err != nil { t.Fatal(err) } if _, err := session.AuthorizeRemoteWithApproval(approval, "controlled", []string{"keyboard-inject"}); err != nil { t.Fatal(err) } var lease tentaclewire.RemoteLease if json.Unmarshal(keyboard_owner_invoke(t, session, "open", nil), &lease) != nil || lease.Epoch == 0 { t.Fatal("keyboard peer failed to open lease") } return lease } func TestRemoteKeyboardProductionAttachDeliversAndRetires(t *testing.T) { policy := tentacle.NewRemotePolicy() program, log := keyboard_owner_helper(t) session := keyboard_owner_session(t, policy, []string{"keyboard-inject", "output-observe"}) runtime := keyboard_attach_runtime(policy, program) ctx, cancel := context.WithCancel(t.Context()) done := runtime.start_remote_keyboard(ctx, session) t.Cleanup(func() { cancel() select { case <-done: case <-time.After(6 * time.Second): t.Error("production keyboard owner did not join") } }) lease := keyboard_attach_open(t, session) if err := keyboard_attach_key(t, session, lease, 1, "a", true); err != nil { t.Fatal(err) } if err := keyboard_attach_key(t, session, lease, 2, "a", false); err != nil { t.Fatal(err) } f := &keyboardOwnerFixture{log: log} prefix := strconv.FormatUint(lease.Epoch, 10) + " " f.wait(t, func() bool { return strings.Contains(f.effects(), prefix+"a false\n") }) // Lock/handoff policy revokes the lease and releases the helper. policy.SetAvailable(false) f.wait(t, func() bool { return strings.Contains(f.effects(), prefix+"cleanup\n") }) policy.SetAvailable(true) if runtime.remote_input_supervisor() != runtime.remoteInput || runtime.remoteInput == nil { t.Fatal("supervisor was not retained for the reader lifetime") } } func TestRemoteKeyboardProductionAttachRechecksReaderUI(t *testing.T) { policy := tentacle.NewRemotePolicy() program, log := keyboard_owner_helper(t) session := keyboard_owner_session(t, policy, []string{"keyboard-inject"}) runtime := keyboard_attach_runtime(policy, program) runtime.output.gate.suspended.Store(true) // XTerm handoff. ctx, cancel := context.WithCancel(t.Context()) done := runtime.start_remote_keyboard(ctx, session) defer func() { cancel(); <-done }() lease := keyboard_attach_open(t, session) _ = keyboard_attach_key(t, session, lease, 1, "a", true) deadline := time.Now().Add(5 * time.Second) for { if _, err := session.RemoteContext(lease, "keyboard-inject"); err != nil { break } if time.Now().After(deadline) { t.Fatal("suspended reader kept keyboard consent") } time.Sleep(2 * time.Millisecond) } f := &keyboardOwnerFixture{log: log} if strings.Contains(f.effects(), "a true") || strings.Contains(f.effects(), "starting") { t.Fatal("suspended reader delivered a remote key", f.effects()) } } func TestRemoteKeyboardProductionAttachRequiresGrantAndHelper(t *testing.T) { policy := tentacle.NewRemotePolicy() program, _ := keyboard_owner_helper(t) for name, test := range map[string]struct { grants []string helper string }{ "no grant": {[]string{"output-observe"}, program}, "missing helper": {[]string{"keyboard-inject"}, filepath.Join(t.TempDir(), "absent")}, } { t.Run(name, func(t *testing.T) { session := keyboard_owner_session(t, policy, test.grants) runtime := keyboard_attach_runtime(policy, test.helper) done := runtime.start_remote_keyboard(t.Context(), session) select { case <-done: case <-time.After(time.Second): t.Fatal("unavailable keyboard owner did not finish immediately") } if name == "missing helper" { lease := keyboard_attach_open(t, session) err := keyboard_attach_key(t, session, lease, 1, "a", true) if err == nil || errors.Is(err, context.DeadlineExceeded) { t.Fatalf("missing helper accepted a key: %v", err) } } }) } }