iop/apps/edge/internal/service/service_test.go
toki b4124f0bd6 feat: CLI setup, edge/node transport refactor, and infrastructure updates
- 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
2026-05-20 16:37:42 +09:00

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)
}
}