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()
+
+}