|
1
|
package main |
|
2
|
|
|
3
|
import ( |
|
4
|
"bytes" |
|
5
|
"path/filepath" |
|
6
|
"testing" |
|
7
|
"time" |
|
8
|
|
|
9
|
"github.com/conflicthq/scuttlebot/pkg/sessionrelay" |
|
10
|
) |
|
11
|
|
|
12
|
func TestFilterMessages(t *testing.T) { |
|
13
|
now := time.Now() |
|
14
|
nick := "gemini-test" |
|
15
|
messages := []message{ |
|
16
|
{Nick: "operator", Text: "gemini-test: hello", At: now}, |
|
17
|
{Nick: "gemini-test", Text: "i am gemini", At: now}, // self |
|
18
|
{Nick: "other", Text: "not for me", At: now}, // no mention |
|
19
|
{Nick: "bridge", Text: "system message", At: now}, // service bot |
|
20
|
} |
|
21
|
|
|
22
|
filtered, _ := filterMessages(messages, now.Add(-time.Minute), nick, "worker") |
|
23
|
if len(filtered) != 1 { |
|
24
|
t.Errorf("expected 1 filtered message, got %d", len(filtered)) |
|
25
|
} |
|
26
|
if filtered[0].Nick != "operator" { |
|
27
|
t.Errorf("expected operator message, got %s", filtered[0].Nick) |
|
28
|
} |
|
29
|
} |
|
30
|
|
|
31
|
func TestLoadConfig(t *testing.T) { |
|
32
|
t.Setenv("SCUTTLEBOT_CONFIG_FILE", filepath.Join(t.TempDir(), "scuttlebot-relay.env")) |
|
33
|
t.Setenv("SCUTTLEBOT_URL", "http://test:8080") |
|
34
|
t.Setenv("SCUTTLEBOT_TOKEN", "test-token") |
|
35
|
t.Setenv("GEMINI_SESSION_ID", "abc") |
|
36
|
t.Setenv("SCUTTLEBOT_TRANSPORT", "irc") |
|
37
|
t.Setenv("SCUTTLEBOT_IRC_ADDR", "127.0.0.1:7667") |
|
38
|
t.Setenv("SCUTTLEBOT_SESSION_ID", "") |
|
39
|
t.Setenv("SCUTTLEBOT_NICK", "") |
|
40
|
|
|
41
|
cfg, err := loadConfig([]string{"--cd", "../.."}) |
|
42
|
if err != nil { |
|
43
|
t.Fatal(err) |
|
44
|
} |
|
45
|
|
|
46
|
if cfg.URL != "http://test:8080" { |
|
47
|
t.Errorf("expected URL http://test:8080, got %s", cfg.URL) |
|
48
|
} |
|
49
|
if cfg.Token != "test-token" { |
|
50
|
t.Errorf("expected token test-token, got %s", cfg.Token) |
|
51
|
} |
|
52
|
if cfg.SessionID != "abc" { |
|
53
|
t.Errorf("expected session ID abc, got %s", cfg.SessionID) |
|
54
|
} |
|
55
|
if cfg.Nick != "gemini-scuttlebot-abc" { |
|
56
|
t.Errorf("expected nick gemini-scuttlebot-abc, got %s", cfg.Nick) |
|
57
|
} |
|
58
|
if cfg.Transport != sessionrelay.TransportIRC { |
|
59
|
t.Errorf("expected transport irc, got %s", cfg.Transport) |
|
60
|
} |
|
61
|
if cfg.IRCAddr != "127.0.0.1:7667" { |
|
62
|
t.Errorf("expected irc addr 127.0.0.1:7667, got %s", cfg.IRCAddr) |
|
63
|
} |
|
64
|
} |
|
65
|
|
|
66
|
func TestRelayStateShouldInterruptOnlyWhenRecentlyBusy(t *testing.T) { |
|
67
|
t.Helper() |
|
68
|
|
|
69
|
var state relayState |
|
70
|
now := time.Date(2026, 3, 31, 21, 47, 0, 0, time.UTC) |
|
71
|
state.observeOutput([]byte("Working (1s • esc to interrupt)"), now) |
|
72
|
|
|
73
|
if !state.shouldInterrupt(now.Add(defaultBusyWindow / 2)) { |
|
74
|
t.Fatal("shouldInterrupt = false, want true for recent busy session") |
|
75
|
} |
|
76
|
if state.shouldInterrupt(now.Add(defaultBusyWindow + time.Millisecond)) { |
|
77
|
t.Fatal("shouldInterrupt = true, want false after busy window expires") |
|
78
|
} |
|
79
|
} |
|
80
|
|
|
81
|
func TestInjectMessagesIdleSkipsCtrlCAndSubmits(t *testing.T) { |
|
82
|
t.Helper() |
|
83
|
|
|
84
|
var writer bytes.Buffer |
|
85
|
cfg := config{ |
|
86
|
Nick: "gemini-scuttlebot-1234", |
|
87
|
InterruptOnMessage: true, |
|
88
|
} |
|
89
|
state := &relayState{} |
|
90
|
batch := []message{{ |
|
91
|
Nick: "glengoolie", |
|
92
|
Text: "gemini-scuttlebot-1234: check README.md", |
|
93
|
}} |
|
94
|
|
|
95
|
if err := injectMessages(&writer, cfg, state, "#general", batch); err != nil { |
|
96
|
t.Fatal(err) |
|
97
|
} |
|
98
|
|
|
99
|
want := bracketedPasteStart + "[IRC operator messages]\n[general] glengoolie: check README.md\n" + bracketedPasteEnd + "\r" |
|
100
|
if writer.String() != want { |
|
101
|
t.Fatalf("injectMessages idle = %q, want %q", writer.String(), want) |
|
102
|
} |
|
103
|
} |
|
104
|
|
|
105
|
func TestInjectMessagesBusySendsCtrlCBeforeSubmit(t *testing.T) { |
|
106
|
t.Helper() |
|
107
|
|
|
108
|
var writer bytes.Buffer |
|
109
|
cfg := config{ |
|
110
|
Nick: "gemini-scuttlebot-1234", |
|
111
|
InterruptOnMessage: true, |
|
112
|
} |
|
113
|
state := &relayState{} |
|
114
|
state.observeOutput([]byte("Working (2s • esc to interrupt)"), time.Now()) |
|
115
|
batch := []message{{ |
|
116
|
Nick: "glengoolie", |
|
117
|
Text: "gemini-scuttlebot-1234: stop and re-read bridge.go", |
|
118
|
}} |
|
119
|
|
|
120
|
if err := injectMessages(&writer, cfg, state, "#general", batch); err != nil { |
|
121
|
t.Fatal(err) |
|
122
|
} |
|
123
|
|
|
124
|
want := string([]byte{3}) + bracketedPasteStart + "[IRC operator messages]\n[general] glengoolie: stop and re-read bridge.go\n" + bracketedPasteEnd + "\r" |
|
125
|
if writer.String() != want { |
|
126
|
t.Fatalf("injectMessages busy = %q, want %q", writer.String(), want) |
|
127
|
} |
|
128
|
} |
|
129
|
|