14 - Add Signin Handler
Tags: go, gin, authentication, testing
Published: 2020-12-20 21:47:02

---
title: 14 - Add Signin Handler
published: true
description: Setting up an account server in Golang
tags: golang, gin, authentication, testing
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/aaysf904ijkki1qkl2vp.png
series: Full Stack Memorization App
---

Today's tutorial will be the first of 2 parts on implementing sign in functionality. We'll follow many of the same patterns we used to sign up users, implementing some new methods in the process!

As seen in the diagram, we'll start with the `handler` layer signin in method, and next time flush out the details of all the other layers, some of which functionality we've already implemented!

![Account Overview - Signin](https://dev-to-uploads.s3.amazonaws.com/i/dc0f9oy6a9qway40hoxm.png)

As always, checkout the [repo on Github](https://github.com/JacobSNGoodwin/memrizr) with all of the code for this tutorial, including a branch for each lesson!

If you prefer video, check out the video version below!
{% youtube Rkx_SBVzxwM %}

## Add Signin to UserService Interface

When a user signs in, we want to accept a user's email and password, just like we would when a user signs up. While the implementation details of signing in and signing up differ, the method signatures are the same. Let's add `Signin` to the `UserService` interface in `~/model/interfaces.go`. As with `Signin`, we'll pass a partially filled `*model.User`, and return an error if signing in fails. If signing in is successful, then the user passed to the method will be modified to contain all user details. 

```go
// UserService defines methods the handler layer expects
// any service it interacts with to implement
type UserService interface {
	Get(ctx context.Context, uid uuid.UUID) (*User, error)
	Signup(ctx context.Context, u *User) error
	Signin(ctx context.Context, u *User) error
}
```

This update will break our `mockUserService` and service-layer `UserService` as they do not implement this method. Let's add an incomplete implementation of `Signup` in `~/service/user_service.go`, which we'll complete in the next tutorial.

```go
// Signin reaches our to a UserRepository check if the user exists
// and then compares the supplied password with the provided password
// if a valid email/password combo is provided, u will hold all
// available user fields
func (s *userService) Signin(ctx context.Context, u *model.User) error {
	panic("Not implemented")
}
```

## Add Mock Signin Method

Before adding our handler logic and unit test, let's update our `~/model/mocks/user_service.go` so that with a `Signin` method. See previous tutorials on testing and [testify](https://github.com/stretchr/testify) to learn more!

```go
func (m *MockUserService) Signin(ctx context.Context, u *model.User) error {
	ret := m.Called(ctx, u)

	var r0 error
	if ret.Get(0) != nil {
		r0 = ret.Get(0).(error)
	}

	return r0
}
```

## Add Handler

Since we'll be adding a lot of code for this handler, let's copy the `Signin` handler from `~/handler/handler.go` into its own file, `~/handler/signin.go`. We'll also update the contents of this method as follows.

```go
package handler

// IMPORTS OMITTED

// signinReq is not exported
type signinReq struct {
	Email    string `json:"email" binding:"required,email"`
	Password string `json:"password" binding:"required,gte=6,lte=30"`
}

// Signin used to authenticate extant user
func (h *Handler) Signin(c *gin.Context) {
	var req signinReq

	if ok := bindData(c, &req); !ok {
		return
	}

	u := &model.User{
		Email:    req.Email,
		Password: req.Password,
	}

	ctx := c.Request.Context()
	err := h.UserService.Signin(ctx, u)

	if err != nil {
		log.Printf("Failed to sign in user: %v\n", err.Error())
		c.JSON(apperrors.Status(err), gin.H{
			"error": err,
		})
		return
	}

	tokens, err := h.TokenService.NewPairFromUser(ctx, u, "")

	if err != nil {
		log.Printf("Failed to create tokens for user: %v\n", err.Error())

		c.JSON(apperrors.Status(err), gin.H{
			"error": err,
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"tokens": tokens,
	})
}
```

We do the following in this handler method.

1. Bind data to `signinReq` using the helper `bind_data` function we previously created. This function sends a "bad request" HTTP status 400 error if the data cannot be bound or fails validation. In reality, it would be wise to create a separate test for `bind_data` (turns out this tutorial is flawed 😂).
2. If we do have valid data, we create a partially filled `*model.User` from the request's email and password fields.
3. Extract the request context from the gin context using `c.Request.Context()`.
4. Reach out to the `UserService.Signin` method and handle any errors. If there is any error, we use our `apperrors.Status()` function to determine is the error is among those defined in our custom `apperrors`.
5. If the user signs in successfully, we'll send a new token pair, just as we do when signing up. We also make sure to handle any errors.


## Testing Signin

Let's create a new file for this test, `~/handler/signin_test.go`. In this file we'll test the following.

1. Invalid request data - Although we won't test all validation error combinations, we will test one case to make sure that we receive the proper error JSON response for a bad request.
2. Error returned by UserService.Signin
3. Successful call to `TokenService.NewPairFromUser` (which means a successful handler result).
4. Error from `TokenService.NewPairFromUser`. Again, we just want to make sure the handler sends the proper HTTP response in this case.

### Test Body and Setup

We'll instantiate our mock services, gin engine/router, and handler in the setup portion of our test (before the `t.Run` blocks). 

However, I decided that it will be clearer to define our mock method responses, i.e. `mock.On(...)`, inside of the individual `t.Run` blocks instead of in the setup. I did this because I ended up creating so many mock method call definitions and variables in the setup that it was hard to understand what variables corresponded to which test cases.

```go
package handler

// IMPORTS OMITTED

func TestSignin(t *testing.T) {
	// Setup
	gin.SetMode(gin.TestMode)

	// setup mock services, gin engine/router, handler layer
	mockUserService := new(mocks.MockUserService)
	mockTokenService := new(mocks.MockTokenService)

	router := gin.Default()

	NewHandler(&Config{
		R:            router,
		UserService:  mockUserService,
		TokenService: mockTokenService,
  })
  
  // Tests will be added here below
  // ...
}
```

### Bad Request Data Case

In this case we create a JSON request body with an invalid email address and send this to our handler which we instantiated in the setup portion of the test. We assert that an HTTP Status of 400, `http.StatusBadRequest`, is sent and that the `mockUserService.Signin` and `mockTokenService.NewPairFromUser` methods don't get called as the handler should return before reaching these methods. 

```go
	t.Run("Bad request data", func(t *testing.T) {
		// a response recorder for getting written http response
		rr := httptest.NewRecorder()

		// create a request body with invalid fields
		reqBody, err := json.Marshal(gin.H{
			"email":    "notanemail",
			"password": "short",
		})
		assert.NoError(t, err)

		request, err := http.NewRequest(http.MethodPost, "/signin", bytes.NewBuffer(reqBody))
		assert.NoError(t, err)

		request.Header.Set("Content-Type", "application/json")
		router.ServeHTTP(rr, request)

		assert.Equal(t, http.StatusBadRequest, rr.Code)
    mockUserService.AssertNotCalled(t, "Signin")
    mockTokenService.AssertNotCalled(t, "NewTokensFromUser")
	})
```

### Error from UserService.Signin Case

In this case, we define a mock response for `Signin` which returns an error. We create an example of such an error and store it in `mockError`. We then assert that this error is relayed and sent as JSON to the user with HTTP status 401, `http.StatusUnauthorized`, and the assert that `mockUserService.Signin` method is called, but that `mockTokenService.NewPairFromUser` is not called. 

```go
	t.Run("Error Returned from UserService.Signin", func(t *testing.T) {
		email := "bob@bob.com"
		password := "pwdoesnotmatch123"

		mockUSArgs := mock.Arguments{
			mock.AnythingOfType("*context.emptyCtx"),
			&model.User{Email: email, Password: password},
		}

		// so we can check for a known status code
		mockError := apperrors.NewAuthorization("invalid email/password combo")

		mockUserService.On("Signin", mockUSArgs...).Return(mockError)

		// a response recorder for getting written http response
		rr := httptest.NewRecorder()

		// create a request body with valid fields
		reqBody, err := json.Marshal(gin.H{
			"email":    email,
			"password": password,
		})
		assert.NoError(t, err)

		request, err := http.NewRequest(http.MethodPost, "/signin", bytes.NewBuffer(reqBody))
		assert.NoError(t, err)

		request.Header.Set("Content-Type", "application/json")
		router.ServeHTTP(rr, request)

    mockUserService.AssertCalled(t, "Signin", mockUSArgs...)
    mockTokenService.AssertNotCalled(t, "NewTokensFromUser")
		assert.Equal(t, http.StatusUnauthorized, rr.Code)
	})
```

### Success Case

In this case, we respond to `mockUserService.Signin` with no error (`nil`). We also mock a valid token response from `mockTokenService.NewPairFromUser`. We assert that both of the above methods are called and that a valid HTTP response with HTTP status 200, `http.StatusOK`, is returned.

```go
  t.Run("Successful Token Creation", func(t *testing.T) {
		email := "bob@bob.com"
		password := "pwworksgreat123"

		mockUSArgs := mock.Arguments{
			mock.AnythingOfType("*context.emptyCtx"),
			&model.User{Email: email, Password: password},
		}

		mockUserService.On("Signin", mockUSArgs...).Return(nil)

		mockTSArgs := mock.Arguments{
			mock.AnythingOfType("*context.emptyCtx"),
			&model.User{Email: email, Password: password},
			"",
		}

		mockTokenPair := &model.TokenPair{
			IDToken:      "idToken",
			RefreshToken: "refreshToken",
		}

		mockTokenService.On("NewPairFromUser", mockTSArgs...).Return(mockTokenPair, nil)

		// a response recorder for getting written http response
		rr := httptest.NewRecorder()

		// create a request body with valid fields
		reqBody, err := json.Marshal(gin.H{
			"email":    email,
			"password": password,
		})
		assert.NoError(t, err)

		request, err := http.NewRequest(http.MethodPost, "/signin", bytes.NewBuffer(reqBody))
		assert.NoError(t, err)

		request.Header.Set("Content-Type", "application/json")
		router.ServeHTTP(rr, request)

		respBody, err := json.Marshal(gin.H{
			"tokens": mockTokenPair,
		})
		assert.NoError(t, err)

		assert.Equal(t, http.StatusOK, rr.Code)
		assert.Equal(t, respBody, rr.Body.Bytes())

		mockUserService.AssertCalled(t, "Signin", mockUSArgs...)
		mockTokenService.AssertCalled(t, "NewPairFromUser", mockTSArgs...)
	})
```

### Error from TokenService.NewPairFromUser Case

In this case, we want the `mockUserService.Signin` method to be called successfully, but want `mockTokenService.NewPairFromUser` to produce an error.  We then expect an HTTP 500, `http.StatusInternalServerError`, as a response. 

```go
  t.Run("Failed Token Creation", func(t *testing.T) {
		email := "cannotproducetoken@bob.com"
		password := "cannotproducetoken"

		mockUSArgs := mock.Arguments{
			mock.AnythingOfType("*context.emptyCtx"),
			&model.User{Email: email, Password: password},
		}

		mockUserService.On("Signin", mockUSArgs...).Return(nil)

		mockTSArgs := mock.Arguments{
			mock.AnythingOfType("*context.emptyCtx"),
			&model.User{Email: email, Password: password},
			"",
		}

		mockError := apperrors.NewInternal()
		mockTokenService.On("NewPairFromUser", mockTSArgs...).Return(nil, mockError)
		// a response recorder for getting written http response
		rr := httptest.NewRecorder()

		// create a request body with valid fields
		reqBody, err := json.Marshal(gin.H{
			"email":    email,
			"password": password,
		})
		assert.NoError(t, err)

		request, err := http.NewRequest(http.MethodPost, "/signin", bytes.NewBuffer(reqBody))
		assert.NoError(t, err)

		request.Header.Set("Content-Type", "application/json")
		router.ServeHTTP(rr, request)

		respBody, err := json.Marshal(gin.H{
			"error": mockError,
		})
		assert.NoError(t, err)

		assert.Equal(t, mockError.Status(), rr.Code)
		assert.Equal(t, respBody, rr.Body.Bytes())

		mockUserService.AssertCalled(t, "Signin", mockUSArgs...)
		mockTokenService.AssertCalled(t, "NewPairFromUser", mockTSArgs...)
	})
```

## Conclusion

That's all for today, chicos! Next time, we'll write the concrete implementation of `UserService.Signin` as well as the needed `UserRepository` methods to get a user from the database and verify their entered password.  

¡Chau, hasta luego!