...

Source file src/github.com/linkerd/linkerd2/web/srv/api_handlers_test.go

Documentation: github.com/linkerd/linkerd2/web/srv

     1  package srv
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"io"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"os"
    11  	"testing"
    12  
    13  	"github.com/go-test/deep"
    14  	"github.com/julienschmidt/httprouter"
    15  	"github.com/linkerd/linkerd2/pkg/healthcheck"
    16  	vizApi "github.com/linkerd/linkerd2/viz/metrics-api"
    17  	pb "github.com/linkerd/linkerd2/viz/metrics-api/gen/viz"
    18  )
    19  
    20  type mockHealthChecker struct {
    21  	results []*healthcheck.CheckResult
    22  }
    23  
    24  func (c *mockHealthChecker) RunChecks(observer healthcheck.CheckObserver) (bool, bool) {
    25  	for _, result := range c.results {
    26  		observer(result)
    27  	}
    28  	return true, false
    29  }
    30  
    31  func TestHandleApiCheck(t *testing.T) {
    32  	// Setup handler using a mock health checker
    33  	mockResults := []*healthcheck.CheckResult{
    34  		{
    35  			Category:    healthcheck.LinkerdConfigChecks,
    36  			Description: "check3-description",
    37  			HintURL:     healthcheck.DefaultHintBaseURL + "check3-hint-anchor",
    38  			Warning:     false,
    39  			Err:         nil,
    40  		},
    41  		{
    42  			Category:    healthcheck.LinkerdConfigChecks,
    43  			Description: "check4-description-kubectl",
    44  			HintURL:     healthcheck.DefaultHintBaseURL + "check4-hint-anchor",
    45  			Warning:     true,
    46  			Err:         nil,
    47  		},
    48  		{
    49  			Category:    healthcheck.KubernetesAPIChecks,
    50  			Description: "check1-description",
    51  			HintURL:     healthcheck.DefaultHintBaseURL + "check1-hint-anchor",
    52  			Warning:     false,
    53  			Err:         nil,
    54  		},
    55  		{
    56  			Category:    healthcheck.KubernetesAPIChecks,
    57  			Description: "check2-description",
    58  			HintURL:     healthcheck.DefaultHintBaseURL + "check2-hint-anchor",
    59  			Warning:     true,
    60  			Err:         errors.New("check2-error"),
    61  		},
    62  	}
    63  	h := &handler{
    64  		hc: &mockHealthChecker{
    65  			results: mockResults,
    66  		},
    67  	}
    68  
    69  	// Handle request recording the response
    70  	req := httptest.NewRequest("GET", "/api/check", nil)
    71  	w := httptest.NewRecorder()
    72  	h.handleAPICheck(w, req, httprouter.Params{})
    73  	resp := w.Result()
    74  	defer resp.Body.Close()
    75  	body, err := io.ReadAll(resp.Body)
    76  	if err != nil {
    77  		t.Fatalf("not expecting error reading response body but got: %v", err)
    78  	}
    79  
    80  	// Check we receive the headers and body expected
    81  	expectedHeaders := http.Header{
    82  		"Content-Type": []string{"application/json"},
    83  	}
    84  	if diff := deep.Equal(resp.Header, expectedHeaders); diff != nil {
    85  		t.Errorf("Unexpected header: %v", diff)
    86  	}
    87  	apiCheckOutputGolden, err := os.ReadFile("testdata/api_check_output.json")
    88  	if err != nil {
    89  		t.Fatalf("not expecting error reading api check output golden file but got: %v", err)
    90  	}
    91  	apiCheckOutputGoldenCompact := &bytes.Buffer{}
    92  	err = json.Compact(apiCheckOutputGoldenCompact, apiCheckOutputGolden)
    93  	if err != nil {
    94  		t.Fatalf("not expecting error compacting api check output golden file but got: %v", err)
    95  	}
    96  	if !bytes.Equal(body, apiCheckOutputGoldenCompact.Bytes()) {
    97  		t.Errorf("expecting response body to be\n %s\n but got\n %s", apiCheckOutputGoldenCompact.Bytes(), body)
    98  	}
    99  }
   100  
   101  func TestHandleApiGateway(t *testing.T) {
   102  	mockAPIClient := &vizApi.MockAPIClient{
   103  		GatewaysResponseToReturn: &pb.GatewaysResponse{
   104  			Response: &pb.GatewaysResponse_Ok_{
   105  				Ok: &pb.GatewaysResponse_Ok{
   106  					GatewaysTable: &pb.GatewaysTable{
   107  						Rows: []*pb.GatewaysTable_Row{
   108  							{
   109  								Namespace:   "test_namespace",
   110  								Name:        "test_gateway",
   111  								ClusterName: "multi_cluster",
   112  								Alive:       true,
   113  							},
   114  						},
   115  					},
   116  				},
   117  			},
   118  		},
   119  	}
   120  	server := FakeServer()
   121  
   122  	handler := &handler{
   123  		render:    server.RenderTemplate,
   124  		apiClient: mockAPIClient,
   125  	}
   126  
   127  	t.Run("Returns expected gateway response", func(t *testing.T) {
   128  		recorder := httptest.NewRecorder()
   129  		req := httptest.NewRequest("GET", "/api/gateways", nil)
   130  		handler.handleAPIGateways(recorder, req, httprouter.Params{})
   131  		resp := recorder.Result()
   132  		defer resp.Body.Close()
   133  		body, err := io.ReadAll(resp.Body)
   134  		if err != nil {
   135  			t.Fatalf("not expecting error reading response body but got: %v", err)
   136  		}
   137  
   138  		if recorder.Code != http.StatusOK {
   139  			t.Errorf("Incorrect StatusCode: %+v", recorder.Code)
   140  			t.Errorf("Expected              %+v", http.StatusOK)
   141  		}
   142  
   143  		header := http.Header{
   144  			"Content-Type": []string{"application/json"},
   145  		}
   146  		if diff := deep.Equal(recorder.Header(), header); diff != nil {
   147  			t.Errorf("Unexpected header: %v", diff)
   148  		}
   149  
   150  		apiGatewayOutputGolden, err := os.ReadFile("testdata/api_gateway_output.json")
   151  		if err != nil {
   152  			t.Fatalf("not expecting error reading api check output golden file but got: %v", err)
   153  		}
   154  		apiGatewayOutputGoldenCompact := &bytes.Buffer{}
   155  		err = json.Compact(apiGatewayOutputGoldenCompact, apiGatewayOutputGolden)
   156  		if err != nil {
   157  			t.Fatalf("not expecting error compacting api check output golden file but got: %v", err)
   158  		}
   159  		bodyCompact := &bytes.Buffer{}
   160  		err = json.Compact(bodyCompact, body)
   161  		if err != nil {
   162  			t.Fatalf("failed to compact response body: %s", err)
   163  		}
   164  		if !bytes.Equal(bodyCompact.Bytes(), apiGatewayOutputGoldenCompact.Bytes()) {
   165  			t.Errorf("expecting response body to be\n %s\n but got\n %s", apiGatewayOutputGoldenCompact.Bytes(), bodyCompact.Bytes())
   166  		}
   167  	})
   168  
   169  	t.Run("Returns error when invalid timeWindow is passed", func(t *testing.T) {
   170  		recorder := httptest.NewRecorder()
   171  		req := httptest.NewRequest("GET", "/api/gateways?window=1t", nil)
   172  		handler.handleAPIGateways(recorder, req, httprouter.Params{})
   173  		resp := recorder.Result()
   174  		defer resp.Body.Close()
   175  		_, err := io.ReadAll(resp.Body)
   176  		if err != nil {
   177  			t.Fatalf("not expecting error reading response body but got: %v", err)
   178  		}
   179  		if recorder.Code == http.StatusOK {
   180  			t.Errorf("Incorrect StatusCode: %+v", recorder.Code)
   181  			t.Errorf("Expected              %+v", http.StatusInternalServerError)
   182  		}
   183  	})
   184  }
   185  

View as plain text