github.com/MetalBlockchain/metalgo@v1.11.9/api/health/service_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package health
     5  
     6  import (
     7  	"context"
     8  	"net/http"
     9  	"testing"
    10  
    11  	"github.com/prometheus/client_golang/prometheus"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"github.com/MetalBlockchain/metalgo/ids"
    15  	"github.com/MetalBlockchain/metalgo/utils/logging"
    16  )
    17  
    18  func TestServiceResponses(t *testing.T) {
    19  	require := require.New(t)
    20  
    21  	check := CheckerFunc(func(context.Context) (interface{}, error) {
    22  		return "", nil
    23  	})
    24  
    25  	h, err := New(logging.NoLog{}, prometheus.NewRegistry())
    26  	require.NoError(err)
    27  
    28  	s := &Service{
    29  		log:    logging.NoLog{},
    30  		health: h,
    31  	}
    32  
    33  	require.NoError(h.RegisterReadinessCheck("check", check))
    34  	require.NoError(h.RegisterHealthCheck("check", check))
    35  	require.NoError(h.RegisterLivenessCheck("check", check))
    36  
    37  	{
    38  		reply := APIReply{}
    39  		require.NoError(s.Readiness(nil, &APIArgs{}, &reply))
    40  
    41  		require.Len(reply.Checks, 1)
    42  		require.Contains(reply.Checks, "check")
    43  		require.Equal(notYetRunResult, reply.Checks["check"])
    44  		require.False(reply.Healthy)
    45  	}
    46  
    47  	{
    48  		reply := APIReply{}
    49  		require.NoError(s.Health(nil, &APIArgs{}, &reply))
    50  
    51  		require.Len(reply.Checks, 1)
    52  		require.Contains(reply.Checks, "check")
    53  		require.Equal(notYetRunResult, reply.Checks["check"])
    54  		require.False(reply.Healthy)
    55  	}
    56  
    57  	{
    58  		reply := APIReply{}
    59  		require.NoError(s.Liveness(nil, &APIArgs{}, &reply))
    60  
    61  		require.Len(reply.Checks, 1)
    62  		require.Contains(reply.Checks, "check")
    63  		require.Equal(notYetRunResult, reply.Checks["check"])
    64  		require.False(reply.Healthy)
    65  	}
    66  
    67  	h.Start(context.Background(), checkFreq)
    68  	defer h.Stop()
    69  
    70  	awaitReadiness(t, h, true)
    71  	awaitHealthy(t, h, true)
    72  	awaitLiveness(t, h, true)
    73  
    74  	{
    75  		reply := APIReply{}
    76  		require.NoError(s.Readiness(nil, &APIArgs{}, &reply))
    77  
    78  		result := reply.Checks["check"]
    79  		require.Equal("", result.Details)
    80  		require.Nil(result.Error)
    81  		require.Zero(result.ContiguousFailures)
    82  		require.True(reply.Healthy)
    83  	}
    84  
    85  	{
    86  		reply := APIReply{}
    87  		require.NoError(s.Health(nil, &APIArgs{}, &reply))
    88  
    89  		result := reply.Checks["check"]
    90  		require.Equal("", result.Details)
    91  		require.Nil(result.Error)
    92  		require.Zero(result.ContiguousFailures)
    93  		require.True(reply.Healthy)
    94  	}
    95  
    96  	{
    97  		reply := APIReply{}
    98  		require.NoError(s.Liveness(nil, &APIArgs{}, &reply))
    99  
   100  		result := reply.Checks["check"]
   101  		require.Equal("", result.Details)
   102  		require.Nil(result.Error)
   103  		require.Zero(result.ContiguousFailures)
   104  		require.True(reply.Healthy)
   105  	}
   106  }
   107  
   108  func TestServiceTagResponse(t *testing.T) {
   109  	check := CheckerFunc(func(context.Context) (interface{}, error) {
   110  		return "", nil
   111  	})
   112  
   113  	subnetID1 := ids.GenerateTestID()
   114  	subnetID2 := ids.GenerateTestID()
   115  
   116  	// test cases
   117  	type testMethods struct {
   118  		name     string
   119  		register func(Health, string, Checker, ...string) error
   120  		check    func(*Service, *http.Request, *APIArgs, *APIReply) error
   121  		await    func(*testing.T, Reporter, bool)
   122  	}
   123  
   124  	tests := []testMethods{
   125  		{
   126  			name: "Readiness",
   127  			register: func(h Health, s1 string, c Checker, s2 ...string) error {
   128  				return h.RegisterReadinessCheck(s1, c, s2...)
   129  			},
   130  			check: func(s *Service, req *http.Request, a1 *APIArgs, a2 *APIReply) error {
   131  				return s.Readiness(req, a1, a2)
   132  			},
   133  			await: awaitReadiness,
   134  		},
   135  		{
   136  			name: "Health",
   137  			register: func(h Health, s1 string, c Checker, s2 ...string) error {
   138  				return h.RegisterHealthCheck(s1, c, s2...)
   139  			},
   140  			check: func(s *Service, r *http.Request, a1 *APIArgs, a2 *APIReply) error {
   141  				return s.Health(r, a1, a2)
   142  			},
   143  			await: awaitHealthy,
   144  		},
   145  		{
   146  			name: "Liveness",
   147  			register: func(h Health, s1 string, c Checker, s2 ...string) error {
   148  				return h.RegisterLivenessCheck(s1, c, s2...)
   149  			},
   150  			check: func(s *Service, r *http.Request, a1 *APIArgs, a2 *APIReply) error {
   151  				return s.Liveness(r, a1, a2)
   152  			},
   153  			await: awaitLiveness,
   154  		},
   155  	}
   156  
   157  	for _, test := range tests {
   158  		t.Run(test.name, func(t *testing.T) {
   159  			require := require.New(t)
   160  
   161  			h, err := New(logging.NoLog{}, prometheus.NewRegistry())
   162  			require.NoError(err)
   163  			require.NoError(test.register(h, "check1", check))
   164  			require.NoError(test.register(h, "check2", check, subnetID1.String()))
   165  			require.NoError(test.register(h, "check3", check, subnetID2.String()))
   166  			require.NoError(test.register(h, "check4", check, subnetID1.String(), subnetID2.String()))
   167  
   168  			s := &Service{
   169  				log:    logging.NoLog{},
   170  				health: h,
   171  			}
   172  
   173  			// default checks
   174  			{
   175  				reply := APIReply{}
   176  				require.NoError(test.check(s, nil, &APIArgs{}, &reply))
   177  				require.Len(reply.Checks, 4)
   178  				require.Contains(reply.Checks, "check1")
   179  				require.Contains(reply.Checks, "check2")
   180  				require.Contains(reply.Checks, "check3")
   181  				require.Contains(reply.Checks, "check4")
   182  				require.Equal(notYetRunResult, reply.Checks["check1"])
   183  				require.False(reply.Healthy)
   184  
   185  				require.NoError(test.check(s, nil, &APIArgs{Tags: []string{subnetID1.String()}}, &reply))
   186  				require.Len(reply.Checks, 2)
   187  				require.Contains(reply.Checks, "check2")
   188  				require.Contains(reply.Checks, "check4")
   189  				require.Equal(notYetRunResult, reply.Checks["check2"])
   190  				require.False(reply.Healthy)
   191  			}
   192  
   193  			h.Start(context.Background(), checkFreq)
   194  
   195  			test.await(t, h, true)
   196  
   197  			{
   198  				reply := APIReply{}
   199  				require.NoError(test.check(s, nil, &APIArgs{Tags: []string{subnetID1.String()}}, &reply))
   200  				require.Len(reply.Checks, 2)
   201  				require.Contains(reply.Checks, "check2")
   202  				require.Contains(reply.Checks, "check4")
   203  				require.True(reply.Healthy)
   204  			}
   205  
   206  			// stop the health check
   207  			h.Stop()
   208  
   209  			{
   210  				// now we'll add a new check which is unhealthy by default (notYetRunResult)
   211  				require.NoError(test.register(h, "check5", check, subnetID1.String()))
   212  
   213  				reply := APIReply{}
   214  				require.NoError(test.check(s, nil, &APIArgs{Tags: []string{subnetID1.String()}}, &reply))
   215  				require.Len(reply.Checks, 3)
   216  				require.Contains(reply.Checks, "check2")
   217  				require.Contains(reply.Checks, "check4")
   218  				require.Contains(reply.Checks, "check5")
   219  				require.Equal(notYetRunResult, reply.Checks["check5"])
   220  				require.False(reply.Healthy)
   221  			}
   222  		})
   223  	}
   224  }