github.com/splucs/witchcraft-go-server@v1.7.0/status/routes/routes_test.go (about)

     1  // Copyright (c) 2018 Palantir Technologies. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package routes
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/palantir/witchcraft-go-server/conjure/witchcraft/api/health"
    28  	"github.com/palantir/witchcraft-go-server/status"
    29  	"github.com/palantir/witchcraft-go-server/witchcraft/refreshable"
    30  	"github.com/palantir/witchcraft-go-server/witchcraft/wresource"
    31  	"github.com/palantir/witchcraft-go-server/wrouter"
    32  	"github.com/palantir/witchcraft-go-server/wrouter/whttprouter"
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  )
    36  
    37  func TestAddStatusRoutes(t *testing.T) {
    38  	type testMetadata struct {
    39  		Message string
    40  		Value   int
    41  	}
    42  
    43  	for i, tc := range []struct {
    44  		endpoint  string
    45  		routeFunc func(resource wresource.Resource, source status.Source) error
    46  		status    int
    47  		metadata  testMetadata
    48  	}{
    49  		{
    50  			endpoint:  status.LivenessEndpoint,
    51  			routeFunc: AddLivenessRoutes,
    52  			status:    http.StatusOK,
    53  			metadata: testMetadata{
    54  				Message: "hello",
    55  				Value:   13,
    56  			},
    57  		},
    58  		{
    59  			endpoint:  status.ReadinessEndpoint,
    60  			routeFunc: AddReadinessRoutes,
    61  			status:    http.StatusServiceUnavailable,
    62  			metadata: testMetadata{
    63  				Message: "goodbye",
    64  				Value:   1,
    65  			},
    66  		},
    67  	} {
    68  		func() {
    69  			r := wrouter.New(whttprouter.New(), nil)
    70  			resource := wresource.New("test", r)
    71  			err := tc.routeFunc(resource, statusFunc(func() (int, interface{}) {
    72  				return tc.status, tc.metadata
    73  			}))
    74  			require.NoError(t, err, "Case %d", i)
    75  
    76  			server := httptest.NewServer(r)
    77  			defer server.Close()
    78  
    79  			resp, err := http.Get(strings.Join([]string{server.URL, tc.endpoint}, "/"))
    80  			require.NoError(t, err, "Case %d", i)
    81  			assert.Equal(t, tc.status, resp.StatusCode, "Case %d", i)
    82  
    83  			bytes, err := ioutil.ReadAll(resp.Body)
    84  			require.NoError(t, err, "Case %d", i)
    85  			var gotObj testMetadata
    86  			err = json.Unmarshal(bytes, &gotObj)
    87  			require.NoError(t, err, "Case %d", i)
    88  			assert.Equal(t, tc.metadata, gotObj)
    89  		}()
    90  	}
    91  }
    92  
    93  func TestAddHealthRoute(t *testing.T) {
    94  	for _, test := range []struct {
    95  		name           string
    96  		metadata       health.HealthStatus
    97  		sharedSecret   string
    98  		actualSecret   string
    99  		expectedStatus int
   100  	}{
   101  		{
   102  			"zero healthy check",
   103  			health.HealthStatus{
   104  				Checks: make(map[health.CheckType]health.HealthCheckResult),
   105  			},
   106  			"",
   107  			"",
   108  			200,
   109  		},
   110  		{
   111  			"one healthy check",
   112  			health.HealthStatus{
   113  				Checks: map[health.CheckType]health.HealthCheckResult{
   114  					"SCHEMA_VERSION": {
   115  						Type:   "SCHEMA_VERSION",
   116  						State:  health.HealthStateHealthy,
   117  						Params: make(map[string]interface{}),
   118  					},
   119  				},
   120  			},
   121  			"",
   122  			"secret shouldn't matter",
   123  			200,
   124  		},
   125  		{
   126  			"one healthy and one error",
   127  			health.HealthStatus{
   128  				Checks: map[health.CheckType]health.HealthCheckResult{
   129  					"SCHEMA_VERSION": {
   130  						Type:   "SCHEMA_VERSION",
   131  						State:  health.HealthStateHealthy,
   132  						Params: make(map[string]interface{}),
   133  					},
   134  					"SERVER_ERROR": {
   135  						Type:   "SERVER_ERROR",
   136  						State:  health.HealthStateError,
   137  						Params: make(map[string]interface{}),
   138  					},
   139  				},
   140  			},
   141  			"",
   142  			"",
   143  			522,
   144  		},
   145  		{
   146  			"health, terminal, error",
   147  			health.HealthStatus{
   148  				Checks: map[health.CheckType]health.HealthCheckResult{
   149  					"SCHEMA_VERSION": {
   150  						Type:   "SCHEMA_VERSION",
   151  						State:  health.HealthStateHealthy,
   152  						Params: make(map[string]interface{}),
   153  					},
   154  					"SERVER_TERMINAL": {
   155  						Type:   "SERVER_TERMINAL",
   156  						State:  health.HealthStateTerminal,
   157  						Params: make(map[string]interface{}),
   158  					},
   159  					"SERVER_ERROR": {
   160  						Type:   "SERVER_ERROR",
   161  						State:  health.HealthStateError,
   162  						Params: make(map[string]interface{}),
   163  					},
   164  				},
   165  			},
   166  			"",
   167  			"",
   168  			523,
   169  		},
   170  		{
   171  			"one healthy check with secret",
   172  			health.HealthStatus{
   173  				Checks: map[health.CheckType]health.HealthCheckResult{
   174  					"SCHEMA_VERSION": {
   175  						Type:   "SCHEMA_VERSION",
   176  						State:  health.HealthStateHealthy,
   177  						Params: make(map[string]interface{}),
   178  					},
   179  				},
   180  			},
   181  			"top-secret",
   182  			"top-secret",
   183  			200,
   184  		},
   185  		{
   186  			"one healthy check with incorrect secret",
   187  			health.HealthStatus{
   188  				Checks: map[health.CheckType]health.HealthCheckResult{
   189  					"SCHEMA_VERSION": {
   190  						Type:   "SCHEMA_VERSION",
   191  						State:  health.HealthStateHealthy,
   192  						Params: make(map[string]interface{}),
   193  					},
   194  				},
   195  			},
   196  			"top-secret",
   197  			"bad-secret",
   198  			401,
   199  		},
   200  	} {
   201  		t.Run(test.name, func(t *testing.T) {
   202  			r := wrouter.New(whttprouter.New())
   203  			resource := wresource.New("test", r)
   204  			err := AddHealthRoutes(resource, healthCheck{value: test.metadata}, refreshable.NewString(refreshable.NewDefaultRefreshable(test.sharedSecret)))
   205  			require.NoError(t, err)
   206  
   207  			server := httptest.NewServer(r)
   208  			defer server.Close()
   209  
   210  			client := http.DefaultClient
   211  			request, err := http.NewRequest(http.MethodGet, strings.Join([]string{server.URL, status.HealthEndpoint}, "/"), nil)
   212  			require.NoError(t, err)
   213  			request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", test.actualSecret))
   214  			resp, err := client.Do(request)
   215  			require.NoError(t, err)
   216  			assert.Equal(t, test.expectedStatus, resp.StatusCode)
   217  			bytes, err := ioutil.ReadAll(resp.Body)
   218  			require.NoError(t, err)
   219  			var gotObj health.HealthStatus
   220  			err = json.Unmarshal(bytes, &gotObj)
   221  			require.NoError(t, err)
   222  
   223  			expectedChecks := test.metadata
   224  			// 401 does not return any health check data
   225  			if test.expectedStatus == 401 {
   226  				expectedChecks.Checks = map[health.CheckType]health.HealthCheckResult{}
   227  			}
   228  			assert.Equal(t, expectedChecks, gotObj)
   229  		})
   230  	}
   231  }
   232  
   233  type statusFunc func() (int, interface{})
   234  
   235  func (f statusFunc) Status() (int, interface{}) {
   236  	return f()
   237  }
   238  
   239  type healthCheck struct {
   240  	value health.HealthStatus
   241  }
   242  
   243  func (h healthCheck) HealthStatus(ctx context.Context) health.HealthStatus {
   244  	return h.value
   245  }