package service_test import ( "context" "fmt" "net" "strings" "testing" "time" toki "git.toki-labs.com/toki/proto-socket/go" "google.golang.org/protobuf/proto" edgenode "iop/apps/edge/internal/node" edgeservice "iop/apps/edge/internal/service" "iop/packages/go/config" 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.GetAdapter() != "cli" { t.Errorf("Adapter: got %q want cli", req.GetAdapter()) } if req.GetTarget() != "codex" { t.Errorf("Target: got %q want codex", req.GetTarget()) } 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", "antigravity", "", 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) } } func TestListNodeSnapshotsWithConfig(t *testing.T) { reg := edgenode.NewRegistry() reg.Register(&edgenode.NodeEntry{NodeID: "node-1", Alias: "alpha"}) store := edgenode.NewNodeStore() store.Add(&edgenode.NodeRecord{ ID: "node-1", Alias: "alpha", Token: "tok-1", Runtime: config.RuntimeConf{ Concurrency: 3, WorkspaceRoot: "/workspace", }, Adapters: config.AdaptersConf{ Ollama: config.OllamaConf{ Enabled: true, BaseURL: "http://localhost:11434", ContextSize: 2048, }, }, }) svc := edgeservice.New(reg, nil) svc.SetNodeStore(store) snaps := svc.ListNodeSnapshots() if len(snaps) != 1 { t.Fatalf("ListNodeSnapshots: got %d want 1", len(snaps)) } snap := snaps[0] if snap.NodeID != "node-1" || snap.Label != "node0" { t.Errorf("snapshot basic info wrong: %+v", snap) } if snap.Config == nil { t.Fatal("expected snap.Config to be populated") } if snap.Config.Runtime == nil || snap.Config.Runtime.Concurrency != 3 { t.Errorf("unexpected runtime config: %+v", snap.Config.Runtime) } var ollama *iop.AdapterConfig for _, a := range snap.Config.Adapters { if a.Type == "ollama" { ollama = a break } } if ollama == nil { t.Fatal("expected ollama adapter config") } if !ollama.Enabled || ollama.GetOllama().GetBaseUrl() != "http://localhost:11434" { t.Errorf("unexpected ollama config: %+v", ollama) } } func TestListNodeSnapshotsIncludesAgentKind(t *testing.T) { reg := edgenode.NewRegistry() // generic node: kind/lifecycle defaults are applied by Register. reg.Register(&edgenode.NodeEntry{NodeID: "node-generic"}) // OTO agent: kind and a non-default lifecycle must pass through unchanged. reg.Register(&edgenode.NodeEntry{ NodeID: "node-oto", AgentKind: config.AgentKindOTOAgent, LifecycleState: "registering", }) svc := edgeservice.New(reg, nil) byID := map[string]edgeservice.NodeSnapshot{} for _, s := range svc.ListNodeSnapshots() { byID[s.NodeID] = s } generic, ok := byID["node-generic"] if !ok { t.Fatal("node-generic snapshot missing") } if generic.AgentKind != config.AgentKindGenericNode { t.Errorf("generic AgentKind: got %q want %q", generic.AgentKind, config.AgentKindGenericNode) } if generic.LifecycleState != edgenode.LifecycleConnected { t.Errorf("generic LifecycleState: got %q want %q", generic.LifecycleState, edgenode.LifecycleConnected) } oto, ok := byID["node-oto"] if !ok { t.Fatal("node-oto snapshot missing") } if oto.AgentKind != config.AgentKindOTOAgent { t.Errorf("oto AgentKind: got %q want %q", oto.AgentKind, config.AgentKindOTOAgent) } if oto.LifecycleState != "registering" { t.Errorf("oto LifecycleState: got %q want %q", oto.LifecycleState, "registering") } } func TestResolveNodeSnapshotCarriesAgentKind(t *testing.T) { reg := edgenode.NewRegistry() reg.Register(&edgenode.NodeEntry{ NodeID: "node-oto", Alias: "oto-1", AgentKind: config.AgentKindOTOAgent, }) svc := edgeservice.New(reg, nil) snap, err := svc.ResolveNodeSnapshot("oto-1") if err != nil { t.Fatalf("ResolveNodeSnapshot: %v", err) } if snap.AgentKind != config.AgentKindOTOAgent { t.Errorf("AgentKind: got %q want %q", snap.AgentKind, config.AgentKindOTOAgent) } if snap.LifecycleState != edgenode.LifecycleConnected { t.Errorf("LifecycleState: got %q want %q", snap.LifecycleState, edgenode.LifecycleConnected) } } func TestServiceCapabilitiesAndDomainAgents(t *testing.T) { cases := []struct { state string expectedState string expectedAvail bool expectedStat string }{ {"", "connected", true, "ready"}, {"connected", "connected", true, "ready"}, {"deploying", "deploying", true, "busy"}, {"running", "running", true, "busy"}, {"busy", "busy", true, "busy"}, {"error", "error", false, "error"}, {"failed", "failed", false, "error"}, {"unknown-state", "unknown-state", true, "busy"}, } for _, tc := range cases { t.Run(fmt.Sprintf("state-%s", tc.state), func(t *testing.T) { reg := edgenode.NewRegistry() reg.Register(&edgenode.NodeEntry{ NodeID: "generic-1", Alias: "gen-node", AgentKind: config.AgentKindGenericNode, }) reg.Register(&edgenode.NodeEntry{ NodeID: "oto-1", Alias: "deployer", AgentKind: config.AgentKindOTOAgent, LifecycleState: tc.state, }) svc := edgeservice.New(reg, nil) caps := svc.GetCapabilities() if len(caps) != 2 { t.Fatalf("expected 2 capabilities, got %d", len(caps)) } capMap := map[string]*iop.EdgeCapabilitySummary{} for _, c := range caps { capMap[c.Kind] = c } if c := capMap["run"]; c == nil || !c.Available || c.Status != "ready" { t.Errorf("invalid run capability: %+v", c) } if c := capMap["build-deploy"]; c == nil || c.Available != tc.expectedAvail || c.Status != tc.expectedStat { t.Errorf("state %q: invalid build-deploy capability: %+v (want avail=%v, stat=%q)", tc.state, c, tc.expectedAvail, tc.expectedStat) } agents := svc.GetDomainAgents() if len(agents) != 1 { t.Fatalf("expected 1 domain agent, got %d", len(agents)) } a := agents[0] if a.AgentKind != config.AgentKindOTOAgent || a.Available != tc.expectedAvail || a.LifecycleState != tc.expectedState { t.Errorf("invalid domain agent summary: %+v", a) } }) } } func TestServiceExecuteCommand(t *testing.T) { reg := edgenode.NewRegistry() reg.Register(&edgenode.NodeEntry{ NodeID: "oto-1", Alias: "deployer", AgentKind: config.AgentKindOTOAgent, }) svc := edgeservice.New(reg, nil) t.Run("health.check", func(t *testing.T) { var events []*iop.EdgeCommandEvent onEvent := func(e *iop.EdgeCommandEvent) { events = append(events, e) } req := &iop.EdgeCommandRequest{ CommandId: "cmd-hc", Operation: "health.check", } resp, err := svc.ExecuteCommand(context.Background(), req, onEvent) if err != nil { t.Fatalf("ExecuteCommand error: %v", err) } if resp.Status != "completed" { t.Errorf("status: got %q want completed", resp.Status) } if len(events) != 2 { t.Fatalf("expected 2 events, got %d", len(events)) } if events[0].Phase != "started" || events[1].Phase != "completed" { t.Errorf("unexpected event sequence: %+v", events) } }) t.Run("agent.status found", func(t *testing.T) { var events []*iop.EdgeCommandEvent onEvent := func(e *iop.EdgeCommandEvent) { events = append(events, e) } req := &iop.EdgeCommandRequest{ CommandId: "cmd-status", Operation: "agent.status", TargetSelector: "deployer", } resp, err := svc.ExecuteCommand(context.Background(), req, onEvent) if err != nil { t.Fatalf("ExecuteCommand error: %v", err) } if resp.Status != "completed" { t.Errorf("status: got %q want completed", resp.Status) } if len(events) != 2 { t.Fatalf("expected 2 events, got %d", len(events)) } if events[0].Phase != "started" || events[1].Phase != "completed" { t.Errorf("unexpected event sequence: %+v", events) } }) t.Run("agent.status missing", func(t *testing.T) { req := &iop.EdgeCommandRequest{ CommandId: "cmd-status", Operation: "agent.status", TargetSelector: "missing-agent", } resp, err := svc.ExecuteCommand(context.Background(), req, func(*iop.EdgeCommandEvent) {}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.Status != "error" || resp.Error == "" { t.Errorf("expected error status, got: %+v", resp) } }) t.Run("agent.command does not fake success on nil client", func(t *testing.T) { var events []*iop.EdgeCommandEvent onEvent := func(e *iop.EdgeCommandEvent) { events = append(events, e) } req := &iop.EdgeCommandRequest{ CommandId: "cmd-run", Operation: "agent.command", TargetSelector: "deployer", Parameters: map[string]string{ "command": "capabilities", }, } resp, err := svc.ExecuteCommand(context.Background(), req, onEvent) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.Status != "error" { t.Errorf("expected error status, got: %q", resp.Status) } if len(events) < 2 { t.Fatalf("expected at least started and failed events, got %d", len(events)) } if events[0].Phase != "started" || events[len(events)-1].Phase != "failed" { t.Errorf("unexpected event sequence: %+v", events) } }) t.Run("agent.command missing command param", func(t *testing.T) { req := &iop.EdgeCommandRequest{ CommandId: "cmd-run", Operation: "agent.command", TargetSelector: "deployer", } resp, err := svc.ExecuteCommand(context.Background(), req, func(*iop.EdgeCommandEvent) {}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.Status != "unsupported" { t.Errorf("expected unsupported status, got %q", resp.Status) } }) t.Run("unsupported operation", func(t *testing.T) { req := &iop.EdgeCommandRequest{ CommandId: "cmd-bad", Operation: "bad.op", } resp, err := svc.ExecuteCommand(context.Background(), req, func(*iop.EdgeCommandEvent) {}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.Status != "unsupported" { t.Errorf("status: got %q want unsupported", resp.Status) } }) } func TestServiceExecuteCommandAgentCommandCapabilitiesSuccess(t *testing.T) { edgeConn, nodeConn := net.Pipe() defer edgeConn.Close() defer nodeConn.Close() parserMap := toki.ParserMap{ toki.TypeNameOf(&iop.NodeCommandRequest{}): func(b []byte) (proto.Message, error) { m := &iop.NodeCommandRequest{} return m, proto.Unmarshal(b, m) }, toki.TypeNameOf(&iop.NodeCommandResponse{}): func(b []byte) (proto.Message, error) { m := &iop.NodeCommandResponse{} return m, proto.Unmarshal(b, m) }, } edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap) nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap) toki.AddRequestListenerTyped(&nodeClient.Communicator, func(req *iop.NodeCommandRequest) (*iop.NodeCommandResponse, error) { if req.GetType() != iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES { return nil, fmt.Errorf("unexpected command type: %v", req.GetType()) } if req.GetAdapter() != "cli" || req.GetTarget() != "codex" || req.GetSessionId() != "sess-success" { return nil, fmt.Errorf("unexpected adapter/target/session: %s/%s/%s", req.GetAdapter(), req.GetTarget(), req.GetSessionId()) } return &iop.NodeCommandResponse{ RequestId: req.RequestId, Type: req.Type, Result: map[string]string{"caps": "cli,codex"}, }, nil }) reg := edgenode.NewRegistry() reg.Register(&edgenode.NodeEntry{ NodeID: "oto-1", Alias: "deployer", AgentKind: config.AgentKindOTOAgent, Client: edgeClient, }) svc := edgeservice.New(reg, nil) var events []*iop.EdgeCommandEvent onEvent := func(e *iop.EdgeCommandEvent) { events = append(events, e) } req := &iop.EdgeCommandRequest{ CommandId: "cmd-success-1", Operation: "agent.command", TargetSelector: "deployer", Parameters: map[string]string{ "command": "capabilities", "adapter": "cli", "target": "codex", "session_id": "sess-success", }, } resp, err := svc.ExecuteCommand(context.Background(), req, onEvent) if err != nil { t.Fatalf("ExecuteCommand: %v", err) } if resp.Status != "completed" { t.Errorf("status: got %q want completed", resp.Status) } if !strings.Contains(resp.Summary, "caps=cli,codex") { t.Errorf("summary: got %q, expected containing caps=cli,codex", resp.Summary) } if len(events) < 2 { t.Fatalf("expected at least 2 events, got %d", len(events)) } if events[0].Phase != "started" || events[len(events)-1].Phase != "completed" { t.Errorf("unexpected event sequence: %+v", events) } if events[len(events)-1].Summary != "Command capabilities completed successfully" { t.Errorf("unexpected event summary: got %q, expected 'Command capabilities completed successfully'", events[len(events)-1].Summary) } } func TestServiceExecuteCommandAgentCommandCapabilitiesNodeError(t *testing.T) { edgeConn, nodeConn := net.Pipe() defer edgeConn.Close() defer nodeConn.Close() parserMap := toki.ParserMap{ toki.TypeNameOf(&iop.NodeCommandRequest{}): func(b []byte) (proto.Message, error) { m := &iop.NodeCommandRequest{} return m, proto.Unmarshal(b, m) }, toki.TypeNameOf(&iop.NodeCommandResponse{}): func(b []byte) (proto.Message, error) { m := &iop.NodeCommandResponse{} return m, proto.Unmarshal(b, m) }, } edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap) nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap) toki.AddRequestListenerTyped(&nodeClient.Communicator, func(req *iop.NodeCommandRequest) (*iop.NodeCommandResponse, error) { if req.GetSessionId() != "sess-error" { return nil, fmt.Errorf("unexpected session_id: %s", req.GetSessionId()) } return &iop.NodeCommandResponse{ RequestId: req.RequestId, Type: req.Type, Error: "node failure", }, nil }) reg := edgenode.NewRegistry() reg.Register(&edgenode.NodeEntry{ NodeID: "oto-1", Alias: "deployer", AgentKind: config.AgentKindOTOAgent, Client: edgeClient, }) svc := edgeservice.New(reg, nil) var events []*iop.EdgeCommandEvent onEvent := func(e *iop.EdgeCommandEvent) { events = append(events, e) } req := &iop.EdgeCommandRequest{ CommandId: "cmd-error-1", Operation: "agent.command", TargetSelector: "deployer", Parameters: map[string]string{ "command": "capabilities", "adapter": "cli", "target": "codex", "session_id": "sess-error", }, } resp, err := svc.ExecuteCommand(context.Background(), req, onEvent) if err != nil { t.Fatalf("ExecuteCommand: %v", err) } if resp.Status != "error" { t.Errorf("status: got %q want error", resp.Status) } if !strings.Contains(resp.Error, "node reported error: node failure") { t.Errorf("error: got %q, expected containing node failure", resp.Error) } if len(events) < 2 { t.Fatalf("expected at least 2 events, got %d", len(events)) } if events[0].Phase != "started" || events[len(events)-1].Phase != "failed" { t.Errorf("unexpected event sequence: %+v", events) } expectedSummary := "Command capabilities failed: node reported error: node failure" if events[len(events)-1].Summary != expectedSummary { t.Errorf("unexpected event summary: got %q, expected %q", events[len(events)-1].Summary, expectedSummary) } }