test(03-02): add failing tests for hook server integration and poll slowdown
- TestHookServerStartsWithDaemon, TestHookServerStopsWithDaemon - TestHookServerPortBusy (graceful degradation) - TestPollSlowdown (20s when hooks active, 5s otherwise) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
127
daemon_test.go
127
daemon_test.go
@@ -2,9 +2,13 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -212,7 +216,128 @@ func newTestDaemon(t *testing.T) *Daemon {
|
|||||||
t.Fatalf("labels: %v", err)
|
t.Fatalf("labels: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewDaemon(sockPath, procDir, claudeDir, labels)
|
d := NewDaemon(sockPath, procDir, claudeDir, labels)
|
||||||
|
d.hookPort = 0 // disable hook server by default in tests
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHookServerStartsWithDaemon(t *testing.T) {
|
||||||
|
d := newTestDaemon(t)
|
||||||
|
|
||||||
|
// Assign a dynamic port for the hook server
|
||||||
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("listen: %v", err)
|
||||||
|
}
|
||||||
|
port := ln.Addr().(*net.TCPAddr).Port
|
||||||
|
ln.Close()
|
||||||
|
|
||||||
|
d.hookPort = port
|
||||||
|
|
||||||
|
if err := d.Start(); err != nil {
|
||||||
|
t.Fatalf("start: %v", err)
|
||||||
|
}
|
||||||
|
defer d.Stop()
|
||||||
|
|
||||||
|
// POST to /hook should return 200
|
||||||
|
url := fmt.Sprintf("http://127.0.0.1:%d/hook", port)
|
||||||
|
resp, err := http.Post(url, "application/json", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("POST /hook: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
// Empty body -> 400 (bad JSON), but the server is alive
|
||||||
|
// A valid POST should return 200
|
||||||
|
body := `{"session_id":"test","hook_event_name":"Stop","cwd":"/tmp"}`
|
||||||
|
resp2, err := http.Post(url, "application/json", io.NopCloser(
|
||||||
|
io.Reader(strings.NewReader(body)),
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("POST /hook valid: %v", err)
|
||||||
|
}
|
||||||
|
defer resp2.Body.Close()
|
||||||
|
if resp2.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("status = %d, want 200", resp2.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHookServerStopsWithDaemon(t *testing.T) {
|
||||||
|
d := newTestDaemon(t)
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("listen: %v", err)
|
||||||
|
}
|
||||||
|
port := ln.Addr().(*net.TCPAddr).Port
|
||||||
|
ln.Close()
|
||||||
|
|
||||||
|
d.hookPort = port
|
||||||
|
|
||||||
|
if err := d.Start(); err != nil {
|
||||||
|
t.Fatalf("start: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Stop()
|
||||||
|
|
||||||
|
// Give a moment for shutdown
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
// Port should no longer be listening
|
||||||
|
url := fmt.Sprintf("http://127.0.0.1:%d/hook", port)
|
||||||
|
_, err = http.Post(url, "application/json", nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected connection error after Stop, but POST succeeded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHookServerPortBusy(t *testing.T) {
|
||||||
|
d := newTestDaemon(t)
|
||||||
|
|
||||||
|
// Occupy a port
|
||||||
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("listen: %v", err)
|
||||||
|
}
|
||||||
|
defer ln.Close()
|
||||||
|
port := ln.Addr().(*net.TCPAddr).Port
|
||||||
|
|
||||||
|
d.hookPort = port
|
||||||
|
|
||||||
|
// Start should succeed despite port being busy (graceful degradation)
|
||||||
|
if err := d.Start(); err != nil {
|
||||||
|
t.Fatalf("start should succeed even with port busy: %v", err)
|
||||||
|
}
|
||||||
|
defer d.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPollSlowdown(t *testing.T) {
|
||||||
|
d := newTestDaemon(t)
|
||||||
|
|
||||||
|
// No hook received -> default 5s
|
||||||
|
got := d.currentPollInterval()
|
||||||
|
if got != 5*time.Second {
|
||||||
|
t.Errorf("default poll = %v, want 5s", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate recent hook
|
||||||
|
d.mu.Lock()
|
||||||
|
d.lastHookTime = time.Now()
|
||||||
|
d.mu.Unlock()
|
||||||
|
|
||||||
|
got = d.currentPollInterval()
|
||||||
|
if got != 20*time.Second {
|
||||||
|
t.Errorf("after hook poll = %v, want 20s", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate hook > 60s ago
|
||||||
|
d.mu.Lock()
|
||||||
|
d.lastHookTime = time.Now().Add(-61 * time.Second)
|
||||||
|
d.mu.Unlock()
|
||||||
|
|
||||||
|
got = d.currentPollInterval()
|
||||||
|
if got != 5*time.Second {
|
||||||
|
t.Errorf("after 60s poll = %v, want 5s", got)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDaemonStartStop(t *testing.T) {
|
func TestDaemonStartStop(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user