@@ -3,6 +3,7 @@ package tools
33import (
44 "context"
55 "os/exec"
6+ "strings"
67 "testing"
78 "time"
89)
@@ -45,6 +46,52 @@ func TestParseStreamLine_InvalidJSON(t *testing.T) {
4546 }
4647}
4748
49+ func TestParseStreamLine_GeminiMessage (t * testing.T ) {
50+ line := []byte (`{"type":"message","role":"assistant","content":"Hello","delta":true}` )
51+ evt := parseStreamLine (line )
52+ if evt .Type != "text" {
53+ t .Errorf ("Type = %q, want text" , evt .Type )
54+ }
55+ if evt .Content != "Hello" {
56+ t .Errorf ("Content = %q" , evt .Content )
57+ }
58+ }
59+
60+ func TestParseStreamLine_GeminiResult (t * testing.T ) {
61+ line := []byte (`{"type":"result","status":"success","stats":{"total_tokens":100}}` )
62+ evt := parseStreamLine (line )
63+ if evt .Type != "result" {
64+ t .Errorf ("Type = %q, want result" , evt .Type )
65+ }
66+ }
67+
68+ func TestParseStreamLine_GeminiUserMessage (t * testing.T ) {
69+ line := []byte (`{"type":"message","role":"user","content":"prompt"}` )
70+ evt := parseStreamLine (line )
71+ if evt .Type == "text" {
72+ t .Error ("user messages should not be parsed as text events" )
73+ }
74+ }
75+
76+ func TestParseStreamLine_CodexItemCompleted (t * testing.T ) {
77+ line := []byte (`{"type":"item.completed","item":{"id":"item_1","type":"agent_message","text":"Hello"}}` )
78+ evt := parseStreamLine (line )
79+ if evt .Type != "text" {
80+ t .Errorf ("Type = %q, want text" , evt .Type )
81+ }
82+ if evt .Content != "Hello" {
83+ t .Errorf ("Content = %q" , evt .Content )
84+ }
85+ }
86+
87+ func TestParseStreamLine_CodexReasoningIgnored (t * testing.T ) {
88+ line := []byte (`{"type":"item.completed","item":{"type":"reasoning","text":"thinking..."}}` )
89+ evt := parseStreamLine (line )
90+ if evt .Type == "text" {
91+ t .Error ("reasoning items should not be parsed as text events" )
92+ }
93+ }
94+
4895func TestParseStreamLine_EmptyType (t * testing.T ) {
4996 line := []byte (`{"content":"orphan"}` )
5097 evt := parseStreamLine (line )
@@ -168,3 +215,59 @@ func TestStreamRunner_RealClaude(t *testing.T) {
168215 t .Error ("expected at least one event" )
169216 }
170217}
218+
219+ func TestStreamRunner_RealGemini (t * testing.T ) {
220+ if _ , err := exec .LookPath ("gemini" ); err != nil {
221+ t .Skip ("gemini not on PATH" )
222+ }
223+ agents := DetectAgents ()
224+ var info AgentInfo
225+ for _ , a := range agents {
226+ if a .Kind == AgentGemini {
227+ info = a
228+ break
229+ }
230+ }
231+ r := NewStreamRunner (info )
232+ var eventCount int
233+ out , err := r .RunStream (context .Background (), "Say hello in exactly one word." , 30 * time .Second , func (evt StreamEvent ) {
234+ eventCount ++
235+ })
236+ if err != nil {
237+ if strings .Contains (err .Error (), "Permission" ) || strings .Contains (err .Error (), "denied" ) || strings .Contains (err .Error (), "auth" ) {
238+ t .Skipf ("gemini auth not configured: %v" , err )
239+ }
240+ t .Fatalf ("RunStream: %v" , err )
241+ }
242+ if out == "" {
243+ t .Error ("expected non-empty output" )
244+ }
245+ }
246+
247+ func TestStreamRunner_RealCodex (t * testing.T ) {
248+ if _ , err := exec .LookPath ("codex" ); err != nil {
249+ t .Skip ("codex not on PATH" )
250+ }
251+ agents := DetectAgents ()
252+ var info AgentInfo
253+ for _ , a := range agents {
254+ if a .Kind == AgentCodex {
255+ info = a
256+ break
257+ }
258+ }
259+ r := NewStreamRunner (info )
260+ var eventCount int
261+ out , err := r .RunStream (context .Background (), "Say hello in exactly one word." , 30 * time .Second , func (evt StreamEvent ) {
262+ eventCount ++
263+ })
264+ if err != nil {
265+ if strings .Contains (err .Error (), "auth" ) || strings .Contains (err .Error (), "API key" ) || strings .Contains (err .Error (), "login" ) {
266+ t .Skipf ("codex auth not configured: %v" , err )
267+ }
268+ t .Fatalf ("RunStream: %v" , err )
269+ }
270+ if out == "" {
271+ t .Error ("expected non-empty output" )
272+ }
273+ }
0 commit comments