github.com/instill-ai/component@v0.16.0-beta/pkg/connector/util/httpclient/httpclient_test.go (about) 1 package httpclient 2 3 import ( 4 "fmt" 5 "io" 6 "net/http" 7 "net/http/httptest" 8 "testing" 9 10 qt "github.com/frankban/quicktest" 11 "go.uber.org/zap" 12 "go.uber.org/zap/zaptest/observer" 13 14 "github.com/instill-ai/x/errmsg" 15 ) 16 17 func TestClient_SendReqAndUnmarshal(t *testing.T) { 18 c := qt.New(t) 19 20 const testName = "Pokédex" 21 const path = "/137" 22 data := struct{ Name string }{Name: "Porygon"} 23 24 c.Run("ok - with default headers", func(c *qt.C) { 25 h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 26 c.Check(r.URL.Path, qt.Equals, path) 27 28 c.Check(r.Header.Get("Content-Type"), qt.Equals, MIMETypeJSON) 29 c.Check(r.Header.Get("Accept"), qt.Equals, MIMETypeJSON) 30 31 c.Assert(r.Body, qt.IsNotNil) 32 defer r.Body.Close() 33 34 body, err := io.ReadAll(r.Body) 35 c.Assert(err, qt.IsNil) 36 c.Check(body, qt.JSONEquals, data) 37 38 w.Header().Set("Content-Type", "application/json") 39 fmt.Fprintln(w, `{"added": 1}`) 40 }) 41 42 srv := httptest.NewServer(h) 43 c.Cleanup(srv.Close) 44 45 client := New(testName, srv.URL) 46 47 var got okBody 48 resp, err := client.R(). 49 SetBody(data). 50 SetResult(&got). 51 Post(path) 52 53 c.Assert(err, qt.IsNil) 54 c.Check(resp.IsError(), qt.IsFalse) 55 c.Check(got.Added, qt.Equals, 1) 56 }) 57 58 c.Run("nok - client error", func(c *qt.C) { 59 zCore, zLogs := observer.New(zap.InfoLevel) 60 host := "https://uninitialized.server.zz" 61 62 client := New(testName, host, WithLogger(zap.New(zCore))) 63 64 _, err := client.R().Post(path) 65 c.Check(err, qt.ErrorMatches, ".*no such host*") 66 67 logs := zLogs.All() 68 c.Assert(logs, qt.HasLen, 1) 69 70 entry := logs[0].ContextMap() 71 c.Check(err, qt.ErrorMatches, fmt.Sprintf(".*%s", entry["error"])) 72 c.Check(entry["url"], qt.Equals, host+path) 73 }) 74 75 testcases := []struct { 76 name string 77 gotStatus int 78 gotBody string 79 gotContentType string 80 wantIssue string 81 wantLogFields []string 82 }{ 83 { 84 name: "nok - 401 (unexpected response body)", 85 gotStatus: http.StatusUnauthorized, 86 gotContentType: "plain/text", 87 gotBody: `Incorrect API key`, 88 wantIssue: fmt.Sprintf("%s responded with a 401 status code. Incorrect API key", testName), 89 wantLogFields: []string{"url", "body", "status"}, 90 }, 91 { 92 name: "nok - 401 (no response body)", 93 gotStatus: http.StatusUnauthorized, 94 wantIssue: fmt.Sprintf("%s responded with a 401 status code. Please refer to %s's API reference for more information.", testName, testName), 95 wantLogFields: []string{"url", "body", "status"}, 96 }, 97 { 98 name: "nok - 401", 99 gotStatus: http.StatusUnauthorized, 100 gotContentType: "application/json", 101 gotBody: `{ "message": "Incorrect API key provided." }`, 102 wantIssue: fmt.Sprintf("%s responded with a 401 status code. Incorrect API key provided.", testName), 103 wantLogFields: []string{"url", "body", "status"}, 104 }, 105 { 106 name: "nok - JSON error", 107 gotStatus: http.StatusOK, 108 gotContentType: "application/json", 109 gotBody: `{ `, 110 wantLogFields: []string{"url", "body", "error"}, 111 }, 112 } 113 114 for _, tc := range testcases { 115 c.Run(tc.name, func(c *qt.C) { 116 h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 117 w.Header().Set("Content-Type", tc.gotContentType) 118 w.WriteHeader(tc.gotStatus) 119 fmt.Fprintln(w, tc.gotBody) 120 }) 121 122 srv := httptest.NewServer(h) 123 c.Cleanup(srv.Close) 124 125 var errResp errBody 126 zCore, zLogs := observer.New(zap.InfoLevel) 127 client := New(testName, srv.URL, 128 WithLogger(zap.New(zCore)), 129 WithEndUserError(errResp), 130 ) 131 132 _, err := client.R().SetResult(new(okBody)).Post(path) 133 c.Check(err, qt.IsNotNil) 134 c.Check(errmsg.Message(err), qt.Equals, tc.wantIssue) 135 136 // Error log contains desired keys. 137 for _, k := range tc.wantLogFields { 138 logs := zLogs.FilterFieldKey(k) 139 c.Check(logs.Len(), qt.Equals, 1, qt.Commentf("missing field in log: %s", k)) 140 } 141 142 // All logs contain the "name" key. Sometimes (e.g. on 143 // unmarshalling error) we'll have > 1 log so the assertion above 144 // is too particular. 145 logs := zLogs.FilterFieldKey("name") 146 c.Assert(logs.Len(), qt.Not(qt.Equals), 0) 147 c.Check(logs.All()[0].ContextMap()["name"], qt.Equals, testName) 148 }) 149 } 150 } 151 152 type okBody struct { 153 Added int `json:"added"` 154 } 155 156 // errBody is the error paylaod of the test API. 157 type errBody struct { 158 Msg string `json:"message"` 159 } 160 161 // Message is a way to access the error message from the error payload. 162 func (e errBody) Message() string { 163 return e.Msg 164 }