github.com/mmrath/gobase@v0.0.1/client/test/user_handler_test.go (about)

     1  package test
     2  
     3  import (
     4  	"bytes"
     5  	"github.com/mmrath/gobase/common/auth"
     6  	"github.com/mmrath/gobase/common/crypto"
     7  	"log"
     8  	"net/http"
     9  	"regexp"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/dgrijalva/jwt-go"
    14  	"github.com/gavv/httpexpect"
    15  	"github.com/mmrath/gobase/model"
    16  	"github.com/stretchr/testify/require"
    17  	"github.com/stretchr/testify/suite"
    18  )
    19  
    20  type AccountTestSuite struct {
    21  	TestSuite
    22  }
    23  
    24  func TestAccountSuite(t *testing.T) {
    25  	suite.Run(t, new(AccountTestSuite))
    26  }
    27  
    28  func (s *AccountTestSuite) TestPing() {
    29  	resp, err := http.Get(s.server.URL + "/ping")
    30  	require.NoError(s.T(), err)
    31  	require.Equal(s.T(), 200, resp.StatusCode)
    32  
    33  	buf := new(bytes.Buffer)
    34  	_, err = buf.ReadFrom(resp.Body)
    35  	require.NoError(s.T(), err)
    36  	contents := buf.String()
    37  	require.Equal(s.T(), "pong", contents)
    38  }
    39  
    40  func (s *AccountTestSuite) TestSignUpActivateAndLogin() {
    41  	he := httpexpect.New(s.T(), s.server.URL)
    42  	testEmail := "test@example.com"
    43  	testPassword := "Secret123"
    44  	registerRequest := map[string]interface{}{
    45  		"firstName": "Murali",
    46  		"lastName":  "Rath",
    47  		"email":     testEmail,
    48  		"password":  testPassword,
    49  	}
    50  	he.POST("/api/account/register").
    51  		WithJSON(registerRequest).
    52  		Expect().
    53  		Status(http.StatusOK)
    54  
    55  	// Login should throw an error now
    56  	msg := s.mailer.PopLastMessage()
    57  	require.NotNil(s.T(), msg)
    58  	require.Equal(s.T(), "Activate your account", msg.Subject)
    59  	require.Equal(s.T(), registerRequest["email"], msg.To[0].Email)
    60  
    61  	resp := he.POST("/api/account/login").
    62  		WithJSON(model.LoginRequest{Email: testEmail, Password: testPassword}).
    63  		Expect()
    64  	resp.Status(http.StatusUnauthorized)
    65  	resp.JSON().Path("$.details.cause").Equal("user is not activated")
    66  
    67  	re := regexp.MustCompile("/account/activate\\?key=([0-9a-f\\-]+)")
    68  	key := re.FindStringSubmatch(msg.Html)[1]
    69  	he.GET("/api/account/activate").
    70  		WithQuery("key", key).
    71  		Expect().
    72  		Status(http.StatusOK)
    73  
    74  	resp = he.POST("/api/account/login").
    75  		WithJSON(model.LoginRequest{Email: testEmail, Password: testPassword}).
    76  		Expect()
    77  	resp.Status(http.StatusOK)
    78  	token := resp.Header("Authorization").Match("Bearer (.*)").Raw()[1]
    79  
    80  	jwtService := auth.NewJWTService(s.cfg.JWT)
    81  	jwtToken, err := jwtService.Decode(token)
    82  	require.NoError(s.T(), err)
    83  	err = jwtToken.Claims.Valid()
    84  	require.NoError(s.T(), err)
    85  	claims := jwtToken.Claims.(jwt.MapClaims)
    86  	require.NotNil(s.T(), claims["jti"])
    87  	require.Equal(s.T(), testEmail, claims["sub"])
    88  }
    89  
    90  func (s *AccountTestSuite) TestSignUpWithInvalidEmail() {
    91  	he := httpexpect.New(s.T(), s.server.URL)
    92  	signupRequest := map[string]interface{}{
    93  		"firstName": "Murali",
    94  		"lastName":  "Rath",
    95  		"email":     "test234",
    96  		"password":  "Secret123",
    97  	}
    98  	resp := he.POST("/api/account/register").WithJSON(signupRequest).Expect()
    99  	resp.Status(http.StatusBadRequest)
   100  	resp.JSON().Path("$.details.fieldErrors[0].field").Equal("email")
   101  	resp.JSON().Path("$.details.fieldErrors[0].message").Equal("must be a valid email address")
   102  }
   103  
   104  func (s *AccountTestSuite) TestSignUpWithDuplicateEmail() {
   105  	he := httpexpect.New(s.T(), s.server.URL)
   106  	signupRequest := map[string]interface{}{
   107  		"firstName": "Murali",
   108  		"lastName":  "Rath",
   109  		"email":     "test@example.com",
   110  		"password":  "Secret123",
   111  	}
   112  	resp := he.POST("/api/account/register").WithJSON(signupRequest).Expect()
   113  	resp.Status(http.StatusOK)
   114  
   115  	// 2nd Request
   116  	resp = he.POST("/api/account/register").WithJSON(signupRequest).Expect()
   117  	resp.Status(http.StatusBadRequest)
   118  
   119  	resp.JSON().Path("$.details.fieldErrors[0].field").Equal("email")
   120  	resp.JSON().Path("$.details.fieldErrors[0].message").Equal("user already exists")
   121  }
   122  
   123  func (s *AccountTestSuite) TestSignUpWithInvalidPassword() {
   124  	he := httpexpect.New(s.T(), s.server.URL)
   125  	signupRequest := map[string]interface{}{
   126  		"firstName": "Murali",
   127  		"lastName":  "Rath",
   128  		"email":     "test@example.com",
   129  		"password":  "123", /// too short
   130  	}
   131  	// 2nd Request
   132  	resp := he.POST("/api/account/register").WithJSON(signupRequest).Expect()
   133  	resp.Status(http.StatusBadRequest)
   134  
   135  	resp.JSON().Path("$.details.fieldErrors[0].field").Equal("password")
   136  	resp.JSON().Path("$.details.fieldErrors[0].message").Equal("the length must be between 6 and 32")
   137  }
   138  
   139  func (s *AccountTestSuite) TestSignUpWithLongPassword() {
   140  	he := httpexpect.New(s.T(), s.server.URL)
   141  	signupRequest := map[string]interface{}{
   142  		"firstName": "Murali",
   143  		"lastName":  "Rath",
   144  		"email":     "test@example.com",
   145  		"password":  "12345678901234567890123456789012",
   146  	}
   147  	// 2nd Request
   148  	resp := he.POST("/api/account/register").WithJSON(signupRequest).Expect()
   149  	resp.Status(http.StatusOK)
   150  }
   151  
   152  func (s *AccountTestSuite) TestSignUpWithTooLongPassword() {
   153  	he := httpexpect.New(s.T(), s.server.URL)
   154  	signupRequest := map[string]interface{}{
   155  		"firstName": "Murali",
   156  		"lastName":  "Rath",
   157  		"email":     "test@example.com",
   158  		"password":  "12345678901234567890123456789012232",
   159  	}
   160  	// 2nd Request
   161  	resp := he.POST("/api/account/register").WithJSON(signupRequest).Expect()
   162  	resp.Status(http.StatusBadRequest)
   163  	resp.JSON().Path("$.details.fieldErrors[0].field").Equal("password")
   164  	resp.JSON().Path("$.details.fieldErrors[0].message").Equal("the length must be between 6 and 32")
   165  }
   166  
   167  func (s *AccountTestSuite) TestActivateWithWrongKey() {
   168  	he := httpexpect.New(s.T(), s.server.URL)
   169  	resp := he.GET("/api/account/activate").
   170  		WithQuery("key", "wrong-key").
   171  		Expect().
   172  		Status(http.StatusBadRequest)
   173  	resp.JSON().Path("$.details.cause").Equal("invalid activation token")
   174  }
   175  
   176  func (s *AccountTestSuite) TestResetPassword() {
   177  	testEmail := "testuser@localhost"
   178  	he := httpexpect.New(s.T(), s.server.URL)
   179  	initResetRequest := map[string]interface{}{
   180  		"email": testEmail,
   181  	}
   182  	resp := he.POST("/api/account/reset-password/init").
   183  		WithJSON(initResetRequest).Expect()
   184  	resp.Status(http.StatusOK)
   185  
   186  	// Login should throw an error now
   187  	msg := s.mailer.PopLastMessage()
   188  	require.NotNil(s.T(), msg)
   189  	require.Equal(s.T(), "Reset password", msg.Subject)
   190  	require.Equal(s.T(), initResetRequest["email"], msg.To[0].Email)
   191  
   192  	re := regexp.MustCompile("/account/reset-password\\?key=([0-9a-f\\-]+)")
   193  	key := re.FindStringSubmatch(msg.Html)[1]
   194  
   195  	resetRequest := map[string]interface{}{
   196  		"resetToken":  key,
   197  		"newPassword": "Secret123",
   198  	}
   199  
   200  	he.POST("/api/account/reset-password/finish").
   201  		WithJSON(resetRequest).
   202  		Expect().
   203  		Status(http.StatusOK)
   204  
   205  	resp = he.POST("/api/account/login").
   206  		WithJSON(model.LoginRequest{Email: testEmail, Password: "Secret123"}).
   207  		Expect()
   208  	resp.Status(http.StatusOK)
   209  	token := resp.Header("Authorization").Match("Bearer (.*)").Raw()[1]
   210  
   211  	jwtService := auth.NewJWTService(s.cfg.JWT)
   212  	jwtToken, err := jwtService.Decode(token)
   213  	require.NoError(s.T(), err)
   214  	err = jwtToken.Claims.Valid()
   215  	require.NoError(s.T(), err)
   216  	claims := jwtToken.Claims.(jwt.MapClaims)
   217  	require.NotNil(s.T(), claims["jti"])
   218  	require.Equal(s.T(), testEmail, claims["sub"])
   219  
   220  }
   221  
   222  func (s *AccountTestSuite) TestWithWrongUsername() {
   223  	testEmail := "none@gobase.mmrath.com"
   224  	he := httpexpect.New(s.T(), s.server.URL)
   225  
   226  	resp := he.POST("/api/account/login").
   227  		WithJSON(model.LoginRequest{Email: testEmail, Password: "Secret123"}).
   228  		Expect()
   229  	resp.Status(http.StatusUnauthorized)
   230  	_ = resp.Header("Authorization").NotMatch("Bearer (.*)")
   231  	resp.JSON().Path("$.details.cause").Equal("invalid email or password")
   232  }
   233  
   234  func (s *AccountTestSuite) TestWithWrongPassword() {
   235  	testEmail := "none@gobase.mmrath.com"
   236  
   237  	s.createUser(testEmail, "Secret123")
   238  
   239  	he := httpexpect.New(s.T(), s.server.URL)
   240  
   241  	resp := he.POST("/api/account/login").
   242  		WithJSON(model.LoginRequest{Email: testEmail, Password: "incorrectPassword"}).
   243  		Expect()
   244  	resp.Status(http.StatusUnauthorized)
   245  	_ = resp.Header("Authorization").NotMatch("Bearer (.*)")
   246  	resp.JSON().Path("$.details.cause").Equal("invalid email or password")
   247  }
   248  
   249  func (s *AccountTestSuite) TestChangePassword() {
   250  	testEmail := "testuser1@localhost"
   251  	password := "Secret123"
   252  	newPassword := "NewSecret123"
   253  	s.createUser(testEmail, "Secret123")
   254  	he := httpexpect.New(s.T(), s.server.URL)
   255  
   256  	resp := he.POST("/api/account/login").
   257  		WithJSON(model.LoginRequest{Email: testEmail, Password: password}).
   258  		Expect()
   259  	resp.Status(http.StatusOK)
   260  	token := resp.Header("Authorization").Match("Bearer (.*)").Raw()[1]
   261  	require.NotNil(s.T(), token)
   262  
   263  	resp = he.POST("/api/account/change-password").
   264  		WithJSON(model.ChangePasswordRequest{CurrentPassword: password, NewPassword: newPassword}).
   265  		WithHeader("Authorization", "Bearer "+token).
   266  		Expect()
   267  	resp.Status(http.StatusOK)
   268  
   269  	// Try with the old password
   270  	resp = he.POST("/api/account/login").
   271  		WithJSON(model.LoginRequest{Email: testEmail, Password: password}).
   272  		Expect()
   273  	resp.Status(http.StatusUnauthorized)
   274  	resp.JSON().Path("$.details.cause").Equal("invalid email or password")
   275  
   276  	// Try with the new password
   277  	resp = he.POST("/api/account/login").
   278  		WithJSON(model.LoginRequest{Email: testEmail, Password: newPassword}).
   279  		Expect()
   280  	resp.Status(http.StatusOK)
   281  	token = resp.Header("Authorization").Match("Bearer (.*)").Raw()[1]
   282  	require.NotNil(s.T(), token)
   283  
   284  }
   285  
   286  func (s *AccountTestSuite) createUser(email string, password string) {
   287  	stmts := []string{
   288  		`INSERT INTO public.user_account(
   289  	first_name, last_name, email, phone_number, active, created_at, created_by, updated_at, updated_by, version)
   290  	VALUES ('Test', 'Test', ?, NULL, true, current_timestamp, 'test', current_timestamp, 'test', 1) RETURNING ID`,
   291  		`INSERT INTO public.user_credential(
   292  	id, password_hash, expires_at, invalid_attempts, locked, activation_key, activation_key_expires_at, activated, reset_key, reset_key_expires_at, reset_at, updated_at, version)
   293  	SELECT id, ?, ?, 0, false, null, null, true, NULL, NULL, NULL, current_timestamp, 1 FROM user_account where email = ?`,
   294  	}
   295  
   296  	_, err := s.db.Exec(stmts[0], email)
   297  	if err != nil {
   298  		log.Printf("Error executing statement %s", stmts[0])
   299  		panic(err)
   300  	}
   301  	passwordHash, err := crypto.HashPassword(crypto.SHA256([]byte(password)))
   302  	if err != nil {
   303  		log.Printf("Error hasing password")
   304  		panic(err)
   305  	}
   306  	_, err = s.db.Exec(stmts[1], passwordHash, time.Now().Add(time.Second*1200), email)
   307  	if err != nil {
   308  		log.Printf("Error creating credentials")
   309  		panic(err)
   310  	}
   311  }