okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

dawntui: skills, instructions and mcp on listView with global/session toggles and a pane without enter96704f7

main
4.1 KiB116 linesraw
1// MCP form command quoting, environment parsing, and credential retention semantics.
2// Wire-format command tokenization and sentinel deletion operate inside unexported form
3// submission models before reaching any daemon RPC, unreachable by daemon E2E.
4package tui
5
6import (
7	"strings"
8	"testing"
9
10	"albedo/cli/internal/config"
11	"albedo/cli/internal/daemon"
12
13	tea "charm.land/bubbletea/v2"
14	"github.com/charmbracelet/x/ansi"
15)
16
17func typeMCP(m CapabilityPageModel, text string) CapabilityPageModel {
18	m, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyExtended, Text: text})
19	return m
20}
21
22func key(m CapabilityPageModel, k rune) CapabilityPageModel {
23	m, _ = m.Update(tea.KeyPressMsg{Code: k})
24	return m
25}
26
27func ctrl(m CapabilityPageModel, k rune) CapabilityPageModel {
28	m, _ = m.Update(tea.KeyPressMsg{Code: k, Mod: tea.ModCtrl})
29	return m
30}
31
32func mcpPage(t *testing.T) CapabilityPageModel {
33	t.Helper()
34	m := NewCapabilityPageModel(nil, "s", "mcp")
35	m.SetSize(100, 30)
36	m.Loading = false
37	m.ExtensionEnabled = true
38	return m
39}
40
41func TestAddingAnHTTPServerAsksForAuthBeforeSaving(t *testing.T) {
42	m := mcpPage(t)
43	m = ctrl(m, 'o')
44	if m.Form == nil || m.Form.current() != fieldURL {
45		t.Fatal("ctrl+o should open the add form on the URL")
46	}
47	m = typeMCP(m, "http://100.64.0.19:8787/mcp")
48	if got := m.Form.Inputs[fieldName].Value(); got != "mcp-100-64-0-19" {
49		t.Fatalf("name should follow the URL, got %q", got)
50	}
51	m = key(m, tea.KeyEnter) // name
52	m = key(m, tea.KeyEnter) // bearer token
53	if m.Form.current() != fieldToken || m.Saving {
54		t.Fatalf("enter should walk to the bearer token without saving, at %q", m.Form.current())
55	}
56	m = typeMCP(m, "sensitive-token")
57	if strings.Contains(ansi.Strip(m.View()), "sensitive-token") || !strings.Contains(ansi.Strip(m.View()), "bearer token") {
58		t.Fatalf("the token field must be labelled and masked:\n%s", ansi.Strip(m.View()))
59	}
60	sub, err := m.Form.submission(nil)
61	if err != nil {
62		t.Fatal(err)
63	}
64	if sub.Name != "mcp-100-64-0-19" || sub.Server.Type != "http" || sub.Server.URL != "http://100.64.0.19:8787/mcp" || (sub.Secrets.BearerToken == nil || *sub.Secrets.BearerToken != "sensitive-token") {
65		t.Fatalf("unexpected submission %+v", sub)
66	}
67}
68
69func TestStdioServerParsesItsCommandAndSuggestsAName(t *testing.T) {
70	m := mcpPage(t)
71	m = ctrl(m, 'o')
72	m = key(m, tea.KeyUp)
73	m = key(m, tea.KeyRight)
74	if m.Form.Transport != "stdio" {
75		t.Fatal("← → should switch the transport")
76	}
77	m = key(m, tea.KeyDown)
78	m = typeMCP(m, `npx -y @modelcontextprotocol/server-filesystem "/tmp/my dir"`)
79	m = key(m, tea.KeyDown)
80	m = key(m, tea.KeyDown)
81	m = typeMCP(m, "TOKEN=abc DEBUG=1")
82	sub, err := m.Form.submission(nil)
83	if err != nil {
84		t.Fatal(err)
85	}
86	if sub.Name != "filesystem" || sub.Server.Command != "npx" || strings.Join(sub.Server.Args, "|") != "-y|@modelcontextprotocol/server-filesystem|/tmp/my dir" {
87		t.Fatalf("unexpected stdio submission %+v", sub)
88	}
89	env := sub.Secrets.Env
90	if env["TOKEN"] == nil || *env["TOKEN"] != "abc" || env["DEBUG"] == nil || *env["DEBUG"] != "1" || !sub.Secrets.RemoveBearerToken {
91		t.Fatalf("env should be stored privately, got %+v", sub.Secrets)
92	}
93	if strings.Contains(ansi.Strip(m.View()), "abc") {
94		t.Fatal("env values must be masked")
95	}
96}
97
98func TestEditingKeepsOrRemovesStoredSecrets(t *testing.T) {
99	stored := daemon.MCPSecretNames{BearerToken: true, Headers: []string{"X-Key"}}
100	enabled := false
101	f := newMCPForm("docs", config.MCPServer{Type: "http", URL: "https://docs.example/mcp", Enabled: &enabled}, stored)
102	sub, err := f.submission([]capabilityItem{{ID: "docs"}})
103	if err != nil || sub.Secrets.BearerToken != nil || sub.Secrets.RemoveBearerToken || sub.Secrets.Headers != nil || sub.Secrets.ClearHeaders || sub.Server.Enabled == nil {
104		t.Fatalf("a blank edit must keep stored secrets and other fields: %+v %v", sub, err)
105	}
106
107	f.Inputs[fieldToken].SetValue("-")
108	f.Inputs[fieldHeader].SetValue("X-Key")
109	f.Inputs[fieldValue].SetValue("-")
110	sub, _ = f.submission(nil)
111	removed := sub.Secrets.Headers
112	header, named := removed["X-Key"]
113	if !sub.Secrets.RemoveBearerToken || !named || header != nil {
114		t.Fatalf("- should remove stored secrets: %+v", sub.Secrets)
115	}
116}