github.com/decred/politeia@v1.4.0/politeiawww/legacy/wwwuser_test.go (about)

     1  // Copyright (c) 2017-2020 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package legacy
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/hex"
    10  	"encoding/json"
    11  	"io"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/davecgh/go-spew/spew"
    18  	"github.com/decred/politeia/politeiad/api/v1/identity"
    19  	www "github.com/decred/politeia/politeiawww/api/www/v1"
    20  	"github.com/go-test/deep"
    21  	"github.com/gorilla/mux"
    22  	"github.com/gorilla/sessions"
    23  )
    24  
    25  func TestHandleNewUser(t *testing.T) {
    26  	p, cleanup := newTestPoliteiawww(t)
    27  	defer cleanup()
    28  
    29  	id, err := identity.New()
    30  	if err != nil {
    31  		t.Fatalf("%v", err)
    32  	}
    33  
    34  	// Setup tests
    35  	var tests = []struct {
    36  		name       string
    37  		reqBody    interface{}
    38  		wantStatus int
    39  		wantError  error
    40  	}{
    41  		{
    42  			"invalid request body",
    43  			"",
    44  			http.StatusBadRequest,
    45  			www.UserError{
    46  				ErrorCode: www.ErrorStatusInvalidInput,
    47  			},
    48  		},
    49  		{
    50  			"processNewUser error",
    51  			www.NewUser{},
    52  			http.StatusBadRequest,
    53  			www.UserError{
    54  				ErrorCode: www.ErrorStatusMalformedEmail,
    55  			},
    56  		},
    57  		{
    58  			"success",
    59  			www.NewUser{
    60  				Email:     "user@example.com",
    61  				Password:  "password",
    62  				PublicKey: hex.EncodeToString(id.Public.Key[:]),
    63  				Username:  "user",
    64  			},
    65  			http.StatusOK,
    66  			nil,
    67  		},
    68  	}
    69  
    70  	// Run tests
    71  	for _, v := range tests {
    72  		t.Run(v.name, func(t *testing.T) {
    73  			// Setup request
    74  			r := newPostReq(t, www.RouteNewUser, v.reqBody)
    75  			w := httptest.NewRecorder()
    76  
    77  			// Run test case
    78  			p.handleNewUser(w, r)
    79  			res := w.Result()
    80  			body, _ := io.ReadAll(res.Body)
    81  			res.Body.Close()
    82  
    83  			// Validate response
    84  			if res.StatusCode != v.wantStatus {
    85  				t.Errorf("got status code %v, want %v",
    86  					res.StatusCode, v.wantStatus)
    87  			}
    88  
    89  			if res.StatusCode == http.StatusOK {
    90  				// Test case passes; next case
    91  				return
    92  			}
    93  
    94  			var ue www.UserError
    95  			err = json.Unmarshal(body, &ue)
    96  			if err != nil {
    97  				t.Errorf("unmarshal UserError: %v", err)
    98  			}
    99  
   100  			got := errToStr(ue)
   101  			want := errToStr(v.wantError)
   102  			if got != want {
   103  				t.Errorf("got error %v, want %v", got, want)
   104  			}
   105  		})
   106  	}
   107  }
   108  
   109  func TestHandleVerifyNewUser(t *testing.T) {
   110  	p, cleanup := newTestPoliteiawww(t)
   111  	defer cleanup()
   112  
   113  	// Create an unverified user to test against
   114  	usr, id := newUser(t, p, false, false)
   115  	token := hex.EncodeToString(usr.NewUserVerificationToken)
   116  	s := id.SignMessage([]byte(token))
   117  	sig := hex.EncodeToString(s[:])
   118  
   119  	// Test invalid query param. We have to run it individually
   120  	// so that we can set the wrong query param.
   121  	t.Run("invalid query params", func(t *testing.T) {
   122  		// Setup request
   123  		r := httptest.NewRequest(http.MethodGet, www.RouteVerifyNewUser, nil)
   124  		w := httptest.NewRecorder()
   125  
   126  		q := r.URL.Query()
   127  		q.Add("hello", "world")
   128  		r.URL.RawQuery = q.Encode()
   129  
   130  		// Run test case
   131  		p.handleVerifyNewUser(w, r)
   132  		res := w.Result()
   133  		body, _ := io.ReadAll(res.Body)
   134  		res.Body.Close()
   135  
   136  		// Validate response
   137  		if res.StatusCode != http.StatusBadRequest {
   138  			t.Errorf("got status code %v, want %v",
   139  				res.StatusCode, http.StatusBadRequest)
   140  		}
   141  
   142  		var ue www.UserError
   143  		err := json.Unmarshal(body, &ue)
   144  		if err != nil {
   145  			t.Errorf("unmarshal UserError: %v", err)
   146  		}
   147  
   148  		got := errToStr(ue)
   149  		want := www.ErrorStatus[www.ErrorStatusInvalidInput]
   150  		if got != want {
   151  			t.Errorf("got error %v, want %v", got, want)
   152  		}
   153  	})
   154  
   155  	// Setup remaining tests
   156  	var tests = []struct {
   157  		name       string
   158  		params     www.VerifyNewUser
   159  		wantStatus int
   160  		wantError  error
   161  	}{
   162  		{
   163  			"processVerifyNewUser error",
   164  			www.VerifyNewUser{}, http.StatusBadRequest,
   165  			www.UserError{
   166  				ErrorCode: www.ErrorStatusVerificationTokenInvalid,
   167  			},
   168  		},
   169  		{
   170  			"success",
   171  			www.VerifyNewUser{
   172  				Email:             usr.Email,
   173  				VerificationToken: token,
   174  				Signature:         sig,
   175  			},
   176  			http.StatusOK,
   177  			nil,
   178  		},
   179  	}
   180  
   181  	// Run tests
   182  	for _, v := range tests {
   183  		t.Run(v.name, func(t *testing.T) {
   184  			// Setup request
   185  			r := httptest.NewRequest(http.MethodGet, www.RouteVerifyNewUser, nil)
   186  			w := httptest.NewRecorder()
   187  
   188  			q := r.URL.Query()
   189  			q.Add("email", v.params.Email)
   190  			q.Add("verificationtoken", v.params.VerificationToken)
   191  			q.Add("signature", v.params.Signature)
   192  			r.URL.RawQuery = q.Encode()
   193  
   194  			// Run test case
   195  			p.handleVerifyNewUser(w, r)
   196  			res := w.Result()
   197  			body, _ := io.ReadAll(res.Body)
   198  			res.Body.Close()
   199  
   200  			// Validate response
   201  			if res.StatusCode != v.wantStatus {
   202  				t.Errorf("got status code %v, want %v",
   203  					res.StatusCode, v.wantStatus)
   204  			}
   205  
   206  			if res.StatusCode == http.StatusOK {
   207  				// Test case passes; next case
   208  				return
   209  			}
   210  
   211  			var ue www.UserError
   212  			err := json.Unmarshal(body, &ue)
   213  			if err != nil {
   214  				t.Errorf("unmarshal UserError: %v", err)
   215  			}
   216  
   217  			got := errToStr(ue)
   218  			want := errToStr(v.wantError)
   219  			if got != want {
   220  				t.Errorf("got error %v, want %v", got, want)
   221  			}
   222  		})
   223  	}
   224  }
   225  
   226  func TestHandleResendVerification(t *testing.T) {
   227  	p, cleanup := newTestPoliteiawww(t)
   228  	defer cleanup()
   229  
   230  	// Create a verified user
   231  	usrVerified, _ := newUser(t, p, true, false)
   232  
   233  	// Create an unverified user that has already had the
   234  	// verification email resent.
   235  	usrResent, _ := newUser(t, p, false, false)
   236  	usrResent.ResendNewUserVerificationExpiry = time.Now().Unix() + 100
   237  	err := p.db.UserUpdate(*usrResent)
   238  	if err != nil {
   239  		t.Fatalf("%v", err)
   240  	}
   241  
   242  	// Create a user for the success case
   243  	usr, id := newUser(t, p, false, false)
   244  	usrPubkey := id.Public.String()
   245  
   246  	// Setup tests
   247  	var tests = []struct {
   248  		name       string
   249  		reqBody    interface{}
   250  		wantStatus int
   251  		wantError  error
   252  	}{
   253  		{
   254  			"invalid request body",
   255  			"",
   256  			http.StatusBadRequest,
   257  			www.UserError{
   258  				ErrorCode: www.ErrorStatusInvalidInput,
   259  			},
   260  		},
   261  		{
   262  			"user not found",
   263  			www.ResendVerification{
   264  				Email:     "",
   265  				PublicKey: usrPubkey,
   266  			},
   267  			http.StatusOK,
   268  			nil,
   269  		},
   270  		{
   271  			"user already verified",
   272  			www.ResendVerification{
   273  				Email: usrVerified.Email,
   274  			},
   275  			http.StatusOK,
   276  			nil,
   277  		},
   278  		{
   279  			"verification already resent",
   280  			www.ResendVerification{
   281  				Email: usrResent.Email,
   282  			},
   283  			http.StatusOK,
   284  			nil,
   285  		},
   286  		{
   287  			"processResendVerification error",
   288  			www.ResendVerification{
   289  				Email:     usr.Email,
   290  				PublicKey: "abc",
   291  			},
   292  			http.StatusBadRequest,
   293  			www.UserError{
   294  				ErrorCode: www.ErrorStatusInvalidPublicKey,
   295  			},
   296  		},
   297  		{
   298  			"success",
   299  			www.ResendVerification{
   300  				Email:     usr.Email,
   301  				PublicKey: usrPubkey,
   302  			},
   303  			http.StatusOK,
   304  			nil,
   305  		},
   306  	}
   307  
   308  	// Run tests
   309  	for _, v := range tests {
   310  		t.Run(v.name, func(t *testing.T) {
   311  			r := newPostReq(t, www.RouteResendVerification, v.reqBody)
   312  			w := httptest.NewRecorder()
   313  			p.handleResendVerification(w, r)
   314  			res := w.Result()
   315  			body, _ := io.ReadAll(res.Body)
   316  			res.Body.Close()
   317  
   318  			// Validate response
   319  			if res.StatusCode != v.wantStatus {
   320  				t.Errorf("got status code %v, want %v",
   321  					res.StatusCode, v.wantStatus)
   322  			}
   323  
   324  			if res.StatusCode == http.StatusOK {
   325  				// Test case passes; next case
   326  				return
   327  			}
   328  
   329  			var ue www.UserError
   330  			err := json.Unmarshal(body, &ue)
   331  			if err != nil {
   332  				t.Errorf("unmarshal UserError: %v", err)
   333  			}
   334  
   335  			got := errToStr(ue)
   336  			want := errToStr(v.wantError)
   337  			if got != want {
   338  				t.Errorf("got error %v, want %v", got, want)
   339  			}
   340  		})
   341  	}
   342  }
   343  
   344  func TestHandleLogin(t *testing.T) {
   345  	p, cleanup := newTestPoliteiawww(t)
   346  	defer cleanup()
   347  
   348  	// loginMinWaitTime is a global variable used to prevent
   349  	// timing attacks. We're not testing it here so we
   350  	// temporarily zero it out to make the tests run faster.
   351  	m := loginMinWaitTime
   352  	loginMinWaitTime = 0
   353  	defer func() {
   354  		loginMinWaitTime = m
   355  	}()
   356  
   357  	// Create a user to test against. newUser() sets the
   358  	// password to be the same as the username.
   359  	u, _ := newUser(t, p, true, false)
   360  	password := u.Username
   361  	successReply, err := p.createLoginReply(u, u.LastLoginTime)
   362  	if err != nil {
   363  		t.Fatalf("%v", err)
   364  	}
   365  	successReply.SessionMaxAge = testSessionMaxAge
   366  
   367  	// Setup tests
   368  	var tests = []struct {
   369  		name       string
   370  		reqBody    interface{}
   371  		wantStatus int
   372  		wantReply  *www.LoginReply
   373  		wantError  error
   374  	}{
   375  		{
   376  			"invalid request body",
   377  			"",
   378  			http.StatusBadRequest,
   379  			nil,
   380  			www.UserError{
   381  				ErrorCode: www.ErrorStatusInvalidInput,
   382  			},
   383  		},
   384  		{
   385  			"processLogin error",
   386  			www.Login{},
   387  			http.StatusUnauthorized,
   388  			nil,
   389  			www.UserError{
   390  				ErrorCode: www.ErrorStatusInvalidLogin,
   391  			},
   392  		},
   393  		{
   394  			"success",
   395  			www.Login{
   396  				Email:    u.Email,
   397  				Password: password,
   398  			},
   399  			http.StatusOK,
   400  			successReply,
   401  			nil,
   402  		},
   403  	}
   404  
   405  	// Run tests
   406  	for _, v := range tests {
   407  		t.Run(v.name, func(t *testing.T) {
   408  			// Setup request
   409  			r := newPostReq(t, www.RouteLogin, v.reqBody)
   410  			w := httptest.NewRecorder()
   411  
   412  			// Run test case
   413  			p.handleLogin(w, r)
   414  			res := w.Result()
   415  			body, _ := io.ReadAll(res.Body)
   416  			res.Body.Close()
   417  
   418  			// Validate response
   419  			if res.StatusCode != v.wantStatus {
   420  				t.Errorf("got status code %v, want %v",
   421  					res.StatusCode, v.wantStatus)
   422  			}
   423  
   424  			if res.StatusCode == http.StatusOK {
   425  				// A user session should have been added to the response
   426  				// cookie.
   427  				var sessionID string
   428  				for _, v := range res.Cookies() {
   429  					if v.Name == www.CookieSession && v.Value != "" {
   430  						sessionID = v.Value
   431  					}
   432  				}
   433  				if sessionID == "" {
   434  					t.Errorf("no session cookie in response")
   435  				}
   436  
   437  				// A user session should have been added to the session
   438  				// store. The best way to check this is to add a session
   439  				// cookie onto a request and use the getSession() method.
   440  				req := httptest.NewRequest(http.MethodGet, "/",
   441  					bytes.NewReader([]byte{}))
   442  				opts := newSessionOptions()
   443  				c := sessions.NewCookie(www.CookieSession, sessionID, opts)
   444  				req.AddCookie(c)
   445  				s, err := p.sessions.GetSession(req)
   446  				if err != nil {
   447  					t.Error(err)
   448  				}
   449  				if s.IsNew {
   450  					t.Errorf("session not saved to session store")
   451  				}
   452  
   453  				// Check response body
   454  				var lr www.LoginReply
   455  				err = json.Unmarshal(body, &lr)
   456  				if err != nil {
   457  					t.Errorf("unmarshal LoginReply: %v", err)
   458  				}
   459  
   460  				diff := deep.Equal(lr, *v.wantReply)
   461  				if diff != nil {
   462  					t.Errorf("LoginReply got/want diff:\n%v",
   463  						spew.Sdump(diff))
   464  				}
   465  
   466  				// Test case passes; next case
   467  				return
   468  			}
   469  
   470  			var ue www.UserError
   471  			err := json.Unmarshal(body, &ue)
   472  			if err != nil {
   473  				t.Errorf("unmarshal UserError: %v", err)
   474  			}
   475  
   476  			got := errToStr(ue)
   477  			want := errToStr(v.wantError)
   478  			if got != want {
   479  				t.Errorf("got error %v, want %v", got, want)
   480  			}
   481  		})
   482  	}
   483  }
   484  
   485  func TestHandleChangePassword(t *testing.T) {
   486  	p, cleanup := newTestPoliteiawww(t)
   487  	defer cleanup()
   488  
   489  	// Create a user to test against. newUser()
   490  	// sets the password to be the username.
   491  	usr, _ := newUser(t, p, true, false)
   492  	currPass := usr.Username
   493  	newPass := currPass + "aaa"
   494  
   495  	var tests = []struct {
   496  		name       string
   497  		reqBody    interface{}
   498  		wantStatus int
   499  		wantError  error
   500  	}{
   501  		// Middleware will catch any invalid user sessions.
   502  		// We can assume that the request contains a valid
   503  		// user session.
   504  
   505  		{
   506  			"invalid request body",
   507  			"",
   508  			http.StatusBadRequest,
   509  			www.UserError{
   510  				ErrorCode: www.ErrorStatusInvalidInput,
   511  			},
   512  		},
   513  		{
   514  			"processChangePassword error",
   515  			www.ChangePassword{
   516  				CurrentPassword: "",
   517  				NewPassword:     newPass,
   518  			},
   519  			http.StatusBadRequest,
   520  			www.UserError{
   521  				ErrorCode: www.ErrorStatusInvalidPassword,
   522  			},
   523  		},
   524  		{
   525  			"success",
   526  			www.ChangePassword{
   527  				CurrentPassword: currPass,
   528  				NewPassword:     newPass,
   529  			},
   530  			http.StatusOK,
   531  			nil,
   532  		},
   533  	}
   534  
   535  	// Run tests
   536  	for _, v := range tests {
   537  		t.Run(v.name, func(t *testing.T) {
   538  			// Setup request
   539  			r := newPostReq(t, www.RouteChangePassword, v.reqBody)
   540  			addSessionToReq(t, p, r, usr.ID.String())
   541  			w := httptest.NewRecorder()
   542  
   543  			// Run test case
   544  			p.handleChangePassword(w, r)
   545  			res := w.Result()
   546  			body, _ := io.ReadAll(res.Body)
   547  			res.Body.Close()
   548  
   549  			// Validate response
   550  			if res.StatusCode != v.wantStatus {
   551  				t.Errorf("got status code %v, want %v",
   552  					res.StatusCode, v.wantStatus)
   553  			}
   554  
   555  			if res.StatusCode == http.StatusOK {
   556  				// Test case passes; next case
   557  				return
   558  			}
   559  
   560  			var ue www.UserError
   561  			err := json.Unmarshal(body, &ue)
   562  			if err != nil {
   563  				t.Errorf("unmarshal UserError: %v", err)
   564  			}
   565  
   566  			got := errToStr(ue)
   567  			want := errToStr(v.wantError)
   568  			if got != want {
   569  				t.Errorf("got error %v, want %v", got, want)
   570  			}
   571  		})
   572  	}
   573  }
   574  
   575  func TestHandleResetPassword(t *testing.T) {
   576  	p, cleanup := newTestPoliteiawww(t)
   577  	defer cleanup()
   578  
   579  	// Create a test user
   580  	usr, _ := newUser(t, p, true, false)
   581  
   582  	// Remove the min wait time requirement so that the tests
   583  	// aren't slow.
   584  	wt := resetPasswordMinWaitTime
   585  	resetPasswordMinWaitTime = 0 * time.Millisecond
   586  	defer func() {
   587  		resetPasswordMinWaitTime = wt
   588  	}()
   589  
   590  	// Setup tests
   591  	var tests = []struct {
   592  		name       string
   593  		reqBody    interface{}
   594  		wantStatus int // HTTP status code
   595  		wantError  error
   596  	}{
   597  		{
   598  			"invalid request body",
   599  			"",
   600  			http.StatusBadRequest,
   601  			www.UserError{
   602  				ErrorCode: www.ErrorStatusInvalidInput,
   603  			},
   604  		},
   605  		{
   606  			"processResetPassword error",
   607  			www.ResetPassword{
   608  				Username: "wrongusername",
   609  			},
   610  			http.StatusBadRequest,
   611  			www.UserError{
   612  				ErrorCode: www.ErrorStatusUserNotFound,
   613  			},
   614  		},
   615  		{
   616  			"success",
   617  			www.ResetPassword{
   618  				Username: usr.Username,
   619  				Email:    usr.Email,
   620  			},
   621  			http.StatusOK,
   622  			nil,
   623  		},
   624  	}
   625  
   626  	// Run tests
   627  	for _, v := range tests {
   628  		t.Run(v.name, func(t *testing.T) {
   629  			// Setup request
   630  			r := newPostReq(t, www.RouteResetPassword, v.reqBody)
   631  			w := httptest.NewRecorder()
   632  
   633  			// Run test case
   634  			p.handleResetPassword(w, r)
   635  			res := w.Result()
   636  			body, _ := io.ReadAll(res.Body)
   637  			res.Body.Close()
   638  
   639  			// Validate response
   640  			if res.StatusCode != v.wantStatus {
   641  				t.Errorf("got status code %v, want %v",
   642  					res.StatusCode, v.wantStatus)
   643  			}
   644  
   645  			if res.StatusCode == http.StatusOK {
   646  				// Test case passes; next case
   647  				return
   648  			}
   649  
   650  			var ue www.UserError
   651  			err := json.Unmarshal(body, &ue)
   652  			if err != nil {
   653  				t.Errorf("unmarshal UserError: %v", err)
   654  			}
   655  
   656  			got := errToStr(ue)
   657  			want := errToStr(v.wantError)
   658  			if got != want {
   659  				t.Errorf("got error %v, want %v", got, want)
   660  			}
   661  		})
   662  	}
   663  }
   664  
   665  func TestHandleVerifyResetPassword(t *testing.T) {
   666  	p, cleanup := newTestPoliteiawww(t)
   667  	defer cleanup()
   668  
   669  	// Create a user that has already been assigned a reset
   670  	// password verification token.
   671  	usr, _ := newUser(t, p, true, false)
   672  	token, expiry, err := newVerificationTokenAndExpiry()
   673  	if err != nil {
   674  		t.Fatal(err)
   675  	}
   676  	usr.ResetPasswordVerificationToken = token
   677  	usr.ResetPasswordVerificationExpiry = expiry
   678  	err = p.db.UserUpdate(*usr)
   679  	if err != nil {
   680  		t.Fatal(err)
   681  	}
   682  	verificationToken := hex.EncodeToString(token)
   683  
   684  	// Setup tests
   685  	var tests = []struct {
   686  		name       string
   687  		reqBody    interface{}
   688  		wantStatus int // HTTP status code
   689  		wantErr    error
   690  	}{
   691  		{
   692  			"invalid request body",
   693  			"",
   694  			http.StatusBadRequest,
   695  			www.UserError{
   696  				ErrorCode: www.ErrorStatusInvalidInput,
   697  			},
   698  		},
   699  		{
   700  			"processVerifyResetPassword error",
   701  			www.VerifyResetPassword{
   702  				Username: "wrongusername",
   703  			},
   704  			http.StatusBadRequest,
   705  			www.UserError{
   706  				ErrorCode: www.ErrorStatusUserNotFound,
   707  			},
   708  		},
   709  		{
   710  			"success",
   711  			www.VerifyResetPassword{
   712  				Username:          usr.Username,
   713  				VerificationToken: verificationToken,
   714  				NewPassword:       "helloworld",
   715  			},
   716  			http.StatusOK,
   717  			nil,
   718  		},
   719  	}
   720  
   721  	// Run tests
   722  	for _, v := range tests {
   723  		t.Run(v.name, func(t *testing.T) {
   724  			// Setup request
   725  			r := newPostReq(t, www.RouteVerifyResetPassword, v.reqBody)
   726  			w := httptest.NewRecorder()
   727  
   728  			// Run test case
   729  			p.handleVerifyResetPassword(w, r)
   730  			res := w.Result()
   731  			body, _ := io.ReadAll(res.Body)
   732  			res.Body.Close()
   733  
   734  			// Check status code
   735  			if res.StatusCode != v.wantStatus {
   736  				t.Errorf("got status code %v, want %v",
   737  					res.StatusCode, v.wantStatus)
   738  			}
   739  			if res.StatusCode == http.StatusOK {
   740  				// Test case passes; next case
   741  				return
   742  			}
   743  
   744  			// Check user error
   745  			var ue www.UserError
   746  			err := json.Unmarshal(body, &ue)
   747  			if err != nil {
   748  				t.Errorf("unmarshal UserError: %v", err)
   749  			}
   750  			got := errToStr(ue)
   751  			want := errToStr(v.wantErr)
   752  			if got != want {
   753  				t.Errorf("got error %v, want %v", got, want)
   754  			}
   755  		})
   756  	}
   757  }
   758  
   759  func TestHandleChangeUsername(t *testing.T) {
   760  	p, cleanup := newTestPoliteiawww(t)
   761  	defer cleanup()
   762  
   763  	// Create a user to test against. newUser()
   764  	// sets the password to be the username.
   765  	usr, _ := newUser(t, p, true, false)
   766  	pass := usr.Username
   767  
   768  	// Setup tests
   769  	var tests = []struct {
   770  		name       string
   771  		reqBody    interface{}
   772  		wantStatus int
   773  		wantError  error
   774  	}{
   775  		// Middleware will catch any invalid user sessions.
   776  		// We can assume that the request contains a valid
   777  		// user session.
   778  
   779  		{
   780  			"invalid request body",
   781  			"",
   782  			http.StatusBadRequest,
   783  			www.UserError{
   784  				ErrorCode: www.ErrorStatusInvalidInput,
   785  			},
   786  		},
   787  		{
   788  			"processChangeUsername error",
   789  			www.ChangeUsername{},
   790  			http.StatusBadRequest,
   791  			www.UserError{
   792  				ErrorCode: www.ErrorStatusInvalidPassword,
   793  			},
   794  		},
   795  		{
   796  			"success",
   797  			www.ChangeUsername{
   798  				Password:    pass,
   799  				NewUsername: usr.Username + "aaa",
   800  			},
   801  			http.StatusOK,
   802  			nil,
   803  		},
   804  	}
   805  
   806  	// Run tests
   807  	for _, v := range tests {
   808  		t.Run(v.name, func(t *testing.T) {
   809  			// Setup request
   810  			r := newPostReq(t, www.RouteChangeUsername, v.reqBody)
   811  			addSessionToReq(t, p, r, usr.ID.String())
   812  			w := httptest.NewRecorder()
   813  
   814  			// Run test case
   815  			p.handleChangeUsername(w, r)
   816  			res := w.Result()
   817  			body, _ := io.ReadAll(res.Body)
   818  			res.Body.Close()
   819  
   820  			// Validate response
   821  			if res.StatusCode != v.wantStatus {
   822  				t.Errorf("got status code %v, want %v",
   823  					res.StatusCode, v.wantStatus)
   824  			}
   825  
   826  			if res.StatusCode == http.StatusOK {
   827  				// Test case passes; next case
   828  				return
   829  			}
   830  
   831  			var ue www.UserError
   832  			err := json.Unmarshal(body, &ue)
   833  			if err != nil {
   834  				t.Errorf("unmarshal UserError: %v", err)
   835  			}
   836  
   837  			got := errToStr(ue)
   838  			want := errToStr(v.wantError)
   839  			if got != want {
   840  				t.Errorf("got error %v, want %v", got, want)
   841  			}
   842  		})
   843  	}
   844  }
   845  
   846  func TestHandleUserDetails(t *testing.T) {
   847  	p, cleanup := newTestPoliteiawww(t)
   848  	defer cleanup()
   849  
   850  	// Create a user whose details we can fetch
   851  	usr, _ := newUser(t, p, true, false)
   852  
   853  	// Setup tests
   854  	var tests = []struct {
   855  		name       string // Test name
   856  		uuid       string // UUID for route param
   857  		loggedIn   bool   // Should request contain a user session
   858  		wantStatus int    // Wanted response status code
   859  		wantError  error  // Wanted response error
   860  	}{
   861  		// The UUID is a route param so an invalid length UUID will
   862  		// be caught by the router. A correct length UUID with an
   863  		// invalid format will not be caught by the router and needs
   864  		// to be tested for.
   865  		{
   866  			"invalid uuid format",
   867  			"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
   868  			false, http.StatusBadRequest,
   869  			www.UserError{
   870  				ErrorCode: www.ErrorStatusInvalidInput,
   871  			},
   872  		},
   873  		{
   874  			"process user details error",
   875  			"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
   876  			false, http.StatusBadRequest,
   877  			www.UserError{
   878  				ErrorCode: www.ErrorStatusUserNotFound,
   879  			},
   880  		},
   881  		{
   882  			"logged in user success",
   883  			usr.ID.String(),
   884  			true,
   885  			http.StatusOK,
   886  			nil,
   887  		},
   888  		{
   889  			"public user success",
   890  			usr.ID.String(),
   891  			false,
   892  			http.StatusOK,
   893  			nil,
   894  		},
   895  	}
   896  
   897  	// Run tests
   898  	for _, v := range tests {
   899  		t.Run(v.name, func(t *testing.T) {
   900  			// Setup request
   901  			r := httptest.NewRequest(http.MethodGet, www.RouteUserDetails, nil)
   902  			r = mux.SetURLVars(r, map[string]string{
   903  				"userid": v.uuid,
   904  			})
   905  			w := httptest.NewRecorder()
   906  
   907  			// Initialize the user session
   908  			if v.loggedIn {
   909  				err := p.sessions.NewSession(w, r, usr.ID.String())
   910  				if err != nil {
   911  					t.Fatalf("%v", err)
   912  				}
   913  			}
   914  
   915  			// Run test case
   916  			p.handleUserDetails(w, r)
   917  			res := w.Result()
   918  			body, _ := io.ReadAll(res.Body)
   919  			res.Body.Close()
   920  
   921  			// Validate response
   922  			if res.StatusCode != v.wantStatus {
   923  				t.Errorf("got status code %v, want %v",
   924  					res.StatusCode, v.wantStatus)
   925  			}
   926  
   927  			if res.StatusCode == http.StatusOK {
   928  				// Test case passes; next case
   929  				return
   930  			}
   931  
   932  			var ue www.UserError
   933  			err := json.Unmarshal(body, &ue)
   934  			if err != nil {
   935  				t.Errorf("unmarshal UserError: %v", err)
   936  			}
   937  
   938  			got := errToStr(ue)
   939  			want := errToStr(v.wantError)
   940  			if got != want {
   941  				t.Errorf("got error %v, want %v", got, want)
   942  			}
   943  		})
   944  	}
   945  }
   946  
   947  func TestHandleEditUser(t *testing.T) {
   948  	p, cleanup := newTestPoliteiawww(t)
   949  	defer cleanup()
   950  
   951  	var notif uint64 = 0x01
   952  	usr, _ := newUser(t, p, true, true)
   953  
   954  	var tests = []struct {
   955  		name       string
   956  		reqBody    interface{}
   957  		wantStatus int
   958  		wantError  error
   959  	}{
   960  		// Middleware will catch any invalid admin sessions.
   961  		// We can assume that the request contains a valid
   962  		// admin session.
   963  
   964  		{
   965  			"invalid request body",
   966  			"",
   967  			http.StatusBadRequest,
   968  			www.UserError{
   969  				ErrorCode: www.ErrorStatusInvalidInput,
   970  			},
   971  		},
   972  		{
   973  			"success",
   974  			www.EditUser{
   975  				EmailNotifications: &notif,
   976  			},
   977  			http.StatusOK,
   978  			nil,
   979  		},
   980  	}
   981  
   982  	// Run tests
   983  	for _, v := range tests {
   984  		t.Run(v.name, func(t *testing.T) {
   985  			// Setup request
   986  			r := newPostReq(t, www.RouteEditUser, v.reqBody)
   987  			addSessionToReq(t, p, r, usr.ID.String())
   988  			w := httptest.NewRecorder()
   989  
   990  			// Run test case
   991  			p.handleEditUser(w, r)
   992  			res := w.Result()
   993  			body, _ := io.ReadAll(res.Body)
   994  			res.Body.Close()
   995  
   996  			// Validate response
   997  			if res.StatusCode != v.wantStatus {
   998  				t.Errorf("got status code %v, want %v",
   999  					res.StatusCode, v.wantStatus)
  1000  			}
  1001  
  1002  			if res.StatusCode == http.StatusOK {
  1003  				// Test case passes; next case
  1004  				return
  1005  			}
  1006  
  1007  			var ue www.UserError
  1008  			err := json.Unmarshal(body, &ue)
  1009  			if err != nil {
  1010  				t.Errorf("unmarshal UserError: %v", err)
  1011  			}
  1012  
  1013  			got := errToStr(ue)
  1014  			want := errToStr(v.wantError)
  1015  			if got != want {
  1016  				t.Errorf("got error %v, want %v", got, want)
  1017  			}
  1018  		})
  1019  	}
  1020  }
  1021  
  1022  const (
  1023  	testSessionMaxAge = 86400 // One day
  1024  )
  1025  
  1026  func newSessionOptions() *sessions.Options {
  1027  	return &sessions.Options{
  1028  		Path:     "/",
  1029  		MaxAge:   testSessionMaxAge,
  1030  		Secure:   true,
  1031  		HttpOnly: true,
  1032  		SameSite: http.SameSiteStrictMode,
  1033  	}
  1034  }
  1035  
  1036  // newPostReq returns an httptest post request that was created using the
  1037  // passed in data.
  1038  func newPostReq(t *testing.T, route string, body interface{}) *http.Request {
  1039  	t.Helper()
  1040  
  1041  	b, err := json.Marshal(body)
  1042  	if err != nil {
  1043  		t.Fatalf("%v", err)
  1044  	}
  1045  
  1046  	return httptest.NewRequest(http.MethodPost, route, bytes.NewReader(b))
  1047  }
  1048  
  1049  // addSessionToReq initializes a user session and adds a session cookie to the
  1050  // given http request. The user session is saved to the politeiawww session
  1051  // store during intialization.
  1052  func addSessionToReq(t *testing.T, p *Politeiawww, req *http.Request, userID string) {
  1053  	t.Helper()
  1054  
  1055  	// Init session adds a session cookie onto the http response.
  1056  	r := httptest.NewRequest(http.MethodGet, "/", bytes.NewReader([]byte{}))
  1057  	w := httptest.NewRecorder()
  1058  	err := p.sessions.NewSession(w, r, userID)
  1059  	if err != nil {
  1060  		t.Fatal(err)
  1061  	}
  1062  	res := w.Result()
  1063  	res.Body.Close()
  1064  
  1065  	// Grab the session cookie from the response and add it to the
  1066  	// request.
  1067  	var c *http.Cookie
  1068  	for _, v := range res.Cookies() {
  1069  		if v.Name == www.CookieSession {
  1070  			c = v
  1071  			break
  1072  		}
  1073  	}
  1074  	req.AddCookie(c)
  1075  
  1076  	// Verify the session was added successfully.
  1077  	s, err := p.sessions.GetSession(req)
  1078  	if err != nil {
  1079  		t.Fatal(err)
  1080  	}
  1081  	if s.IsNew {
  1082  		t.Fatal("session not found in store")
  1083  	}
  1084  }