- Add CLI core setup for edge and node services - Refactor edge transport layer (server, integration tests) - Refactor node transport layer (parser, session, heartbeat, client) - Add main_test.go files for edge and node commands - Add input package for edge service - Add go.work and go.work.sum for workspace support - Update configs, docs, and project rules
263 lines
8 KiB
Go
263 lines
8 KiB
Go
package service_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
edgenode "iop/apps/edge/internal/node"
|
|
edgeservice "iop/apps/edge/internal/service"
|
|
iop "iop/proto/gen/iop"
|
|
)
|
|
|
|
func TestBuildRunRequestNormalizesSessionAndTimeout(t *testing.T) {
|
|
req, runID, err := edgeservice.BuildRunRequest(edgeservice.SubmitRunRequest{
|
|
Adapter: "cli",
|
|
Target: "codex",
|
|
Prompt: "hello",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BuildRunRequest: %v", err)
|
|
}
|
|
if runID == "" || req.GetRunId() != runID {
|
|
t.Fatalf("run id mismatch: runID=%q req=%q", runID, req.GetRunId())
|
|
}
|
|
if req.GetSessionId() != edgeservice.DefaultSessionID {
|
|
t.Fatalf("SessionId: got %q want %q", req.GetSessionId(), edgeservice.DefaultSessionID)
|
|
}
|
|
if req.GetTimeoutSec() != edgeservice.DefaultTimeoutSec {
|
|
t.Fatalf("TimeoutSec: got %d want %d", req.GetTimeoutSec(), edgeservice.DefaultTimeoutSec)
|
|
}
|
|
if req.GetSessionMode() != iop.RunSessionMode_RUN_SESSION_MODE_CREATE_IF_MISSING {
|
|
t.Fatalf("SessionMode: got %v", req.GetSessionMode())
|
|
}
|
|
}
|
|
|
|
func TestBuildNodeCommandRequest_NewTypes(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
cmdType iop.NodeCommandType
|
|
prefix string
|
|
}{
|
|
{"capabilities", iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES, "caps"},
|
|
{"sessions", iop.NodeCommandType_NODE_COMMAND_TYPE_SESSION_LIST, "sessions"},
|
|
{"transport", iop.NodeCommandType_NODE_COMMAND_TYPE_TRANSPORT_STATUS, "transport"},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
req := edgeservice.BuildNodeCommandRequest(tc.cmdType, tc.prefix, "cli", "codex", "", 0)
|
|
if req.GetType() != tc.cmdType {
|
|
t.Errorf("Type: got %v want %v", req.GetType(), tc.cmdType)
|
|
}
|
|
if req.GetSessionId() != edgeservice.DefaultSessionID {
|
|
t.Errorf("SessionId: got %q want default", req.GetSessionId())
|
|
}
|
|
if req.GetTimeoutSec() != edgeservice.DefaultTimeoutSec {
|
|
t.Errorf("TimeoutSec: got %d want default", req.GetTimeoutSec())
|
|
}
|
|
if req.GetRequestId() == "" {
|
|
t.Error("RequestId: empty")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildUsageStatusRequestAndWaitTimeout(t *testing.T) {
|
|
req := edgeservice.BuildUsageStatusRequest("cli", "gemini", "", 0)
|
|
if req.GetSessionId() != edgeservice.DefaultSessionID {
|
|
t.Fatalf("SessionId: got %q want %q", req.GetSessionId(), edgeservice.DefaultSessionID)
|
|
}
|
|
if req.GetTimeoutSec() != edgeservice.DefaultTimeoutSec {
|
|
t.Fatalf("TimeoutSec: got %d want %d", req.GetTimeoutSec(), edgeservice.DefaultTimeoutSec)
|
|
}
|
|
if got, want := edgeservice.StatusWaitTimeout(req), 35*time.Second; got != want {
|
|
t.Fatalf("StatusWaitTimeout: got %v want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestBuildRunRequest_SessionAndBackground(t *testing.T) {
|
|
req, runID, err := edgeservice.BuildRunRequest(edgeservice.SubmitRunRequest{
|
|
Adapter: "cli",
|
|
Target: "codex",
|
|
SessionID: "session-a",
|
|
Background: true,
|
|
TimeoutSec: 30,
|
|
Prompt: "hello",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BuildRunRequest: %v", err)
|
|
}
|
|
if runID == "" {
|
|
t.Fatal("expected non-empty runID")
|
|
}
|
|
if req.GetSessionId() != "session-a" {
|
|
t.Errorf("SessionId: got %q want %q", req.GetSessionId(), "session-a")
|
|
}
|
|
if !req.GetBackground() {
|
|
t.Error("Background: expected true")
|
|
}
|
|
}
|
|
|
|
func TestResolveNode_RequiresExplicitSelectionForMultipleNodes(t *testing.T) {
|
|
reg := edgenode.NewRegistry()
|
|
reg.Register(&edgenode.NodeEntry{NodeID: "node-1"})
|
|
reg.Register(&edgenode.NodeEntry{NodeID: "node-2"})
|
|
svc := edgeservice.New(reg, nil)
|
|
|
|
if _, err := svc.ResolveNode(""); err == nil {
|
|
t.Error("expected error for implicit resolve with multiple nodes")
|
|
}
|
|
if e, err := svc.ResolveNode("node-1"); err != nil || e.NodeID != "node-1" {
|
|
t.Errorf("failed explicit resolve: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBuildRunRequestDoesNotInjectConsoleSource(t *testing.T) {
|
|
req, _, err := edgeservice.BuildRunRequest(edgeservice.SubmitRunRequest{
|
|
Adapter: "cli",
|
|
Target: "codex",
|
|
Prompt: "hello",
|
|
Metadata: nil,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BuildRunRequest: %v", err)
|
|
}
|
|
if req.GetMetadata() == nil {
|
|
t.Fatal("expected non-nil metadata map")
|
|
}
|
|
if v, ok := req.GetMetadata()["source"]; ok {
|
|
t.Errorf("BuildRunRequest should not inject source, got source=%q", v)
|
|
}
|
|
}
|
|
|
|
func TestBuildRunRequestCopiesMetadata(t *testing.T) {
|
|
req, _, err := edgeservice.BuildRunRequest(edgeservice.SubmitRunRequest{
|
|
Adapter: "cli",
|
|
Target: "codex",
|
|
Prompt: "hello",
|
|
Metadata: map[string]string{"source": "edge-ops-console", "x-custom": "value"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BuildRunRequest: %v", err)
|
|
}
|
|
if got := req.GetMetadata()["source"]; got != "edge-ops-console" {
|
|
t.Errorf("source: got %q, want %q", got, "edge-ops-console")
|
|
}
|
|
if got := req.GetMetadata()["x-custom"]; got != "value" {
|
|
t.Errorf("x-custom: got %q, want %q", got, "value")
|
|
}
|
|
}
|
|
|
|
func TestListNodesReturnsSnapshots(t *testing.T) {
|
|
reg := edgenode.NewRegistry()
|
|
reg.Register(&edgenode.NodeEntry{NodeID: "node-1", Alias: "alpha"})
|
|
reg.Register(&edgenode.NodeEntry{NodeID: "node-2"})
|
|
svc := edgeservice.New(reg, nil)
|
|
|
|
snaps := svc.ListNodeSnapshots()
|
|
if len(snaps) != 2 {
|
|
t.Fatalf("ListNodeSnapshots: got %d want 2", len(snaps))
|
|
}
|
|
byID := map[string]edgeservice.NodeSnapshot{}
|
|
for _, s := range snaps {
|
|
byID[s.NodeID] = s
|
|
}
|
|
if got := byID["node-1"]; got.Alias != "alpha" || got.Label != "node0" {
|
|
t.Errorf("node-1 snapshot: %+v, want Label=node0", got)
|
|
}
|
|
if got := byID["node-2"]; got.Alias != "" || got.Label != "node1" {
|
|
t.Errorf("node-2 snapshot: %+v, want Label=node1", got)
|
|
}
|
|
}
|
|
|
|
func TestResolveNodeSnapshotReturnsDTO(t *testing.T) {
|
|
reg := edgenode.NewRegistry()
|
|
reg.Register(&edgenode.NodeEntry{NodeID: "node-1", Alias: "alpha"})
|
|
svc := edgeservice.New(reg, nil)
|
|
|
|
snap, err := svc.ResolveNodeSnapshot("alpha")
|
|
if err != nil {
|
|
t.Fatalf("ResolveNodeSnapshot: %v", err)
|
|
}
|
|
if snap.NodeID != "node-1" || snap.Label != "node0" {
|
|
t.Errorf("snapshot: %+v", snap)
|
|
}
|
|
}
|
|
|
|
func TestSubmitRunReturnsDispatchMetadata(t *testing.T) {
|
|
dispatch := edgeservice.RunDispatch{
|
|
RunID: "run-x",
|
|
NodeID: "node-1",
|
|
NodeLabel: "alpha",
|
|
Adapter: "cli",
|
|
Target: "codex",
|
|
SessionID: "session-a",
|
|
Background: true,
|
|
TimeoutSec: 30,
|
|
}
|
|
handle := &edgeservice.RunHandle{RunDispatch: dispatch}
|
|
if handle.RunID != dispatch.RunID || handle.NodeLabel != dispatch.NodeLabel {
|
|
t.Fatalf("RunHandle does not expose embedded RunDispatch fields: %+v", handle)
|
|
}
|
|
if handle.Background != true || handle.TimeoutSec != 30 {
|
|
t.Errorf("RunHandle dispatch fields wrong: %+v", handle)
|
|
}
|
|
}
|
|
|
|
func TestBuildCancelRunRequest(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
req edgeservice.CancelRunRequest
|
|
wantSessionID string
|
|
}{
|
|
{
|
|
name: "explicit session",
|
|
req: edgeservice.CancelRunRequest{
|
|
RunID: "run-123",
|
|
Adapter: "cli",
|
|
Target: "claude",
|
|
SessionID: "session-a",
|
|
},
|
|
wantSessionID: "session-a",
|
|
},
|
|
{
|
|
name: "empty session normalizes to default",
|
|
req: edgeservice.CancelRunRequest{
|
|
RunID: "run-456",
|
|
Adapter: "ollama",
|
|
Target: "llama3",
|
|
SessionID: "",
|
|
},
|
|
wantSessionID: edgeservice.DefaultSessionID,
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
proto := edgeservice.BuildCancelRunRequest(tc.req)
|
|
if proto.GetRunId() != tc.req.RunID {
|
|
t.Errorf("RunId: got %q want %q", proto.GetRunId(), tc.req.RunID)
|
|
}
|
|
if proto.GetAdapter() != tc.req.Adapter {
|
|
t.Errorf("Adapter: got %q want %q", proto.GetAdapter(), tc.req.Adapter)
|
|
}
|
|
if proto.GetTarget() != tc.req.Target {
|
|
t.Errorf("Target: got %q want %q", proto.GetTarget(), tc.req.Target)
|
|
}
|
|
if proto.GetSessionId() != tc.wantSessionID {
|
|
t.Errorf("SessionId: got %q want %q", proto.GetSessionId(), tc.wantSessionID)
|
|
}
|
|
if proto.GetAction() != iop.CancelAction_CANCEL_ACTION_CANCEL_RUN {
|
|
t.Errorf("Action: got %v want CANCEL_ACTION_CANCEL_RUN", proto.GetAction())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveNode_AllowsSingleNodeFallback(t *testing.T) {
|
|
reg := edgenode.NewRegistry()
|
|
reg.Register(&edgenode.NodeEntry{NodeID: "node-1"})
|
|
svc := edgeservice.New(reg, nil)
|
|
|
|
if e, err := svc.ResolveNode(""); err != nil || e.NodeID != "node-1" {
|
|
t.Errorf("expected single node fallback, got error: %v", err)
|
|
}
|
|
}
|