github.com/jayanpraveen/tildly@v0.0.0-20221205145005-1db0c7d7e885/handler/url_handler_test.go (about) 1 package handler 2 3 import ( 4 "fmt" 5 "net/http" 6 "net/http/httptest" 7 "os" 8 "strings" 9 "testing" 10 11 "github.com/google/go-cmp/cmp" 12 m "github.com/jayanpraveen/tildly/entity" 13 ) 14 15 type HandleTester func(method string, url string, body string) *httptest.ResponseRecorder 16 17 func generateHandleTester(t *testing.T, handleFunc http.Handler, expSC int) HandleTester { 18 return func(method string, url string, body string) *httptest.ResponseRecorder { 19 20 if url == "" { 21 url = "/" 22 } 23 24 if method == "" { 25 method = http.MethodGet 26 } 27 28 res := httptest.NewRecorder() 29 req := httptest.NewRequest(method, url, strings.NewReader(body)) 30 31 // Calling the given handler 32 handleFunc.ServeHTTP(res, req) 33 34 // Check http status 35 assertEquals(t, expSC, res.Code) 36 37 return res 38 } 39 } 40 41 /* 42 * This functions follows the convention similar to JUnit's `assertEquals` method. 43 * The exp or expected is the value the user expects from the program (the known one). 44 * The act or actual is the value the program produces (the unknown one). 45 */ 46 func assertEquals(t *testing.T, exp interface{}, act interface{}) { 47 if !cmp.Equal(exp, act) { 48 t.Errorf(" Expected: %v, Actual: %v string", exp, act) 49 } 50 } 51 52 type MockService struct { 53 SaveUrlFunc func(longUrl string, expireAt int64) (string, error) 54 GetUrlByHashFunc func(hash string) (*m.Url, error) 55 } 56 57 func (m *MockService) SaveUrl(longUrl string, exipreAt int64) (string, error) { 58 return m.SaveUrlFunc(longUrl, 1257894000) 59 } 60 61 func (m *MockService) GetUrlByHash(hash string) (*m.Url, error) { 62 return m.GetUrlByHashFunc(hash) 63 } 64 65 func Test_handleIndex(t *testing.T) { 66 res := httptest.NewRecorder() 67 req := httptest.NewRequest(http.MethodGet, "/", nil) 68 uh := UrlHandler{} 69 h := uh.handleIndex() 70 h(res, req) 71 72 // Check http status 73 assertEquals(t, http.StatusOK, res.Code) 74 75 // Check response output 76 assertEquals(t, []byte("tildly !\n"), res.Body.Bytes()) 77 } 78 79 func Test_handleLongUrl(t *testing.T) { 80 81 t.Run("post valid url", func(t *testing.T) { 82 srv := &MockService{ 83 SaveUrlFunc: func(longUrl string, exipreAt int64) (string, error) { 84 return "UIOP", nil 85 }, 86 } 87 uh := NewUrlHandler(srv) 88 h := uh.handleLongUrl() 89 90 expSC := http.StatusCreated 91 expBody := "short-url: UIOP" 92 93 method := http.MethodPost 94 url := "/api/longUrl" 95 body := `{"longUrl": "http://go.dev/docs"}` 96 97 gh := generateHandleTester(t, h, expSC) 98 res := gh(method, url, body) 99 100 // Check response output 101 assertEquals(t, expBody, res.Body.String()) 102 103 }) 104 105 t.Run("post invalid url", func(t *testing.T) { 106 srv := &MockService{ 107 SaveUrlFunc: func(longUrl string, exipreAt int64) (string, error) { 108 return "", fmt.Errorf("") 109 }, 110 } 111 uh := NewUrlHandler(srv) 112 h := uh.handleLongUrl() 113 114 expSC := http.StatusBadRequest 115 expBody := fmt.Sprintln(`{"status":400,"msg":"Not a valid URL"}`) // Using `ln` becuase Json.NewDecoder adds \n at eof while marshlling 116 117 method := http.MethodPost 118 url := "/api/longUrl" 119 body := `{"longUrl": "htt/go.dev/docs"}` 120 121 gh := generateHandleTester(t, h, expSC) 122 res := gh(method, url, body) 123 124 // Check response output 125 assertEquals(t, expBody, res.Body.String()) 126 127 // when given empty url 128 body = `{"longUrl": ""}` 129 res = gh(method, url, body) 130 assertEquals(t, expBody, res.Body.String()) 131 132 }) 133 134 t.Run("post invalid JSON", func(t *testing.T) { 135 srv := &MockService{ 136 SaveUrlFunc: func(longUrl string, exipreAt int64) (string, error) { 137 return "", nil 138 }, 139 } 140 uh := NewUrlHandler(srv) 141 h := uh.handleLongUrl() 142 143 expSC := http.StatusBadRequest 144 expBody := fmt.Sprintln(`{"status":400,"msg":"Not a proper JSON format"}`) 145 146 method := http.MethodPost 147 url := "/api/longUrl" 148 body := `{"longUrl" "htt/go.dev/docs"}` // Invalid JSON: missing ':' in longUrl 149 150 gh := generateHandleTester(t, h, expSC) 151 res := gh(method, url, body) 152 153 // Check response output 154 assertEquals(t, expBody, res.Body.String()) 155 }) 156 157 t.Run("returns interal server error", func(t *testing.T) { 158 srv := &MockService{ 159 SaveUrlFunc: func(longUrl string, exipreAt int64) (string, error) { 160 return "", fmt.Errorf("Failed to save url") 161 }, 162 } 163 164 uh := NewUrlHandler(srv) 165 h := uh.handleLongUrl() 166 167 expSC := http.StatusInternalServerError 168 expBody := fmt.Sprintln(`{"status":500,"msg":"Internal server error"}`) 169 170 method := http.MethodPost 171 url := "/api/longUrl" 172 body := `{"longUrl": "http://go.dev/docs"}` 173 174 gh := generateHandleTester(t, h, expSC) 175 res := gh(method, url, body) 176 177 // Check response output 178 assertEquals(t, expBody, res.Body.String()) 179 }) 180 181 } 182 183 func Test_handleShortUrl(t *testing.T) { 184 185 t.Run("short url exist", func(t *testing.T) { 186 srv := &MockService{ 187 GetUrlByHashFunc: func(hash string) (*m.Url, error) { 188 return &m.Url{ 189 Hash: "XikHsqW", 190 LongUrl: "https://go.dev", 191 CreatedAt: 1257894000, 192 }, nil 193 }, 194 } 195 196 uh := NewUrlHandler(srv) 197 h := uh.handleShortUrl() 198 199 expSC := http.StatusPermanentRedirect 200 201 method := http.MethodGet 202 url := "/XikHsqW" 203 body := "" 204 205 gh := generateHandleTester(t, h, expSC) 206 gh(method, url, body) 207 208 }) 209 210 t.Run("short url doesn't exist", func(t *testing.T) { 211 srv := &MockService{ 212 GetUrlByHashFunc: func(hash string) (*m.Url, error) { 213 return nil, fmt.Errorf("short url not found") 214 }, 215 } 216 217 uh := NewUrlHandler(srv) 218 h := uh.handleShortUrl() 219 220 expSC := http.StatusNotFound 221 222 method := http.MethodGet 223 url := "/XikHsqW" 224 body := "" 225 226 gh := generateHandleTester(t, h, expSC) 227 gh(method, url, body) 228 }) 229 } 230 231 // test fixture 232 func init() { 233 if err := os.Chdir("./testdata"); err != nil { 234 panic(err) 235 } 236 }