package server import ( "context" "net/http" "net/http/httptest" "testing" "time" "git.dcentral.systems/toolz/goplt/internal/health" "git.dcentral.systems/toolz/goplt/internal/metrics" "github.com/gin-gonic/gin" "go.opentelemetry.io/otel/trace/noop" ) func TestNewServer(t *testing.T) { gin.SetMode(gin.TestMode) mockConfig := &mockConfigProvider{ values: map[string]any{ "environment": "test", "server.port": 8080, "server.host": "127.0.0.1", "server.read_timeout": "30s", "server.write_timeout": "30s", }, } mockLogger := &mockLogger{} healthRegistry := health.NewRegistry() metricsRegistry := metrics.NewMetrics() mockErrorBus := &mockErrorBusServer{} tracer := noop.NewTracerProvider() srv, err := NewServer(mockConfig, mockLogger, healthRegistry, metricsRegistry, mockErrorBus, tracer) if err != nil { t.Fatalf("Failed to create server: %v", err) } if srv == nil { t.Fatal("Expected server, got nil") } if srv.httpServer == nil { t.Error("Expected http server, got nil") } if srv.router == nil { t.Error("Expected router, got nil") } } func TestNewServer_DefaultValues(t *testing.T) { gin.SetMode(gin.TestMode) mockConfig := &mockConfigProvider{ values: map[string]any{ "environment": "test", }, } mockLogger := &mockLogger{} healthRegistry := health.NewRegistry() metricsRegistry := metrics.NewMetrics() mockErrorBus := &mockErrorBusServer{} tracer := noop.NewTracerProvider() srv, err := NewServer(mockConfig, mockLogger, healthRegistry, metricsRegistry, mockErrorBus, tracer) if err != nil { t.Fatalf("Failed to create server: %v", err) } if srv.httpServer.Addr != "0.0.0.0:8080" { t.Errorf("Expected default address 0.0.0.0:8080, got %s", srv.httpServer.Addr) } } func TestServer_Router(t *testing.T) { gin.SetMode(gin.TestMode) mockConfig := &mockConfigProvider{ values: map[string]any{ "environment": "test", }, } mockLogger := &mockLogger{} healthRegistry := health.NewRegistry() metricsRegistry := metrics.NewMetrics() mockErrorBus := &mockErrorBusServer{} tracer := noop.NewTracerProvider() srv, err := NewServer(mockConfig, mockLogger, healthRegistry, metricsRegistry, mockErrorBus, tracer) if err != nil { t.Fatalf("Failed to create server: %v", err) } router := srv.Router() if router == nil { t.Error("Expected router, got nil") } } func TestServer_Shutdown(t *testing.T) { gin.SetMode(gin.TestMode) mockConfig := &mockConfigProvider{ values: map[string]any{ "environment": "test", "server.port": 0, // Use random port }, } mockLogger := &mockLogger{} healthRegistry := health.NewRegistry() metricsRegistry := metrics.NewMetrics() mockErrorBus := &mockErrorBusServer{} tracer := noop.NewTracerProvider() srv, err := NewServer(mockConfig, mockLogger, healthRegistry, metricsRegistry, mockErrorBus, tracer) if err != nil { t.Fatalf("Failed to create server: %v", err) } // Start server in background go func() { _ = srv.Start() }() // Wait a bit for server to start time.Sleep(100 * time.Millisecond) // Shutdown ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { t.Errorf("Shutdown failed: %v", err) } } func TestServer_HealthEndpoints(t *testing.T) { gin.SetMode(gin.TestMode) mockConfig := &mockConfigProvider{ values: map[string]any{ "environment": "test", }, } mockLogger := &mockLogger{} healthRegistry := health.NewRegistry() metricsRegistry := metrics.NewMetrics() mockErrorBus := &mockErrorBusServer{} tracer := noop.NewTracerProvider() srv, err := NewServer(mockConfig, mockLogger, healthRegistry, metricsRegistry, mockErrorBus, tracer) if err != nil { t.Fatalf("Failed to create server: %v", err) } // Test /healthz endpoint req := httptest.NewRequest(http.MethodGet, "/healthz", nil) w := httptest.NewRecorder() srv.router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status 200 for /healthz, got %d", w.Code) } // Test /ready endpoint req = httptest.NewRequest(http.MethodGet, "/ready", nil) w = httptest.NewRecorder() srv.router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status 200 for /ready, got %d", w.Code) } } func TestServer_MetricsEndpoint(t *testing.T) { gin.SetMode(gin.TestMode) mockConfig := &mockConfigProvider{ values: map[string]any{ "environment": "test", }, } mockLogger := &mockLogger{} healthRegistry := health.NewRegistry() metricsRegistry := metrics.NewMetrics() mockErrorBus := &mockErrorBusServer{} tracer := noop.NewTracerProvider() srv, err := NewServer(mockConfig, mockLogger, healthRegistry, metricsRegistry, mockErrorBus, tracer) if err != nil { t.Fatalf("Failed to create server: %v", err) } req := httptest.NewRequest(http.MethodGet, "/metrics", nil) w := httptest.NewRecorder() srv.router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status 200 for /metrics, got %d", w.Code) } // Prometheus handler may return empty body if no metrics are recorded yet // This is acceptable - we just verify the endpoint works _ = w.Body.String() } // mockConfigProvider implements config.ConfigProvider for testing. type mockConfigProvider struct { values map[string]any } func (m *mockConfigProvider) Get(key string) any { return m.values[key] } func (m *mockConfigProvider) GetString(key string) string { if val, ok := m.values[key].(string); ok { return val } if val, ok := m.values[key]; ok { return val.(string) } return "" } func (m *mockConfigProvider) GetInt(key string) int { if val, ok := m.values[key].(int); ok { return val } return 0 } func (m *mockConfigProvider) GetBool(key string) bool { if val, ok := m.values[key].(bool); ok { return val } return false } func (m *mockConfigProvider) GetDuration(key string) time.Duration { if val, ok := m.values[key].(string); ok { dur, err := time.ParseDuration(val) if err == nil { return dur } } if val, ok := m.values[key].(time.Duration); ok { return val } return 0 } func (m *mockConfigProvider) GetStringSlice(key string) []string { if val, ok := m.values[key].([]string); ok { return val } return nil } func (m *mockConfigProvider) IsSet(key string) bool { _, ok := m.values[key] return ok } func (m *mockConfigProvider) Unmarshal(v any) error { return nil } // Note: mockLogger and mockErrorBusMiddleware are defined in middleware_test.go // We use mockErrorBusServer here to avoid conflicts type mockErrorBusServer struct { errors []error ctxs []context.Context } func (m *mockErrorBusServer) Publish(ctx context.Context, err error) { m.errors = append(m.errors, err) m.ctxs = append(m.ctxs, ctx) }