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
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
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
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