We have a GoLang backend service(OAuth enabled) that accepts http requests, with Authorization header with value "Bearer" + OAuthTokenString.
How to write a unit or integration test case for backend service to verify that backend service is OAuth enabled(verifies the token)? am not sure, we cannot create a mock service(httptest.NewServer) with OAuth enabled....
This is a very interesting question. I can see that your team is concerned about minimizing possible errors through testing the code. This is an aspect that many developers often forget.
Without having seen your code, it is a bit difficult to suggest a 100% correct answer for your case.
I will assume that my example will serve as a guide to write your own test or in the best case to optimize the example that I suggest
I was using gin gonic as the HTTP web framework for my project and I wrote a method Authenticate that is called as middleware for each protected endpoint. Then for testing I only created an http server through the gin.Default () method
// Authenticate auth an endpoint
func Authenticate() gin.HandlerFunc {
return func(c *gin.Context) {
var someErr errors.BukyError
someErr.SetUnauthorized()
// Fetch token from the headers
requiredToken := c.GetHeader(constants.AuthorizationHeader)
if len(requiredToken) == 0 {
c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
return
}
splittedToken := strings.SplitN(requiredToken, " ", 2)
if len(splittedToken) != 2 || strings.ToLower(splittedToken[0]) != "bearer" {
primErr := fmt.Errorf("wrong bearer token format on Authorization Header")
someErr.PrimitiveErr = &primErr
c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
return
}
// Get email from encoded token
jwtToken, claims, err := helpers.DecodeJWT(splittedToken[1], false)
if err != nil {
someErr.PrimitiveErr = &err
c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
return
}
if _, err := helpers.VerifyObjectIDs(claims.Subject); !err.IsNilError() {
c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
return
}
// Set the User variable so that we can easily retrieve from other middlewares
// c.Set("User", result)
c.Set(constants.ReqBukyJWTKey, jwtToken)
c.Set(constants.ReqBukyClaimsKey, claims)
// Call the next middlware
c.Next()
}
}
And then I just tested like following
func TestAuthenticate(t *testing.T) {
userID := primitive.NewObjectID().Hex()
email := "email#email.com"
firstName := "My Name"
lastName := "My Lastname"
scopes := []string{"im_scope"}
statusOK := "statusOK"
someProtectedPath := constants.UsersPath + "/" + userID
engine := gin.Default()
engine.GET(someProtectedPath, Authenticate(), func(c *gin.Context) {
c.String(http.StatusOK, statusOK)
})
t.Run("NoTokenHeader", func(t *testing.T) {
t.Run("UnsetHeader", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", someProtectedPath, nil)
engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
t.Run("EmptyHeader", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", someProtectedPath, nil)
req.Header.Set(constants.AuthorizationHeader, "")
engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
})
t.Run("TokenWithBadFormat", func(t *testing.T) {
t.Run("1", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", someProtectedPath, nil)
badFormatedToken := "hola.hola"
req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", badFormatedToken))
engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
t.Run("2", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", someProtectedPath, nil)
badFormatedToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ."
req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", badFormatedToken))
engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
t.Run("3", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", someProtectedPath, nil)
badFormatedToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.hola.hola.hola"
req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearere %s", badFormatedToken))
engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
})
t.Run("ExpiredToken", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", someProtectedPath, nil)
expirationTime := time.Second
expiredToken, _, err := helpers.GenerateAccessJWT(userID, email, firstName, lastName, scopes, expirationTime)
time.Sleep(expirationTime * 2)
req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", expiredToken))
engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.Nil(t, err)
})
t.Run("ValidToken", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", someProtectedPath, nil)
validToken, _, err := helpers.GenerateAccessJWT(userID, email, firstName, lastName, scopes)
req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", validToken))
engine.ServeHTTP(w, req)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, w.Code)
})
}
Related
i'm new to Golang and i'm trying to write a test for a simple HTTP client.
i read a lot of ways of doing so also here in SO but none of them seems to work.
I'm having troubles mocking the client response
This is how my client looks right now:
type API struct {
Client *http.Client
}
func (api *API) MyClient(qp string) ([]byte, error) {
url := fmt.Sprintf("http://localhost:8000/myapi?qp=%s", qp)
resp, err := api.Client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
// handling error and doing stuff with body that needs to be unit tested
if err != nil {
return nil, err
}
return body, err
}
And this is my test function:
func TestDoStuffWithTestServer(t *testing.T) {
// Start a local HTTP server
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Write([]byte(`OK`))
}))
defer server.Close()
// Use Client & URL from our local test server
api := API{server.Client()}
body, _ := api.MyClient("1d")
fmt.Println(body)
}
As i said, this is how they look right cause i try lot of ways on doing so.
My problem is that i'm not able to mock the client respose. in this example my body is empty. my understanding was that rw.Write([]byte(OK)) should mock the response 🤔
In the end i solved it like this:
myclient:
type API struct {
Endpoint string
}
func (api *API) MyClient(slot string) ([]byte, error) {
url := fmt.Sprintf("%s/myresource?qp=%s", api.Endpoint, slot)
c := http.Client{}
resp, err := c.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, err
}
test:
func TestDoStuffWithTestServer(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Write([]byte(`{Result: [{Cluster_name: "cl1", Pings: 2}]}`))
}))
defer server.Close()
api := API{Endpoint: server.URL}
res, _ := api.MyClient("1d")
expected := []byte(`{Result: [{Cluster_name: "cl1", Pings: 2}]}`)
if !bytes.Equal(expected, res) {
t.Errorf("%s != %s", string(res), string(expected))
}
}
still, not 100% sure is the right way of doing so in Go
I use gorilla/websocket for ws and labstack/echo as router. I need to create unit test for the handler. I find topic with solving this problem with default go router, but i don't understand how to use it with echo.
I have this:
func TestWS(t *testing.T){
provider := handler.New(coordinateservice.New())
e := echo.New()
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/admin/orders/:id/details", nil)
c := e.NewContext(req, rec)
c.SetPath("/admin/orders/:id/details")
c.SetParamNames("id")
c.SetParamValues("9999")
if assert.NoError(t, provider.OrderHandler.OpenWs(c)) {
assert.Equal(t, http.StatusOK, rec.Code)
}
u := url.URL{Scheme: "ws", Host: "127.0.0.1", Path: "/admin/orders/9999/details"}
fmt.Println(u.String())
// Connect to the server
ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
t.Fatalf("%v", err)
}
defer ws.Close()
_, p, err := ws.ReadMessage()
if err != nil {
t.Fatalf("%v", err)
}
fmt.Println(string(p))
}
And error websocket: the client is not using the websocket protocol: 'upgrade' token not found in 'Connection' header in this line:
ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
t.Fatalf("%v", err)
}
What i need to do for connecting ws to the echo handler?
The this is my golang unit test websocket example.
The stability of my httptest library is not guaranteed, but you can refer to using net.Pipe to create a connection for ws.
use echo:
func main() {
app := echo.New()
app.GET("/", hello)
client := httptest.NewClient(app)
go func() {
// use http.Handler, if not has host.
client.NewRequest("GET", "/example/wsio").WithWebsocket(handlerGobwasWebsocket).Do().Out()
// use network
client.NewRequest("GET", "http://localhost:8088/example/wsio").WithWebsocket(handlerGobwasWebsocket).Do().Out()
}()
app.Start(":8088")
}
func hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
func handlerGobwasWebsocket(conn net.Conn) {
go func() {
wsutil.WriteClientBinary(conn, []byte("aaaaaa"))
wsutil.WriteClientBinary(conn, []byte("bbbbbb"))
wsutil.WriteClientBinary(conn, []byte("ccccc"))
}()
defer conn.Close()
for {
b, err := wsutil.ReadServerBinary(conn)
fmt.Println("ws io client read: ", string(b), err)
if err != nil {
fmt.Println("gobwas client err:", err)
return
}
}
}
I'm using gin gonic. I've a function that extracts a token from a cookie, which actually works. I'm using this function in a route handler and want to test the handler function, but I don't know how.
Function:
// Extracts token from a cookie
func tokenFromCookie(c *gin.Context, name string) (string, error) {
token, err := c.Cookie(name)
if err != nil {
return "", err
}
return token, nil
}
Route:
func RefreshTokenHandler(accessTokenKey string, refreshTokenKey string) gin.HandlerFunc {
fn := func(c *gin.Context) {
token, err := tokenFromCookie(c, "refresh_token")
if err != nil {
_ = c.Error(err).SetMeta(noCookie)
return
}
c.JSON(http.StatusOK, gin.H{
"token": token,
})
})
Route definition:
func CreateRoutes(r *gin.Engine) *gin.Engine {
r.Use(errorHandler)
// Auth
auth := r.Group("/auth")
{
auth.GET("/refresh-token", RefreshTokenHandler(accessTokenSignatureKey, refreshTokenSignatureKey))
}
return r
}
Unit test:
func TestRefreshTokenHandler(t *testing.T) {
req, _ := http.NewRequest("GET", "/auth/refresh-token", nil)
req.AddCookie(&http.Cookie{
Name: "refresh_token",
Value: "token",
MaxAge: 604800,
Expires: time.Now().Add(time.Hour * 24 * 7),
Path: "/",
Domain: "127.0.0.1",
HttpOnly: true,
SameSite: http.SameSiteNoneMode,
Secure: secure
}
)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
got := w.Code
if gotCode != 200 {
t.Errorf("GET /auth/refresh-token; got %d, want 200", got)
}
}
The tokenFromCookie() function throws an error though:
http: named cookie not present
This is a similar unit test that I found in the gin gonic repo:
func TestContextGetCookie(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("GET", "/get", nil)
c.Request.Header.Set("Cookie", "user=gin")
cookie, _ := c.Cookie("user")
assert.Equal(t, "gin", cookie)
_, err := c.Cookie("nokey")
assert.Error(t, err)
}
Yet I don't understand why my code doesn't work and how to re-write it.
To view the list cookies you can try this:
fmt.Println(c.Request.Cookies())
I don't understand the point of creating new function tokenFromCookie.
func RefreshTokenHandler(accessTokenKey string, refreshTokenKey string) gin.HandlerFunc {
fn := func(c *gin.Context) {
//token, err := tokenFromCookie(c, "refresh_token")
token, err := c.Cookie("refresh_token")
if err != nil {
_ = c.Error(err).SetMeta(noCookie)
return
}
c.JSON(http.StatusOK, gin.H{
"token": token,
})
})
I have a middleware to log this service access. But I'm confused to do the unit testing several times I surfed googling. I have not found the right way to solve this
package accesslog
import (
"net/http"
"time"
"github.com/go-chi/chi/middleware"
"transactionService/pkg/log"
)
func Handler(logger log.Logger) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = log.WithRequest(ctx, r)
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
start := time.Now()
defer func() {
logger.With(ctx, "duration", time.Since(start), "status", ww.Status()).
Infof("%s %s %s %d %d", r.Method, r.URL.Path, r.Proto, ww.Status(), ww.BytesWritten())
}()
next.ServeHTTP(ww, r.WithContext(ctx))
}
return http.HandlerFunc(fn)
}
}
solved, this is my code to solve it
package accesslog
import (
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-chi/chi"
"transactionService/pkg/log"
)
func TestHandler(t *testing.T) {
logger, _ := log.NewForTest()
r := chi.NewRouter()
r.Use(Handler(logger))
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("test"))
})
ts := httptest.NewServer(r)
defer ts.Close()
if resp, body := testRequest(t, ts, "GET", "/", nil); body != "root" && resp.StatusCode != 200 {
t.Fatalf(body)
}
}
func testRequest(t *testing.T, ts *httptest.Server, method, path string, body io.Reader) (*http.Response, string) {
req, err := http.NewRequest(method, ts.URL+path, body)
if err != nil {
t.Fatal(err)
return nil, ""
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
return nil, ""
}
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
return nil, ""
}
defer resp.Body.Close()
return resp, string(respBody)
}
I want to write tests for handlers in Google App Engine that use Gorilla mux to read variables from the request URL.
I understand from the documentation that you can create a fake context and request to use with testing.
I'm calling the handler directly in the test but the handler isn't seeing the path parameter as expected.
func TestRouter(t *testing.T) {
inst, _ := aetest.NewInstance(nil) //ignoring error for brevity
defer inst.Close()
//tried adding this line because the test would not work with or without it
httptest.NewServer(makeRouter())
req, _ := inst.NewRequest("GET", "/user/john#example.com/id-123", nil)
req.Header.Add("X-Requested-With", "XMLHttpRequest")
resp := httptest.NewRecorder()
restHandler(resp, req)
}
func restHandler(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r)
params := mux.Vars(r)
email := params["email"]
//`email` is always empty
}
The problem is that the handler always sees an empty "email" parameter because the path is not interpreted by Gorilla mux.
The router is as below:
func makeRouter() *mux.Router {
r := mux.Router()
rest := mux.NewRouter().Headers("Authorization", "").
PathPrefix("/api").Subrouter()
app := r.Headers("X-Requested-With", "XMLHttpRequest").Subrouter()
app.HandleFunc("/user/{email}/{id}", restHandler).Methods(http.MethodGet)
//using negroni for path prefix /api
r.PathPrefx("/api").Handler(negroni.New(
negroni.HandlerFunc(authCheck), //for access control
negroni.Wrap(rest),
))
return r
}
All my searches have not gotten anything specific to App Engine unit testing with Gorilla mux.
Since what you're testing is the handler, you could just get an instance of the router and call ServeHTTP on it. Here is how it should be based on your code.
main.go
func init() {
r := makeRouter()
http.Handle("/", r)
}
func makeRouter() *mux.Router {
r := mux.NewRouter()
app := r.Headers("X-Requested-With", "XMLHttpRequest").Subrouter()
app.HandleFunc("/user/{email}/{id}", restHandler).Methods(http.MethodGet)
return r
}
func restHandler(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
email := params["email"]
fmt.Fprintf(w, email)
}
main_test.go
func TestRouter(t *testing.T) {
inst, _ := aetest.NewInstance(nil) //ignoring error for brevity
defer inst.Close()
req, _ := inst.NewRequest("GET", "/user/john#example.com/id-123", nil)
req.Header.Add("X-Requested-With", "XMLHttpRequest")
rec := httptest.NewRecorder()
r := makeRouter()
r.ServeHTTP(rec, req)
if email := rec.Body.String(); email != "john#example.com" {
t.Errorf("router failed, expected: %s, got: %s", "john#example.com", email)
}
}
Notice I removed the rest routes since that's not part of your test, but the idea would be the same. Also didn't check for errors for simplicity.