github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/healthz/health_test.go (about)

     1  package healthz_test
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/kyma-incubator/compass/components/director/internal/healthz"
    12  	"github.com/kyma-incubator/compass/components/director/internal/healthz/automock"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  var (
    17  	firstConfig = healthz.IndicatorConfig{
    18  		Name:         "First",
    19  		Interval:     time.Second,
    20  		Timeout:      time.Second,
    21  		InitialDelay: time.Second,
    22  		Threshold:    1,
    23  	}
    24  
    25  	secondConfig = healthz.IndicatorConfig{
    26  		Name:         "Second",
    27  		Interval:     time.Second,
    28  		Timeout:      time.Second,
    29  		InitialDelay: time.Second,
    30  		Threshold:    1,
    31  	}
    32  )
    33  
    34  func TestNew(t *testing.T) {
    35  	t.Run("should return not nil health", func(t *testing.T) {
    36  		// GIVEN
    37  		ctx := context.TODO()
    38  		// WHEN
    39  		health, err := healthz.New(ctx, healthz.Config{})
    40  		// THEN
    41  		require.NotNil(t, health)
    42  		require.NoError(t, err)
    43  	})
    44  	t.Run("should return error on invalid interval", func(t *testing.T) {
    45  		// GIVEN
    46  		ctx := context.TODO()
    47  		// WHEN
    48  		health, err := healthz.New(ctx, healthz.Config{Indicators: []healthz.IndicatorConfig{{
    49  			Interval: 0,
    50  		}}})
    51  		// THEN
    52  		require.Nil(t, health)
    53  		require.Error(t, err)
    54  		require.Contains(t, err.Error(), "interval")
    55  	})
    56  	t.Run("should return error on invalid timeout", func(t *testing.T) {
    57  		// GIVEN
    58  		ctx := context.TODO()
    59  		// WHEN
    60  		health, err := healthz.New(ctx, healthz.Config{Indicators: []healthz.IndicatorConfig{{
    61  			Interval: time.Second,
    62  			Timeout:  0,
    63  		}}})
    64  		// THEN
    65  		require.Nil(t, health)
    66  		require.Error(t, err)
    67  		require.Contains(t, err.Error(), "timeout")
    68  	})
    69  	t.Run("should return error on invalid initial delay", func(t *testing.T) {
    70  		// GIVEN
    71  		ctx := context.TODO()
    72  		// WHEN
    73  		health, err := healthz.New(ctx, healthz.Config{Indicators: []healthz.IndicatorConfig{{
    74  			Interval:     time.Second,
    75  			Timeout:      time.Second,
    76  			InitialDelay: -1,
    77  		}}})
    78  		// THEN
    79  		require.Nil(t, health)
    80  		require.Error(t, err)
    81  		require.Contains(t, err.Error(), "initial delay ")
    82  	})
    83  	t.Run("should return error on invalid threshold", func(t *testing.T) {
    84  		// GIVEN
    85  		ctx := context.TODO()
    86  		// WHEN
    87  		health, err := healthz.New(ctx, healthz.Config{Indicators: []healthz.IndicatorConfig{{
    88  			Interval:     time.Second,
    89  			Timeout:      time.Second,
    90  			InitialDelay: time.Second,
    91  			Threshold:    -1,
    92  		}}})
    93  		// THEN
    94  		require.Nil(t, health)
    95  		require.Error(t, err)
    96  		require.Contains(t, err.Error(), "threshold")
    97  	})
    98  }
    99  
   100  func TestFullFlow(t *testing.T) {
   101  	t.Run("should configure properly(first config exists) and return UP when both indicators succeed", func(t *testing.T) {
   102  		// GIVEN
   103  		ctx, cancel := context.WithCancel(context.TODO())
   104  		defer cancel()
   105  
   106  		healthCfg := healthz.Config{Indicators: []healthz.IndicatorConfig{firstConfig}}
   107  
   108  		firstStatus := &automock.Status{}
   109  		defer firstStatus.AssertExpectations(t)
   110  		firstStatus.On("Error").Return(nil)
   111  
   112  		firstInd := &automock.Indicator{}
   113  		defer firstInd.AssertExpectations(t)
   114  		firstInd.On("Name").Return("First")
   115  		firstInd.On("Run", ctx).Return(nil)
   116  		firstInd.On("Status").Return(firstStatus)
   117  		firstInd.On("Configure", firstConfig).Return()
   118  
   119  		secondStatus := &automock.Status{}
   120  		defer secondStatus.AssertExpectations(t)
   121  		secondStatus.On("Error").Return(nil)
   122  
   123  		secondInd := &automock.Indicator{}
   124  		defer secondInd.AssertExpectations(t)
   125  		secondInd.On("Name").Return("Second")
   126  		secondInd.On("Run", ctx).Return(nil)
   127  		secondInd.On("Status").Return(secondStatus)
   128  		secondInd.On("Configure", healthz.NewDefaultConfig()).Return()
   129  
   130  		// WHEN
   131  		health, err := healthz.New(ctx, healthCfg)
   132  		require.NoError(t, err)
   133  
   134  		health.RegisterIndicator(firstInd).
   135  			RegisterIndicator(secondInd).
   136  			Start()
   137  		status := health.ReportStatus()
   138  
   139  		// THEN
   140  		require.Equal(t, status, healthz.UP)
   141  		AssertHandlerStatusCodeForHealth(t, health, http.StatusOK, healthz.UP)
   142  	})
   143  
   144  	t.Run("should configure properly(both config exist) and return DOWN when one indicator fails", func(t *testing.T) {
   145  		// GIVEN
   146  		ctx, cancel := context.WithCancel(context.TODO())
   147  		defer cancel()
   148  
   149  		healthCfg := healthz.Config{Indicators: []healthz.IndicatorConfig{firstConfig, secondConfig}}
   150  
   151  		firstStatus := &automock.Status{}
   152  		defer firstStatus.AssertExpectations(t)
   153  		firstStatus.On("Error").Return(errors.New("some error"))
   154  		firstStatus.On("Details").Return("some details")
   155  
   156  		firstInd := &automock.Indicator{}
   157  		defer firstInd.AssertExpectations(t)
   158  		firstInd.On("Name").Return("First").Times(3)
   159  		firstInd.On("Run", ctx).Return(nil)
   160  		firstInd.On("Status").Return(firstStatus)
   161  		firstInd.On("Configure", firstConfig).Return()
   162  
   163  		secondStatus := &automock.Status{}
   164  		defer secondStatus.AssertExpectations(t)
   165  		secondStatus.On("Error").Return(nil)
   166  
   167  		secondInd := &automock.Indicator{}
   168  		defer secondInd.AssertExpectations(t)
   169  		secondInd.On("Name").Return("Second")
   170  		secondInd.On("Run", ctx).Return(nil)
   171  		secondInd.On("Status").Return(secondStatus)
   172  		secondInd.On("Configure", secondConfig).Return()
   173  
   174  		// WHEN
   175  		health, err := healthz.New(ctx, healthCfg)
   176  		require.NoError(t, err)
   177  
   178  		health.RegisterIndicator(firstInd).
   179  			RegisterIndicator(secondInd).
   180  			Start()
   181  
   182  		status := health.ReportStatus()
   183  
   184  		// THEN
   185  		require.Equal(t, status, healthz.DOWN)
   186  		AssertHandlerStatusCodeForHealth(t, health, http.StatusInternalServerError, healthz.DOWN)
   187  	})
   188  
   189  	t.Run("should configure properly(neither config exist) and return DOWN when all indicators fail", func(t *testing.T) {
   190  		// GIVEN
   191  		ctx, cancel := context.WithCancel(context.TODO())
   192  		defer cancel()
   193  
   194  		healthCfg := healthz.Config{Indicators: []healthz.IndicatorConfig{}}
   195  
   196  		firstStatus := &automock.Status{}
   197  		defer firstStatus.AssertExpectations(t)
   198  		firstStatus.On("Error").Return(errors.New("some error"))
   199  		firstStatus.On("Details").Return("some details")
   200  
   201  		firstInd := &automock.Indicator{}
   202  		defer firstInd.AssertExpectations(t)
   203  		firstInd.On("Name").Return("First").Times(3)
   204  		firstInd.On("Run", ctx).Return(nil)
   205  		firstInd.On("Status").Return(firstStatus)
   206  		firstInd.On("Configure", healthz.NewDefaultConfig()).Return()
   207  
   208  		secondStatus := &automock.Status{}
   209  		defer secondStatus.AssertExpectations(t)
   210  		secondStatus.On("Error").Return(errors.New("some error 2"))
   211  		secondStatus.On("Details").Return("some details 2")
   212  
   213  		secondInd := &automock.Indicator{}
   214  		defer secondInd.AssertExpectations(t)
   215  		secondInd.On("Name").Return("Second").Times(3)
   216  		secondInd.On("Run", ctx).Return(nil)
   217  		secondInd.On("Status").Return(secondStatus)
   218  		secondInd.On("Configure", healthz.NewDefaultConfig()).Return()
   219  
   220  		// WHEN
   221  		health, err := healthz.New(ctx, healthCfg)
   222  		require.NoError(t, err)
   223  
   224  		health.RegisterIndicator(firstInd).
   225  			RegisterIndicator(secondInd).
   226  			Start()
   227  
   228  		status := health.ReportStatus()
   229  
   230  		// THEN
   231  		require.Equal(t, status, healthz.DOWN)
   232  		AssertHandlerStatusCodeForHealth(t, health, http.StatusInternalServerError, healthz.DOWN)
   233  	})
   234  }
   235  
   236  func AssertHandlerStatusCodeForHealth(t *testing.T, h *healthz.Health, expectedCode int, expectedBody string) {
   237  	req, err := http.NewRequest("GET", "/health", nil)
   238  	require.NoError(t, err)
   239  
   240  	rr := httptest.NewRecorder()
   241  	handler := http.HandlerFunc(healthz.NewHealthHandler(h))
   242  	// WHEN
   243  	handler.ServeHTTP(rr, req)
   244  	// THEN
   245  	require.Equal(t, expectedCode, rr.Code)
   246  	require.Equal(t, expectedBody, rr.Body.String())
   247  }