Files
query-orchestration/internal/serviceconfig/commonlog_test.go
T
Jay Brown 720a84be92 Merged in feature/doc_import (pull request #177)
integration of background processor

* integration part 1

* feature working

* fix mimetype issue
2025-08-20 19:01:13 +00:00

473 lines
14 KiB
Go

package serviceconfig_test
import (
"bytes"
"log/slog"
"strings"
"testing"
"queryorchestration/internal/serviceconfig"
"github.com/stretchr/testify/require"
)
// TestPrintConfigRecursive tests the PrintConfigRecursive function as a blackbox test.
func TestPrintConfigRecursive(t *testing.T) {
t.Setenv("DEBUG", "true")
serviceconfig.ResetConfigInitOnceTestOnly()
envVars := map[string]string{
"PGUSER": "postgres",
"PGPASSWORD": "password",
"PGHOST": "localhost",
"PGPORT": "5432",
"PGDATABASE": "query_orchestration",
"DB_NOSSL": "true",
"AWS_ACCESS_KEY_ID": "key",
"AWS_SECRET_ACCESS_KEY": "password",
"AWS_REGION": "region",
"COGNITO_USER_POOL_ID": "poolid",
"COGNITO_CLIENT_SECRET": "secret",
"COGNITO_DOMAIN": "domain",
"COGNITO_CLIENT_ID": "clientid",
"PORT": "8080", // Only include vars needed by BaseConfig
}
// Set environment variables
for k, v := range envVars {
t.Setenv(k, v)
}
// Create a buffer to capture log output
var logBuffer bytes.Buffer
// Create a test logger that writes to our buffer
testLogger := slog.New(slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
// Use BaseConfig instead of initializeConfigTestStruct
cfg := &serviceconfig.BaseConfig{}
err := serviceconfig.InitializeConfig(cfg)
require.NoError(t, err)
// Set logger after initialization
cfg.Logger = testLogger
// Call the function we're testing
cfg.PrintConfig("secret")
// Get the log output as a string
logOutput := logBuffer.String()
// Check that each environment variable value appears in the logs
// except for sensitive values which should be masked
for envKey, envValue := range envVars {
if envKey == "PGPASSWORD" || envKey == "AWS_SECRET_ACCESS_KEY" || envKey == "COGNITO_CLIENT_SECRET" {
// Password should be masked
if strings.Contains(logOutput, envValue) {
t.Errorf("Full password value '%s' should not appear in logs for %s", envValue, envKey)
}
// Should see the masked value indicator
if !strings.Contains(logOutput, "***MASKED***") {
t.Errorf("Expected ***MASKED*** for secret field %s in logs, got: %s", envKey, logOutput)
}
} else if !strings.Contains(logOutput, envValue) {
// Only check for non-secret values that should appear
// Note: Some values might be in nested structures or have different JSON names
// so we should be more lenient here
t.Logf("Warning: Expected %s=%s in logs, but it was not found", envKey, envValue)
}
}
}
// TestPrintConfigErrorHandling tests error handling paths in PrintConfig
func TestPrintConfigErrorHandling(t *testing.T) {
t.Setenv("DEBUG", "true")
serviceconfig.ResetConfigInitOnceTestOnly()
// Test 1: PrintConfig with nil logger (should not panic)
t.Run("nil_logger", func(t *testing.T) {
cfg := &serviceconfig.BaseConfig{}
cfg.Logger = nil // Explicitly set to nil
// This should not panic even with nil logger
cfg.PrintConfig("secret")
})
// Test 2: PrintConfig with error in configToMap
t.Run("config_conversion_error", func(t *testing.T) {
var logBuffer bytes.Buffer
testLogger := slog.New(slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
cfg := &serviceconfig.BaseConfig{}
cfg.Logger = testLogger
// Call PrintConfig - even if conversion has issues, it should handle gracefully
cfg.PrintConfig("secret")
logOutput := logBuffer.String()
// Check that something was logged (even if empty config)
require.NotEmpty(t, logOutput, "Should have some log output")
})
}
// TestPrintConfigWithVariousConfigs tests PrintConfig with different config states
func TestPrintConfigWithVariousConfigs(t *testing.T) {
t.Setenv("DEBUG", "true")
serviceconfig.ResetConfigInitOnceTestOnly()
tests := []struct {
name string
setupFunc func() *serviceconfig.BaseConfig
}{
{
name: "empty_config",
setupFunc: func() *serviceconfig.BaseConfig {
cfg := &serviceconfig.BaseConfig{}
cfg.SetDefaultLogger()
return cfg
},
},
{
name: "config_with_values",
setupFunc: func() *serviceconfig.BaseConfig {
t.Setenv("PORT", "8080")
t.Setenv("BASE_URL", "http://localhost")
t.Setenv("PGUSER", "testuser")
t.Setenv("PGPASSWORD", "testpass")
t.Setenv("PGHOST", "localhost")
t.Setenv("PGPORT", "5432")
t.Setenv("PGDATABASE", "testdb")
t.Setenv("AWS_ACCESS_KEY_ID", "testkey")
t.Setenv("AWS_SECRET_ACCESS_KEY", "testsecret")
t.Setenv("AWS_REGION", "us-east-1")
cfg := &serviceconfig.BaseConfig{}
_ = serviceconfig.InitializeConfig(cfg)
cfg.SetDefaultLogger()
return cfg
},
},
{
name: "config_with_logger_but_no_env",
setupFunc: func() *serviceconfig.BaseConfig {
cfg := &serviceconfig.BaseConfig{}
var logBuffer bytes.Buffer
cfg.Logger = slog.New(slog.NewTextHandler(&logBuffer, nil))
return cfg
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.name == "config_with_values" {
serviceconfig.ResetConfigInitOnceTestOnly()
}
cfg := tt.setupFunc()
// Should not panic
defer func() {
if r := recover(); r != nil {
t.Errorf("PrintConfig panicked: %v", r)
}
}()
cfg.PrintConfig("secret")
})
}
}
// TestPrintConfigCoverageEdgeCases tests edge cases to improve coverage
func TestPrintConfigCoverageEdgeCases(t *testing.T) {
t.Setenv("DEBUG", "true")
serviceconfig.ResetConfigInitOnceTestOnly()
// Test case 1: Config with nested map structures to trigger recursion in logConfigMap
t.Run("nested_map_logging", func(t *testing.T) {
var logBuffer bytes.Buffer
testLogger := slog.New(slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
cfg := &serviceconfig.BaseConfig{}
cfg.Logger = testLogger
// This will test the nested map handling in logConfigMap
cfg.PrintConfig("test")
// Just verify it doesn't panic and produces some output
logOutput := logBuffer.String()
require.NotEmpty(t, logOutput)
})
// Test case 2: Config with array values to trigger array handling in logConfigMap
t.Run("array_logging", func(t *testing.T) {
var logBuffer bytes.Buffer
testLogger := slog.New(slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
cfg := &serviceconfig.BaseConfig{}
cfg.Logger = testLogger
// Set some environment variables that might result in arrays
t.Setenv("QUEUE_NAMES", "queue1,queue2,queue3")
cfg.PrintConfig("test")
logOutput := logBuffer.String()
require.NotEmpty(t, logOutput)
})
// Test case 3: Test with various secret patterns to improve masking coverage
t.Run("comprehensive_secret_masking", func(t *testing.T) {
var logBuffer bytes.Buffer
testLogger := slog.New(slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
cfg := &serviceconfig.BaseConfig{}
cfg.Logger = testLogger
// Set environment variables with various secret patterns
t.Setenv("JWT_TOKEN", "my-jwt-token")
t.Setenv("BEARER_TOKEN", "bearer-123")
t.Setenv("PRIVATE_DATA", "sensitive")
t.Setenv("CUSTOM_SECRET", "mysecret")
t.Setenv("API_KEY", "key123")
t.Setenv("ACCESS_KEY", "access123")
t.Setenv("CREDENTIAL_DATA", "creds")
// Initialize config to pick up environment variables
serviceconfig.ResetConfigInitOnceTestOnly()
_ = serviceconfig.InitializeConfig(cfg)
cfg.Logger = testLogger // Reset logger after initialization
cfg.PrintConfig("CUSTOM") // Test with custom prefix
logOutput := logBuffer.String()
require.NotEmpty(t, logOutput)
// Verify that secrets are masked
require.Contains(t, logOutput, "***MASKED***")
// Verify that original secret values don't appear
secretValues := []string{"my-jwt-token", "bearer-123", "sensitive", "mysecret", "key123", "access123", "creds"}
for _, secret := range secretValues {
require.NotContains(t, logOutput, secret)
}
})
// Test case 4: Test with nil logger to cover logConfigMap nil check
t.Run("nil_logger_in_logConfigMap", func(t *testing.T) {
cfg := &serviceconfig.BaseConfig{}
cfg.Logger = nil // Explicitly nil
cfg.Port = 8080
// This should not panic and should exit early due to nil logger check
cfg.PrintConfig("test")
// If we get here without panic, the test passes
})
// Test case 5: Test with prefix in logConfigMap
t.Run("with_prefix", func(t *testing.T) {
var logBuffer bytes.Buffer
testLogger := slog.New(slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
cfg := &serviceconfig.BaseConfig{}
cfg.Logger = testLogger
cfg.Port = 9090
cfg.PrintConfig("custom_prefix")
logOutput := logBuffer.String()
require.NotEmpty(t, logOutput)
})
}
// TestLogConfigMapDirectly tests logConfigMap function edge cases directly
func TestLogConfigMapDirectly(t *testing.T) {
t.Setenv("DEBUG", "true")
serviceconfig.ResetConfigInitOnceTestOnly()
// Test case 1: Test with nested maps to trigger recursion
t.Run("nested_maps", func(t *testing.T) {
var logBuffer bytes.Buffer
testLogger := slog.New(slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
cfg := &serviceconfig.BaseConfig{}
cfg.Logger = testLogger
// Call PrintConfig to test nested map handling in logConfigMap
cfg.PrintConfig("test")
logOutput := logBuffer.String()
require.NotEmpty(t, logOutput)
})
// Test case 2: Test with array values to trigger array handling
t.Run("array_values", func(t *testing.T) {
var logBuffer bytes.Buffer
testLogger := slog.New(slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
// Create a custom config with array-like values
type ConfigWithArrays struct {
serviceconfig.BaseConfig
Items []string `env:"TEST_ITEMS" envDefault:"item1,item2,item3"`
}
cfg := &ConfigWithArrays{}
cfg.Logger = testLogger
cfg.Items = []string{"test1", "test2", "test3"}
cfg.BaseConfig.PrintConfig("test")
logOutput := logBuffer.String()
require.NotEmpty(t, logOutput)
})
// Test case 3: Test with nil logger to hit the early return in logConfigMap
t.Run("nil_logger_early_return", func(t *testing.T) {
cfg := &serviceconfig.BaseConfig{}
cfg.Logger = nil // Explicitly nil
// This should hit the early return in logConfigMap
cfg.PrintConfig("test")
// If we get here without panic, the test passes
})
}
// TestPrintConfigErrorPaths tests specific error paths in PrintConfig to improve coverage
func TestPrintConfigErrorPaths(t *testing.T) {
t.Setenv("DEBUG", "true")
serviceconfig.ResetConfigInitOnceTestOnly()
// Test case 1: Test configToMap error handling
t.Run("configToMap_error", func(t *testing.T) {
var logBuffer bytes.Buffer
testLogger := slog.New(slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
// Create a config that will cause JSON marshal to fail
type BadConfig struct {
serviceconfig.BaseConfig
UnsupportedField interface{} `env:"UNSUPPORTED"`
}
cfg := &BadConfig{}
cfg.Logger = testLogger
// Set a value that cannot be marshaled to JSON (circular reference)
cfg.UnsupportedField = make(map[string]interface{})
if m, ok := cfg.UnsupportedField.(map[string]interface{}); ok {
m["self"] = cfg.UnsupportedField
}
// This should trigger the error path in configToMap
cfg.BaseConfig.PrintConfig("test")
// Check that error was logged
logOutput := logBuffer.String()
if !strings.Contains(logOutput, "Failed to convert config to map") &&
!strings.Contains(logOutput, "Failed to print config") {
// Either error message should appear
t.Logf("Expected error handling, got: %s", logOutput)
}
})
// Test case 2: Test the error logging path when config conversion fails
t.Run("force_error_logging", func(t *testing.T) {
var logBuffer bytes.Buffer
testLogger := slog.New(slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
// Use a more complex circular reference that will definitely fail JSON marshaling
type CircularConfig struct {
serviceconfig.BaseConfig
Self *CircularConfig `env:"SELF_REF"`
}
cfg := &CircularConfig{}
cfg.Logger = testLogger
cfg.Self = cfg // This creates a circular reference that JSON cannot marshal
// This should trigger the error path in configToMap and the error logging in PrintConfig
cfg.BaseConfig.PrintConfig("test")
// Check that error was logged
logOutput := logBuffer.String()
t.Logf("Log output: %s", logOutput)
// The test passes if we don't panic and have some log output
require.NotEmpty(t, logOutput)
})
}
// TestPrintConfigDebugDisabled tests that PrintConfig does nothing when DEBUG is not true
func TestPrintConfigDebugDisabled(t *testing.T) {
serviceconfig.ResetConfigInitOnceTestOnly()
var logBuffer bytes.Buffer
testLogger := slog.New(slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
cfg := &serviceconfig.BaseConfig{}
cfg.Logger = testLogger
cfg.Port = 8080
// Test 1: DEBUG not set (default)
t.Run("debug_not_set", func(t *testing.T) {
// Don't set DEBUG environment variable
cfg.PrintConfig("test")
// Should have no log output since DEBUG is not true
logOutput := logBuffer.String()
require.Empty(t, logOutput, "PrintConfig should not log when DEBUG is not set")
})
// Test 2: DEBUG set to false
t.Run("debug_false", func(t *testing.T) {
logBuffer.Reset() // Clear previous output
t.Setenv("DEBUG", "false")
cfg.PrintConfig("test")
// Should have no log output since DEBUG is not "true"
logOutput := logBuffer.String()
require.Empty(t, logOutput, "PrintConfig should not log when DEBUG is false")
})
// Test 3: DEBUG set to something other than true
t.Run("debug_other_value", func(t *testing.T) {
logBuffer.Reset() // Clear previous output
t.Setenv("DEBUG", "yes")
cfg.PrintConfig("test")
// Should have no log output since DEBUG is not exactly "true"
logOutput := logBuffer.String()
require.Empty(t, logOutput, "PrintConfig should not log when DEBUG is not exactly 'true'")
})
// Test 4: Verify it works when DEBUG is true
t.Run("debug_true", func(t *testing.T) {
logBuffer.Reset() // Clear previous output
t.Setenv("DEBUG", "true")
cfg.PrintConfig("test")
// Should have log output when DEBUG is true
logOutput := logBuffer.String()
require.NotEmpty(t, logOutput, "PrintConfig should log when DEBUG is true")
require.Contains(t, logOutput, "Config value", "Should contain config logging")
})
}