Added server tests

This commit is contained in:
Eduard Urbach 2024-03-28 12:22:45 +01:00
parent 245a085020
commit 805f92468a
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
10 changed files with 116 additions and 36 deletions

View File

@ -15,7 +15,7 @@ func TestBytes(t *testing.T) {
return ctx.Bytes([]byte("Hello")) 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, response.Status(), 200)
assert.Equal(t, string(response.Body()), "Hello") assert.Equal(t, string(response.Body()), "Hello")
} }
@ -27,7 +27,7 @@ func TestString(t *testing.T) {
return ctx.String("Hello") return ctx.String("Hello")
}) })
response := s.Request("GET", "/", nil) response := s.Request("GET", "/", nil, nil)
assert.Equal(t, response.Status(), 200) assert.Equal(t, response.Status(), 200)
assert.Equal(t, string(response.Body()), "Hello") assert.Equal(t, string(response.Body()), "Hello")
} }
@ -39,7 +39,7 @@ func TestError(t *testing.T) {
return ctx.Status(401).Error("Not logged in") 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, response.Status(), 401)
assert.Equal(t, string(response.Body()), "") 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")) 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, response.Status(), 401)
assert.Equal(t, string(response.Body()), "") assert.Equal(t, string(response.Body()), "")
} }

7
Header.go Normal file
View File

@ -0,0 +1,7 @@
package web
// Header is used to store HTTP headers.
type Header struct {
Key string
Value string
}

View File

@ -40,12 +40,22 @@ s.Run(":8080")
## Tests ## Tests
``` ```
PASS: TestRouter PASS: TestBytes
PASS: TestMiddleware 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: TestPanic
PASS: TestRun PASS: TestRun
PASS: TestUnavailablePort PASS: TestUnavailablePort
coverage: 100.0% of statements coverage: 95.5% of statements
``` ```
## Benchmarks ## Benchmarks

View File

@ -24,7 +24,7 @@ type request struct {
method string method string
path string path string
query string query string
headers []header headers []Header
body []byte body []byte
params []router.Parameter params []router.Parameter
} }

View File

@ -20,20 +20,35 @@ func TestRequest(t *testing.T) {
return ctx.String(fmt.Sprintf("%s %s %s %s", method, scheme, host, path)) 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, response.Status(), 200)
assert.Equal(t, string(response.Body()), "GET http example.com /request") 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) { func TestRequestParam(t *testing.T) {
s := web.NewServer() s := web.NewServer()
s.Get("/blog/:article", func(ctx web.Context) error { s.Get("/blog/:article", func(ctx web.Context) error {
article := ctx.Request().Param("article") 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, response.Status(), 200)
assert.Equal(t, string(response.Body()), "my-article") assert.Equal(t, string(response.Body()), "my-article")
} }

View File

@ -20,7 +20,7 @@ type Response interface {
// response represents the HTTP response used in the given context. // response represents the HTTP response used in the given context.
type response struct { type response struct {
body []byte body []byte
headers []header headers []Header
status uint16 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. // SetBody replaces the response body with the new contents.

View File

@ -18,7 +18,7 @@ func TestWrite(t *testing.T) {
return err return err
}) })
response := s.Request("GET", "/", nil) response := s.Request("GET", "/", nil, nil)
assert.Equal(t, response.Status(), 200) assert.Equal(t, response.Status(), 200)
assert.Equal(t, string(response.Body()), "Hello") assert.Equal(t, string(response.Body()), "Hello")
} }
@ -31,7 +31,7 @@ func TestWriteString(t *testing.T) {
return err return err
}) })
response := s.Request("GET", "/", nil) response := s.Request("GET", "/", nil, nil)
assert.Equal(t, response.Status(), 200) assert.Equal(t, response.Status(), 200)
assert.Equal(t, string(response.Body()), "Hello") 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) uncompressed := bytes.Repeat([]byte("This text should be compressed to a size smaller than the original."), 5)
s.Use(func(ctx web.Context) error { s.Use(func(ctx web.Context) error {
err := ctx.Next() defer func() {
body := ctx.Response().Body() body := ctx.Response().Body()
ctx.Response().SetBody(nil) ctx.Response().SetBody(nil)
zip := gzip.NewWriter(ctx.Response()) zip := gzip.NewWriter(ctx.Response())
zip.Write(body) zip.Write(body)
zip.Close() zip.Close()
return err }()
return ctx.Next()
}) })
s.Get("/", func(ctx web.Context) error { s.Get("/", func(ctx web.Context) error {
return ctx.Bytes(uncompressed) return ctx.Bytes(uncompressed)
}) })
response := s.Request("GET", "/", nil) response := s.Request("GET", "/", nil, nil)
assert.Equal(t, response.Status(), 200) assert.Equal(t, response.Status(), 200)
assert.True(t, len(response.Body()) < len(uncompressed)) assert.True(t, len(response.Body()) < len(uncompressed))
@ -75,7 +77,7 @@ func TestResponseHeader(t *testing.T) {
return ctx.String(contentType) 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.Status(), 200)
assert.Equal(t, response.Header("Content-Type"), "text/plain") assert.Equal(t, response.Header("Content-Type"), "text/plain")
assert.Equal(t, response.Header("Non existent header"), "") assert.Equal(t, response.Header("Non existent header"), "")
@ -91,7 +93,7 @@ func TestResponseHeaderOverwrite(t *testing.T) {
return nil return nil
}) })
response := s.Request("GET", "/", nil) response := s.Request("GET", "/", nil, nil)
assert.Equal(t, response.Status(), 200) assert.Equal(t, response.Status(), 200)
assert.Equal(t, response.Header("Content-Type"), "text/html") assert.Equal(t, response.Header("Content-Type"), "text/html")
assert.Equal(t, string(response.Body()), "") assert.Equal(t, string(response.Body()), "")

View File

@ -18,7 +18,7 @@ import (
// Server is the interface for an HTTP server. // Server is the interface for an HTTP server.
type Server interface { type Server interface {
Get(path string, handler Handler) 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] Router() *router.Router[Handler]
Run(address string) error Run(address string) error
Use(handlers ...Handler) Use(handlers ...Handler)
@ -67,8 +67,9 @@ func (s *server) Get(path string, handler Handler) {
// Request performs a synthetic request and returns the response. // 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. // 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. // 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 := s.newContext()
ctx.request.headers = headers
s.handleRequest(ctx, method, url, io.Discard) s.handleRequest(ctx, method, url, io.Discard)
return ctx.Response() return ctx.Response()
} }
@ -178,7 +179,7 @@ func (s *server) handleConnection(conn net.Conn) {
key := message[:colon] key := message[:colon]
value := message[colon+2 : len(message)-2] 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, Key: key,
Value: value, Value: value,
}) })
@ -219,12 +220,12 @@ func (s *server) newContext() *context {
request: request{ request: request{
reader: bufio.NewReader(nil), reader: bufio.NewReader(nil),
body: make([]byte, 0), body: make([]byte, 0),
headers: make([]header, 0, 8), headers: make([]Header, 0, 8),
params: make([]router.Parameter, 0, 8), params: make([]router.Parameter, 0, 8),
}, },
response: response{ response: response{
body: make([]byte, 0, 1024), body: make([]byte, 0, 1024),
headers: make([]header, 0, 8), headers: make([]Header, 0, 8),
status: 200, status: 200,
}, },
} }

51
Server_test.go Normal file
View File

@ -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")
}

View File

@ -2,12 +2,6 @@ package web
import "strings" 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. // isRequestMethod returns true if the given string is a valid HTTP request method.
func isRequestMethod(method string) bool { func isRequestMethod(method string) bool {
switch method { switch method {