diff --git a/context_binder_test.go b/context_binder_test.go new file mode 100644 index 00000000..74d1a160 --- /dev/null +++ b/context_binder_test.go @@ -0,0 +1,89 @@ +package iris + +import ( + "encoding/xml" + "net/url" + "strconv" + "testing" +) + +// Contains tests for context.ReadJSON/ReadXML/ReadFORM + +type testBinderData struct { + Username string + Mail string + Data []string `form:"mydata" json:"mydata"` +} + +type testBinderXMLData struct { + XMLName xml.Name `xml:"info"` + FirstAttr string `xml:"first,attr"` + SecondAttr string `xml:"second,attr"` + Name string `xml:"name",json:"name"` + Birth string `xml:"birth",json:"birth"` + Stars int `xml:"stars",json:"stars"` +} + +func TestBindForm(t *testing.T) { + initDefault() + Post("/form", func(ctx *Context) { + obj := testBinderData{} + err := ctx.ReadForm(&obj) + if err != nil { + t.Fatalf("Error when parsing the FORM: %s", err.Error()) + } + ctx.JSON(StatusOK, obj) + }) + + e := Tester(t) + passed := map[string]interface{}{"Username": "myusername", "Mail": "mymail@iris-go.com", "mydata": url.Values{"[0]": []string{"mydata1"}, + "[1]": []string{"mydata2"}}} + + expectedObject := testBinderData{Username: "myusername", Mail: "mymail@iris-go.com", Data: []string{"mydata1", "mydata2"}} + + e.POST("/form").WithForm(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject) +} + +func TestBindJSON(t *testing.T) { + initDefault() + Post("/json", func(ctx *Context) { + obj := testBinderData{} + err := ctx.ReadJSON(&obj) + if err != nil { + t.Fatalf("Error when parsing the JSON body: %s", err.Error()) + } + ctx.JSON(StatusOK, obj) + }) + + e := Tester(t) + passed := map[string]interface{}{"Username": "myusername", "Mail": "mymail@iris-go.com", "mydata": []string{"mydata1", "mydata2"}} + expectedObject := testBinderData{Username: "myusername", Mail: "mymail@iris-go.com", Data: []string{"mydata1", "mydata2"}} + + e.POST("/json").WithJSON(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject) +} + +func TestBindXML(t *testing.T) { + initDefault() + + Post("/xml", func(ctx *Context) { + obj := testBinderXMLData{} + err := ctx.ReadXML(&obj) + if err != nil { + t.Fatalf("Error when parsing the XML body: %s", err.Error()) + } + ctx.XML(StatusOK, obj) + }) + + e := Tester(t) + expectedObj := testBinderXMLData{ + XMLName: xml.Name{Local: "info", Space: "info"}, + FirstAttr: "this is the first attr", + SecondAttr: "this is the second attr", + Name: "Iris web framework", + Birth: "13 March 2016", + Stars: 4064, + } + // so far no WithXML or .XML like WithJSON and .JSON on httpexpect I added a feature request as post issue and we're waiting + expectedBody := `<` + expectedObj.XMLName.Local + ` first="` + expectedObj.FirstAttr + `" second="` + expectedObj.SecondAttr + `">` + expectedObj.Name + `` + expectedObj.Birth + `` + strconv.Itoa(expectedObj.Stars) + `` + e.POST("/xml").WithText(expectedBody).Expect().Status(StatusOK).Body().Equal(expectedBody) +} diff --git a/initiatory.go b/initiatory.go index 6d238e18..ec3f3186 100644 --- a/initiatory.go +++ b/initiatory.go @@ -40,6 +40,10 @@ var ( ) func init() { + initDefault() +} + +func initDefault() { Default = New() Config = Default.Config Logger = Default.Logger diff --git a/iris.go b/iris.go index 7eae811d..fc1352e2 100644 --- a/iris.go +++ b/iris.go @@ -254,7 +254,6 @@ func (s *Framework) ListenTLSWithErr(addr string, certFile string, keyFile strin s.Config.Server.ListeningAddr = addr s.Config.Server.CertFile = certFile s.Config.Server.KeyFile = keyFile - return s.openServer() } diff --git a/iris_test.go b/iris_test.go new file mode 100644 index 00000000..e14220d7 --- /dev/null +++ b/iris_test.go @@ -0,0 +1,7 @@ +package iris + +// Notes: +// +// We use Default := New() and not api := New() or just Default because we want to cover as much code as possible +// The tests are usually end-to-end, except some features like plugins, which we have normal unit testing and end-to-end tests +// diff --git a/mux_test.go b/mux_test.go new file mode 100644 index 00000000..2e3b28eb --- /dev/null +++ b/mux_test.go @@ -0,0 +1,365 @@ +package iris + +// Contains tests for the mux(Router) + +import ( + "fmt" + "strconv" + "testing" + + "github.com/gavv/httpexpect" +) + +const ( + testEnableSubdomain = false + testSubdomain = "mysubdomain.com" +) + +func testSubdomainHost() string { + return testSubdomain + strconv.Itoa(HTTPServer.Port()) +} + +func testSubdomainURL() (subdomainURL string) { + subdomainHost := testSubdomainHost() + if HTTPServer.IsSecure() { + subdomainURL = "https://" + subdomainHost + } else { + subdomainURL = "http://" + subdomainHost + } + return +} + +func subdomainTester(e *httpexpect.Expect) *httpexpect.Expect { + es := e.Builder(func(req *httpexpect.Request) { + req.WithURL(testSubdomainURL()) + }) + return es +} + +type param struct { + Key string + Value string +} + +type testRoute struct { + Method string + Path string + RequestPath string + RequestQuery string + Body string + Status int + Register bool + Params []param + URLParams []param +} + +func TestMuxSimple(t *testing.T) { + testRoutes := []testRoute{ + // FOUND - registed + {"GET", "/test_get", "/test_get", "", "hello, get!", 200, true, nil, nil}, + {"POST", "/test_post", "/test_post", "", "hello, post!", 200, true, nil, nil}, + {"PUT", "/test_put", "/test_put", "", "hello, put!", 200, true, nil, nil}, + {"DELETE", "/test_delete", "/test_delete", "", "hello, delete!", 200, true, nil, nil}, + {"HEAD", "/test_head", "/test_head", "", "hello, head!", 200, true, nil, nil}, + {"OPTIONS", "/test_options", "/test_options", "", "hello, options!", 200, true, nil, nil}, + {"CONNECT", "/test_connect", "/test_connect", "", "hello, connect!", 200, true, nil, nil}, + {"PATCH", "/test_patch", "/test_patch", "", "hello, patch!", 200, true, nil, nil}, + {"TRACE", "/test_trace", "/test_trace", "", "hello, trace!", 200, true, nil, nil}, + // NOT FOUND - not registed + {"GET", "/test_get_nofound", "/test_get_nofound", "", "Not Found", 404, false, nil, nil}, + {"POST", "/test_post_nofound", "/test_post_nofound", "", "Not Found", 404, false, nil, nil}, + {"PUT", "/test_put_nofound", "/test_put_nofound", "", "Not Found", 404, false, nil, nil}, + {"DELETE", "/test_delete_nofound", "/test_delete_nofound", "", "Not Found", 404, false, nil, nil}, + {"HEAD", "/test_head_nofound", "/test_head_nofound", "", "Not Found", 404, false, nil, nil}, + {"OPTIONS", "/test_options_nofound", "/test_options_nofound", "", "Not Found", 404, false, nil, nil}, + {"CONNECT", "/test_connect_nofound", "/test_connect_nofound", "", "Not Found", 404, false, nil, nil}, + {"PATCH", "/test_patch_nofound", "/test_patch_nofound", "", "Not Found", 404, false, nil, nil}, + {"TRACE", "/test_trace_nofound", "/test_trace_nofound", "", "Not Found", 404, false, nil, nil}, + // Parameters + {"GET", "/test_get_parameter1/:name", "/test_get_parameter1/iris", "", "name=iris", 200, true, []param{{"name", "iris"}}, nil}, + {"GET", "/test_get_parameter2/:name/details/:something", "/test_get_parameter2/iris/details/anything", "", "name=iris,something=anything", 200, true, []param{{"name", "iris"}, {"something", "anything"}}, nil}, + {"GET", "/test_get_parameter2/:name/details/:something/*else", "/test_get_parameter2/iris/details/anything/elsehere", "", "name=iris,something=anything,else=/elsehere", 200, true, []param{{"name", "iris"}, {"something", "anything"}, {"else", "elsehere"}}, nil}, + // URL Parameters + {"GET", "/test_get_urlparameter1/first", "/test_get_urlparameter1/first", "name=irisurl", "name=irisurl", 200, true, nil, []param{{"name", "irisurl"}}}, + {"GET", "/test_get_urlparameter2/second", "/test_get_urlparameter2/second", "name=irisurl&something=anything", "name=irisurl,something=anything", 200, true, nil, []param{{"name", "irisurl"}, {"something", "anything"}}}, + {"GET", "/test_get_urlparameter2/first/second/third", "/test_get_urlparameter2/first/second/third", "name=irisurl&something=anything&else=elsehere", "name=irisurl,something=anything,else=elsehere", 200, true, nil, []param{{"name", "irisurl"}, {"something", "anything"}, {"else", "elsehere"}}}, + } + + initDefault() + + for idx := range testRoutes { + r := testRoutes[idx] + if r.Register { + HandleFunc(r.Method, r.Path, func(ctx *Context) { + ctx.SetStatusCode(r.Status) + if r.Params != nil && len(r.Params) > 0 { + ctx.SetBodyString(ctx.Params.String()) + } else if r.URLParams != nil && len(r.URLParams) > 0 { + if len(r.URLParams) != len(ctx.URLParams()) { + t.Fatalf("Error when comparing length of url parameters %d != %d", len(r.URLParams), len(ctx.URLParams())) + } + paramsKeyVal := "" + for idxp, p := range r.URLParams { + val := ctx.URLParam(p.Key) + paramsKeyVal += p.Key + "=" + val + "," + if idxp == len(r.URLParams)-1 { + paramsKeyVal = paramsKeyVal[0 : len(paramsKeyVal)-1] + } + } + ctx.SetBodyString(paramsKeyVal) + } else { + ctx.SetBodyString(r.Body) + } + + }) + } + } + + e := Tester(t) + + // run the tests (1) + for idx := range testRoutes { + r := testRoutes[idx] + e.Request(r.Method, r.RequestPath).WithQueryString(r.RequestQuery). + Expect(). + Status(r.Status).Body().Equal(r.Body) + } + +} + +func TestMuxSimpleParty(t *testing.T) { + + initDefault() + + h := func(c *Context) { c.WriteString(c.HostString() + c.PathString()) } + + if testEnableSubdomain { + subdomainParty := Party(testSubdomain + ".") + { + subdomainParty.Get("/", h) + subdomainParty.Get("/path1", h) + subdomainParty.Get("/path2", h) + subdomainParty.Get("/namedpath/:param1/something/:param2", h) + subdomainParty.Get("/namedpath/:param1/something/:param2/else", h) + } + } + + // simple + p := Party("/party1") + { + p.Get("/", h) + p.Get("/path1", h) + p.Get("/path2", h) + p.Get("/namedpath/:param1/something/:param2", h) + p.Get("/namedpath/:param1/something/:param2/else", h) + } + + e := Tester(t) + + request := func(reqPath string) { + e.Request("GET", reqPath). + Expect(). + Status(StatusOK).Body().Equal(HTTPServer.Host() + reqPath) + } + + // run the tests + request("/party1/") + request("/party1/path1") + request("/party1/path2") + request("/party1/namedpath/theparam1/something/theparam2") + request("/party1/namedpath/theparam1/something/theparam2/else") + + if testEnableSubdomain { + es := subdomainTester(e) + subdomainRequest := func(reqPath string) { + es.Request("GET", reqPath). + Expect(). + Status(StatusOK).Body().Equal(testSubdomainHost() + reqPath) + } + + subdomainRequest("/") + subdomainRequest("/path1") + subdomainRequest("/path2") + subdomainRequest("/namedpath/theparam1/something/theparam2") + subdomainRequest("/namedpath/theparam1/something/theparam2/else") + } +} + +func TestMuxPathEscape(t *testing.T) { + initDefault() + + Get("/details/:name", func(ctx *Context) { + name := ctx.Param("name") + highlight := ctx.URLParam("highlight") + ctx.Text(StatusOK, fmt.Sprintf("name=%s,highlight=%s", name, highlight)) + }) + + e := Tester(t) + + e.GET("/details/Sakamoto desu ga"). + WithQuery("highlight", "text"). + Expect().Status(StatusOK).Body().Equal("name=Sakamoto desu ga,highlight=text") +} + +func TestMuxCustomErrors(t *testing.T) { + var ( + notFoundMessage = "Iris custom message for 404 not found" + internalServerMessage = "Iris custom message for 500 internal server error" + testRoutesCustomErrors = []testRoute{ + // NOT FOUND CUSTOM ERRORS - not registed + {"GET", "/test_get_nofound_custom", "/test_get_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"POST", "/test_post_nofound_custom", "/test_post_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"PUT", "/test_put_nofound_custom", "/test_put_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"DELETE", "/test_delete_nofound_custom", "/test_delete_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"HEAD", "/test_head_nofound_custom", "/test_head_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"OPTIONS", "/test_options_nofound_custom", "/test_options_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"CONNECT", "/test_connect_nofound_custom", "/test_connect_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"PATCH", "/test_patch_nofound_custom", "/test_patch_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + {"TRACE", "/test_trace_nofound_custom", "/test_trace_nofound_custom", "", notFoundMessage, 404, false, nil, nil}, + // SERVER INTERNAL ERROR 500 PANIC CUSTOM ERRORS - registed + {"GET", "/test_get_panic_custom", "/test_get_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"POST", "/test_post_panic_custom", "/test_post_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"PUT", "/test_put_panic_custom", "/test_put_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"DELETE", "/test_delete_panic_custom", "/test_delete_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"HEAD", "/test_head_panic_custom", "/test_head_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"OPTIONS", "/test_options_panic_custom", "/test_options_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"CONNECT", "/test_connect_panic_custom", "/test_connect_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"PATCH", "/test_patch_panic_custom", "/test_patch_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + {"TRACE", "/test_trace_panic_custom", "/test_trace_panic_custom", "", internalServerMessage, 500, true, nil, nil}, + } + ) + initDefault() + // first register the testRoutes needed + for _, r := range testRoutesCustomErrors { + if r.Register { + HandleFunc(r.Method, r.Path, func(ctx *Context) { + ctx.EmitError(r.Status) + }) + } + } + + // register the custom errors + OnError(404, func(ctx *Context) { + ctx.Write("%s", notFoundMessage) + }) + + OnError(500, func(ctx *Context) { + ctx.Write("%s", internalServerMessage) + }) + + // create httpexpect instance that will call fasthtpp.RequestHandler directly + e := Tester(t) + + // run the tests + for _, r := range testRoutesCustomErrors { + e.Request(r.Method, r.RequestPath). + Expect(). + Status(r.Status).Body().Equal(r.Body) + } +} + +type testUserAPI struct { + *Context +} + +// GET /users +func (u testUserAPI) Get() { + u.Write("Get Users\n") +} + +// GET /users/:param1 which its value passed to the id argument +func (u testUserAPI) GetBy(id string) { // id equals to u.Param("param1") + u.Write("Get By %s\n", id) +} + +// PUT /users +func (u testUserAPI) Put() { + u.Write("Put, name: %s\n", u.FormValue("name")) +} + +// POST /users/:param1 +func (u testUserAPI) PostBy(id string) { + u.Write("Post By %s, name: %s\n", id, u.FormValue("name")) +} + +// DELETE /users/:param1 +func (u testUserAPI) DeleteBy(id string) { + u.Write("Delete By %s\n", id) +} + +func TestMuxAPI(t *testing.T) { + initDefault() + + middlewareResponseText := "I assume that you are authenticated\n" + API("/users", testUserAPI{}, func(ctx *Context) { // optional middleware for .API + // do your work here, or render a login window if not logged in, get the user and send it to the next middleware, or do all here + ctx.Set("user", "username") + ctx.Next() + }, func(ctx *Context) { + if ctx.Get("user") == "username" { + ctx.Write(middlewareResponseText) + ctx.Next() + } else { + ctx.SetStatusCode(StatusUnauthorized) + } + }) + + e := Tester(t) + + userID := "4077" + formname := "kataras" + + e.GET("/users").Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Get Users\n") + e.GET("/users/" + userID).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Get By " + userID + "\n") + e.PUT("/users").WithFormField("name", formname).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Put, name: " + formname + "\n") + e.POST("/users/"+userID).WithFormField("name", formname).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Post By " + userID + ", name: " + formname + "\n") + e.DELETE("/users/" + userID).Expect().Status(StatusOK).Body().Equal(middlewareResponseText + "Delete By " + userID + "\n") +} + +type myTestHandlerData struct { + Sysname string // this will be the same for all requests + Version int // this will be the same for all requests + DynamicPathParameter string // this will be different for each request +} + +type myTestCustomHandler struct { + data myTestHandlerData +} + +func (m *myTestCustomHandler) Serve(ctx *Context) { + data := &m.data + data.DynamicPathParameter = ctx.Param("myparam") + ctx.JSON(StatusOK, data) +} + +func TestMuxCustomHandler(t *testing.T) { + initDefault() + myData := myTestHandlerData{ + Sysname: "Redhat", + Version: 1, + } + Handle("GET", "/custom_handler_1/:myparam", &myTestCustomHandler{myData}) + Handle("GET", "/custom_handler_2/:myparam", &myTestCustomHandler{myData}) + + e := Tester(t) + // two times per testRoute + param1 := "thisimyparam1" + expectedData1 := myData + expectedData1.DynamicPathParameter = param1 + e.GET("/custom_handler_1/" + param1).Expect().Status(StatusOK).JSON().Equal(expectedData1) + + param2 := "thisimyparam2" + expectedData2 := myData + expectedData2.DynamicPathParameter = param2 + e.GET("/custom_handler_1/" + param2).Expect().Status(StatusOK).JSON().Equal(expectedData2) + + param3 := "thisimyparam3" + expectedData3 := myData + expectedData3.DynamicPathParameter = param3 + e.GET("/custom_handler_2/" + param3).Expect().Status(StatusOK).JSON().Equal(expectedData3) + + param4 := "thisimyparam4" + expectedData4 := myData + expectedData4.DynamicPathParameter = param4 + e.GET("/custom_handler_2/" + param4).Expect().Status(StatusOK).JSON().Equal(expectedData4) +} diff --git a/plugin.go b/plugin.go index d0a49e72..4f24abec 100644 --- a/plugin.go +++ b/plugin.go @@ -214,11 +214,13 @@ type pluginContainer struct { customEvents map[string][]func() downloader *pluginDownloadManager logger *logger.Logger + mu sync.Mutex } // Add activates the plugins and if succeed then adds it to the activated plugins list func (p *pluginContainer) Add(plugins ...Plugin) error { for _, plugin := range plugins { + if p.activatedPlugins == nil { p.activatedPlugins = make([]Plugin, 0) } @@ -232,10 +234,16 @@ func (p *pluginContainer) Add(plugins ...Plugin) error { } // Activate the plugin, if no error then add it to the plugins if pluginObj, ok := plugin.(pluginActivate); ok { - err := pluginObj.Activate(p) + tempPluginContainer := *p + err := pluginObj.Activate(&tempPluginContainer) if err != nil { return errPluginActivate.Format(pName, err.Error()) } + tempActivatedPluginsLen := len(tempPluginContainer.activatedPlugins) + if tempActivatedPluginsLen != len(p.activatedPlugins)+tempActivatedPluginsLen+1 { // see test: plugin_test.go TestPluginActivate && TestPluginActivationError + p.activatedPlugins = tempPluginContainer.activatedPlugins + } + } // All ok, add it to the plugins list diff --git a/plugin_test.go b/plugin_test.go new file mode 100644 index 00000000..9b1c2f63 --- /dev/null +++ b/plugin_test.go @@ -0,0 +1,195 @@ +package iris + +import ( + "fmt" + "testing" +) + +const ( + testPluginExDescription = "Description for My test plugin" + testPluginExName = "My test plugin" +) + +type testPluginEx struct { + named, activated, descriptioned bool + prelistenran, postlistenran, precloseran bool +} + +func (t *testPluginEx) GetName() string { + fmt.Println("GetName Struct") + t.named = true + return testPluginExName +} + +func (t *testPluginEx) GetDescription() string { + fmt.Println("GetDescription Struct") + t.descriptioned = true + return testPluginExDescription +} + +func (t *testPluginEx) Activate(p PluginContainer) error { + fmt.Println("Activate Struct") + t.activated = true + return nil +} + +func (t *testPluginEx) PreListen(*Framework) { + fmt.Println("PreListen Struct") + t.prelistenran = true +} + +func (t *testPluginEx) PostListen(*Framework) { + fmt.Println("PostListen Struct") + t.postlistenran = true +} + +func (t *testPluginEx) PreClose(*Framework) { + fmt.Println("PreClose Struct") + t.precloseran = true +} + +func ExamplePlugins_Add() { + initDefault() + + Plugins.Add(PreListenFunc(func(*Framework) { + fmt.Println("PreListen Func") + })) + + Plugins.Add(PostListenFunc(func(*Framework) { + fmt.Println("PostListen Func") + })) + + Plugins.Add(PreCloseFunc(func(*Framework) { + fmt.Println("PreClose Func") + })) + + myplugin := &testPluginEx{} + Plugins.Add(myplugin) + desc := Plugins.GetDescription(myplugin) + fmt.Println(desc) + + NoListen() + CloseWithErr() + + // Output: + // GetName Struct + // Activate Struct + // GetDescription Struct + // Description for My test plugin + // PreListen Func + // PreListen Struct + // PostListen Func + // PostListen Struct + // PreClose Func + // PreClose Struct +} + +// if a plugin has GetName, then it should be registered only one time, the name exists for that reason, it's like unique ID +func TestPluginDublicateName(t *testing.T) { + var plugins pluginContainer + firstNamedPlugin := &testPluginEx{} + sameNamedPlugin := &testPluginEx{} + // err := plugins.Add(firstNamedPlugin, sameNamedPlugin) or + err := plugins.Add(firstNamedPlugin) + if err != nil { + t.Fatalf("Unexpected error when adding a plugin with name: %s", testPluginExName) + } + err = plugins.Add(sameNamedPlugin) + if err == nil { + t.Fatalf("Expected an error because of dublicate named plugin!") + } + if len(plugins.activatedPlugins) != 1 { + t.Fatalf("Expected: %d activated plugin but we got: %d", 1, len(plugins.activatedPlugins)) + } +} + +type testPluginActivationType struct { + shouldError bool +} + +func (t testPluginActivationType) Activate(p PluginContainer) error { + p.Add(&testPluginEx{}) + if t.shouldError { + return fmt.Errorf("An error happens, this plugin and the added plugins by this plugin should not be registered") + } + return nil +} + +func TestPluginActivate(t *testing.T) { + var plugins pluginContainer + myplugin := testPluginActivationType{shouldError: false} + plugins.Add(myplugin) + + if len(plugins.activatedPlugins) != 2 { // 2 because it registeres a second plugin also + t.Fatalf("Expected activated plugins to be: %d but we got: %d", 0, len(plugins.activatedPlugins)) + } +} + +// if any error returned from the Activate plugin's method, then this plugin and the plugins it registers should not be registered at all +func TestPluginActivationError(t *testing.T) { + var plugins pluginContainer + myplugin := testPluginActivationType{shouldError: true} + plugins.Add(myplugin) + + if len(plugins.activatedPlugins) > 0 { + t.Fatalf("Expected activated plugins to be: %d but we got: %d", 0, len(plugins.activatedPlugins)) + } +} + +func TestPluginEvents(t *testing.T) { + var plugins pluginContainer + var prelistenran, postlistenran, precloseran bool + + plugins.Add(PreListenFunc(func(*Framework) { + prelistenran = true + })) + + plugins.Add(PostListenFunc(func(*Framework) { + postlistenran = true + })) + + plugins.Add(PreCloseFunc(func(*Framework) { + precloseran = true + })) + + myplugin := &testPluginEx{} + plugins.Add(myplugin) + if len(plugins.activatedPlugins) != 4 { + t.Fatalf("Expected: %d plugins to be registed but we got: %d", 4, len(plugins.activatedPlugins)) + } + desc := plugins.GetDescription(myplugin) + if desc != testPluginExDescription { + t.Fatalf("Expected: %s as Description of the plugin but got: %s", testPluginExDescription, desc) + } + + plugins.DoPreListen(nil) + plugins.DoPostListen(nil) + plugins.DoPreClose(nil) + + if !prelistenran { + t.Fatalf("Expected to run PreListen Func but it doesnt!") + } + if !postlistenran { + t.Fatalf("Expected to run PostListen Func but it doesnt!") + } + if !precloseran { + t.Fatalf("Expected to run PostListen Func but it doesnt!") + } + + if !myplugin.named { + t.Fatalf("Plugin should be named with: %s!", testPluginExName) + } + if !myplugin.activated { + t.Fatalf("Plugin should be activated but it's not!") + } + if !myplugin.prelistenran { + t.Fatalf("Expected to run PreListen Struct but it doesnt!") + } + if !myplugin.postlistenran { + t.Fatalf("Expected to run PostListen Struct but it doesnt!") + } + if !myplugin.precloseran { + t.Fatalf("Expected to run PostListen Struct but it doesnt!") + } + +} diff --git a/render_test.go b/render_test.go new file mode 100644 index 00000000..d1c98401 --- /dev/null +++ b/render_test.go @@ -0,0 +1,85 @@ +package iris + +// Contains tests for render/rest & render/template + +import ( + "encoding/xml" + "strconv" + "testing" +) + +type renderTestInformationType struct { + XMLName xml.Name `xml:"info"` + FirstAttr string `xml:"first,attr"` + SecondAttr string `xml:"second,attr"` + Name string `xml:"name",json:"name"` + Birth string `xml:"birth",json:"birth"` + Stars int `xml:"stars",json:"stars"` +} + +func TestRenderRest(t *testing.T) { + initDefault() + + dataContents := []byte("Some binary data here.") + textContents := "Plain text here" + JSONPContents := map[string]string{"hello": "jsonp"} + JSONPCallback := "callbackName" + JSONXMLContents := renderTestInformationType{ + XMLName: xml.Name{Local: "info", Space: "info"}, // only need to verify that later + FirstAttr: "this is the first attr", + SecondAttr: "this is the second attr", + Name: "Iris web framework", + Birth: "13 March 2016", + Stars: 4064, + } + markdownContents := "# Hello dynamic markdown from Iris" + + Get("/data", func(ctx *Context) { + ctx.Data(StatusOK, dataContents) + }) + + Get("/text", func(ctx *Context) { + ctx.Text(StatusOK, textContents) + }) + + Get("/jsonp", func(ctx *Context) { + ctx.JSONP(StatusOK, JSONPCallback, JSONPContents) + }) + + Get("/json", func(ctx *Context) { + ctx.JSON(StatusOK, JSONXMLContents) + }) + Get("/xml", func(ctx *Context) { + ctx.XML(StatusOK, JSONXMLContents) + }) + + Get("/markdown", func(ctx *Context) { + ctx.Markdown(StatusOK, markdownContents) + }) + + e := Tester(t) + dataT := e.GET("/data").Expect().Status(StatusOK) + dataT.Header("Content-Type").Equal("application/octet-stream") + dataT.Body().Equal(string(dataContents)) + + textT := e.GET("/text").Expect().Status(StatusOK) + textT.Header("Content-Type").Equal("text/plain; charset=UTF-8") + textT.Body().Equal(textContents) + + JSONPT := e.GET("/jsonp").Expect().Status(StatusOK) + JSONPT.Header("Content-Type").Equal("application/javascript; charset=UTF-8") + JSONPT.Body().Equal(JSONPCallback + `({"hello":"jsonp"});`) + + JSONT := e.GET("/json").Expect().Status(StatusOK) + JSONT.Header("Content-Type").Equal("application/json; charset=UTF-8") + JSONT.JSON().Object().Equal(JSONXMLContents) + + XMLT := e.GET("/xml").Expect().Status(StatusOK) + XMLT.Header("Content-Type").Equal("text/xml; charset=UTF-8") + XMLT.Body().Equal(`<` + JSONXMLContents.XMLName.Local + ` first="` + JSONXMLContents.FirstAttr + `" second="` + JSONXMLContents.SecondAttr + `">` + JSONXMLContents.Name + `` + JSONXMLContents.Birth + `` + strconv.Itoa(JSONXMLContents.Stars) + ``) + + markdownT := e.GET("/markdown").Expect().Status(StatusOK) + markdownT.Header("Content-Type").Equal("text/html; charset=UTF-8") + markdownT.Body().Equal("

" + markdownContents[2:] + "

\n") + +} diff --git a/server_test.go b/server_test.go new file mode 100644 index 00000000..15720145 --- /dev/null +++ b/server_test.go @@ -0,0 +1,158 @@ +package iris + +/* +Linux: /etc/hosts +Windows: $Drive:/windows/system32/drivers/etc/hosts + +127.0.0.1 mydomain.com +127.0.0.1 mysubdomain.mydomain.com + +Windows: + go test -v +Linux: + $ su + $ go test -v +*/ + +import ( + "net/http" + "os" + "testing" + "time" + + "github.com/gavv/httpexpect" + "github.com/kataras/iris/config" +) + +const ( + testTLSCert = `-----BEGIN CERTIFICATE----- + MIIDAzCCAeugAwIBAgIJAPDsxtKV4v3uMA0GCSqGSIb3DQEBBQUAMBgxFjAUBgNV + BAMMDTEyNy4wLjAuMTo0NDMwHhcNMTYwNjI5MTMxMjU4WhcNMjYwNjI3MTMxMjU4 + WjAYMRYwFAYDVQQDDA0xMjcuMC4wLjE6NDQzMIIBIjANBgkqhkiG9w0BAQEFAAOC + AQ8AMIIBCgKCAQEA0KtAOHKrcbLwWJXgRX7XSFyu4HHHpSty4bliv8ET4sLJpbZH + XeVX05Foex7PnrurDP6e+0H5TgqqcpQM17/ZlFcyKrJcHSCgV0ZDB3Sb8RLQSLns + 8a+MOSbn1WZ7TkC7d/cWlKmasQRHQ2V/cWlGooyKNEPoGaEz8MbY0wn2spyIJwsB + dciERC6317VTXbiZdoD8QbAsT+tBvEHM2m2A7B7PQmHNehtyFNbSV5uZNodvv1uv + ZTnDa6IqpjFLb1b2HNFgwmaVPmmkLuy1l9PN+o6/DUnXKKBrfPAx4JOlqTKEQpWs + pnfacTE3sWkkmOSSFltAXfkXIJFKdS/hy5J/KQIDAQABo1AwTjAdBgNVHQ4EFgQU + zr1df/c9+NyTpmyiQO8g3a8NswYwHwYDVR0jBBgwFoAUzr1df/c9+NyTpmyiQO8g + 3a8NswYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEACG5shtMSDgCd + MNjOF+YmD+PX3Wy9J9zehgaDJ1K1oDvBbQFl7EOJl8lRMWITSws22Wxwh8UXVibL + sscKBp14dR3e7DdbwCVIX/JyrJyOaCfy2nNBdf1B06jYFsIHvP3vtBAb9bPNOTBQ + QE0Ztu9kCqgsmu0//sHuBEeA3d3E7wvDhlqRSxTLcLtgC1NSgkFvBw0JvwgpkX6s + M5WpSBZwZv8qpplxhFfqNy8Uf+xrpSW0pGfkHumehkQGC6/Ry7raganS0aHhDPK9 + Z1bEJ2com1bFFAQsm9yIXrRVMGGCtihB2Au0Q4jpEjUbzWYM+ItZyvRAGRM6Qex6 + s/jogMeRsw== + -----END CERTIFICATE----- +` + testTLSKey = `-----BEGIN RSA PRIVATE KEY----- + MIIEpQIBAAKCAQEA0KtAOHKrcbLwWJXgRX7XSFyu4HHHpSty4bliv8ET4sLJpbZH + XeVX05Foex7PnrurDP6e+0H5TgqqcpQM17/ZlFcyKrJcHSCgV0ZDB3Sb8RLQSLns + 8a+MOSbn1WZ7TkC7d/cWlKmasQRHQ2V/cWlGooyKNEPoGaEz8MbY0wn2spyIJwsB + dciERC6317VTXbiZdoD8QbAsT+tBvEHM2m2A7B7PQmHNehtyFNbSV5uZNodvv1uv + ZTnDa6IqpjFLb1b2HNFgwmaVPmmkLuy1l9PN+o6/DUnXKKBrfPAx4JOlqTKEQpWs + pnfacTE3sWkkmOSSFltAXfkXIJFKdS/hy5J/KQIDAQABAoIBAQDCd+bo9I0s8Fun + 4z3Y5oYSDTZ5O/CY0O5GyXPrSzCSM4Cj7EWEj1mTdb9Ohv9tam7WNHHLrcd+4NfK + 4ok5hLVs1vqM6h6IksB7taKATz+Jo0PzkzrsXvMqzERhEBo4aoGMIv2rXIkrEdas + S+pCsp8+nAWtAeBMCn0Slu65d16vQxwgfod6YZfvMKbvfhOIOShl9ejQ+JxVZcMw + Ti8sgvYmFUrdrEH3nCgptARwbx4QwlHGaw/cLGHdepfFsVaNQsEzc7m61fSO70m4 + NYJv48ZgjOooF5AccbEcQW9IxxikwNc+wpFYy5vDGzrBwS5zLZQFpoyMWFhtWdjx + hbmNn1jlAoGBAPs0ZjqsfDrH5ja4dQIdu5ErOccsmoHfMMBftMRqNG5neQXEmoLc + Uz8WeQ/QDf302aTua6E9iSjd7gglbFukVwMndQ1Q8Rwxz10jkXfiE32lFnqK0csx + ltruU6hOeSGSJhtGWBuNrT93G2lmy23fSG6BqOzdU4rn/2GPXy5zaxM/AoGBANSm + /E96RcBUiI6rDVqKhY+7M1yjLB41JrErL9a0Qfa6kYnaXMr84pOqVN11IjhNNTgl + g1lwxlpXZcZh7rYu9b7EEMdiWrJDQV7OxLDHopqUWkQ+3MHwqs6CxchyCq7kv9Df + IKqat7Me6Cyeo0MqcW+UMxlCRBxKQ9jqC7hDfZuXAoGBAJmyS8ImerP0TtS4M08i + JfsCOY21qqs/hbKOXCm42W+be56d1fOvHngBJf0YzRbO0sNo5Q14ew04DEWLsCq5 + +EsDv0hwd7VKfJd+BakV99ruQTyk5wutwaEeJK1bph12MD6L4aiqHJAyLeFldZ45 + +TUzu8mA+XaJz+U/NXtUPvU9AoGBALtl9M+tdy6I0Fa50ujJTe5eEGNAwK5WNKTI + 5D2XWNIvk/Yh4shXlux+nI8UnHV1RMMX++qkAYi3oE71GsKeG55jdk3fFQInVsJQ + APGw3FDRD8M4ip62ki+u+tEr/tIlcAyHtWfjNKO7RuubWVDlZFXqCiXmSdOMdsH/ + bxiREW49AoGACWev/eOzBoQJCRN6EvU2OV0s3b6f1QsPvcaH0zc6bgbBFOGmJU8v + pXhD88tsu9exptLkGVoYZjR0n0QT/2Kkyu93jVDW/80P7VCz8DKYyAJDa4CVwZxO + MlobQSunSDKx/CCJhWkbytCyh1bngAtwSAYLXavYIlJbAzx6FvtAIw4= + -----END RSA PRIVATE KEY----- +` + + testCertFilename = "mycert.cert" + testKeyFilename = "mykey.key" +) + +// Contains the server test for multi running servers +// Note: this test runs two standalone (real) servers +func TestMultiRunningServers(t *testing.T) { + host := "mydomain.com:443" // you have to add it to your hosts file( for windows, as 127.0.0.1 mydomain.com) + + // create the key and cert files on the fly, and delete them when this test finished + certFile, ferr := os.Create(testCertFilename) + if ferr != nil { + t.Fatal(ferr.Error()) + } + + keyFile, ferr := os.Create(testKeyFilename) + if ferr != nil { + t.Fatal(ferr.Error()) + } + + certFile.WriteString(testTLSCert) + keyFile.WriteString(testTLSKey) + + defer func() { + certFile.Close() + time.Sleep(350 * time.Millisecond) + os.Remove(testCertFilename) + + keyFile.Close() + time.Sleep(350 * time.Millisecond) + os.Remove(testKeyFilename) + }() + + initDefault() + Config.DisableBanner = true + + Get("/", func(ctx *Context) { + ctx.Write("Hello from %s", ctx.HostString()) + }) + + secondary := SecondaryListen(config.Server{ListeningAddr: ":80", RedirectTo: "https://" + host}) // start one secondary server + // start the main server + go func() { + err := ListenTLSWithErr(host, testCertFilename, testKeyFilename) + if err != nil { + t.Fatalf("Error on server_test ListenTLSWithErr: %s", err.Error()) + } + }() + + defer func() { + go secondary.Close() + go CloseWithErr() + close(Available) + }() + // prepare test framework + if ok := <-Available; !ok { + t.Fatal("Unexpected error: server cannot start, please report this as bug!!") + } + + handler := HTTPServer.Handler + + testConfiguration := httpexpect.Config{ + Client: &http.Client{ + Transport: httpexpect.NewFastBinder(handler), + Jar: httpexpect.NewJar(), + }, + Reporter: httpexpect.NewAssertReporter(t), + } + + if Config.Tester.Debug { + testConfiguration.Printers = []httpexpect.Printer{ + httpexpect.NewDebugPrinter(t, true), + } + } + // + + e := httpexpect.WithConfig(testConfiguration) + + e.Request("GET", "https://"+host).Expect().Status(StatusOK).Body().Equal("Hello from " + host) + e.Request("GET", "http://"+host).Expect().Status(StatusOK).Body().Equal("Hello from " + host) + +} diff --git a/sessions_test.go b/sessions_test.go new file mode 100644 index 00000000..f9488b2e --- /dev/null +++ b/sessions_test.go @@ -0,0 +1,121 @@ +package iris + +// Contains tests for sessions(sessions package) & flash messages(context) + +import ( + "testing" +) + +func TestSessions(t *testing.T) { + + values := map[string]interface{}{ + "Name": "iris", + "Months": "4", + "Secret": "dsads£2132215£%%Ssdsa", + } + + initDefault() + Config.Server.ListeningAddr = "127.0.0.1:8080" // in order to test the sessions + Config.Sessions.Cookie = "mycustomsessionid" + + writeValues := func(ctx *Context) { + sessValues := ctx.Session().GetAll() + ctx.JSON(StatusOK, sessValues) + } + + if testEnableSubdomain { + Party(testSubdomain+".").Get("/get", func(ctx *Context) { + writeValues(ctx) + }) + } + + Post("set", func(ctx *Context) { + vals := make(map[string]interface{}, 0) + if err := ctx.ReadJSON(&vals); err != nil { + t.Fatalf("Cannot readjson. Trace %s", err.Error()) + } + for k, v := range vals { + ctx.Session().Set(k, v) + } + }) + + Get("/get", func(ctx *Context) { + writeValues(ctx) + }) + + Get("/clear", func(ctx *Context) { + ctx.Session().Clear() + writeValues(ctx) + }) + + Get("/destroy", func(ctx *Context) { + ctx.SessionDestroy() + writeValues(ctx) + // the cookie and all values should be empty + }) + + e := Tester(t) + + e.POST("/set").WithJSON(values).Expect().Status(StatusOK).Cookies().NotEmpty() + e.GET("/get").Expect().Status(StatusOK).JSON().Object().Equal(values) + if testEnableSubdomain { + es := subdomainTester(e) + es.Request("GET", "/get").Expect().Status(StatusOK).JSON().Object().Equal(values) + } + + // test destory which also clears first + d := e.GET("/destroy").Expect().Status(StatusOK) + d.JSON().Object().Empty() + d.Cookies().ContainsOnly(Config.Sessions.Cookie) + // set and clear again + e.POST("/set").WithJSON(values).Expect().Status(StatusOK).Cookies().NotEmpty() + e.GET("/clear").Expect().Status(StatusOK).JSON().Object().Empty() +} + +func FlashMessagesTest(t *testing.T) { + initDefault() + values := map[string]string{"name": "kataras", "package": "iris"} + + Put("/set", func(ctx *Context) { + for k, v := range values { + ctx.SetFlash(k, v) + } + }) + + //we don't get the flash so on the next request the flash messages should be available. + Get("/get_no_getflash", func(ctx *Context) {}) + + Get("/get", func(ctx *Context) { + // one time one handler + kv := make(map[string]string) + for k := range values { + kv[k], _ = ctx.GetFlash(k) + } + //second time on the same handler + for k := range values { + kv[k], _ = ctx.GetFlash(k) + } + + }, func(ctx *Context) { + // third time on a next handler + // test the if next handler has access to them(must) because flash are request lifetime now. + kv := make(map[string]string) + for k := range values { + kv[k], _ = ctx.GetFlash(k) + } + // print them to the client for test the response also + ctx.JSON(StatusOK, kv) + }) + + e := Tester(t) + e.PUT("/set").Expect().Status(StatusOK).Cookies().NotEmpty() + // just a request which does not use the flash message, so flash messages should be available on the next request + e.GET("/get_no_getflash").Expect().Status(StatusOK).Cookies().NotEmpty() + e.GET("/get").Expect().Status(StatusOK).JSON().Object().Equal(values) + // second request ,the flash messages here should be not available and cookie has been removed + // (the true is that the cookie is removed from the first GetFlash, but is available though the whole request saved on context's values for faster get, keep that secret!)* + g := e.GET("/get").Expect().Status(StatusOK) + g.JSON().Object().Empty() + g.Cookies().Empty() + +}