github.com/GuanceCloud/cliutils@v1.1.21/network/http/err_test.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the MIT License.
     3  // This product includes software developed at Guance Cloud (https://www.guance.com/).
     4  // Copyright 2021-present Guance, Inc.
     5  
     6  package http
     7  
     8  import (
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"net/http"
    14  	"net/http/httptest"
    15  	"testing"
    16  	"time"
    17  
    18  	tu "github.com/GuanceCloud/cliutils/testutil"
    19  	"github.com/gin-gonic/gin"
    20  	"github.com/stretchr/testify/assert"
    21  )
    22  
    23  func TestBytesBody(t *testing.T) {
    24  	errOK := NewNamespaceErr(nil, http.StatusOK, "")
    25  	bytesBody := "this is bytes response body"
    26  
    27  	router := gin.New()
    28  	g := router.Group("")
    29  	g.GET("/bytes-body", func(c *gin.Context) {
    30  		c.Writer.Header().Set("Content-Type", "application/octet-stream")
    31  		c.Writer.Header().Set("X-Latest-Time", time.Now().String())
    32  		errOK.HttpBody(c, []byte(bytesBody))
    33  	})
    34  
    35  	ts := httptest.NewServer(router)
    36  
    37  	defer ts.Close()
    38  
    39  	time.Sleep(time.Second)
    40  
    41  	resp, err := http.Get(fmt.Sprintf("%s%s", ts.URL, "/bytes-body"))
    42  	if err != nil {
    43  		t.Error(err)
    44  	}
    45  
    46  	defer resp.Body.Close()
    47  
    48  	b, err := io.ReadAll(resp.Body)
    49  	if err != nil {
    50  		t.Error(err)
    51  	}
    52  
    53  	for k, v := range resp.Header {
    54  		t.Logf("%s: %v", k, v)
    55  	}
    56  
    57  	tu.Equals(t, bytesBody, string(b))
    58  }
    59  
    60  func TestNoSniff(t *testing.T) {
    61  	errOK := NewNamespaceErr(nil, http.StatusOK, "")
    62  	bytesBody := "this is bytes response body"
    63  
    64  	router := gin.New()
    65  	g := router.Group("")
    66  	g.GET("/bytes-body", func(c *gin.Context) {
    67  		c.Writer.Header().Set("Content-Type", "application/octet-stream")
    68  		c.Writer.Header().Set("X-Latest-Time", time.Now().String())
    69  		errOK.HttpBody(c, []byte(bytesBody))
    70  	})
    71  
    72  	g.GET("/obj-body", func(c *gin.Context) {
    73  		errOK.HttpBody(c, map[string]string{})
    74  	})
    75  
    76  	g.GET("/err-body", func(c *gin.Context) {
    77  		HttpErr(c, fmt.Errorf("mocked error"))
    78  	})
    79  
    80  	ts := httptest.NewServer(router)
    81  	defer ts.Close()
    82  	time.Sleep(time.Second)
    83  
    84  	// --------------------
    85  
    86  	for _, x := range []string{
    87  		"/bytes-body", "/obj-body", "/err-body",
    88  	} {
    89  		t.Run(x, func(t *testing.T) {
    90  			resp, err := http.Get(fmt.Sprintf("%s%s", ts.URL, x))
    91  			if err != nil {
    92  				t.Error(err)
    93  			}
    94  			assert.Equal(t, "nosniff", resp.Header.Get("X-Content-Type-Options"))
    95  			for k := range resp.Header {
    96  				t.Logf("%s: %s", k, resp.Header.Get(k))
    97  			}
    98  			body, _ := io.ReadAll(resp.Body)
    99  			t.Logf("body: %s", body)
   100  			resp.Body.Close()
   101  		})
   102  	}
   103  }
   104  
   105  func TestHTTPErr(t *testing.T) {
   106  	errTest := NewNamespaceErr(errors.New("test error"), http.StatusForbidden, "testing")
   107  	errOK := NewNamespaceErr(nil, http.StatusOK, "")
   108  
   109  	DefaultNamespace = "testing2"
   110  	errTest2 := NewErr(errors.New("test error2"), http.StatusForbidden)
   111  
   112  	router := gin.New()
   113  	g := router.Group("")
   114  
   115  	okbody := map[string]interface{}{
   116  		"data1": 1,
   117  		"data2": "abc",
   118  	}
   119  
   120  	g.GET("/err", func(c *gin.Context) { HttpErr(c, errTest) })
   121  	g.GET("/err2", func(c *gin.Context) { HttpErr(c, errTest2) })
   122  	g.GET("/err3", func(c *gin.Context) { HttpErr(c, fmt.Errorf("500 error")) })
   123  	g.GET("/errf", func(c *gin.Context) { HttpErrf(c, errTest, "%s: %s", "this is a test error", "ignore me") })
   124  	g.GET("/ok", func(c *gin.Context) { errOK.WriteBody(c, okbody) })
   125  	g.GET("/ok2", func(c *gin.Context) { errOK.HttpBody(c, okbody) })
   126  	g.GET("/oknilbody", func(c *gin.Context) { errOK.HttpBody(c, nil) })
   127  	g.GET("/errmsg", func(c *gin.Context) { err := Error(errTest, "this is a error with specific message"); HttpErr(c, err) })
   128  	g.GET("/errfmsg", func(c *gin.Context) {
   129  		err := Errorf(errTest, "%s: %v", "this is a message with fmt", map[string]int{"abc": 123})
   130  		HttpErr(c, err)
   131  	})
   132  	g.GET("/errfmsg-with-nil-args", func(c *gin.Context) {
   133  		err := Errorf(ErrTooManyRequest, "Errorf without args")
   134  		HttpErr(c, err)
   135  	})
   136  
   137  	srv := http.Server{
   138  		Addr:    ":8090",
   139  		Handler: router,
   140  	}
   141  
   142  	go func() {
   143  		if e := srv.ListenAndServe(); e != nil && errors.Is(e, http.ErrServerClosed) {
   144  			t.Log(e)
   145  		}
   146  	}()
   147  
   148  	time.Sleep(time.Second)
   149  	defer srv.Close()
   150  
   151  	cases := []struct {
   152  		u      string
   153  		expect string
   154  	}{
   155  		{
   156  			u:      "http://localhost:8090/errmsg",
   157  			expect: `{"error_code":"testing.testError","message":"this is a error with specific message"}`,
   158  		},
   159  
   160  		{
   161  			u:      "http://localhost:8090/errfmsg",
   162  			expect: `{"error_code":"testing.testError","message":"this is a message with fmt: map[abc:123]"}`,
   163  		},
   164  
   165  		{
   166  			u: "http://localhost:8090/err",
   167  			expect: func() string {
   168  				j, err := json.Marshal(errTest)
   169  				if err != nil {
   170  					t.Fatal(err)
   171  				}
   172  				return string(j)
   173  			}(),
   174  		},
   175  		{
   176  			u: "http://localhost:8090/err2",
   177  			expect: func() string {
   178  				j, err := json.Marshal(errTest2)
   179  				if err != nil {
   180  					t.Fatal(err)
   181  				}
   182  				return string(j)
   183  			}(),
   184  		},
   185  		{
   186  			u:      "http://localhost:8090/err3",
   187  			expect: `{"error_code":"testing2.500Error"}`,
   188  		},
   189  		{
   190  			u:      "http://localhost:8090/errf",
   191  			expect: `{"error_code":"testing.testError","message":"this is a test error: ignore me"}`,
   192  		},
   193  		{
   194  			u: "http://localhost:8090/ok",
   195  			expect: func() string {
   196  				j, err := json.Marshal(okbody)
   197  				if err != nil {
   198  					t.Fatal(err)
   199  				}
   200  				return string(j)
   201  			}(),
   202  		},
   203  
   204  		{
   205  			u: "http://localhost:8090/ok2",
   206  			expect: func() string {
   207  				x := struct {
   208  					Content interface{} `json:"content"`
   209  				}{
   210  					Content: okbody,
   211  				}
   212  
   213  				j, err := json.Marshal(x)
   214  				if err != nil {
   215  					t.Fatal(err)
   216  				}
   217  				return string(j)
   218  			}(),
   219  		},
   220  
   221  		{
   222  			u:      "http://localhost:8090/oknilbody",
   223  			expect: "",
   224  		},
   225  		{
   226  			u: "http://localhost:8090/errfmsg-with-nil-args",
   227  			expect: func() string {
   228  				msg := `{"error_code":"reachMaxAPIRateLimit","message":"Errorf without args"}`
   229  				var x interface{}
   230  				if err := json.Unmarshal([]byte(msg), &x); err != nil {
   231  					t.Fatal(err)
   232  				}
   233  				j, err := json.Marshal(x)
   234  				if err != nil {
   235  					t.Fatal(err)
   236  				}
   237  				return string(j)
   238  			}(),
   239  		},
   240  	}
   241  
   242  	for _, tc := range cases {
   243  		t.Run(tc.u, func(t *testing.T) {
   244  			resp, err := http.Get(tc.u)
   245  			if err != nil {
   246  				t.Logf("get error: %s, ignored", err)
   247  				return
   248  			}
   249  
   250  			for k, v := range resp.Header {
   251  				t.Logf("%s: %v", k, v)
   252  			}
   253  
   254  			if resp.Body != nil {
   255  				body, err := io.ReadAll(resp.Body)
   256  				if err != nil {
   257  					t.Error(err)
   258  					return
   259  				}
   260  
   261  				tu.Equals(t, tc.expect, string(body))
   262  				resp.Body.Close()
   263  			} else {
   264  				t.Error("body should not be nil")
   265  			}
   266  		})
   267  	}
   268  }
   269  
   270  func TestErrorf(t *testing.T) {
   271  	var nilArgs []interface{} = nil
   272  	format := "sprintf with nil args"
   273  	str := fmt.Sprintf(format, nilArgs...)
   274  	fmt.Println(str)
   275  
   276  	errWithEmptyFmt := Errorf(NewErr(errors.New("bad gateway"), http.StatusBadGateway), "")
   277  	assert.Nil(t, errWithEmptyFmt.Args)
   278  
   279  	errWithoutArgs := Errorf(ErrUnexpectedInternalServerError, "Errorf without args")
   280  	assert.Nil(t, errWithoutArgs.Args)
   281  
   282  	errWithArgs := Errorf(ErrTooManyRequest, "Errorf with args: %s", "###ERROR###")
   283  	assert.NotNil(t, errWithArgs.Args)
   284  
   285  	g := gin.New()
   286  
   287  	type testCase struct {
   288  		name        string
   289  		url         string
   290  		err         *MsgError
   291  		expectedMsg string
   292  	}
   293  
   294  	cases := []testCase{
   295  		{
   296  			name:        "Errorf With Empty fmt",
   297  			url:         "/errorf-with-empty-fmt",
   298  			err:         errWithEmptyFmt,
   299  			expectedMsg: "",
   300  		},
   301  		{
   302  			name:        "Errorf without args",
   303  			url:         "/errorf-without-args",
   304  			err:         errWithoutArgs,
   305  			expectedMsg: "Errorf without args",
   306  		},
   307  		{
   308  			name:        "Errorf with args",
   309  			url:         "/errorf-with-args",
   310  			err:         errWithArgs,
   311  			expectedMsg: "Errorf with args: ###ERROR###",
   312  		},
   313  	}
   314  
   315  	for i := range cases {
   316  		tc := cases[i]
   317  		g.GET(tc.url, func(c *gin.Context) {
   318  			HttpErr(c, tc.err)
   319  		})
   320  	}
   321  
   322  	type response struct {
   323  		ErrCode string `json:"error_code"`
   324  		Message string `json:"message"`
   325  	}
   326  
   327  	for _, tc := range cases {
   328  		t.Run(tc.name, func(t *testing.T) {
   329  			resp := httptest.NewRecorder()
   330  			req := httptest.NewRequest(http.MethodGet, tc.url, nil)
   331  			g.ServeHTTP(resp, req)
   332  
   333  			assert.Equal(t, resp.Result().StatusCode, tc.err.HttpCode)
   334  
   335  			var rp response
   336  			decoder := json.NewDecoder(resp.Body)
   337  			err := decoder.Decode(&rp)
   338  			assert.NoError(t, err)
   339  
   340  			assert.NotEmpty(t, rp.ErrCode)
   341  			assert.Equal(t, tc.expectedMsg, rp.Message)
   342  		})
   343  	}
   344  }