255 lines
5.5 KiB
Go
255 lines
5.5 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"ollie/toolsrv/protocol"
|
|
)
|
|
|
|
func TestState_Basic(t *testing.T) {
|
|
st := NewState("/tmp")
|
|
|
|
// Test CWD
|
|
if st.CWD() != "/tmp" {
|
|
t.Errorf("CWD() = %q, want /tmp", st.CWD())
|
|
}
|
|
|
|
st.SetCWD("/home")
|
|
if st.CWD() != "/home" {
|
|
t.Errorf("CWD() = %q, want /home", st.CWD())
|
|
}
|
|
|
|
// Test Env
|
|
st.SetEnv("FOO", "bar")
|
|
if st.GetEnv("FOO") != "bar" {
|
|
t.Errorf("GetEnv(FOO) = %q, want bar", st.GetEnv("FOO"))
|
|
}
|
|
|
|
// Test HostInfo
|
|
info := st.HostInfo()
|
|
if !strings.Contains(info, "platform=") {
|
|
t.Errorf("HostInfo() missing platform")
|
|
}
|
|
}
|
|
|
|
func TestState_ToolsWithoutRegistry(t *testing.T) {
|
|
st := NewState("/tmp")
|
|
|
|
// Without registry, ListTools should return nil
|
|
tools := st.ListTools("agent1")
|
|
if tools != nil {
|
|
t.Errorf("ListTools() without registry = %v, want nil", tools)
|
|
}
|
|
|
|
// Load should fail without registry
|
|
err := st.LoadTool("agent1", "shell")
|
|
if err == nil {
|
|
t.Error("LoadTool without registry should fail")
|
|
}
|
|
}
|
|
|
|
func TestState_HandleCtl(t *testing.T) {
|
|
st := NewState("/tmp")
|
|
|
|
// Unknown command
|
|
err := st.HandleCtl("unknown")
|
|
if err == nil {
|
|
t.Error("HandleCtl(unknown) should fail")
|
|
}
|
|
|
|
// load without agent and name
|
|
err = st.HandleCtl("load")
|
|
if err == nil {
|
|
t.Error("HandleCtl(load) without args should fail")
|
|
}
|
|
|
|
// load with only one arg (missing tool name)
|
|
err = st.HandleCtl("load agent1")
|
|
if err == nil {
|
|
t.Error("HandleCtl(load agent1) without tool name should fail")
|
|
}
|
|
|
|
// unload without agent and name
|
|
err = st.HandleCtl("unload")
|
|
if err == nil {
|
|
t.Error("HandleCtl(unload) without args should fail")
|
|
}
|
|
}
|
|
|
|
func TestState_ProcLifecycle(t *testing.T) {
|
|
st := NewState("/tmp")
|
|
|
|
// No procs initially
|
|
pids := st.ListProcs()
|
|
if len(pids) != 0 {
|
|
t.Errorf("ListProcs() = %v, want empty", pids)
|
|
}
|
|
|
|
// GetProc for non-existent
|
|
if st.GetProc(999) != nil {
|
|
t.Error("GetProc(999) should return nil")
|
|
}
|
|
|
|
// DismissProc for non-existent
|
|
if st.DismissProc(999) {
|
|
t.Error("DismissProc(999) should return false")
|
|
}
|
|
|
|
// SignalProc for non-existent
|
|
if err := st.SignalProc(999, 15); err == nil {
|
|
t.Error("SignalProc(999) should fail")
|
|
}
|
|
}
|
|
|
|
func TestState_HandleProcCtl(t *testing.T) {
|
|
st := NewState("/tmp")
|
|
|
|
// Unknown command
|
|
err := st.HandleProcCtl(1, "unknown")
|
|
if err == nil {
|
|
t.Error("HandleProcCtl(unknown) should fail")
|
|
}
|
|
|
|
// signal without number
|
|
err = st.HandleProcCtl(1, "signal")
|
|
if err == nil {
|
|
t.Error("HandleProcCtl(signal) without number should fail")
|
|
}
|
|
|
|
// signal with invalid number
|
|
err = st.HandleProcCtl(1, "signal abc")
|
|
if err == nil {
|
|
t.Error("HandleProcCtl(signal abc) should fail")
|
|
}
|
|
|
|
// dismiss on non-existent (should succeed silently)
|
|
err = st.HandleProcCtl(999, "dismiss")
|
|
if err != nil {
|
|
t.Errorf("HandleProcCtl(dismiss) on non-existent = %v, want nil", err)
|
|
}
|
|
}
|
|
|
|
func TestProc_Stat(t *testing.T) {
|
|
proc := &Proc{
|
|
ID: 1,
|
|
Tool: "shell",
|
|
StartTime: time.Now(),
|
|
done: make(chan struct{}),
|
|
}
|
|
|
|
// Running state
|
|
stat := proc.Stat()
|
|
if !strings.Contains(stat, "exited=false") {
|
|
t.Errorf("Stat() for running proc should contain 'exited=false', got %q", stat)
|
|
}
|
|
|
|
// Mark as exited
|
|
proc.Exited = true
|
|
proc.ExitCode = 0
|
|
proc.EndTime = time.Now()
|
|
|
|
stat = proc.Stat()
|
|
if !strings.Contains(stat, "exited=true") {
|
|
t.Errorf("Stat() for exited proc should contain 'exited=true', got %q", stat)
|
|
}
|
|
}
|
|
|
|
func TestState_NewProcWithoutRegistry(t *testing.T) {
|
|
st := NewState("/tmp")
|
|
|
|
ctx := context.Background()
|
|
_, _, err := st.NewProc(ctx, "tool=shell\ncmd=ls\n", false)
|
|
if err == nil {
|
|
t.Error("NewProc without registry should fail")
|
|
}
|
|
}
|
|
|
|
func TestParsePayload(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
want map[string]string
|
|
}{
|
|
{
|
|
input: "tool=shell\ncmd=ls -la\n",
|
|
want: map[string]string{"tool": "shell", "cmd": "ls -la"},
|
|
},
|
|
{
|
|
input: "key=value with spaces\n",
|
|
want: map[string]string{"key": "value with spaces"},
|
|
},
|
|
{
|
|
input: "multi=line\\none\\ntwo\n",
|
|
want: map[string]string{"multi": "line\none\ntwo"},
|
|
},
|
|
{
|
|
input: "escaped=back\\\\slash\n",
|
|
want: map[string]string{"escaped": "back\\slash"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
got := protocol.ParsePayload(tt.input)
|
|
for k, v := range tt.want {
|
|
if got[k] != v {
|
|
t.Errorf("protocol.ParsePayload(%q)[%q] = %q, want %q", tt.input, k, got[k], v)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGcProcs(t *testing.T) {
|
|
st := NewState("/tmp")
|
|
|
|
// Add a proc that's exited and was read long ago
|
|
st.procMu.Lock()
|
|
st.procs[1] = &Proc{
|
|
ID: 1,
|
|
Exited: true,
|
|
EndTime: time.Now().Add(-20 * time.Minute),
|
|
LastRead: time.Now().Add(-15 * time.Minute),
|
|
done: make(chan struct{}),
|
|
}
|
|
// Add a proc that's exited but never read
|
|
st.procs[2] = &Proc{
|
|
ID: 2,
|
|
Exited: true,
|
|
done: make(chan struct{}),
|
|
}
|
|
// Add a proc that's exited and recently read
|
|
st.procs[3] = &Proc{
|
|
ID: 3,
|
|
Exited: true,
|
|
LastRead: time.Now(),
|
|
done: make(chan struct{}),
|
|
}
|
|
// Add a still-running proc
|
|
st.procs[4] = &Proc{
|
|
ID: 4,
|
|
Exited: false,
|
|
LastRead: time.Now().Add(-20 * time.Minute),
|
|
done: make(chan struct{}),
|
|
}
|
|
st.procMu.Unlock()
|
|
|
|
st.gcProcs()
|
|
|
|
st.procMu.Lock()
|
|
defer st.procMu.Unlock()
|
|
|
|
if _, ok := st.procs[1]; ok {
|
|
t.Error("proc 1 should have been GC'd (exited, read >10min ago)")
|
|
}
|
|
if _, ok := st.procs[2]; !ok {
|
|
t.Error("proc 2 should NOT be GC'd (never read)")
|
|
}
|
|
if _, ok := st.procs[3]; !ok {
|
|
t.Error("proc 3 should NOT be GC'd (recently read)")
|
|
}
|
|
if _, ok := st.procs[4]; !ok {
|
|
t.Error("proc 4 should NOT be GC'd (still running)")
|
|
}
|
|
}
|