v0.2.65 2bf9508

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

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