Added 100% coverage to whats written so far; travis build script; debugged potential issues with testing

This commit is contained in:
mitchelljfs 2018-07-14 14:58:30 -07:00
parent 9d99bce47e
commit 922f6a7b41
4 changed files with 216 additions and 29 deletions

10
.travis.yml Normal file
View File

@ -0,0 +1,10 @@
language: go
go:
- "1.x"
- "1.8"
- "1.10.x"
- master
install:
- "dep ensure"
build:
- "go test -v ./..."

34
Gopkg.lock generated
View File

@ -13,9 +13,41 @@
revision = "4d30d0ff60440c2d0480a15747c96ee71c3c53d4"
version = "v1.2.0"
[[projects]]
branch = "master"
name = "github.com/gopherjs/gopherjs"
packages = ["js"]
revision = "0892b62f0d9fb5857760c3cfca837207185117ee"
[[projects]]
name = "github.com/jtolds/gls"
packages = ["."]
revision = "77f18212c9c7edc9bd6a33d383a7b545ce62f064"
version = "v4.2.1"
[[projects]]
name = "github.com/smartystreets/assertions"
packages = [
".",
"internal/go-render/render",
"internal/oglematchers"
]
revision = "7678a5452ebea5b7090a6b163f844c133f523da2"
version = "1.8.3"
[[projects]]
name = "github.com/smartystreets/goconvey"
packages = [
"convey",
"convey/gotest",
"convey/reporting"
]
revision = "9e8dc3f972df6c8fcc0375ef492c24d0bb204857"
version = "1.6.3"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "35d3fdde995b8cf314934259f07ffb089e30092e11fb8e404ea398ce1c76a431"
inputs-digest = "751d4fc4b7be23b18796aed082f5e83d9ba309c184255d4c63d8225cc0048152"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -14,29 +14,34 @@ const (
post = http.MethodPost
get = http.MethodGet
put = http.MethodPut
patch = http.MethodPatch
delete = http.MethodDelete
)
// HandlerRequest ...
type HandlerRequest struct {
// APIGRequest is used as the input of handler functions.
// The Claims, Path, and QryStr will be populated by the the APIGatewayProxyRequest.
// The Request itself is also passed through if you need further access.
type APIGRequest struct {
Claims map[string]interface{}
Path map[string]string
QryStr map[string]string
Request *events.APIGatewayProxyRequest
}
// HandlerResponse ...
type HandlerResponse struct {
// APIGResponse is used as the output of handler functions.
// Populate Status and Body with your http response or populate Err with your error.
type APIGResponse struct {
Status int
Body []byte
Err error
}
// Handler ...
type Handler func(req *HandlerRequest, res *HandlerResponse)
// APIGHandler is the interface a handler function must implement to be used
// with Get, Post, Put, Patch, and Delete.
type APIGHandler func(req *APIGRequest, res *APIGResponse)
// Router ...
type Router struct {
// APIGRouter is the object that handlers build upon and is used in the end to respond.
type APIGRouter struct {
request *events.APIGatewayProxyRequest
endpoints map[string]*radix.Tree
params map[string]string
@ -45,14 +50,15 @@ type Router struct {
// NOTE: Begin router methods.
// New ...
func New(r *events.APIGatewayProxyRequest, svcprefix string) *Router {
return &Router{
// NewAPIGRouter creates a new router using the request and a prefix to strip from your incoming requests.
func NewAPIGRouter(r *events.APIGatewayProxyRequest, svcprefix string) *APIGRouter {
return &APIGRouter{
request: r,
endpoints: map[string]*radix.Tree{
post: radix.New(),
get: radix.New(),
put: radix.New(),
patch: radix.New(),
delete: radix.New(),
},
params: map[string]string{},
@ -60,28 +66,33 @@ func New(r *events.APIGatewayProxyRequest, svcprefix string) *Router {
}
}
// Get ...
func (r *Router) Get(route string, handler Handler) {
// Get creates a new get endpoint.
func (r *APIGRouter) Get(route string, handler APIGHandler) {
r.addEndpoint(get, route, handler)
}
// Post ...
func (r *Router) Post(route string, handler Handler) {
// Post creates a new post endpoint.
func (r *APIGRouter) Post(route string, handler APIGHandler) {
r.addEndpoint(post, route, handler)
}
// Put ...
func (r *Router) Put(route string, handler Handler) {
// Put creates a new put endpoint.
func (r *APIGRouter) Put(route string, handler APIGHandler) {
r.addEndpoint(put, route, handler)
}
// Delete ...
func (r *Router) Delete(route string, handler Handler) {
// Patch creates a new patch endpoint
func (r *APIGRouter) Patch(route string, handler APIGHandler) {
r.addEndpoint(patch, route, handler)
}
// Delete creates a new delete endpoint.
func (r *APIGRouter) Delete(route string, handler APIGHandler) {
r.addEndpoint(delete, route, handler)
}
// Respond ...
func (r *Router) Respond() events.APIGatewayProxyResponse {
// Respond returns an APIGatewayProxyResponse to respond to the lambda request.
func (r *APIGRouter) Respond() events.APIGatewayProxyResponse {
var (
handlerInterface interface{}
ok bool
@ -109,15 +120,17 @@ func (r *Router) Respond() events.APIGatewayProxyResponse {
return response
}
handler := handlerInterface.(Handler)
handler := handlerInterface.(APIGHandler)
req := &HandlerRequest{
Claims: r.request.RequestContext.Authorizer["claims"].(map[string]interface{}),
req := &APIGRequest{
Path: r.request.PathParameters,
QryStr: r.request.QueryStringParameters,
Request: r.request,
}
res := &HandlerResponse{}
if r.request.RequestContext.Authorizer["claims"] != nil {
req.Claims = r.request.RequestContext.Authorizer["claims"].(map[string]interface{})
}
res := &APIGResponse{}
handler(req, res)
status, respbody, err := res.deconstruct()
@ -147,11 +160,11 @@ func stripSlashesAndSplit(s string) []string {
return strings.Split(s, "/")
}
func (res *HandlerResponse) deconstruct() (int, []byte, error) {
func (res *APIGResponse) deconstruct() (int, []byte, error) {
return res.Status, res.Body, res.Err
}
func (r *Router) addEndpoint(method string, route string, handler Handler) {
func (r *APIGRouter) addEndpoint(method string, route string, handler APIGHandler) {
if _, overwrite := r.endpoints[method].Insert(route, handler); overwrite {
panic("endpoint already existent")
}
@ -163,5 +176,5 @@ func (r *Router) addEndpoint(method string, route string, handler Handler) {
}
}
log.Printf("router: %+v", *r)
log.Printf("endpoint: %+v %+v", method, route)
}

132
router_test.go Normal file
View File

@ -0,0 +1,132 @@
package lambdarouter
import (
"errors"
"log"
"net/http"
"testing"
"github.com/aws/aws-lambda-go/events"
. "github.com/smartystreets/goconvey/convey"
)
func TestRouterSpec(t *testing.T) {
Convey("Given an instantiated router", t, func() {
request := events.APIGatewayProxyRequest{}
rtr := NewAPIGRouter(&request, "shipping")
Convey("When the handler func does NOT return an error", func() {
hdlrfunc := func(req *APIGRequest, res *APIGResponse) {
log.Printf("claims: %+v", req.Claims)
res.Status = http.StatusOK
res.Body = []byte("hello")
res.Err = nil
}
Convey("And a Get handler expecting the pattern /orders/filter/by_user/{id} is defined", func() {
rtr.Get("/orders/filter/by_user/{id}", hdlrfunc)
rtr.Post("/orders", func(req *APIGRequest, res *APIGResponse) {})
rtr.Put("/orders", func(req *APIGRequest, res *APIGResponse) {})
rtr.Patch("/orders", func(req *APIGRequest, res *APIGResponse) {})
rtr.Delete("/orders/{id}", func(req *APIGRequest, res *APIGResponse) {})
Convey("And the request matches the pattern and the path params are filled", func() {
request.HTTPMethod = http.MethodGet
request.Path = "/shipping/orders/filter/by_user/4d50ff90-66e3-4047-bf37-0ca25837e41d"
request.PathParameters = map[string]string{
"id": "4d50ff90-66e3-4047-bf37-0ca25837e41d",
}
request.RequestContext.Authorizer = map[string]interface{}{
"claims": map[string]interface{}{
"cognito:username": "mitchell",
},
}
Convey("The router will return the expected status and body", func() {
response := rtr.Respond()
So(response.StatusCode, ShouldEqual, http.StatusOK)
So(response.Body, ShouldEqual, "hello")
})
})
Convey("And the request does NOT match the pattern", func() {
request.HTTPMethod = http.MethodGet
request.Path = "/orders/filter"
Convey("The router will return and error body and a not found status", func() {
response := rtr.Respond()
So(response.StatusCode, ShouldEqual, http.StatusNotFound)
So(response.Body, ShouldEqual, "{\"error\":\"no route matching path found\"}")
})
})
Convey("And a Get handler expecting the pattern /orders/filter/by_user/{id} is defined AGAIN", func() {
So(func() {
rtr.Get("/orders/filter/by_user/{id}", hdlrfunc)
}, ShouldPanicWith, "endpoint already existent")
})
})
})
Convey("When the handler func does return a record not found", func() {
hdlrfunc := func(req *APIGRequest, res *APIGResponse) {
res.Status = http.StatusBadRequest
res.Body = []byte("hello")
res.Err = errors.New("record not found")
}
Convey("And a Get handler expecting the pattern /orders/filter/by_user/{id} is defined", func() {
rtr.Get("/orders/filter/by_user/{id}", hdlrfunc)
Convey("And the request matches the pattern and the path params are filled", func() {
request.HTTPMethod = http.MethodGet
request.Path = "/shipping/orders/filter/by_user/4d50ff90-66e3-4047-bf37-0ca25837e41d"
request.PathParameters = map[string]string{
"id": "4d50ff90-66e3-4047-bf37-0ca25837e41d",
}
Convey("The router will return the expected status and body", func() {
response := rtr.Respond()
So(response.StatusCode, ShouldEqual, http.StatusNoContent)
So(response.Body, ShouldEqual, "{\"error\":\"record not found\"}")
})
})
})
})
Convey("When the handler func does return a status < 400", func() {
hdlrfunc := func(req *APIGRequest, res *APIGResponse) {
res.Status = http.StatusOK
res.Body = []byte("hello")
res.Err = errors.New("bad request")
}
Convey("And a Get handler expecting the pattern /orders/filter/by_user/{id} is defined", func() {
rtr.Get("/orders/filter/by_user/{id}", hdlrfunc)
Convey("And the request matches the pattern and the path params are filled", func() {
request.HTTPMethod = http.MethodGet
request.Path = "/shipping/orders/filter/by_user/4d50ff90-66e3-4047-bf37-0ca25837e41d"
request.PathParameters = map[string]string{
"id": "4d50ff90-66e3-4047-bf37-0ca25837e41d",
}
Convey("The router will return the expected status and body", func() {
response := rtr.Respond()
So(response.StatusCode, ShouldEqual, http.StatusBadRequest)
So(response.Body, ShouldEqual, "{\"error\":\"bad request\"}")
})
})
})
})
})
}