v0.2.78 cc6b04d

bridge_test.go RawHistoryFolder

1package main
2
3import (
4	"bytes"
5	"context"
6	"errors"
7	"io"
8	"log"
9	"strings"
10	"testing"
11)
12
13func testBridge() *bridge {
14	return &bridge{
15		cwd:     "/project",
16		servers: map[string]*managedServer{},
17		mapping: map[string]toolMapping{},
18		logger:  log.New(io.Discard, "", 0),
19	}
20}
21
22func TestHandleToolCallUnknownToolIsFriendlyError(t *testing.T) {
23	b := testBridge()
24	res := b.handleToolCall("mcp__ghost__tool", nil)
25	if !res.IsError {
26		t.Fatal("expected error result")
27	}
28}
29
30func TestHandleToolCallStartFailureIsFriendlyError(t *testing.T) {
31	b := testBridge()
32	b.servers["bogus"] = newManagedServer("bogus",
33		ServerConfig{Transport: "no-such-transport", ConnectTimeout: 1, RequestTimeout: 1},
34		"/project",
35		log.New(io.Discard, "", 0))
36	b.mapping["mcp__bogus__t"] = toolMapping{serverName: "bogus", mcpTool: "t"}
37
38	res := b.handleToolCall("mcp__bogus__t", nil)
39	if !res.IsError {
40		t.Fatal("expected error result for failed server start")
41	}
42}
43
44func TestLoadServersPassesProjectCWD(t *testing.T) {
45	b := testBridge()
46	b.loadServers(Config{MCPServers: map[string]ServerConfig{
47		"fs": {Transport: "stdio", Command: "dummy"},
48	}})
49	if got := b.servers["fs"].cwd; got != "/project" {
50		t.Fatalf("managed server cwd = %q, want /project", got)
51	}
52}
53
54func TestClassifyTimeoutUsesErrorsIs(t *testing.T) {
55	wrapped := errors.Join(errors.New("call failed"), context.DeadlineExceeded)
56	if !errors.Is(wrapped, context.DeadlineExceeded) {
57		t.Fatal("sanity: errors.Is must see through the wrap")
58	}
59	// The user-facing message for a deadline error must mention the
60	// configured timeout even when the error text never says "timeout".
61	msg := timeoutErrorMessage("slow", 42)
62	if !strings.Contains(msg, "42") || !strings.Contains(msg, "slow") {
63		t.Fatalf("timeout message missing details: %q", msg)
64	}
65}
66
67func TestSanitizeName(t *testing.T) {
68	cases := []struct{ in, want string }{
69		{"filesystem", "filesystem"},
70		{"my-tool", "my_tool"},
71		{"my.tool", "my_tool"},
72		{"3d-render", "t_3d_render"},
73		{"", "unnamed"},
74	}
75	for _, c := range cases {
76		if got := sanitizeName(c.in); got != c.want {
77			t.Errorf("sanitizeName(%q) = %q, want %q", c.in, got, c.want)
78		}
79	}
80}
81
82func TestToolName(t *testing.T) {
83	if got, want := toolName("fs-server", "read.file"), "mcp__fs_server__read_file"; got != want {
84		t.Fatalf("toolName = %q, want %q", got, want)
85	}
86}
87
88func TestRegisterCachedToolLogsCollision(t *testing.T) {
89	var buf bytes.Buffer
90	b := testBridge()
91	b.logger = log.New(&buf, "", 0)
92	// nil Extension: registerCachedTool must not reach b.e.Tool for
93	// the colliding (skipped) registration, and the first registration
94	// needs a real Extension — so pre-seed the mapping instead.
95	b.mapping["mcp__srv__my_tool"] = toolMapping{serverName: "srv", mcpTool: "my-tool"}
96
97	b.registerCachedTool("srv", cachedTool{Name: "my_tool", Schema: []byte(`{}`)})
98
99	if !strings.Contains(buf.String(), "collision") {
100		t.Fatalf("expected collision log, got: %q", buf.String())
101	}
102}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close