309 lines
7.8 KiB
Go
309 lines
7.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/mock"
|
|
|
|
"starter-kit/internal/helper/config"
|
|
)
|
|
|
|
// MockConfig is a mock of config.Config
|
|
type MockConfig struct {
|
|
mock.Mock
|
|
App config.AppConfig
|
|
}
|
|
|
|
func (m *MockConfig) GetAppConfig() *config.AppConfig {
|
|
args := m.Called()
|
|
if args.Get(0) == nil {
|
|
return nil
|
|
}
|
|
return args.Get(0).(*config.AppConfig)
|
|
}
|
|
|
|
func TestNewHealthHandler(t *testing.T) {
|
|
t.Run("creates new health handler with config", func(t *testing.T) {
|
|
cfg := &config.Config{
|
|
App: config.AppConfig{
|
|
Name: "test-app",
|
|
Version: "1.0.0",
|
|
Environment: "test",
|
|
},
|
|
}
|
|
|
|
handler := NewHealthHandler(cfg)
|
|
|
|
assert.NotNil(t, handler)
|
|
assert.Equal(t, cfg.App.Version, handler.appVersion)
|
|
assert.False(t, handler.startTime.IsZero())
|
|
})
|
|
}
|
|
|
|
func TestHealthCheck(t *testing.T) {
|
|
// Setup test cases
|
|
tests := []struct {
|
|
name string
|
|
setupMock func(*MockConfig)
|
|
expectedCode int
|
|
expectedKeys []string
|
|
checkUptime bool
|
|
checkAppInfo bool
|
|
expectedValues map[string]interface{}
|
|
}{
|
|
{
|
|
name: "successful health check",
|
|
setupMock: func(mc *MockConfig) {
|
|
mc.App = config.AppConfig{
|
|
Name: "test-app",
|
|
Version: "1.0.0",
|
|
Environment: "test",
|
|
}
|
|
},
|
|
expectedCode: http.StatusOK,
|
|
expectedKeys: []string{"status", "app", "uptime", "components", "timestamp"},
|
|
expectedValues: map[string]interface{}{
|
|
"status": "ok",
|
|
"app": map[string]interface{}{
|
|
"name": "test-app",
|
|
"version": "1.0.0",
|
|
"env": "test",
|
|
},
|
|
"components": map[string]interface{}{
|
|
"database": "ok",
|
|
"cache": "ok",
|
|
},
|
|
},
|
|
checkUptime: true,
|
|
checkAppInfo: true,
|
|
},
|
|
{
|
|
name: "health check with empty config",
|
|
setupMock: func(mc *MockConfig) {
|
|
mc.App = config.AppConfig{}
|
|
},
|
|
expectedCode: http.StatusOK,
|
|
expectedValues: map[string]interface{}{
|
|
"status": "ok",
|
|
"app": map[string]interface{}{
|
|
"name": "",
|
|
"version": "",
|
|
"env": "",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Setup mock config
|
|
mockCfg := new(MockConfig)
|
|
tt.setupMock(mockCfg)
|
|
|
|
// Setup mock expectations
|
|
mockCfg.On("GetAppConfig").Return(&mockCfg.App)
|
|
|
|
|
|
// Create handler with mock config
|
|
handler := NewHealthHandler(&config.Config{
|
|
App: *mockCfg.GetAppConfig(),
|
|
})
|
|
|
|
|
|
// Create a new request
|
|
req := httptest.NewRequest("GET", "/health", nil)
|
|
|
|
// Create a response recorder
|
|
w := httptest.NewRecorder()
|
|
|
|
// Create a new router and register the handler
|
|
r := gin.New()
|
|
r.GET("/health", handler.HealthCheck)
|
|
|
|
// Serve the request
|
|
r.ServeHTTP(w, req)
|
|
|
|
|
|
// Assert the status code
|
|
assert.Equal(t, tt.expectedCode, w.Code)
|
|
|
|
// Parse the response body
|
|
var response map[string]interface{}
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &response), "Failed to parse response body")
|
|
|
|
// Check expected keys exist
|
|
for _, key := range tt.expectedKeys {
|
|
_, exists := response[key]
|
|
assert.True(t, exists, "Response should contain key: %s", key)
|
|
}
|
|
|
|
// Check expected values
|
|
for key, expectedValue := range tt.expectedValues {
|
|
switch v := expectedValue.(type) {
|
|
case map[string]interface{}:
|
|
actual, exists := response[key].(map[string]interface{})
|
|
require.True(t, exists, "Expected %s to be a map", key)
|
|
for subKey, subValue := range v {
|
|
assert.Equal(t, subValue, actual[subKey], "Mismatch for %s.%s", key, subKey)
|
|
}
|
|
default:
|
|
assert.Equal(t, expectedValue, response[key], "Mismatch for %s", key)
|
|
}
|
|
}
|
|
|
|
// Check uptime if needed
|
|
if tt.checkUptime {
|
|
_, exists := response["uptime"]
|
|
assert.True(t, exists, "Response should contain uptime")
|
|
}
|
|
|
|
// Check app info if needed
|
|
if tt.checkAppInfo {
|
|
appInfo, ok := response["app"].(map[string]interface{})
|
|
assert.True(t, ok, "app should be a map")
|
|
assert.Equal(t, "test-app", appInfo["name"])
|
|
assert.Equal(t, "1.0.0", appInfo["version"])
|
|
assert.Equal(t, "test", appInfo["env"])
|
|
}
|
|
|
|
// Check uptime is a valid duration string
|
|
if tt.checkUptime {
|
|
uptime, ok := response["uptime"].(string)
|
|
assert.True(t, ok, "uptime should be a string")
|
|
_, err := time.ParseDuration(uptime)
|
|
assert.NoError(t, err, "uptime should be a valid duration string")
|
|
}
|
|
|
|
// Check components
|
|
components, ok := response["components"].(map[string]interface{})
|
|
assert.True(t, ok, "components should be a map")
|
|
assert.Equal(t, "ok", components["database"])
|
|
assert.Equal(t, "ok", components["cache"])
|
|
|
|
// Check timestamp format
|
|
timestamp, ok := response["timestamp"].(string)
|
|
assert.True(t, ok, "timestamp should be a string")
|
|
_, err := time.Parse(time.RFC3339, timestamp)
|
|
assert.NoError(t, err, "timestamp should be in RFC3339 format")
|
|
|
|
// Assert that all expectations were met
|
|
mockCfg.AssertExpectations(t)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPing(t *testing.T) {
|
|
// Setup test cases
|
|
tests := []struct {
|
|
name string
|
|
setupMock func(*MockConfig)
|
|
expectedCode int
|
|
expectedValues map[string]string
|
|
}{
|
|
{
|
|
name: "successful ping",
|
|
setupMock: func(mc *MockConfig) {
|
|
mc.App = config.AppConfig{
|
|
Name: "test-app",
|
|
Version: "1.0.0",
|
|
Environment: "test",
|
|
}
|
|
},
|
|
expectedCode: http.StatusOK,
|
|
expectedValues: map[string]string{
|
|
"status": "ok",
|
|
"message": "pong",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Setup mock config
|
|
mockCfg := new(MockConfig)
|
|
tt.setupMock(mockCfg)
|
|
|
|
// Setup mock expectations
|
|
mockCfg.On("GetAppConfig").Return(&mockCfg.App)
|
|
|
|
|
|
// Create handler with mock config
|
|
handler := NewHealthHandler(&config.Config{
|
|
App: *mockCfg.GetAppConfig(),
|
|
})
|
|
|
|
// Create a new request
|
|
req := httptest.NewRequest("GET", "/ping", nil)
|
|
|
|
// Create a response recorder
|
|
w := httptest.NewRecorder()
|
|
|
|
// Create a new router and register the handler
|
|
r := gin.New()
|
|
r.GET("/ping", handler.Ping)
|
|
|
|
// Serve the request
|
|
r.ServeHTTP(w, req)
|
|
|
|
|
|
// Assert the status code
|
|
assert.Equal(t, tt.expectedCode, w.Code)
|
|
|
|
// Parse the response body
|
|
var response map[string]interface{}
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &response), "Failed to parse response body")
|
|
|
|
// Check expected values
|
|
for key, expectedValue := range tt.expectedValues {
|
|
actual, exists := response[key]
|
|
require.True(t, exists, "Expected key %s not found in response", key)
|
|
assert.Equal(t, expectedValue, actual, "Mismatch for key %s", key)
|
|
}
|
|
|
|
// Check timestamp is in the correct format
|
|
timestamp, ok := response["timestamp"].(string)
|
|
require.True(t, ok, "timestamp should be a string")
|
|
_, err := time.Parse(time.RFC3339, timestamp)
|
|
assert.NoError(t, err, "timestamp should be in RFC3339 format")
|
|
|
|
// Assert that all expectations were met
|
|
mockCfg.AssertExpectations(t)
|
|
})
|
|
}
|
|
|
|
// Test with nil config
|
|
t.Run("ping with nil config", func(t *testing.T) {
|
|
handler := &HealthHandler{}
|
|
|
|
// Create a new request
|
|
req := httptest.NewRequest("GET", "/ping", nil)
|
|
|
|
// Create a response recorder
|
|
w := httptest.NewRecorder()
|
|
|
|
// Create a new router and register the handler
|
|
r := gin.New()
|
|
r.GET("/ping", handler.Ping)
|
|
|
|
// Serve the request
|
|
r.ServeHTTP(w, req)
|
|
|
|
// Should still work with default values
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
|
|
// Parse the response body
|
|
var response map[string]interface{}
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &response), "Failed to parse response body")
|
|
|
|
assert.Equal(t, "pong", response["message"], "Response should contain message 'pong'")
|
|
assert.Equal(t, "ok", response["status"], "Response should contain status 'ok'")
|
|
})
|
|
}
|