package service import ( "context" "errors" "sync/atomic" "testing" "time" edgenode "iop/apps/edge/internal/node" "iop/packages/go/config" ) type recordingWorkspaceExecutor struct { calls atomic.Int32 seen chan *SingleRequestBinding } func (e *recordingWorkspaceExecutor) ExecuteSingleRequest(_ context.Context, req SingleRequestRequest, _ SingleRequestController) error { e.calls.Add(1) e.seen <- req.Binding.Clone() return nil } func (e *recordingWorkspaceExecutor) binding(t *testing.T) *SingleRequestBinding { t.Helper() select { case binding := <-e.seen: return binding case <-time.After(time.Second): t.Fatal("executor did not receive an admitted binding") return nil } } func newRecordingWorkspaceExecutor() *recordingWorkspaceExecutor { return &recordingWorkspaceExecutor{seen: make(chan *SingleRequestBinding, 1)} } func workspaceDefinition(ref string, operations ...config.WorkspaceOperation) config.WorkspaceDefinition { workspace := config.WorkspaceDefinition{ Ref: ref, Platform: "darwin", Root: "/Users/operator/project", Operations: operations, MaxReadBytes: 64, MaxWriteBytes: 64, MaxOutputBytes: 128, MaxCommandTimeoutMS: 500, } for _, operation := range operations { if operation == config.WorkspaceOpCommand { workspace.Commands = []config.WorkspaceCommandDefinition{{ID: "test"}} } } return workspace } func workspaceStore(nodeID string, workspace config.WorkspaceDefinition) *edgenode.NodeStore { store := edgenode.NewNodeStore() store.Add(&edgenode.NodeRecord{ID: nodeID, Alias: nodeID, Token: nodeID + "-token", Workspaces: []config.WorkspaceDefinition{workspace}}) return store } func readyWorkspaceService(t *testing.T, nodeID string, workspace config.WorkspaceDefinition, executor *recordingWorkspaceExecutor) (*Service, *edgenode.Registry) { t.Helper() registry := edgenode.NewRegistry() registry.Register(&edgenode.NodeEntry{NodeID: nodeID, Alias: nodeID}) service := New(registry, nil) service.SetNodeStore(workspaceStore(nodeID, workspace)) service.SetSingleRequestExecutor(executor) return service, registry } func workspaceRequest(t *testing.T, ref string) SingleRequestRequest { t.Helper() binding := createTestBinding(t) binding.WorkspaceRef = ref return SingleRequestRequest{RequestID: "workspace-request", Binding: binding, Prompt: "complete task"} } func TestSingleRequestWorkspaceRejectsMissingRuntimeDependencies(t *testing.T) { for name, service := range map[string]*Service{ "missing store and registry": {}, "missing store": New(edgenode.NewRegistry(), nil), "missing registry": &Service{nodeStore: workspaceStore("node-a", workspaceDefinition("approved", config.WorkspaceOpRead))}, } { t.Run(name, func(t *testing.T) { executor := newRecordingWorkspaceExecutor() service.SetSingleRequestExecutor(executor) request := workspaceRequest(t, "approved") _, err := service.StartSingleRequest(context.Background(), request) if !errors.Is(err, ErrSingleRequestWorkspaceUnavailable) { t.Fatalf("StartSingleRequest error = %v, want workspace unavailable", err) } if got := executor.calls.Load(); got != 0 { t.Fatalf("executor calls = %d, want 0", got) } }) } } func TestSingleRequestWorkspaceOperationSpecificLimits(t *testing.T) { tests := []struct { name string operations []config.WorkspaceOperation limits SingleRequestWorkspaceLimits commands []string valid bool }{ {"read", []config.WorkspaceOperation{config.WorkspaceOpRead}, SingleRequestWorkspaceLimits{MaxReadBytes: 1}, nil, true}, {"write", []config.WorkspaceOperation{config.WorkspaceOpWrite}, SingleRequestWorkspaceLimits{MaxWriteBytes: 1}, nil, true}, {"list", []config.WorkspaceOperation{config.WorkspaceOpList}, SingleRequestWorkspaceLimits{MaxOutputBytes: 1}, nil, true}, {"delete", []config.WorkspaceOperation{config.WorkspaceOpDelete}, SingleRequestWorkspaceLimits{}, nil, true}, {"command", []config.WorkspaceOperation{config.WorkspaceOpCommand}, SingleRequestWorkspaceLimits{MaxOutputBytes: 1, MaxCommandTimeoutMS: 1}, []string{"command"}, true}, {"read requires bound", []config.WorkspaceOperation{config.WorkspaceOpRead}, SingleRequestWorkspaceLimits{}, nil, false}, {"list requires output", []config.WorkspaceOperation{config.WorkspaceOpList}, SingleRequestWorkspaceLimits{}, nil, false}, {"command requires id", []config.WorkspaceOperation{config.WorkspaceOpCommand}, SingleRequestWorkspaceLimits{MaxOutputBytes: 1, MaxCommandTimeoutMS: 1}, nil, false}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { workspace := workspaceDefinition("approved", test.operations...) workspace.MaxReadBytes = test.limits.MaxReadBytes workspace.MaxWriteBytes = test.limits.MaxWriteBytes workspace.MaxOutputBytes = test.limits.MaxOutputBytes workspace.MaxCommandTimeoutMS = test.limits.MaxCommandTimeoutMS workspace.Commands = make([]config.WorkspaceCommandDefinition, len(test.commands)) for i, id := range test.commands { workspace.Commands[i] = config.WorkspaceCommandDefinition{ID: id} } _, err := compileSingleRequestWorkspaceBinding(workspace, &edgenode.NodeEntry{NodeID: "node-a", ConnectionGeneration: 1}) if test.valid && err != nil { t.Fatalf("compileSingleRequestWorkspaceBinding: %v", err) } if !test.valid && !errors.Is(err, errSingleRequestWorkspaceMalformed) { t.Fatalf("compileSingleRequestWorkspaceBinding error = %v, want malformed", err) } }) } } func TestSingleRequestWorkspaceAdmissionMatrix(t *testing.T) { approved := workspaceDefinition("approved", config.WorkspaceOpRead) for _, test := range []struct { name string setup func(*edgenode.Registry) *edgenode.NodeStore ref string }{ { name: "unapproved ref", setup: func(registry *edgenode.Registry) *edgenode.NodeStore { registry.Register(&edgenode.NodeEntry{NodeID: "node-a"}) return workspaceStore("node-a", approved) }, ref: "unapproved", }, { name: "foreign ready node is not a fallback", setup: func(registry *edgenode.Registry) *edgenode.NodeStore { registry.Register(&edgenode.NodeEntry{NodeID: "node-b"}) return workspaceStore("node-a", approved) }, ref: "approved", }, { name: "configured owner is pending", setup: func(registry *edgenode.Registry) *edgenode.NodeStore { registry.RegisterIfAbsent(&edgenode.NodeEntry{NodeID: "node-a"}) registry.Register(&edgenode.NodeEntry{NodeID: "node-b"}) return workspaceStore("node-a", approved) }, ref: "approved", }, { name: "malformed empty workspace operations", setup: func(registry *edgenode.Registry) *edgenode.NodeStore { registry.Register(&edgenode.NodeEntry{NodeID: "node-a"}) return workspaceStore("node-a", workspaceDefinition("approved")) }, ref: "approved", }, { name: "duplicate workspace operation", setup: func(registry *edgenode.Registry) *edgenode.NodeStore { registry.Register(&edgenode.NodeEntry{NodeID: "node-a"}) workspace := workspaceDefinition("approved", config.WorkspaceOpRead, config.WorkspaceOpRead) return workspaceStore("node-a", workspace) }, ref: "approved", }, { name: "unsupported workspace operation", setup: func(registry *edgenode.Registry) *edgenode.NodeStore { registry.Register(&edgenode.NodeEntry{NodeID: "node-a"}) workspace := workspaceDefinition("approved", config.WorkspaceOperation("unsupported")) return workspaceStore("node-a", workspace) }, ref: "approved", }, { name: "command without command operation", setup: func(registry *edgenode.Registry) *edgenode.NodeStore { registry.Register(&edgenode.NodeEntry{NodeID: "node-a"}) workspace := workspaceDefinition("approved", config.WorkspaceOpRead) workspace.Commands = []config.WorkspaceCommandDefinition{{ID: "test"}} return workspaceStore("node-a", workspace) }, ref: "approved", }, } { t.Run(test.name, func(t *testing.T) { registry := edgenode.NewRegistry() executor := newRecordingWorkspaceExecutor() service := New(registry, nil) service.SetNodeStore(test.setup(registry)) service.SetSingleRequestExecutor(executor) _, err := service.StartSingleRequest(context.Background(), workspaceRequest(t, test.ref)) if !errors.Is(err, ErrSingleRequestWorkspaceUnavailable) { t.Fatalf("StartSingleRequest error = %v, want unavailable", err) } if got := executor.calls.Load(); got != 0 { t.Fatalf("executor calls = %d, want 0", got) } }) } } func TestSingleRequestWorkspaceFreezesOwnerAcrossRefreshAndReconnect(t *testing.T) { workspace := workspaceDefinition("approved", config.WorkspaceOpCommand) executor := newRecordingWorkspaceExecutor() service, registry := readyWorkspaceService(t, "node-a", workspace, executor) handoff := make(chan struct{}) refreshDone := make(chan struct{}) actorDone := make(chan struct{}) service.beforeSingleRequestHandoff = func() { close(handoff) <-refreshDone } go func() { defer close(actorDone) <-handoff service.SetNodeStore(workspaceStore("node-b", workspaceDefinition("approved", config.WorkspaceOpRead))) close(refreshDone) }() if _, err := service.StartSingleRequest(context.Background(), workspaceRequest(t, "approved")); err != nil { t.Fatalf("StartSingleRequest: %v", err) } <-actorDone bound := executor.binding(t) if bound.Workspace.NodeID != "node-a" || bound.Workspace.ConnectionGeneration == 0 { t.Fatalf("executor binding = %#v, want original ready owner", bound.Workspace) } if len(bound.Workspace.CommandIDs) != 1 || bound.Workspace.CommandIDs[0] != "test" { t.Fatalf("executor binding lost frozen command capability: %#v", bound.Workspace) } if !registry.IsCurrentOwnerGeneration("node-a", bound.Workspace.ConnectionGeneration) { t.Fatal("refresh retargeted the admitted owner") } } func TestSingleRequestWorkspaceRejectsStaleGenerationBeforeExecutor(t *testing.T) { workspace := workspaceDefinition("approved", config.WorkspaceOpRead) executor := newRecordingWorkspaceExecutor() service, registry := readyWorkspaceService(t, "node-a", workspace, executor) handoff := make(chan struct{}) reconnectDone := make(chan struct{}) actorDone := make(chan struct{}) service.beforeSingleRequestHandoff = func() { close(handoff) <-reconnectDone } go func() { defer close(actorDone) <-handoff registry.Unregister("node-a") registry.Register(&edgenode.NodeEntry{NodeID: "node-a", Alias: "node-a"}) close(reconnectDone) }() _, err := service.StartSingleRequest(context.Background(), workspaceRequest(t, "approved")) <-actorDone if !errors.Is(err, ErrSingleRequestWorkspaceStale) { t.Fatalf("StartSingleRequest error = %v, want stale workspace", err) } if got := executor.calls.Load(); got != 0 { t.Fatalf("executor calls = %d, want 0", got) } } func TestSingleRequestWorkspaceAppliesPresetMinima(t *testing.T) { workspace := workspaceDefinition("approved", config.WorkspaceOpCommand) preset := createTestBinding(t).Limits workspace.MaxOutputBytes = preset.MaxOutputBytes + 1 workspace.MaxCommandTimeoutMS = preset.StageTimeoutMS + 1 executor := newRecordingWorkspaceExecutor() service, _ := readyWorkspaceService(t, "node-a", workspace, executor) if _, err := service.StartSingleRequest(context.Background(), workspaceRequest(t, "approved")); err != nil { t.Fatalf("StartSingleRequest: %v", err) } bound := executor.binding(t) if bound.Workspace.Limits.MaxOutputBytes != preset.MaxOutputBytes || bound.Workspace.Limits.MaxCommandTimeoutMS != preset.StageTimeoutMS { t.Fatalf("effective limits = %#v, want preset minima", bound.Workspace.Limits) } }