Can't access mux params when testing API endpoint with custom handlers - api

I have a custom handler for my API endpoints like this:
type HTTPError struct {
Error error
Message string
Code int
}
type endpointREST func(http.ResponseWriter, *http.Request) *HTTPError
func (fn endpointREST) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil {
http.Error(w, e.Message, e.Code)
}
}
my example route look like this:
func GetShare(w http.ResponseWriter, r *http.Request) *HTTPError {
vars := mux.Vars(r)
fmt.Println(r.URL) // http://127.0.0.1:36455/share/5713d228-a042-446d-a5e4-183b19fa832a
fmt.Println(vars) // -->> always empty map when testing
return nil
}
These routes work well (manual, using Postman) after setting them up with
router := mux.NewRouter().StrictSlash(true)
handler := cors.Default().Handler(router)
router.Handle("/share/{id}", endpointREST(GetShare)).Methods("GET")
log.Fatal(http.ListenAndServe(":6969", handler))
The Problem is, that i can't test the API this way, since mux.Vars(r) will always return an empty map in the testing environment.
This is my testing code:
func TestGetShare(t *testing.T) {
Reset()
router := mux.NewRouter()
ts := httptest.NewServer(router)
router.Handle("/share/{id}", endpointREST(GetShare)).Methods("GET")
defer ts.Close()
t.Run("unauthorized", func(t *testing.T) {
req, _ := http.NewRequest("GET", ts.URL + "/share/5713d228-a042-446d-a5e4-183b19fa832a", nil)
res, _ := http.DefaultClient.Do(req)
assert.Equal(t, http.StatusUnauthorized, res.StatusCode)
})
}

I suggest you tu use the SetURLVars test helper func:
// SetURLVars sets the URL variables for the given request, to be accessed via
// mux.Vars for testing route behaviour. Arguments are not modified, a shallow
// copy is returned.
//
// This API should only be used for testing purposes; it provides a way to
// inject variables into the request context. Alternatively, URL variables
// can be set by making a route that captures the required variables,
// starting a server and sending the request to that server.
func SetURLVars(r *http.Request, val map[string]string) *http.Request {
return requestWithVars(r, val)
}

Related

Golang - Sending API POST Request - Not enough arguments error

The following code attempts to send a POST API request with a payload that is in RequestDetails.FormData. When I run main.go function, then I get the following errors.
go run main.go
# command-line-arguments
./main.go:53:17: not enough arguments in call to http.HandleFunc
./main.go:53:33: not enough arguments in call to reqDetails.Send
have ()
want (http.ResponseWriter, *http.Request)
./main.go:53:33: reqDetails.Send() used as value
The code is available below. Anybody knows what I could do wrong here? Thanks a lot for your help.
//main.go
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
)
// RequestDetails contains input data for Send
type RequestDetails struct {
EndPoint string
FormType string
FormData map[string]string
}
// Send sends API POST request to an endpoint
func (rd RequestDetails) Send(w http.ResponseWriter, r *http.Request) {
json_data, err := json.Marshal(rd.FormData)
if err != nil {
log.Fatal(err)
}
resp, err := http.Post(rd.EndPoint, rd.FormType, bytes.NewBuffer(json_data))
if err != nil {
log.Fatal(err)
}
fmt.Println(resp)
}
func main() {
m := map[string]string{
"AuthParamOne": "AP0000001",
"AuthParamTwo": "AP0000002",
"AuthParamThree": "AP0000003",
}
reqDetails := RequestDetails{
EndPoint: "https://httpbin.org/post",
FormType: "application/json",
FormData: m,
}
http.HandleFunc(reqDetails.Send())
}
you have to use HandleFunc in following below:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
for code above follow this:
http.HandleFunc("/test",reqDetails.Send) //-> add reference instead of calling 'reqDetails.Send()'
reference: https://pkg.go.dev/net/http#HandleFunc
please vote up :)
In your Send method, you don't make use of w http.ResponseWriter, r *http.Request, So it seems you don't need them:
func (rd RequestDetails) Send() {...
Also in your last line, HandleFunc requires different arguments which once again is not necessary in your case. Just try to run the Send method:
reqDetails.Send()
The whole main.go file:
//main.go
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
)
// RequestDetails contains input data for Send
type RequestDetails struct {
EndPoint string
FormType string
FormData map[string]string
}
// Send sends API POST request to an endpoint
func (rd RequestDetails) Send() {
json_data, err := json.Marshal(rd.FormData)
if err != nil {
log.Fatal(err)
}
resp, err := http.Post(rd.EndPoint, rd.FormType, bytes.NewBuffer(json_data))
if err != nil {
log.Fatal(err)
}
fmt.Println(resp)
}
func main() {
m := map[string]string{
"AuthParamOne": "AP0000001",
"AuthParamTwo": "AP0000002",
"AuthParamThree": "AP0000003",
}
reqDetails := RequestDetails{
EndPoint: "https://httpbin.org/post",
FormType: "application/json",
FormData: m,
}
reqDetails.Send()
}
if your code like this
watcher := bufio.NewReader(os.Stdin)
input, _ := watcher.ReadString()
fmt.Println(input)
you needed this for reading line line
old -> input, _ := watcher.ReadString()
new -> input, _ := watcher.ReadString('\n')

Rest API in Go (Consume and host)

I am trying to request something, like a book by its id, and then host it locally so that if I write my local URL, like http://localhost:8080​/books?books=<book-id> it would show me the specific result.
To try to be concrete, I need to connect the two. Get the information from that URL, so "consume" and also host it locally, specifically by ID. I am not sure how to do both at once.
To create the paths, I've been using gorilla mux
So separately, I've used this, which would give me all the books at once (URL is not real).
func main() {
response, err := http.Get("https://bookibook.herokuapp.com/books/")
if err != nil {
fmt.Printf("there is no book with this ID %s\n", err)
} else {
data, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(data))
}
}
and then this, which would create a local path for http://localhost:8080/books/ID
import (
"fmt"
"github.com/gorilla/mux"
"log"
"net/http"
)
func getID(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
fmt.Fprintf(w, "Get id %s\n", vars["id"])
}
func main() {
// Configure routes.
router := mux.NewRouter()
router.HandleFunc("/books/{id}/", getID).Methods(http.MethodGet)
// Start HTTP server.
if err := http.ListenAndServe(":8080", router); err != nil {
log.Fatal(err)
}
}

Testing golang middleware that modifies the request

I have some middleware that adds a context with a request id to a request.
func AddContextWithRequestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var ctx context.Context
ctx = NewContextWithRequestID(ctx, r)
next.ServeHTTP(w, r.WithContext(ctx))
})}
How do I write a test for this ?
To test that, you need to run that handler passing in a request, and using a custom next handler that checks that the request was indeed modified.
You can create that handler as follows:
(I am assuming your NewContextWithRequestID adds a "reqId" key to the request with a "1234" value, you should of course modify the assertions as needed)
// create a handler to use as "next" which will verify the request
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
val := r.Context().Value("reqId")
if val == nil {
t.Error("reqId not present")
}
valStr, ok := val.(string)
if !ok {
t.Error("not string")
}
if valStr != "1234" {
t.Error("wrong reqId")
}
})
You can then use that handler as your next one:
// create the handler to test, using our custom "next" handler
handlerToTest := AddContextWithRequestID(nextHandler)
And then invoke that handler:
// create a mock request to use
req := httptest.NewRequest("GET", "http://testing", nil)
// call the handler using a mock response recorder (we'll not use that anyway)
handlerToTest.ServeHTTP(httptest.NewRecorder(), req)
Putting everything together as a working test, that'd be the code below.
Note: I fixed a small bug in your original "AddContextWithRequestID", as the ctx value started with a nil value when you just declared it with no initialization.
import (
"net/http"
"context"
"testing"
"net/http/httptest"
)
func NewContextWithRequestID(ctx context.Context, r *http.Request) context.Context {
return context.WithValue(ctx, "reqId", "1234")
}
func AddContextWithRequestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var ctx = context.Background()
ctx = NewContextWithRequestID(ctx, r)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func TestIt(t *testing.T) {
// create a handler to use as "next" which will verify the request
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
val := r.Context().Value("reqId")
if val == nil {
t.Error("reqId not present")
}
valStr, ok := val.(string)
if !ok {
t.Error("not string")
}
if valStr != "1234" {
t.Error("wrong reqId")
}
})
// create the handler to test, using our custom "next" handler
handlerToTest := AddContextWithRequestID(nextHandler)
// create a mock request to use
req := httptest.NewRequest("GET", "http://testing", nil)
// call the handler using a mock response recorder (we'll not use that anyway)
handlerToTest.ServeHTTP(httptest.NewRecorder(), req)
}

How to check authentication for hundreds and thousands of API endpoints?

I am currently building a web application in golang (with Gorilla) and have implemented a handful of API endpoints. However, I noticed that every time I implement a function like
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {}
I have to add the function below to the body of handler functions to check if request is authorized:
func checkAuthorizedUser (r * http.Request) error {
uid, err := CheckRequestUser (r.Cookie("uid"))
if err != nil {
return errors.New("Can't find cookie value for uid")
}
if !IsValidUser (uid.Value) {
return errors.New("Not a valid user")
}
return nil
}
What happens to me right now is that I have to add checkAuthorizedUser() to every handler function, and I have already have a lot of handler functions so far. I wonder if there is a better way to check whether a client is authorized to access certain endpoint other than explicitly checking authentication in every handler function.
Gorilla has a router you can use. You can then wrap the router with authentication checking. Something like this would work:
func checkPermissions(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
authCheck := true //implement the actual checking
if authCheck {
w.WriteError(w, 400, "error")
return
}
h.ServeHttp(w, r)
}
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", HomeHandler)
r.HandleFunc("/products", ProductsHandler)
r.HandleFunc("/articles", ArticlesHandler)
http.Handle("/", checkPermissions(r))
}
Supporting links:
https://godoc.org/github.com/gorilla/mux#NewRouter
https://github.com/gorilla/mux

methods for interface mocking in go

If I have a handlerfunc like the one below. What is the best way to "mock" or inject a interface that wraps some object for testing?
func GetOrCreateUser(w http.ResponseWriter, r *http.Request) {
user := GetUserFromContext(r.Context())
if created :=user.GetOrCreate(); created {
smtp.SendEmail(...)
return
} else {
w.Write([]byte("Hello User!"))
}
}
The only way that I have come by seems to be to do this:
type Mailer interface { SendMail() }
func HandlerWithMailer(m Mailer) http.handlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user := GetUserFromContext(r.Context())
if created := user.GetOrCreate(); created {
m.SendEmail(...)
return
} else {
w.Write([]byte("Hello User!"))
}
}
}
Then calling the mailer like this in the router (using httprouter):
m := mailer.New() // assuming this returns a new mailer
r := httprouter.New()
r.GET("/mailer", HandlerWithMailer(m))
Which could then be tested by making a struct that implements the interface and returns whatever I want, which can be called in the tests. I know this works, but I am wondering if this is the preferred method of testing in Go and if there is any other way to go about accomplishing this.
I would call my handlers from a struct like so:
type Handlers struct {
mailer *Mailer
}
func(h *Handlers) GetOrCreateUser(w http.ResponseWriter, r *http.Request) {
user := GetUserFromContext(r.Context())
if created :=user.GetOrCreate(); created {
h.mailer.SendEmail(...)
return
} else {
w.Write([]byte("Hello User!"))
}
}
This way you can instansiate the struct with the implementation of Mailer you want to use.
m := mailer.New() // assuming this returns a new mailer
h := Handlers{&m}
r := httprouter.New()
r.GET("/mailer", h.HandlerWithMailer)
and in your tests
m := mockMailer.New() // assuming this returns a new mailer
h := Handlers{&m}
r := httprouter.New()
r.GET("/mailer", h.HandlerWithMailer)