jeudi 14 novembre 2019

'invalid memory address or nil pointer' when I test POST method in Go Echo API

This could be really primitive question but I can't figure it out how to test POST method in API build by Go echo.

I wrote my test following some documents and it seems decent. but I ended up with "invalid memory address or nil pointer" error.

here is my code.

/main.go

package main

import "go-auth-api/model"

func main() {
    model.Init()
    defer model.DB.Close()

    router := newRouter()
    router.Logger.Fatal(router.Start(":8080"))
}

/router.go

package main

import (
    "github.com/labstack/echo"
    "github.com/labstack/echo/middleware"
    "go-auth-api/handler"
    "net/http"
)

func newRouter() *echo.Echo {
    e := echo.New()
    e.HTTPErrorHandler = handler.ErrorHandler

    e.Pre(middleware.RemoveTrailingSlash())
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

    // index
    e.GET("/", func(c echo.Context) error {
        return c.String(http.StatusOK, "Go auth API")
    })

    // user
    e.POST("/sign-up", handler.SignUp)
    e.POST("/login", handler.Login)

    api := e.Group("/api")
    // authentication is required from here
    api.Use(middleware.JWTWithConfig(handler.Config))
    api.GET("/user", handler.GetUserInfo)
    api.GET("/todo", handler.GetUserTodos)
    api.POST("/todo", handler.CreateTodo)
    api.PUT("/todo/:id", handler.PutTodo)
    api.DELETE("/todo/:id", handler.DeleteTodo)

    return e
}

/model/db.go

package model

import (
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/postgres"
)

var DB *gorm.DB

func Init() {
    var err error

    DB, err = gorm.Open("postgres", "host=db port=5432 user=postgres dbname=app password=postgres sslmode=disable")

    if err != nil {
        panic(err.Error())
    }

    DB.AutoMigrate(&User{})
    DB.AutoMigrate(&Todo{})
}

/model/user.go

package model

type User struct {
    ID       int    `json:"id" gorm:"primary_key auto_increment"`
    Name     string `json:"name"`
    Password string `json:"password"`
    Todos    []Todo
}

func CreateUser(user *User) {
    DB.Create(user)
}

func GetUser(u *User) User {
    var user User
    var todos []Todo
    DB.Where(u).First(&user)

    DB.Where(Todo{UserID: uint(user.ID)}).Find(&todos)
    user.Todos = todos

    return user
}

/handler/auth.go

package handler

import (
    "github.com/dgrijalva/jwt-go"
    "github.com/labstack/echo"
    "github.com/labstack/echo/middleware"
    "go-auth-api/model"
    "net/http"
    "time"
)

type jwtCustomClaims struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
    jwt.StandardClaims
}

var signingKey = []byte("test1test2test3")

var Config = middleware.JWTConfig{
    Claims:     &jwtCustomClaims{},
    SigningKey: signingKey,
}

func SignUp(c echo.Context) error {
    user := new(model.User)
    if err := c.Bind(user); err != nil {
        return err
    }

    if user.Name == "" || user.Password == "" {
        return &echo.HTTPError{
            Code:    http.StatusBadRequest,
            Message: "invalid name or password",
        }
    }

    if u := model.GetUser(&model.User{Name: user.Name}); u.Name == user.Name {
        return &echo.HTTPError{
            Code:    http.StatusConflict,
            Message: "name already exists",
        }
    }

    model.CreateUser(user)
    user.Password = "****"

    return c.JSON(http.StatusCreated, user)
}

func Login(c echo.Context) error {
    u := new(model.User)
    if err := c.Bind(u); err != nil {
        return err
    }

    user := model.GetUser(&model.User{Name: u.Name})
    if user.Password != u.Password {
        return &echo.HTTPError{
            Code:    http.StatusBadRequest,
            Message: "invalid name or password",
        }
    }

    claims := jwtCustomClaims{
        user.ID,
        user.Name,
        jwt.StandardClaims{
            ExpiresAt: time.Now().Add(time.Hour * 72).Unix(),
        },
    }

    t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    token, err := t.SignedString(signingKey)
    if err != nil {
        return err
    }

    return c.JSON(http.StatusOK, map[string]string{
        "Token": token,
    })
}

func retrieveUserIdFromToken(c echo.Context) int {
    user := c.Get("user").(*jwt.Token)
    claims := user.Claims.(*jwtCustomClaims)
    id := claims.ID
    return id
}

and finally, this is the test I wrote.

/handler/auth_test.go

package handler

import (
    "github.com/labstack/echo"
    "github.com/stretchr/testify/assert"
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"
)

func TestConnection(t *testing.T) {
    e := echo.New()
    req := httptest.NewRequest(http.MethodGet, "/", nil)
    rec := httptest.NewRecorder()
    _ = e.NewContext(req, rec)


    assert.Equal(t, http.StatusOK, rec.Code)
}

func TestSignUp(t *testing.T) {
    userJson := `{"name": "test", "password": "test1234"}`

    e := echo.New()
    req := httptest.NewRequest(http.MethodPost, "/sign-up", strings.NewReader(userJson))
    req.Header.Set("Content-Type", "application/json")

    rec := httptest.NewRecorder()

    c := e.NewContext(req, rec)

    if assert.NoError(t, SignUp(c)) {
        assert.Equal(t, http.StatusCreated, rec.Code)
    }
}

then I got this following error after running the test

=== RUN   TestConnect
--- PASS: TestConnect (0.00s)
=== RUN   TestSignUp
--- FAIL: TestSignUp (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
        panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0xb0 pc=0x80e4d6]

goroutine 21 [running]:
testing.tRunner.func1(0xc0001a0d00)
        /usr/local/go/src/testing/testing.go:874 +0x3a3
panic(0x916940, 0xdb6920)
        /usr/local/go/src/runtime/panic.go:679 +0x1b2
github.com/jinzhu/gorm.(*DB).clone(0x0, 0xc0001d5dd8)
        /go/pkg/mod/github.com/jinzhu/gorm@v1.9.11/main.go:821 +0x26
github.com/jinzhu/gorm.(*DB).Where(0x0, 0x8d13a0, 0xc0001a5340, 0x0, 0x0, 0x0, 0x7c0cb3)
        /go/pkg/mod/github.com/jinzhu/gorm@v1.9.11/main.go:232 +0x2f
go-auth-api/model.GetUser(0xc0001a5340, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
        /app/model/user.go:17 +0xb2
go-auth-api/handler.SignUp(0xa74dc0, 0xc0001b76c0, 0x0, 0xde6450)
        /app/handler/auth.go:38 +0x133
go-auth-api/handler.TestSignUp(0xc0001a0d00)
        /app/handler/auth_test.go:34 +0x33d
testing.tRunner(0xc0001a0d00, 0x9cbf48)
        /usr/local/go/src/testing/testing.go:909 +0xc9
created by testing.(*T).Run
        /usr/local/go/src/testing/testing.go:960 +0x350
FAIL    go-auth-api/handler     0.021s
FAIL

Api server is working perfect. this error only happens when I execute test.

Am I missing something in the test? or is my architecture wrong?

sorry for such a fundamental question but I'm really stuck.

Aucun commentaire:

Enregistrer un commentaire