Files
zeekayandHanzo Dev 0a22533cda control-plane: route the HTTP surface on zip v1.10.0
Move the control-plane's REST/UI surface off gin's router and onto
github.com/zap-proto/zip v1.10.0 (over zap-proto/http v0.3.0). zip now owns
routing, the middleware chain and the transport; all 130 routes are registered
natively via App.Get/Post/Put/Patch/Delete and App.Group.

The route table is unchanged: 130 method+path pairs before and after, verified
by diff. The only spelling change is the wildcard leaf, "/ui/*filepath" ->
"/ui/*", which matches the same paths.

Handlers are fronted rather than rewritten. ~24k lines of gin handler bodies
are this service's public API and a hand port of that volume cannot be proven
equivalent, so internal/server/ginbridge.go provides the one seam:

  ginHandler(h)     wraps a gin.HandlerFunc, copying the matched zip route's
                    params onto the gin context so c.Param(...) still works.
  ginChain(mws...)  runs the gin middleware stack (CORS, access log, timeout,
                    API-key auth) unmodified, so CORS origin echoing and the
                    auth abort bodies stay byte-for-byte identical.

The net/http adaptor underneath supports buffered, flushed and hijacked
responses, so the SSE and WebSocket routes keep streaming through the same
seam rather than being rewritten badly.

Two things that would otherwise silently break the API:

  - Start listens on "http://:PORT". A bare address binds zip's default ZAP
    transport, which would change the wire protocol of the REST surface.
  - The NoRoute fallback is All("/+"), not All("/*"). "/*" also matches "/"
    and shadowed the root redirect, turning it into a 404.

Coverage: routes_test.go pins the exact 130-route table and the NoRoute
semantics; ginbridge_test.go pins params, status, body, CORS echo, auth abort
and SSE through the bridge. Both pass under the default and "embedded" build
tags. A package TestMain sets gin's mode once, removing the pre-existing
gin.SetMode data race between parallel tests in this package.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-13 11:48:06 -07:00

546 lines
22 KiB
Go

package server
import (
"context"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/hanzoai/agents/control-plane/internal/config"
"github.com/hanzoai/agents/control-plane/internal/events"
"github.com/hanzoai/agents/control-plane/internal/services"
"github.com/hanzoai/agents/control-plane/internal/storage"
"github.com/hanzoai/agents/control-plane/pkg/types"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
"github.com/zap-proto/fiber/v3"
"github.com/zap-proto/zip"
)
// stubStorage implements storage.StorageProvider with minimal functionality for testing
type stubStorage struct {
eventBus *events.ExecutionEventBus
}
func newStubStorage() *stubStorage {
return &stubStorage{
eventBus: events.NewExecutionEventBus(),
}
}
// Required methods for ExecuteHandler
func (s *stubStorage) GetAgent(ctx context.Context, id string) (*types.AgentNode, error) {
return nil, nil
}
func (s *stubStorage) CreateExecutionRecord(ctx context.Context, execution *types.Execution) error {
return nil
}
func (s *stubStorage) GetExecutionRecord(ctx context.Context, executionID string) (*types.Execution, error) {
return nil, nil
}
func (s *stubStorage) UpdateExecutionRecord(ctx context.Context, executionID string, update func(*types.Execution) (*types.Execution, error)) (*types.Execution, error) {
return nil, nil
}
func (s *stubStorage) QueryExecutionRecords(ctx context.Context, filter types.ExecutionFilter) ([]*types.Execution, error) {
return nil, nil
}
func (s *stubStorage) RegisterExecutionWebhook(ctx context.Context, webhook *types.ExecutionWebhook) error {
return nil
}
func (s *stubStorage) StoreWorkflowExecution(ctx context.Context, execution *types.WorkflowExecution) error {
return nil
}
func (s *stubStorage) UpdateWorkflowExecution(ctx context.Context, executionID string, updateFunc func(*types.WorkflowExecution) (*types.WorkflowExecution, error)) error {
return nil
}
func (s *stubStorage) GetWorkflowExecution(ctx context.Context, executionID string) (*types.WorkflowExecution, error) {
return nil, nil
}
func (s *stubStorage) GetExecutionEventBus() *events.ExecutionEventBus {
return s.eventBus
}
// Stub implementations for remaining StorageProvider methods
func (s *stubStorage) Initialize(ctx context.Context, config storage.StorageConfig) error { return nil }
func (s *stubStorage) Close(ctx context.Context) error { return nil }
func (s *stubStorage) HealthCheck(ctx context.Context) error { return nil }
func (s *stubStorage) StoreExecution(ctx context.Context, execution *types.AgentExecution) error {
return nil
}
func (s *stubStorage) GetExecution(ctx context.Context, id int64) (*types.AgentExecution, error) {
return nil, nil
}
func (s *stubStorage) QueryExecutions(ctx context.Context, filters types.ExecutionFilters) ([]*types.AgentExecution, error) {
return nil, nil
}
func (s *stubStorage) QueryWorkflowExecutions(ctx context.Context, filters types.WorkflowExecutionFilters) ([]*types.WorkflowExecution, error) {
return nil, nil
}
func (s *stubStorage) QueryRunSummaries(ctx context.Context, filter types.ExecutionFilter) ([]*storage.RunSummaryAggregation, int, error) {
return nil, 0, nil
}
func (s *stubStorage) GetExecutionWebhook(ctx context.Context, executionID string) (*types.ExecutionWebhook, error) {
return nil, nil
}
func (s *stubStorage) ListDueExecutionWebhooks(ctx context.Context, limit int) ([]*types.ExecutionWebhook, error) {
return nil, nil
}
func (s *stubStorage) TryMarkExecutionWebhookInFlight(ctx context.Context, executionID string, now time.Time) (bool, error) {
return false, nil
}
func (s *stubStorage) UpdateExecutionWebhookState(ctx context.Context, executionID string, update types.ExecutionWebhookStateUpdate) error {
return nil
}
func (s *stubStorage) HasExecutionWebhook(ctx context.Context, executionID string) (bool, error) {
return false, nil
}
func (s *stubStorage) ListExecutionWebhooksRegistered(ctx context.Context, executionIDs []string) (map[string]bool, error) {
return nil, nil
}
func (s *stubStorage) StoreExecutionWebhookEvent(ctx context.Context, event *types.ExecutionWebhookEvent) error {
return nil
}
func (s *stubStorage) ListExecutionWebhookEvents(ctx context.Context, executionID string) ([]*types.ExecutionWebhookEvent, error) {
return nil, nil
}
func (s *stubStorage) ListExecutionWebhookEventsBatch(ctx context.Context, executionIDs []string) (map[string][]*types.ExecutionWebhookEvent, error) {
return nil, nil
}
func (s *stubStorage) StoreWorkflowExecutionEvent(ctx context.Context, event *types.WorkflowExecutionEvent) error {
return nil
}
func (s *stubStorage) ListWorkflowExecutionEvents(ctx context.Context, executionID string, afterSeq *int64, limit int) ([]*types.WorkflowExecutionEvent, error) {
return nil, nil
}
func (s *stubStorage) CleanupOldExecutions(ctx context.Context, retentionPeriod time.Duration, batchSize int) (int, error) {
return 0, nil
}
func (s *stubStorage) MarkStaleExecutions(ctx context.Context, staleAfter time.Duration, limit int) (int, error) {
return 0, nil
}
func (s *stubStorage) CleanupWorkflow(ctx context.Context, workflowID string, dryRun bool) (*types.WorkflowCleanupResult, error) {
return &types.WorkflowCleanupResult{
Success: true,
WorkflowID: workflowID,
DryRun: dryRun,
DeletedRecords: map[string]int{"total": 0},
}, nil
}
func (s *stubStorage) QueryWorkflowDAG(ctx context.Context, rootWorkflowID string) ([]*types.WorkflowExecution, error) {
return nil, nil
}
func (s *stubStorage) CreateOrUpdateWorkflow(ctx context.Context, workflow *types.Workflow) error {
return nil
}
func (s *stubStorage) GetWorkflow(ctx context.Context, workflowID string) (*types.Workflow, error) {
return nil, nil
}
func (s *stubStorage) QueryWorkflows(ctx context.Context, filters types.WorkflowFilters) ([]*types.Workflow, error) {
return nil, nil
}
func (s *stubStorage) CreateOrUpdateSession(ctx context.Context, session *types.Session) error {
return nil
}
func (s *stubStorage) GetSession(ctx context.Context, sessionID string) (*types.Session, error) {
return nil, nil
}
func (s *stubStorage) QuerySessions(ctx context.Context, filters types.SessionFilters) ([]*types.Session, error) {
return nil, nil
}
// Memory operations
func (s *stubStorage) SetMemory(ctx context.Context, memory *types.Memory) error { return nil }
func (s *stubStorage) GetMemory(ctx context.Context, scope, scopeID, key string) (*types.Memory, error) {
return nil, nil
}
func (s *stubStorage) DeleteMemory(ctx context.Context, scope, scopeID, key string) error { return nil }
func (s *stubStorage) ListMemory(ctx context.Context, scope, scopeID string) ([]*types.Memory, error) {
return nil, nil
}
func (s *stubStorage) SetVector(ctx context.Context, record *types.VectorRecord) error { return nil }
func (s *stubStorage) GetVector(ctx context.Context, scope, scopeID, key string) (*types.VectorRecord, error) {
return nil, nil
}
func (s *stubStorage) DeleteVector(ctx context.Context, scope, scopeID, key string) error { return nil }
func (s *stubStorage) DeleteVectorsByPrefix(ctx context.Context, scope, scopeID, prefix string) (int, error) {
return 0, nil
}
func (s *stubStorage) SimilaritySearch(ctx context.Context, scope, scopeID string, queryEmbedding []float32, topK int, filters map[string]interface{}) ([]*types.VectorSearchResult, error) {
return nil, nil
}
// Event operations
func (s *stubStorage) StoreEvent(ctx context.Context, event *types.MemoryChangeEvent) error {
return nil
}
func (s *stubStorage) GetEventHistory(ctx context.Context, filter types.EventFilter) ([]*types.MemoryChangeEvent, error) {
return nil, nil
}
// Distributed Lock operations
func (s *stubStorage) AcquireLock(ctx context.Context, key string, timeout time.Duration) (*types.DistributedLock, error) {
return nil, nil
}
func (s *stubStorage) ReleaseLock(ctx context.Context, lockID string) error { return nil }
func (s *stubStorage) RenewLock(ctx context.Context, lockID string) (*types.DistributedLock, error) {
return nil, nil
}
func (s *stubStorage) GetLockStatus(ctx context.Context, key string) (*types.DistributedLock, error) {
return nil, nil
}
// Agent registry
func (s *stubStorage) RegisterAgent(ctx context.Context, agent *types.AgentNode) error { return nil }
func (s *stubStorage) ListAgents(ctx context.Context, filters types.AgentFilters) ([]*types.AgentNode, error) {
return nil, nil
}
func (s *stubStorage) UpdateAgentHealth(ctx context.Context, id string, status types.HealthStatus) error {
return nil
}
func (s *stubStorage) UpdateAgentHealthAtomic(ctx context.Context, id string, status types.HealthStatus, expectedLastHeartbeat *time.Time) error {
return nil
}
func (s *stubStorage) UpdateAgentHeartbeat(ctx context.Context, id string, heartbeatTime time.Time) error {
return nil
}
func (s *stubStorage) UpdateAgentLifecycleStatus(ctx context.Context, id string, status types.AgentLifecycleStatus) error {
return nil
}
// Configuration
func (s *stubStorage) SetConfig(ctx context.Context, key string, value interface{}) error { return nil }
func (s *stubStorage) GetConfig(ctx context.Context, key string) (interface{}, error) {
return nil, nil
}
// Reasoner Performance and History
func (s *stubStorage) GetReasonerPerformanceMetrics(ctx context.Context, reasonerID string) (*types.ReasonerPerformanceMetrics, error) {
return nil, nil
}
func (s *stubStorage) GetReasonerExecutionHistory(ctx context.Context, reasonerID string, page, limit int) (*types.ReasonerExecutionHistory, error) {
return nil, nil
}
// Agent Configuration Management
func (s *stubStorage) StoreAgentConfiguration(ctx context.Context, config *types.AgentConfiguration) error {
return nil
}
func (s *stubStorage) GetAgentConfiguration(ctx context.Context, agentID, packageID string) (*types.AgentConfiguration, error) {
return nil, nil
}
func (s *stubStorage) QueryAgentConfigurations(ctx context.Context, filters types.ConfigurationFilters) ([]*types.AgentConfiguration, error) {
return nil, nil
}
func (s *stubStorage) UpdateAgentConfiguration(ctx context.Context, config *types.AgentConfiguration) error {
return nil
}
func (s *stubStorage) DeleteAgentConfiguration(ctx context.Context, agentID, packageID string) error {
return nil
}
func (s *stubStorage) ValidateAgentConfiguration(ctx context.Context, agentID, packageID string, config map[string]interface{}) (*types.ConfigurationValidationResult, error) {
return nil, nil
}
// Agent Package Management
func (s *stubStorage) StoreAgentPackage(ctx context.Context, pkg *types.AgentPackage) error {
return nil
}
func (s *stubStorage) GetAgentPackage(ctx context.Context, packageID string) (*types.AgentPackage, error) {
return nil, nil
}
func (s *stubStorage) QueryAgentPackages(ctx context.Context, filters types.PackageFilters) ([]*types.AgentPackage, error) {
return nil, nil
}
func (s *stubStorage) UpdateAgentPackage(ctx context.Context, pkg *types.AgentPackage) error {
return nil
}
func (s *stubStorage) DeleteAgentPackage(ctx context.Context, packageID string) error { return nil }
// Real-time features
func (s *stubStorage) SubscribeToMemoryChanges(ctx context.Context, scope, scopeID string) (<-chan types.MemoryChangeEvent, error) {
return nil, nil
}
func (s *stubStorage) PublishMemoryChange(ctx context.Context, event types.MemoryChangeEvent) error {
return nil
}
func (s *stubStorage) GetWorkflowExecutionEventBus() *events.EventBus[*types.WorkflowExecutionEvent] {
return nil
}
// DID Registry operations
func (s *stubStorage) StoreDID(ctx context.Context, did string, didDocument, publicKey, privateKeyRef, derivationPath string) error {
return nil
}
func (s *stubStorage) GetDID(ctx context.Context, did string) (*types.DIDRegistryEntry, error) {
return nil, nil
}
func (s *stubStorage) ListDIDs(ctx context.Context) ([]*types.DIDRegistryEntry, error) {
return nil, nil
}
// HanzoAgents Server DID operations
func (s *stubStorage) StoreHanzoAgentsServerDID(ctx context.Context, hanzoAgentsServerID, rootDID string, masterSeed []byte, createdAt, lastKeyRotation time.Time) error {
return nil
}
func (s *stubStorage) GetHanzoAgentsServerDID(ctx context.Context, hanzoAgentsServerID string) (*types.HanzoAgentsServerDIDInfo, error) {
return nil, nil
}
func (s *stubStorage) ListHanzoAgentsServerDIDs(ctx context.Context) ([]*types.HanzoAgentsServerDIDInfo, error) {
return nil, nil
}
// Agent DID operations
func (s *stubStorage) StoreAgentDID(ctx context.Context, agentID, agentDID, hanzoAgentsServerDID, publicKeyJWK string, derivationIndex int) error {
return nil
}
func (s *stubStorage) GetAgentDID(ctx context.Context, agentID string) (*types.AgentDIDInfo, error) {
return nil, nil
}
func (s *stubStorage) ListAgentDIDs(ctx context.Context) ([]*types.AgentDIDInfo, error) {
return nil, nil
}
// Component DID operations
func (s *stubStorage) StoreComponentDID(ctx context.Context, componentID, componentDID, agentDID, componentType, componentName string, derivationIndex int) error {
return nil
}
func (s *stubStorage) GetComponentDID(ctx context.Context, componentID string) (*types.ComponentDIDInfo, error) {
return nil, nil
}
func (s *stubStorage) ListComponentDIDs(ctx context.Context, agentDID string) ([]*types.ComponentDIDInfo, error) {
return nil, nil
}
// Multi-step DID operations
func (s *stubStorage) StoreAgentDIDWithComponents(ctx context.Context, agentID, agentDID, hanzoAgentsServerDID, publicKeyJWK string, derivationIndex int, components []storage.ComponentDIDRequest) error {
return nil
}
// Execution VC operations
func (s *stubStorage) StoreExecutionVC(ctx context.Context, vcID, executionID, workflowID, sessionID, issuerDID, targetDID, callerDID, inputHash, outputHash, status string, vcDocument []byte, signature string, storageURI string, documentSizeBytes int64) error {
return nil
}
func (s *stubStorage) GetExecutionVC(ctx context.Context, vcID string) (*types.ExecutionVCInfo, error) {
return nil, nil
}
func (s *stubStorage) ListExecutionVCs(ctx context.Context, filters types.VCFilters) ([]*types.ExecutionVCInfo, error) {
return nil, nil
}
func (s *stubStorage) ListWorkflowVCStatusSummaries(ctx context.Context, workflowIDs []string) ([]*types.WorkflowVCStatusAggregation, error) {
return nil, nil
}
// Workflow VC operations
func (s *stubStorage) StoreWorkflowVC(ctx context.Context, workflowVCID, workflowID, sessionID string, componentVCIDs []string, status string, startTime, endTime *time.Time, totalSteps, completedSteps int, storageURI string, documentSizeBytes int64) error {
return nil
}
func (s *stubStorage) GetWorkflowVC(ctx context.Context, workflowVCID string) (*types.WorkflowVCInfo, error) {
return nil, nil
}
func (s *stubStorage) ListWorkflowVCs(ctx context.Context, workflowID string) ([]*types.WorkflowVCInfo, error) {
return nil, nil
}
func (s *stubStorage) CountExecutionVCs(ctx context.Context, filters types.VCFilters) (int, error) {
return 0, nil
}
// Observability webhook operations
func (s *stubStorage) GetObservabilityWebhook(ctx context.Context) (*types.ObservabilityWebhookConfig, error) {
return nil, nil
}
func (s *stubStorage) SetObservabilityWebhook(ctx context.Context, config *types.ObservabilityWebhookConfig) error {
return nil
}
func (s *stubStorage) DeleteObservabilityWebhook(ctx context.Context) error { return nil }
// Dead Letter Queue operations
func (s *stubStorage) AddToDeadLetterQueue(ctx context.Context, event *types.ObservabilityEvent, errorMessage string, retryCount int) error {
return nil
}
func (s *stubStorage) GetDeadLetterQueueCount(ctx context.Context) (int64, error) { return 0, nil }
func (s *stubStorage) GetDeadLetterQueue(ctx context.Context, limit, offset int) ([]types.ObservabilityDeadLetterEntry, error) {
return nil, nil
}
func (s *stubStorage) DeleteFromDeadLetterQueue(ctx context.Context, ids []int64) error { return nil }
func (s *stubStorage) ClearDeadLetterQueue(ctx context.Context) error { return nil }
// stubPayloadStore implements services.PayloadStore
type stubPayloadStore struct{}
func (s *stubPayloadStore) SaveFromReader(ctx context.Context, r io.Reader) (*services.PayloadRecord, error) {
return nil, nil
}
func (s *stubPayloadStore) SaveBytes(ctx context.Context, data []byte) (*services.PayloadRecord, error) {
return nil, nil
}
func (s *stubPayloadStore) Open(ctx context.Context, uri string) (io.ReadCloser, error) {
return nil, nil
}
func (s *stubPayloadStore) Remove(ctx context.Context, uri string) error {
return nil
}
// stubWebhookDispatcher implements services.WebhookDispatcher
type stubWebhookDispatcher struct{}
func (s *stubWebhookDispatcher) Start(ctx context.Context) error {
return nil
}
func (s *stubWebhookDispatcher) Stop(ctx context.Context) error {
return nil
}
func (s *stubWebhookDispatcher) Notify(ctx context.Context, executionID string) error {
return nil
}
// newTestServer builds a server wired to stubs, with routes not yet set up.
func newTestServer(ui config.UIConfig, api config.APIConfig) *HanzoAgentsServer {
return &HanzoAgentsServer{
App: zip.New(zip.Config{DisableStartupMessage: true}),
storage: newStubStorage(),
payloadStore: &stubPayloadStore{},
webhookDispatcher: &stubWebhookDispatcher{},
config: &config.Config{UI: ui, API: api},
}
}
// doReq drives a request through the zip app and returns status, headers, body.
func doReq(t *testing.T, srv *HanzoAgentsServer, method, target string, hdr map[string]string) (int, http.Header, string) {
t.Helper()
req := httptest.NewRequest(method, target, nil)
for k, v := range hdr {
req.Header.Set(k, v)
}
resp, err := srv.App.Fiber().Test(req, fiber.TestConfig{Timeout: 30 * time.Second, FailOnTimeout: true})
require.NoError(t, err)
defer func() { _ = resp.Body.Close() }()
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
return resp.StatusCode, resp.Header, string(body)
}
func TestSetupRoutesRegistersMetricsAndUI(t *testing.T) {
t.Parallel()
srv := newTestServer(config.UIConfig{Enabled: true, Mode: "embedded"}, config.APIConfig{})
srv.setupRoutes()
t.Run("metrics endpoint", func(t *testing.T) {
code, hdr, _ := doReq(t, srv, http.MethodGet, "/metrics", map[string]string{"Origin": "http://localhost:5173"})
require.Equal(t, http.StatusOK, code)
require.Equal(t, "http://localhost:5173", hdr.Get("Access-Control-Allow-Origin"))
})
t.Run("root redirect", func(t *testing.T) {
code, hdr, _ := doReq(t, srv, http.MethodGet, "/", nil)
require.Equal(t, http.StatusMovedPermanently, code)
require.Equal(t, "/ui/", hdr.Get("Location"))
})
}
func TestSetupRoutesRegistersWorkflowCleanupUIRoute(t *testing.T) {
t.Parallel()
srv := newTestServer(config.UIConfig{Enabled: true, Mode: "embedded"}, config.APIConfig{})
srv.setupRoutes()
code, _, body := doReq(t, srv, http.MethodDelete, "/v1/ui/workflows/run_test_123/cleanup?confirm=true", nil)
require.Equal(t, http.StatusOK, code)
require.Contains(t, body, `"success":true`)
require.Contains(t, body, `"workflow_id":"run_test_123"`)
}
func TestSetupRoutesRegistersHealthEndpoint(t *testing.T) {
t.Parallel()
healthy := func(api config.APIConfig) *HanzoAgentsServer {
srv := newTestServer(config.UIConfig{Enabled: false}, api)
srv.storageHealthOverride = func(context.Context) gin.H { return gin.H{"status": "healthy"} }
return srv
}
t.Run("health endpoint returns healthy status", func(t *testing.T) {
srv := healthy(config.APIConfig{})
srv.setupRoutes()
code, _, body := doReq(t, srv, http.MethodGet, "/health", nil)
require.Equal(t, http.StatusOK, code)
require.Contains(t, body, "healthy")
})
t.Run("health endpoint accessible without API key", func(t *testing.T) {
srv := healthy(config.APIConfig{
Auth: config.AuthConfig{APIKey: "super-secret-key"},
})
srv.setupRoutes()
// Request without API key should still succeed for /health
code, _, _ := doReq(t, srv, http.MethodGet, "/health", nil)
require.Equal(t, http.StatusOK, code)
})
t.Run("health endpoint returns CORS headers", func(t *testing.T) {
srv := healthy(config.APIConfig{})
srv.setupRoutes()
code, hdr, _ := doReq(t, srv, http.MethodGet, "/health", map[string]string{"Origin": "http://localhost:3000"})
require.Equal(t, http.StatusOK, code)
require.Equal(t, "http://localhost:3000", hdr.Get("Access-Control-Allow-Origin"))
})
}
//nolint:unused // Reserved for future test cases
type stubHealthMonitor struct {
*services.HealthMonitor
}
func TestUnregisterAgentFromMonitoringResponses(t *testing.T) {
t.Parallel()
srv := &HanzoAgentsServer{}
t.Run("missing node id returns 400", func(t *testing.T) {
req, _ := http.NewRequest(http.MethodDelete, "/internal/nodes//monitor", nil)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = append(c.Params, gin.Param{Key: "node_id", Value: ""})
c.Request = req
srv.unregisterAgentFromMonitoring(c)
require.Equal(t, http.StatusBadRequest, w.Code)
})
t.Run("health monitor unavailable", func(t *testing.T) {
req, _ := http.NewRequest(http.MethodDelete, "/internal/nodes/node-1/monitor", nil)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = append(c.Params, gin.Param{Key: "node_id", Value: "node-1"})
c.Request = req
srv.unregisterAgentFromMonitoring(c)
require.Equal(t, http.StatusServiceUnavailable, w.Code)
})
t.Run("successful unregister", func(t *testing.T) {
hm := services.NewHealthMonitor(nil, services.HealthMonitorConfig{}, nil, nil, nil, nil)
req, _ := http.NewRequest(http.MethodDelete, "/internal/nodes/node-42/monitor", nil)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = append(c.Params, gin.Param{Key: "node_id", Value: "node-42"})
c.Request = req
srv.healthMonitor = hm
srv.unregisterAgentFromMonitoring(c)
require.Equal(t, http.StatusOK, w.Code)
})
}