From 805f92468af6178ced47dded8a9dbe47c1d7eab2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 28 Mar 2024 12:22:45 +0100 Subject: [PATCH] Added server tests --- Context_test.go | 8 ++++---- Header.go | 7 +++++++ README.md | 16 ++++++++++++--- Request.go | 2 +- Request_test.go | 21 +++++++++++++++++--- Response.go | 4 ++-- Response_test.go | 26 ++++++++++++------------ Server.go | 11 ++++++----- Server_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ http.go | 6 ------ 10 files changed, 116 insertions(+), 36 deletions(-) create mode 100644 Header.go create mode 100644 Server_test.go diff --git a/Context_test.go b/Context_test.go index c4d5b31..05bc24f 100644 --- a/Context_test.go +++ b/Context_test.go @@ -15,7 +15,7 @@ func TestBytes(t *testing.T) { return ctx.Bytes([]byte("Hello")) }) - response := s.Request("GET", "/", nil) + response := s.Request("GET", "/", nil, nil) assert.Equal(t, response.Status(), 200) assert.Equal(t, string(response.Body()), "Hello") } @@ -27,7 +27,7 @@ func TestString(t *testing.T) { return ctx.String("Hello") }) - response := s.Request("GET", "/", nil) + response := s.Request("GET", "/", nil, nil) assert.Equal(t, response.Status(), 200) assert.Equal(t, string(response.Body()), "Hello") } @@ -39,7 +39,7 @@ func TestError(t *testing.T) { return ctx.Status(401).Error("Not logged in") }) - response := s.Request("GET", "/", nil) + response := s.Request("GET", "/", nil, nil) assert.Equal(t, response.Status(), 401) assert.Equal(t, string(response.Body()), "") } @@ -51,7 +51,7 @@ func TestErrorMultiple(t *testing.T) { return ctx.Status(401).Error("Not logged in", errors.New("Missing auth token")) }) - response := s.Request("GET", "/", nil) + response := s.Request("GET", "/", nil, nil) assert.Equal(t, response.Status(), 401) assert.Equal(t, string(response.Body()), "") } diff --git a/Header.go b/Header.go new file mode 100644 index 0000000..b6699fe --- /dev/null +++ b/Header.go @@ -0,0 +1,7 @@ +package web + +// Header is used to store HTTP headers. +type Header struct { + Key string + Value string +} diff --git a/README.md b/README.md index 1b715b6..515dccd 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,22 @@ s.Run(":8080") ## Tests ``` -PASS: TestRouter -PASS: TestMiddleware +PASS: TestBytes +PASS: TestString +PASS: TestError +PASS: TestErrorMultiple +PASS: TestRequest +PASS: TestRequestHeader +PASS: TestRequestParam +PASS: TestWrite +PASS: TestWriteString +PASS: TestResponseCompression +PASS: TestResponseHeader +PASS: TestResponseHeaderOverwrite PASS: TestPanic PASS: TestRun PASS: TestUnavailablePort -coverage: 100.0% of statements +coverage: 95.5% of statements ``` ## Benchmarks diff --git a/Request.go b/Request.go index f820237..9ec8ef9 100644 --- a/Request.go +++ b/Request.go @@ -24,7 +24,7 @@ type request struct { method string path string query string - headers []header + headers []Header body []byte params []router.Parameter } diff --git a/Request_test.go b/Request_test.go index cbeda40..6a08fd0 100644 --- a/Request_test.go +++ b/Request_test.go @@ -20,20 +20,35 @@ func TestRequest(t *testing.T) { return ctx.String(fmt.Sprintf("%s %s %s %s", method, scheme, host, path)) }) - response := s.Request("GET", "http://example.com/request?x=1", nil) + response := s.Request("GET", "http://example.com/request?x=1", []web.Header{{"Accept", "*/*"}}, nil) assert.Equal(t, response.Status(), 200) assert.Equal(t, string(response.Body()), "GET http example.com /request") } +func TestRequestHeader(t *testing.T) { + s := web.NewServer() + + s.Get("/", func(ctx web.Context) error { + accept := ctx.Request().Header("Accept") + empty := ctx.Request().Header("") + return ctx.String(accept + empty) + }) + + response := s.Request("GET", "/", []web.Header{{"Accept", "*/*"}}, nil) + assert.Equal(t, response.Status(), 200) + assert.Equal(t, string(response.Body()), "*/*") +} + func TestRequestParam(t *testing.T) { s := web.NewServer() s.Get("/blog/:article", func(ctx web.Context) error { article := ctx.Request().Param("article") - return ctx.String(article) + empty := ctx.Request().Param("") + return ctx.String(article + empty) }) - response := s.Request("GET", "/blog/my-article", nil) + response := s.Request("GET", "/blog/my-article", nil, nil) assert.Equal(t, response.Status(), 200) assert.Equal(t, string(response.Body()), "my-article") } diff --git a/Response.go b/Response.go index 24ed518..a544f01 100644 --- a/Response.go +++ b/Response.go @@ -20,7 +20,7 @@ type Response interface { // response represents the HTTP response used in the given context. type response struct { body []byte - headers []header + headers []Header status uint16 } @@ -49,7 +49,7 @@ func (res *response) SetHeader(key string, value string) { } } - res.headers = append(res.headers, header{Key: key, Value: value}) + res.headers = append(res.headers, Header{Key: key, Value: value}) } // SetBody replaces the response body with the new contents. diff --git a/Response_test.go b/Response_test.go index ead6599..dc62965 100644 --- a/Response_test.go +++ b/Response_test.go @@ -18,7 +18,7 @@ func TestWrite(t *testing.T) { return err }) - response := s.Request("GET", "/", nil) + response := s.Request("GET", "/", nil, nil) assert.Equal(t, response.Status(), 200) assert.Equal(t, string(response.Body()), "Hello") } @@ -31,7 +31,7 @@ func TestWriteString(t *testing.T) { return err }) - response := s.Request("GET", "/", nil) + response := s.Request("GET", "/", nil, nil) assert.Equal(t, response.Status(), 200) assert.Equal(t, string(response.Body()), "Hello") } @@ -41,20 +41,22 @@ func TestResponseCompression(t *testing.T) { uncompressed := bytes.Repeat([]byte("This text should be compressed to a size smaller than the original."), 5) s.Use(func(ctx web.Context) error { - err := ctx.Next() - body := ctx.Response().Body() - ctx.Response().SetBody(nil) - zip := gzip.NewWriter(ctx.Response()) - zip.Write(body) - zip.Close() - return err + defer func() { + body := ctx.Response().Body() + ctx.Response().SetBody(nil) + zip := gzip.NewWriter(ctx.Response()) + zip.Write(body) + zip.Close() + }() + + return ctx.Next() }) s.Get("/", func(ctx web.Context) error { return ctx.Bytes(uncompressed) }) - response := s.Request("GET", "/", nil) + response := s.Request("GET", "/", nil, nil) assert.Equal(t, response.Status(), 200) assert.True(t, len(response.Body()) < len(uncompressed)) @@ -75,7 +77,7 @@ func TestResponseHeader(t *testing.T) { return ctx.String(contentType) }) - response := s.Request("GET", "/", nil) + response := s.Request("GET", "/", nil, nil) assert.Equal(t, response.Status(), 200) assert.Equal(t, response.Header("Content-Type"), "text/plain") assert.Equal(t, response.Header("Non existent header"), "") @@ -91,7 +93,7 @@ func TestResponseHeaderOverwrite(t *testing.T) { return nil }) - response := s.Request("GET", "/", nil) + response := s.Request("GET", "/", nil, nil) assert.Equal(t, response.Status(), 200) assert.Equal(t, response.Header("Content-Type"), "text/html") assert.Equal(t, string(response.Body()), "") diff --git a/Server.go b/Server.go index f372ac0..51e8130 100644 --- a/Server.go +++ b/Server.go @@ -18,7 +18,7 @@ import ( // Server is the interface for an HTTP server. type Server interface { Get(path string, handler Handler) - Request(method string, path string, body io.Reader) Response + Request(method string, path string, headers []Header, body io.Reader) Response Router() *router.Router[Handler] Run(address string) error Use(handlers ...Handler) @@ -67,8 +67,9 @@ func (s *server) Get(path string, handler Handler) { // Request performs a synthetic request and returns the response. // This function keeps the response in memory so it's slightly slower than a real request. // However it is very useful inside tests where you don't want to spin up a real web server. -func (s *server) Request(method string, url string, body io.Reader) Response { +func (s *server) Request(method string, url string, headers []Header, body io.Reader) Response { ctx := s.newContext() + ctx.request.headers = headers s.handleRequest(ctx, method, url, io.Discard) return ctx.Response() } @@ -178,7 +179,7 @@ func (s *server) handleConnection(conn net.Conn) { key := message[:colon] value := message[colon+2 : len(message)-2] - ctx.request.headers = append(ctx.request.headers, header{ + ctx.request.headers = append(ctx.request.headers, Header{ Key: key, Value: value, }) @@ -219,12 +220,12 @@ func (s *server) newContext() *context { request: request{ reader: bufio.NewReader(nil), body: make([]byte, 0), - headers: make([]header, 0, 8), + headers: make([]Header, 0, 8), params: make([]router.Parameter, 0, 8), }, response: response{ body: make([]byte, 0, 1024), - headers: make([]header, 0, 8), + headers: make([]Header, 0, 8), status: 200, }, } diff --git a/Server_test.go b/Server_test.go new file mode 100644 index 0000000..ff942a8 --- /dev/null +++ b/Server_test.go @@ -0,0 +1,51 @@ +package web_test + +import ( + "net" + "net/http" + "syscall" + "testing" + + "git.akyoto.dev/go/assert" + "git.akyoto.dev/go/web" +) + +func TestPanic(t *testing.T) { + s := web.NewServer() + + s.Get("/panic", func(ctx web.Context) error { + panic("Something unbelievable happened") + }) + + defer func() { + r := recover() + + if r == nil { + t.Error("Didn't panic") + } + }() + + s.Request("GET", "/panic", nil, nil) +} + +func TestRun(t *testing.T) { + s := web.NewServer() + + go func() { + _, err := http.Get("http://127.0.0.1:8080/") + assert.Nil(t, err) + err = syscall.Kill(syscall.Getpid(), syscall.SIGTERM) + assert.Nil(t, err) + }() + + s.Run(":8080") +} + +func TestUnavailablePort(t *testing.T) { + listener, err := net.Listen("tcp", ":8080") + assert.Nil(t, err) + defer listener.Close() + + s := web.NewServer() + s.Run(":8080") +} diff --git a/http.go b/http.go index ce7c32c..b7ec1cc 100644 --- a/http.go +++ b/http.go @@ -2,12 +2,6 @@ package web import "strings" -// header is used to store HTTP headers. -type header struct { - Key string - Value string -} - // isRequestMethod returns true if the given string is a valid HTTP request method. func isRequestMethod(method string) bool { switch method {