Story 1.2: Database Layer - Test database client creation, connection, ping, and close - Test connection pooling configuration - Tests skip if database is not available (short mode) Story 1.3: Health Monitoring and Metrics - Test health registry registration and checking - Test database health checker - Test liveness and readiness checks - Test metrics creation, middleware, and handler - Test Prometheus metrics endpoint Story 1.4: Error Handling and Error Bus - Test channel-based error bus creation - Test error publishing with context - Test nil error handling - Test channel full scenario - Test graceful shutdown - Fix Close() method to handle multiple calls safely Story 1.5: HTTP Server and Middleware - Test server creation with all middleware - Test request ID middleware - Test logging middleware - Test panic recovery middleware - Test CORS middleware - Test timeout middleware - Test health and metrics endpoints - Test server shutdown Story 1.6: OpenTelemetry Tracing - Test tracer initialization (enabled/disabled) - Test development and production modes - Test OTLP exporter configuration - Test graceful shutdown - Test no-op tracer provider All tests follow Go testing best practices: - Table-driven tests where appropriate - Parallel execution - Proper mocking of interfaces - Skip tests requiring external dependencies in short mode
200 lines
4.0 KiB
Go
200 lines
4.0 KiB
Go
package errorbus
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
)
|
|
|
|
func TestNewChannelBus(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mockLogger := &mockLogger{}
|
|
bus := NewChannelBus(mockLogger, 100)
|
|
|
|
if bus == nil {
|
|
t.Fatal("Expected bus, got nil")
|
|
}
|
|
|
|
if bus.errors == nil {
|
|
t.Error("Expected errors channel, got nil")
|
|
}
|
|
|
|
if bus.logger == nil {
|
|
t.Error("Expected logger, got nil")
|
|
}
|
|
|
|
// Clean up
|
|
_ = bus.Close()
|
|
}
|
|
|
|
func TestNewChannelBus_DefaultBufferSize(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mockLogger := &mockLogger{}
|
|
bus := NewChannelBus(mockLogger, 0)
|
|
|
|
if bus == nil {
|
|
t.Fatal("Expected bus, got nil")
|
|
}
|
|
|
|
// Clean up
|
|
_ = bus.Close()
|
|
}
|
|
|
|
func TestChannelBus_Publish(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mockLogger := &mockLogger{}
|
|
bus := NewChannelBus(mockLogger, 10)
|
|
|
|
testErr := errors.New("test error")
|
|
ctx := context.Background()
|
|
|
|
// Publish error
|
|
bus.Publish(ctx, testErr)
|
|
|
|
// Wait a bit for the error to be processed
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Verify error was logged
|
|
if len(mockLogger.errors) == 0 {
|
|
t.Error("Expected error to be logged")
|
|
}
|
|
|
|
// Clean up
|
|
_ = bus.Close()
|
|
}
|
|
|
|
func TestChannelBus_Publish_NilError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mockLogger := &mockLogger{}
|
|
bus := NewChannelBus(mockLogger, 10)
|
|
|
|
ctx := context.Background()
|
|
|
|
// Publish nil error (should be ignored)
|
|
bus.Publish(ctx, nil)
|
|
|
|
// Wait a bit
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Verify nil error was not logged
|
|
if len(mockLogger.errors) > 0 {
|
|
t.Error("Expected nil error to be ignored")
|
|
}
|
|
|
|
// Clean up
|
|
_ = bus.Close()
|
|
}
|
|
|
|
func TestChannelBus_Publish_WithContext(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mockLogger := &mockLogger{}
|
|
bus := NewChannelBus(mockLogger, 10)
|
|
|
|
testErr := errors.New("test error")
|
|
ctx := context.WithValue(context.Background(), "request_id", "test-request-id")
|
|
|
|
bus.Publish(ctx, testErr)
|
|
|
|
// Wait for processing
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Verify error was logged with context
|
|
if len(mockLogger.errors) == 0 {
|
|
t.Error("Expected error to be logged")
|
|
}
|
|
|
|
// Clean up
|
|
_ = bus.Close()
|
|
}
|
|
|
|
func TestChannelBus_Close(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mockLogger := &mockLogger{}
|
|
bus := NewChannelBus(mockLogger, 10)
|
|
|
|
// Publish some errors
|
|
for i := 0; i < 5; i++ {
|
|
bus.Publish(context.Background(), errors.New("test error"))
|
|
}
|
|
|
|
// Close and wait
|
|
if err := bus.Close(); err != nil {
|
|
t.Errorf("Close failed: %v", err)
|
|
}
|
|
|
|
// Verify channel is closed
|
|
select {
|
|
case <-bus.errors:
|
|
// Channel is closed, this is expected
|
|
default:
|
|
t.Error("Expected errors channel to be closed")
|
|
}
|
|
}
|
|
|
|
func TestChannelBus_Close_MultipleTimes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mockLogger := &mockLogger{}
|
|
bus := NewChannelBus(mockLogger, 10)
|
|
|
|
// Close first time
|
|
if err := bus.Close(); err != nil {
|
|
t.Errorf("First Close failed: %v", err)
|
|
}
|
|
|
|
// Close second time should be safe (uses sync.Once)
|
|
// The channel is already closed, but Close() should handle this gracefully
|
|
if err := bus.Close(); err != nil {
|
|
t.Errorf("Second Close failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestChannelBus_ChannelFull(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mockLogger := &mockLogger{}
|
|
// Use small buffer to test channel full scenario
|
|
bus := NewChannelBus(mockLogger, 1)
|
|
|
|
// Fill the channel
|
|
bus.Publish(context.Background(), errors.New("error1"))
|
|
|
|
// This should not block (channel is full, should log directly)
|
|
bus.Publish(context.Background(), errors.New("error2"))
|
|
|
|
// Wait a bit
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Clean up
|
|
_ = bus.Close()
|
|
}
|
|
|
|
// mockLogger implements logger.Logger for testing.
|
|
type mockLogger struct {
|
|
errors []string
|
|
}
|
|
|
|
func (m *mockLogger) Debug(msg string, fields ...logger.Field) {}
|
|
func (m *mockLogger) Info(msg string, fields ...logger.Field) {}
|
|
func (m *mockLogger) Warn(msg string, fields ...logger.Field) {}
|
|
func (m *mockLogger) Error(msg string, fields ...logger.Field) {
|
|
m.errors = append(m.errors, msg)
|
|
}
|
|
|
|
func (m *mockLogger) With(fields ...logger.Field) logger.Logger {
|
|
return m
|
|
}
|
|
|
|
func (m *mockLogger) WithContext(ctx context.Context) logger.Logger {
|
|
return m
|
|
}
|