6597f8b9d1
Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
90 lines
3.4 KiB
Go
90 lines
3.4 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/kandev/kandev/internal/agentctl/server/adapter"
|
|
ws "github.com/kandev/kandev/pkg/websocket"
|
|
)
|
|
|
|
// turnStartRecordingAdapter adds the optional TurnStartRecorder half onto a
|
|
// bare AgentAdapter, mirroring recordingAdapter/steerableAdapter's
|
|
// embed-and-panic-on-touch pattern in prompt_steer_routing_test.go.
|
|
type turnStartRecordingAdapter struct {
|
|
adapter.AgentAdapter
|
|
turnStart time.Time
|
|
recorded bool
|
|
}
|
|
|
|
func (a turnStartRecordingAdapter) RecordedTurnStart(_ string) (time.Time, bool) {
|
|
return a.turnStart, a.recorded
|
|
}
|
|
|
|
func TestHandleWSBackgroundProbe_NoAdapter_Errors(t *testing.T) {
|
|
s := newTestServer(t)
|
|
msg, _ := ws.NewRequest("req-1", "agent.background.probe", map[string]string{"session_id": "sess-1"})
|
|
|
|
resp := s.handleWSBackgroundProbe(context.Background(), msg)
|
|
|
|
if resp.Type != ws.MessageTypeError {
|
|
t.Fatalf("expected an error response, got %q", resp.Type)
|
|
}
|
|
}
|
|
|
|
// AC-46: an adapter that doesn't implement TurnStartRecorder at all (not an
|
|
// ACP adapter) is one of the failure conditions that must resolve to
|
|
// unknown, not an error.
|
|
func TestHandleWSBackgroundProbe_AdapterWithoutTurnStartRecorder_Unknown(t *testing.T) {
|
|
s := newTestServer(t)
|
|
s.procMgr.SetAdapterForTest(&recordingAdapter{})
|
|
msg, _ := ws.NewRequest("req-1", "agent.background.probe", map[string]string{"session_id": "sess-1"})
|
|
|
|
resp := s.handleWSBackgroundProbe(context.Background(), msg)
|
|
assertBackgroundProbeResult(t, resp, "unknown")
|
|
}
|
|
|
|
// AC-46: no recorded turn start for this session (e.g. probed before any
|
|
// turn ever started) resolves to unknown.
|
|
func TestHandleWSBackgroundProbe_NoRecordedTurnStart_Unknown(t *testing.T) {
|
|
s := newTestServer(t)
|
|
s.procMgr.SetAdapterForTest(turnStartRecordingAdapter{recorded: false})
|
|
msg, _ := ws.NewRequest("req-1", "agent.background.probe", map[string]string{"session_id": "sess-1"})
|
|
|
|
resp := s.handleWSBackgroundProbe(context.Background(), msg)
|
|
assertBackgroundProbeResult(t, resp, "unknown")
|
|
}
|
|
|
|
// AC-45: the request carries only session_id (no timestamp — the turn start
|
|
// was already recorded adapter-side, per D3); the response is always
|
|
// exactly one of the three valid result literals.
|
|
//
|
|
// newTestServer's procMgr has no real agent process running, so AgentPID()
|
|
// returns 0 — D9's "agent process exited" case. This exercises the exact
|
|
// production path a live server takes between adapter recovery and process
|
|
// launch, and must resolve to unknown rather than any tri-state-tolerant
|
|
// answer: a pid-0 root must never be walked into a false "live".
|
|
func TestHandleWSBackgroundProbe_RecordedTurnStart_NoRunningProcess_Unknown(t *testing.T) {
|
|
s := newTestServer(t)
|
|
s.procMgr.SetAdapterForTest(turnStartRecordingAdapter{turnStart: time.Now(), recorded: true})
|
|
msg, _ := ws.NewRequest("req-1", "agent.background.probe", map[string]string{"session_id": "sess-1"})
|
|
|
|
resp := s.handleWSBackgroundProbe(context.Background(), msg)
|
|
assertBackgroundProbeResult(t, resp, "unknown")
|
|
}
|
|
|
|
func assertBackgroundProbeResult(t *testing.T, resp *ws.Message, want string) {
|
|
t.Helper()
|
|
if resp.Type != ws.MessageTypeResponse {
|
|
t.Fatalf("expected a response (not error), got %q", resp.Type)
|
|
}
|
|
var payload BackgroundProbeResponse
|
|
if err := resp.ParsePayload(&payload); err != nil {
|
|
t.Fatalf("parse response: %v", err)
|
|
}
|
|
if payload.Result != want {
|
|
t.Fatalf("got result %q, want %q", payload.Result, want)
|
|
}
|
|
}
|