mirror of
https://github.com/hanzoai/agents.git
synced 2026-10-30 18:48:36 +00:00
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]>
546 lines
22 KiB
Go
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)
|
|
})
|
|
}
|