1// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
2// Use of this source code is governed by a MIT style
3// license that can be found in the LICENSE file.
4
5package gin
6
7import (
8	"fmt"
9	"io/ioutil"
10	"net/http"
11	"net/http/httptest"
12	"os"
13	"path/filepath"
14	"testing"
15
16	"github.com/stretchr/testify/assert"
17)
18
19func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder {
20	req, _ := http.NewRequest(method, path, nil)
21	w := httptest.NewRecorder()
22	r.ServeHTTP(w, req)
23	return w
24}
25
26func testRouteOK(method string, t *testing.T) {
27	passed := false
28	passedAny := false
29	r := New()
30	r.Any("/test2", func(c *Context) {
31		passedAny = true
32	})
33	r.Handle(method, "/test", func(c *Context) {
34		passed = true
35	})
36
37	w := performRequest(r, method, "/test")
38	assert.True(t, passed)
39	assert.Equal(t, w.Code, http.StatusOK)
40
41	performRequest(r, method, "/test2")
42	assert.True(t, passedAny)
43}
44
45// TestSingleRouteOK tests that POST route is correctly invoked.
46func testRouteNotOK(method string, t *testing.T) {
47	passed := false
48	router := New()
49	router.Handle(method, "/test_2", func(c *Context) {
50		passed = true
51	})
52
53	w := performRequest(router, method, "/test")
54
55	assert.False(t, passed)
56	assert.Equal(t, w.Code, http.StatusNotFound)
57}
58
59// TestSingleRouteOK tests that POST route is correctly invoked.
60func testRouteNotOK2(method string, t *testing.T) {
61	passed := false
62	router := New()
63	router.HandleMethodNotAllowed = true
64	var methodRoute string
65	if method == "POST" {
66		methodRoute = "GET"
67	} else {
68		methodRoute = "POST"
69	}
70	router.Handle(methodRoute, "/test", func(c *Context) {
71		passed = true
72	})
73
74	w := performRequest(router, method, "/test")
75
76	assert.False(t, passed)
77	assert.Equal(t, w.Code, http.StatusMethodNotAllowed)
78}
79
80func TestRouterMethod(t *testing.T) {
81	router := New()
82	router.PUT("/hey2", func(c *Context) {
83		c.String(200, "sup2")
84	})
85
86	router.PUT("/hey", func(c *Context) {
87		c.String(200, "called")
88	})
89
90	router.PUT("/hey3", func(c *Context) {
91		c.String(200, "sup3")
92	})
93
94	w := performRequest(router, "PUT", "/hey")
95
96	assert.Equal(t, w.Code, 200)
97	assert.Equal(t, w.Body.String(), "called")
98}
99
100func TestRouterGroupRouteOK(t *testing.T) {
101	testRouteOK("GET", t)
102	testRouteOK("POST", t)
103	testRouteOK("PUT", t)
104	testRouteOK("PATCH", t)
105	testRouteOK("HEAD", t)
106	testRouteOK("OPTIONS", t)
107	testRouteOK("DELETE", t)
108	testRouteOK("CONNECT", t)
109	testRouteOK("TRACE", t)
110}
111
112func TestRouteNotOK(t *testing.T) {
113	testRouteNotOK("GET", t)
114	testRouteNotOK("POST", t)
115	testRouteNotOK("PUT", t)
116	testRouteNotOK("PATCH", t)
117	testRouteNotOK("HEAD", t)
118	testRouteNotOK("OPTIONS", t)
119	testRouteNotOK("DELETE", t)
120	testRouteNotOK("CONNECT", t)
121	testRouteNotOK("TRACE", t)
122}
123
124func TestRouteNotOK2(t *testing.T) {
125	testRouteNotOK2("GET", t)
126	testRouteNotOK2("POST", t)
127	testRouteNotOK2("PUT", t)
128	testRouteNotOK2("PATCH", t)
129	testRouteNotOK2("HEAD", t)
130	testRouteNotOK2("OPTIONS", t)
131	testRouteNotOK2("DELETE", t)
132	testRouteNotOK2("CONNECT", t)
133	testRouteNotOK2("TRACE", t)
134}
135
136func TestRouteRedirectTrailingSlash(t *testing.T) {
137	router := New()
138	router.RedirectFixedPath = false
139	router.RedirectTrailingSlash = true
140	router.GET("/path", func(c *Context) {})
141	router.GET("/path2/", func(c *Context) {})
142	router.POST("/path3", func(c *Context) {})
143	router.PUT("/path4/", func(c *Context) {})
144
145	w := performRequest(router, "GET", "/path/")
146	assert.Equal(t, w.Header().Get("Location"), "/path")
147	assert.Equal(t, w.Code, 301)
148
149	w = performRequest(router, "GET", "/path2")
150	assert.Equal(t, w.Header().Get("Location"), "/path2/")
151	assert.Equal(t, w.Code, 301)
152
153	w = performRequest(router, "POST", "/path3/")
154	assert.Equal(t, w.Header().Get("Location"), "/path3")
155	assert.Equal(t, w.Code, 307)
156
157	w = performRequest(router, "PUT", "/path4")
158	assert.Equal(t, w.Header().Get("Location"), "/path4/")
159	assert.Equal(t, w.Code, 307)
160
161	w = performRequest(router, "GET", "/path")
162	assert.Equal(t, w.Code, 200)
163
164	w = performRequest(router, "GET", "/path2/")
165	assert.Equal(t, w.Code, 200)
166
167	w = performRequest(router, "POST", "/path3")
168	assert.Equal(t, w.Code, 200)
169
170	w = performRequest(router, "PUT", "/path4/")
171	assert.Equal(t, w.Code, 200)
172
173	router.RedirectTrailingSlash = false
174
175	w = performRequest(router, "GET", "/path/")
176	assert.Equal(t, w.Code, 404)
177	w = performRequest(router, "GET", "/path2")
178	assert.Equal(t, w.Code, 404)
179	w = performRequest(router, "POST", "/path3/")
180	assert.Equal(t, w.Code, 404)
181	w = performRequest(router, "PUT", "/path4")
182	assert.Equal(t, w.Code, 404)
183}
184
185func TestRouteRedirectFixedPath(t *testing.T) {
186	router := New()
187	router.RedirectFixedPath = true
188	router.RedirectTrailingSlash = false
189
190	router.GET("/path", func(c *Context) {})
191	router.GET("/Path2", func(c *Context) {})
192	router.POST("/PATH3", func(c *Context) {})
193	router.POST("/Path4/", func(c *Context) {})
194
195	w := performRequest(router, "GET", "/PATH")
196	assert.Equal(t, w.Header().Get("Location"), "/path")
197	assert.Equal(t, w.Code, 301)
198
199	w = performRequest(router, "GET", "/path2")
200	assert.Equal(t, w.Header().Get("Location"), "/Path2")
201	assert.Equal(t, w.Code, 301)
202
203	w = performRequest(router, "POST", "/path3")
204	assert.Equal(t, w.Header().Get("Location"), "/PATH3")
205	assert.Equal(t, w.Code, 307)
206
207	w = performRequest(router, "POST", "/path4")
208	assert.Equal(t, w.Header().Get("Location"), "/Path4/")
209	assert.Equal(t, w.Code, 307)
210}
211
212// TestContextParamsGet tests that a parameter can be parsed from the URL.
213func TestRouteParamsByName(t *testing.T) {
214	name := ""
215	lastName := ""
216	wild := ""
217	router := New()
218	router.GET("/test/:name/:last_name/*wild", func(c *Context) {
219		name = c.Params.ByName("name")
220		lastName = c.Params.ByName("last_name")
221		var ok bool
222		wild, ok = c.Params.Get("wild")
223
224		assert.True(t, ok)
225		assert.Equal(t, name, c.Param("name"))
226		assert.Equal(t, name, c.Param("name"))
227		assert.Equal(t, lastName, c.Param("last_name"))
228
229		assert.Empty(t, c.Param("wtf"))
230		assert.Empty(t, c.Params.ByName("wtf"))
231
232		wtf, ok := c.Params.Get("wtf")
233		assert.Empty(t, wtf)
234		assert.False(t, ok)
235	})
236
237	w := performRequest(router, "GET", "/test/john/smith/is/super/great")
238
239	assert.Equal(t, w.Code, 200)
240	assert.Equal(t, name, "john")
241	assert.Equal(t, lastName, "smith")
242	assert.Equal(t, wild, "/is/super/great")
243}
244
245// TestHandleStaticFile - ensure the static file handles properly
246func TestRouteStaticFile(t *testing.T) {
247	// SETUP file
248	testRoot, _ := os.Getwd()
249	f, err := ioutil.TempFile(testRoot, "")
250	if err != nil {
251		t.Error(err)
252	}
253	defer os.Remove(f.Name())
254	f.WriteString("Gin Web Framework")
255	f.Close()
256
257	dir, filename := filepath.Split(f.Name())
258
259	// SETUP gin
260	router := New()
261	router.Static("/using_static", dir)
262	router.StaticFile("/result", f.Name())
263
264	w := performRequest(router, "GET", "/using_static/"+filename)
265	w2 := performRequest(router, "GET", "/result")
266
267	assert.Equal(t, w, w2)
268	assert.Equal(t, w.Code, 200)
269	assert.Equal(t, w.Body.String(), "Gin Web Framework")
270	assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
271
272	w3 := performRequest(router, "HEAD", "/using_static/"+filename)
273	w4 := performRequest(router, "HEAD", "/result")
274
275	assert.Equal(t, w3, w4)
276	assert.Equal(t, w3.Code, 200)
277}
278
279// TestHandleStaticDir - ensure the root/sub dir handles properly
280func TestRouteStaticListingDir(t *testing.T) {
281	router := New()
282	router.StaticFS("/", Dir("./", true))
283
284	w := performRequest(router, "GET", "/")
285
286	assert.Equal(t, w.Code, 200)
287	assert.Contains(t, w.Body.String(), "gin.go")
288	assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
289}
290
291// TestHandleHeadToDir - ensure the root/sub dir handles properly
292func TestRouteStaticNoListing(t *testing.T) {
293	router := New()
294	router.Static("/", "./")
295
296	w := performRequest(router, "GET", "/")
297
298	assert.Equal(t, w.Code, 404)
299	assert.NotContains(t, w.Body.String(), "gin.go")
300}
301
302func TestRouterMiddlewareAndStatic(t *testing.T) {
303	router := New()
304	static := router.Group("/", func(c *Context) {
305		c.Writer.Header().Add("Last-Modified", "Mon, 02 Jan 2006 15:04:05 MST")
306		c.Writer.Header().Add("Expires", "Mon, 02 Jan 2006 15:04:05 MST")
307		c.Writer.Header().Add("X-GIN", "Gin Framework")
308	})
309	static.Static("/", "./")
310
311	w := performRequest(router, "GET", "/gin.go")
312
313	assert.Equal(t, w.Code, 200)
314	assert.Contains(t, w.Body.String(), "package gin")
315	assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
316	assert.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST")
317	assert.Equal(t, w.HeaderMap.Get("Expires"), "Mon, 02 Jan 2006 15:04:05 MST")
318	assert.Equal(t, w.HeaderMap.Get("x-GIN"), "Gin Framework")
319}
320
321func TestRouteNotAllowedEnabled(t *testing.T) {
322	router := New()
323	router.HandleMethodNotAllowed = true
324	router.POST("/path", func(c *Context) {})
325	w := performRequest(router, "GET", "/path")
326	assert.Equal(t, w.Code, http.StatusMethodNotAllowed)
327
328	router.NoMethod(func(c *Context) {
329		c.String(http.StatusTeapot, "responseText")
330	})
331	w = performRequest(router, "GET", "/path")
332	assert.Equal(t, w.Body.String(), "responseText")
333	assert.Equal(t, w.Code, http.StatusTeapot)
334}
335
336func TestRouteNotAllowedDisabled(t *testing.T) {
337	router := New()
338	router.HandleMethodNotAllowed = false
339	router.POST("/path", func(c *Context) {})
340	w := performRequest(router, "GET", "/path")
341	assert.Equal(t, w.Code, 404)
342
343	router.NoMethod(func(c *Context) {
344		c.String(http.StatusTeapot, "responseText")
345	})
346	w = performRequest(router, "GET", "/path")
347	assert.Equal(t, w.Body.String(), "404 page not found")
348	assert.Equal(t, w.Code, 404)
349}
350
351func TestRouterNotFound(t *testing.T) {
352	router := New()
353	router.RedirectFixedPath = true
354	router.GET("/path", func(c *Context) {})
355	router.GET("/dir/", func(c *Context) {})
356	router.GET("/", func(c *Context) {})
357
358	testRoutes := []struct {
359		route  string
360		code   int
361		header string
362	}{
363		{"/path/", 301, "map[Location:[/path]]"},   // TSR -/
364		{"/dir", 301, "map[Location:[/dir/]]"},     // TSR +/
365		{"", 301, "map[Location:[/]]"},             // TSR +/
366		{"/PATH", 301, "map[Location:[/path]]"},    // Fixed Case
367		{"/DIR/", 301, "map[Location:[/dir/]]"},    // Fixed Case
368		{"/PATH/", 301, "map[Location:[/path]]"},   // Fixed Case -/
369		{"/DIR", 301, "map[Location:[/dir/]]"},     // Fixed Case +/
370		{"/../path", 301, "map[Location:[/path]]"}, // CleanPath
371		{"/nope", 404, ""},                         // NotFound
372	}
373	for _, tr := range testRoutes {
374		w := performRequest(router, "GET", tr.route)
375		assert.Equal(t, w.Code, tr.code)
376		if w.Code != 404 {
377			assert.Equal(t, fmt.Sprint(w.Header()), tr.header)
378		}
379	}
380
381	// Test custom not found handler
382	var notFound bool
383	router.NoRoute(func(c *Context) {
384		c.AbortWithStatus(404)
385		notFound = true
386	})
387	w := performRequest(router, "GET", "/nope")
388	assert.Equal(t, w.Code, 404)
389	assert.True(t, notFound)
390
391	// Test other method than GET (want 307 instead of 301)
392	router.PATCH("/path", func(c *Context) {})
393	w = performRequest(router, "PATCH", "/path/")
394	assert.Equal(t, w.Code, 307)
395	assert.Equal(t, fmt.Sprint(w.Header()), "map[Location:[/path]]")
396
397	// Test special case where no node for the prefix "/" exists
398	router = New()
399	router.GET("/a", func(c *Context) {})
400	w = performRequest(router, "GET", "/")
401	assert.Equal(t, w.Code, 404)
402}
403
404func TestRouteRawPath(t *testing.T) {
405	route := New()
406	route.UseRawPath = true
407
408	route.POST("/project/:name/build/:num", func(c *Context) {
409		name := c.Params.ByName("name")
410		num := c.Params.ByName("num")
411
412		assert.Equal(t, c.Param("name"), name)
413		assert.Equal(t, c.Param("num"), num)
414
415		assert.Equal(t, "Some/Other/Project", name)
416		assert.Equal(t, "222", num)
417	})
418
419	w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222")
420	assert.Equal(t, w.Code, 200)
421}
422
423func TestRouteRawPathNoUnescape(t *testing.T) {
424	route := New()
425	route.UseRawPath = true
426	route.UnescapePathValues = false
427
428	route.POST("/project/:name/build/:num", func(c *Context) {
429		name := c.Params.ByName("name")
430		num := c.Params.ByName("num")
431
432		assert.Equal(t, c.Param("name"), name)
433		assert.Equal(t, c.Param("num"), num)
434
435		assert.Equal(t, "Some%2FOther%2FProject", name)
436		assert.Equal(t, "333", num)
437	})
438
439	w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333")
440	assert.Equal(t, w.Code, 200)
441}
442