252 lines
6.5 KiB
Go
252 lines
6.5 KiB
Go
package session
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"ollie/toolsrv/protocol"
|
|
)
|
|
|
|
func TestSetCwd_ExpandsTilde(t *testing.T) {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil || home == "" {
|
|
t.Skip("cannot determine home directory")
|
|
}
|
|
|
|
s := &Session{}
|
|
s.SetCwd("~/projects")
|
|
|
|
want := home + "/projects"
|
|
if got := s.Cwd(); got != want {
|
|
t.Errorf("SetCwd(~/projects): got %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestSetCwd_ExpandsEnvVars(t *testing.T) {
|
|
t.Setenv("OLLIE_TEST_DIR", "/tmp/testdir")
|
|
|
|
s := &Session{}
|
|
s.SetCwd("$OLLIE_TEST_DIR/sub")
|
|
|
|
want := "/tmp/testdir/sub"
|
|
if got := s.Cwd(); got != want {
|
|
t.Errorf("SetCwd($OLLIE_TEST_DIR/sub): got %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestSetCwd_AbsolutePathUnchanged(t *testing.T) {
|
|
s := &Session{}
|
|
s.SetCwd("/usr/local/bin")
|
|
|
|
want := "/usr/local/bin"
|
|
if got := s.Cwd(); got != want {
|
|
t.Errorf("SetCwd(/usr/local/bin): got %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestEventSubscription(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
ch := SubscribeEvents(ctx)
|
|
|
|
// Give subscription time to register
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Publish events
|
|
for i := 0; i < 5; i++ {
|
|
PublishEvent(fmt.Sprintf("test.event.%d", i), fmt.Sprintf("payload%d", i))
|
|
}
|
|
|
|
// Collect events
|
|
timeout := time.After(1 * time.Second)
|
|
var received []Event
|
|
for {
|
|
select {
|
|
case ev := <-ch:
|
|
received = append(received, ev)
|
|
if len(received) >= 5 {
|
|
goto done
|
|
}
|
|
case <-timeout:
|
|
goto done
|
|
}
|
|
}
|
|
done:
|
|
if len(received) < 5 {
|
|
t.Errorf("Expected 5 events, got %d", len(received))
|
|
for i, ev := range received {
|
|
t.Logf(" [%d] topic=%s payload=%s", i, ev.Topic, ev.Payload)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEventSubscriptionFiltered(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
ch := SubscribeEventsFiltered(ctx, "*")
|
|
|
|
// Give subscription time to register
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Publish events
|
|
for i := 0; i < 5; i++ {
|
|
PublishEvent(fmt.Sprintf("test.event.%d", i), fmt.Sprintf("payload%d", i))
|
|
}
|
|
|
|
// Collect events
|
|
timeout := time.After(1 * time.Second)
|
|
var received []Event
|
|
for {
|
|
select {
|
|
case ev := <-ch:
|
|
received = append(received, ev)
|
|
if len(received) >= 5 {
|
|
goto done
|
|
}
|
|
case <-timeout:
|
|
goto done
|
|
}
|
|
}
|
|
done:
|
|
if len(received) < 5 {
|
|
t.Errorf("Expected 5 events, got %d", len(received))
|
|
for i, ev := range received {
|
|
t.Logf(" [%d] topic=%s payload=%s", i, ev.Topic, ev.Payload)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEventSubscriptionRapidFire(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
ch := SubscribeEventsFiltered(ctx, "*")
|
|
|
|
// Give subscription time to register
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Rapid-fire publish (simulating state changes during a turn)
|
|
states := []string{"thinking", "calling: shell", "thinking", "calling: file_read", "thinking", "idle"}
|
|
for _, state := range states {
|
|
PublishEvent("session.abc.agent.xyz.state", state)
|
|
}
|
|
|
|
// Collect events
|
|
timeout := time.After(2 * time.Second)
|
|
var received []Event
|
|
for {
|
|
select {
|
|
case ev := <-ch:
|
|
received = append(received, ev)
|
|
t.Logf("Received: topic=%s payload=%s", ev.Topic, ev.Payload)
|
|
if len(received) >= len(states) {
|
|
goto done
|
|
}
|
|
case <-timeout:
|
|
goto done
|
|
}
|
|
}
|
|
done:
|
|
if len(received) != len(states) {
|
|
t.Errorf("Expected %d events, got %d", len(states), len(received))
|
|
}
|
|
for i, ev := range received {
|
|
if i < len(states) && ev.Payload != states[i] {
|
|
t.Errorf("Event %d: expected payload %q, got %q", i, states[i], ev.Payload)
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Bypass request tracking tests ---
|
|
|
|
func TestBypassPending_MultipleConcurrent(t *testing.T) {
|
|
s := &Session{ID: "test-session"}
|
|
|
|
// Add multiple bypass requests
|
|
req1 := &protocol.BypassRequest{ID: "req-1", Cmd: "rm -rf /tmp/a", Cwd: "/home/user", Env: map[string]string{"OLLIE_UNAME": "agent1"}}
|
|
req2 := &protocol.BypassRequest{ID: "req-2", Cmd: "rm -rf /tmp/b", Cwd: "/home/user", Env: map[string]string{"OLLIE_UNAME": "agent2"}}
|
|
req3 := &protocol.BypassRequest{ID: "req-3", Cmd: "rm -rf /tmp/c", Cwd: "/home/user", Env: map[string]string{"OLLIE_UNAME": "agent1"}}
|
|
|
|
s.SetBypassPending(req1, nil)
|
|
s.SetBypassPending(req2, nil)
|
|
s.SetBypassPending(req3, nil)
|
|
|
|
// Should have all three pending
|
|
pending := s.BypassPending()
|
|
if len(pending) != 3 {
|
|
t.Errorf("Expected 3 pending requests, got %d", len(pending))
|
|
}
|
|
|
|
// BypassPendingByID should find each
|
|
if got := s.BypassPendingByID("req-1"); got == nil || got.Cmd != "rm -rf /tmp/a" {
|
|
t.Errorf("BypassPendingByID(req-1) failed")
|
|
}
|
|
if got := s.BypassPendingByID("req-2"); got == nil || got.Cmd != "rm -rf /tmp/b" {
|
|
t.Errorf("BypassPendingByID(req-2) failed")
|
|
}
|
|
if got := s.BypassPendingByID("req-3"); got == nil || got.Cmd != "rm -rf /tmp/c" {
|
|
t.Errorf("BypassPendingByID(req-3) failed")
|
|
}
|
|
|
|
// Non-existent ID should return nil
|
|
if got := s.BypassPendingByID("req-999"); got != nil {
|
|
t.Errorf("BypassPendingByID(req-999) should return nil, got %v", got)
|
|
}
|
|
}
|
|
|
|
func TestBypassResolve_RemovesFromMap(t *testing.T) {
|
|
s := &Session{ID: "test-session"}
|
|
|
|
req1 := &protocol.BypassRequest{ID: "req-1", Cmd: "cmd1", Env: map[string]string{"OLLIE_UNAME": "agent1"}}
|
|
req2 := &protocol.BypassRequest{ID: "req-2", Cmd: "cmd2", Env: map[string]string{"OLLIE_UNAME": "agent2"}}
|
|
|
|
s.SetBypassPending(req1, nil)
|
|
s.SetBypassPending(req2, nil)
|
|
|
|
// Resolve req-1 (will fail on toolsrv connection, but map update should succeed)
|
|
err := s.ResolveBypass("req-1", true)
|
|
// Error expected because no dialFn, but the request should be removed
|
|
if err == nil {
|
|
t.Log("ResolveBypass succeeded (unexpected but ok for this test)")
|
|
}
|
|
|
|
// req-1 should be gone, req-2 should remain
|
|
pending := s.BypassPending()
|
|
if len(pending) != 1 {
|
|
t.Errorf("Expected 1 pending request after resolve, got %d", len(pending))
|
|
}
|
|
if s.BypassPendingByID("req-1") != nil {
|
|
t.Errorf("req-1 should be removed after resolve")
|
|
}
|
|
if s.BypassPendingByID("req-2") == nil {
|
|
t.Errorf("req-2 should still be pending")
|
|
}
|
|
}
|
|
|
|
func TestBypassResolve_NonExistent(t *testing.T) {
|
|
s := &Session{ID: "test-session"}
|
|
|
|
err := s.ResolveBypass("nonexistent", true)
|
|
if err == nil {
|
|
t.Errorf("ResolveBypass should fail for non-existent ID")
|
|
}
|
|
}
|
|
|
|
func TestBypassPending_Empty(t *testing.T) {
|
|
s := &Session{ID: "test-session"}
|
|
|
|
pending := s.BypassPending()
|
|
if len(pending) != 0 {
|
|
t.Errorf("Expected 0 pending requests for new session, got %d", len(pending))
|
|
}
|
|
|
|
if s.BypassPendingByID("any") != nil {
|
|
t.Errorf("BypassPendingByID should return nil for new session")
|
|
}
|
|
}
|