github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/lorry/httpserver/server_test.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package httpserver 21 22 import ( 23 "context" 24 "encoding/json" 25 "fmt" 26 "testing" 27 28 "github.com/stretchr/testify/assert" 29 "github.com/valyala/fasthttp" 30 31 "github.com/1aal/kubeblocks/pkg/lorry/engines/models" 32 "github.com/1aal/kubeblocks/pkg/lorry/operations" 33 "github.com/1aal/kubeblocks/pkg/lorry/util" 34 ) 35 36 func mockServer(t *testing.T) *server { 37 fakeOps := map[string]operations.Operation{ 38 "fake-1": operations.NewFakeOperations(operations.FakeDefault, nil), 39 "fake-2": operations.NewFakeOperations(operations.FakePreCheck, func(ctx context.Context, request *operations.OpsRequest) error { 40 return fmt.Errorf("fake pre check error") 41 }), 42 "fake-3": operations.NewFakeOperations(operations.FakeDo, func(ctx context.Context, request *operations.OpsRequest) (*operations.OpsResponse, error) { 43 return nil, models.ErrNoImplemented 44 }), 45 "fake-4": operations.NewFakeOperations(operations.FakeDo, func(ctx context.Context, request *operations.OpsRequest) (*operations.OpsResponse, error) { 46 return nil, util.NewProbeError("fake probe error") 47 }), 48 "fake-5": operations.NewFakeOperations(operations.FakeDo, func(ctx context.Context, request *operations.OpsRequest) (*operations.OpsResponse, error) { 49 return nil, fmt.Errorf("fake do error") 50 }), 51 "fake-6": operations.NewFakeOperations(operations.FakeDo, func(ctx context.Context, request *operations.OpsRequest) (*operations.OpsResponse, error) { 52 return &operations.OpsResponse{ 53 Data: map[string]any{ 54 "data": request.Data, 55 }, 56 Metadata: map[string]string{ 57 "fake-meta": "fake", 58 }, 59 }, nil 60 }), 61 } 62 63 s := NewServer(fakeOps) 64 assert.NotNil(t, s) 65 fakeServer, ok := s.(*server) 66 assert.True(t, ok) 67 68 return fakeServer 69 } 70 71 func mockHTTPRequest(url string, method string, body string) *fasthttp.RequestCtx { 72 ctx := new(fasthttp.RequestCtx) 73 ctx.Request.SetRequestURI(url) 74 ctx.Request.Header.SetMethod(method) 75 ctx.Request.SetBodyString(body) 76 ctx.Request.Header.Set("User-Agent", "fake-agent") 77 78 return ctx 79 } 80 81 func parseErrorResponse(t *testing.T, rawErrorResponse []byte) *ErrorResponse { 82 resp := &ErrorResponse{} 83 err := json.Unmarshal(rawErrorResponse, resp) 84 assert.Nil(t, err) 85 86 return resp 87 } 88 89 func TestRouter(t *testing.T) { 90 fakeServer := mockServer(t) 91 92 handler := fakeServer.Router() 93 assert.NotNil(t, handler) 94 fakeRouterHandler := fakeServer.apiLogger(handler) 95 96 t.Run("unmarshal HTTP body failed", func(t *testing.T) { 97 ctx := mockHTTPRequest("/v1.0/fake-1", fasthttp.MethodPost, `test`) 98 fakeRouterHandler(ctx) 99 100 response := parseErrorResponse(t, ctx.Response.Body()) 101 assert.Equal(t, fasthttp.StatusBadRequest, ctx.Response.StatusCode()) 102 assert.Equal(t, "ERR_MALFORMED_REQUEST", response.ErrorCode) 103 assert.Equal(t, "unmarshal HTTP body failed: invalid character 'e' in literal true (expecting 'r')", response.Message) 104 }) 105 106 t.Run("pre check failed", func(t *testing.T) { 107 ctx := mockHTTPRequest("/v1.0/fake-2", fasthttp.MethodPost, `{"data": "test"}`) 108 fakeRouterHandler(ctx) 109 110 response := parseErrorResponse(t, ctx.Response.Body()) 111 assert.Equal(t, fasthttp.StatusInternalServerError, ctx.Response.StatusCode()) 112 assert.Equal(t, "ERR_PRECHECK_FAILED", response.ErrorCode) 113 assert.Equal(t, "operation precheck failed: fake pre check error", response.Message) 114 }) 115 116 t.Run("do check not implemented", func(t *testing.T) { 117 ctx := mockHTTPRequest("/v1.0/fake-3", fasthttp.MethodPost, `{"data": "test"}`) 118 fakeRouterHandler(ctx) 119 120 response := parseErrorResponse(t, ctx.Response.Body()) 121 assert.Equal(t, fasthttp.StatusNotImplemented, ctx.Response.StatusCode()) 122 assert.Equal(t, "ERR_OPERATION_FAILED", response.ErrorCode) 123 assert.Equal(t, "operation exec failed: no implemented", response.Message) 124 }) 125 126 t.Run("do check probe error", func(t *testing.T) { 127 ctx := mockHTTPRequest("/v1.0/fake-4", fasthttp.MethodPost, `{"data": "test"}`) 128 fakeRouterHandler(ctx) 129 130 assert.Equal(t, fasthttp.StatusNoContent, ctx.Response.StatusCode()) 131 assert.Empty(t, ctx.Response.Body()) 132 }) 133 134 t.Run("do check failed", func(t *testing.T) { 135 ctx := mockHTTPRequest("/v1.0/fake-5", fasthttp.MethodPost, `{"data": "test"}`) 136 fakeRouterHandler(ctx) 137 138 response := parseErrorResponse(t, ctx.Response.Body()) 139 assert.Equal(t, fasthttp.StatusInternalServerError, ctx.Response.StatusCode()) 140 assert.Equal(t, "ERR_OPERATION_FAILED", response.ErrorCode) 141 assert.Equal(t, "operation exec failed: fake do error", response.Message) 142 }) 143 144 t.Run("return meta data", func(t *testing.T) { 145 ctx := mockHTTPRequest("/v1.0/fake-6", fasthttp.MethodPost, `{"data": "test"}`) 146 fakeRouterHandler(ctx) 147 148 assert.Equal(t, fasthttp.StatusOK, ctx.Response.StatusCode()) 149 assert.Equal(t, string(ctx.Response.Body()), `{"data":"InRlc3Qi"}`) 150 assert.Equal(t, []byte("fake"), ctx.Response.Header.Peek("KB.fake-meta")) 151 }) 152 } 153 154 func TestStartNonBlocking(t *testing.T) { 155 fakeServer := mockServer(t) 156 157 err := fakeServer.StartNonBlocking() 158 assert.Nil(t, err) 159 err = fakeServer.Close() 160 assert.Nil(t, err) 161 }