2021-12-25 02:16:58 +01:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2022-06-17 21:39:12 +02:00
|
|
|
"bytes"
|
2022-07-26 16:16:44 +02:00
|
|
|
"encoding/json"
|
2022-06-19 22:41:20 +02:00
|
|
|
"fmt"
|
2022-06-17 21:39:12 +02:00
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2022-07-11 15:42:08 +02:00
|
|
|
"strings"
|
2021-12-25 02:16:58 +01:00
|
|
|
"testing"
|
2022-06-17 21:39:12 +02:00
|
|
|
|
2022-07-12 04:10:19 +02:00
|
|
|
"lbryio/lbry-id/store"
|
2021-12-25 02:16:58 +01:00
|
|
|
)
|
|
|
|
|
2022-07-28 01:45:09 +02:00
|
|
|
// TODO - maybe this test could just be one of the TestServerRegisterAccountVerification tests now
|
2021-12-25 02:16:58 +01:00
|
|
|
func TestServerRegisterSuccess(t *testing.T) {
|
2022-07-24 00:13:56 +02:00
|
|
|
testStore := &TestStore{}
|
2022-07-26 16:16:44 +02:00
|
|
|
env := map[string]string{
|
2022-07-28 01:45:09 +02:00
|
|
|
"ACCOUNT_VERIFICATION_MODE": "EmailVerify",
|
2022-07-26 16:16:44 +02:00
|
|
|
}
|
2022-07-28 01:45:09 +02:00
|
|
|
testMail := TestMail{}
|
|
|
|
testAuth := TestAuth{TestNewVerifyTokenString: "abcd1234abcd1234abcd1234abcd1234"}
|
|
|
|
s := Server{&testAuth, testStore, &TestEnv{env}, &testMail}
|
2022-06-17 21:39:12 +02:00
|
|
|
|
2022-07-15 21:36:11 +02:00
|
|
|
requestBody := []byte(`{"email": "abc@example.com", "password": "123", "clientSaltSeed": "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234" }`)
|
2022-06-17 21:39:12 +02:00
|
|
|
|
|
|
|
req := httptest.NewRequest(http.MethodPost, PathRegister, bytes.NewBuffer(requestBody))
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
|
|
|
s.register(w, req)
|
|
|
|
body, _ := ioutil.ReadAll(w.Body)
|
|
|
|
|
2022-06-21 17:52:03 +02:00
|
|
|
expectStatusCode(t, w, http.StatusCreated)
|
2022-06-17 21:39:12 +02:00
|
|
|
|
2022-07-26 16:16:44 +02:00
|
|
|
var result RegisterResponse
|
|
|
|
err := json.Unmarshal(body, &result)
|
|
|
|
|
2022-07-28 01:45:09 +02:00
|
|
|
expectedResponse := RegisterResponse{Verified: false}
|
|
|
|
if err != nil || result != expectedResponse {
|
2022-07-26 16:16:44 +02:00
|
|
|
t.Errorf("Unexpected value for register response. Want: %+v Got: %+v Err: %+v", expectedResponse, result, err)
|
2022-06-17 21:39:12 +02:00
|
|
|
}
|
|
|
|
|
2022-07-26 16:16:44 +02:00
|
|
|
if testStore.Called.CreateAccount == nil {
|
2022-06-17 21:39:12 +02:00
|
|
|
t.Errorf("Expected Store.CreateAccount to be called")
|
|
|
|
}
|
2022-07-28 01:45:09 +02:00
|
|
|
|
|
|
|
if testMail.SendVerificationEmailCall == nil {
|
|
|
|
// We're doing EmailVerify for this test.
|
|
|
|
t.Fatalf("Expected Store.SendVerificationEmail to be called")
|
|
|
|
}
|
2021-12-25 02:16:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestServerRegisterErrors(t *testing.T) {
|
2022-06-19 22:41:20 +02:00
|
|
|
tt := []struct {
|
2022-07-28 01:45:09 +02:00
|
|
|
name string
|
|
|
|
email string
|
|
|
|
expectedStatusCode int
|
|
|
|
expectedErrorString string
|
|
|
|
expectedCallSendVerificationEmail bool
|
|
|
|
expectedCallCreateAccount bool
|
|
|
|
|
|
|
|
storeErrors TestStoreFunctionsErrors
|
|
|
|
mailError error
|
|
|
|
failGenToken bool
|
2022-06-19 22:41:20 +02:00
|
|
|
}{
|
2022-06-22 01:46:10 +02:00
|
|
|
{
|
2022-07-28 01:45:09 +02:00
|
|
|
name: "validation error", // missing email address
|
|
|
|
email: "",
|
|
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
|
|
expectedErrorString: http.StatusText(http.StatusBadRequest) + ": Request failed validation: Invalid or missing 'email'",
|
|
|
|
expectedCallSendVerificationEmail: false,
|
|
|
|
expectedCallCreateAccount: false,
|
2022-06-22 01:46:10 +02:00
|
|
|
|
|
|
|
// Just check one validation error (missing email address) to make sure the
|
|
|
|
// validate function is called. We'll check the rest of the validation
|
|
|
|
// errors in the other test below.
|
|
|
|
},
|
2022-06-19 22:41:20 +02:00
|
|
|
{
|
2022-07-28 01:45:09 +02:00
|
|
|
name: "existing account",
|
|
|
|
email: "abc@example.com",
|
|
|
|
expectedStatusCode: http.StatusConflict,
|
|
|
|
expectedErrorString: http.StatusText(http.StatusConflict) + ": Error registering",
|
|
|
|
expectedCallSendVerificationEmail: false,
|
|
|
|
expectedCallCreateAccount: true,
|
2022-06-19 22:41:20 +02:00
|
|
|
|
|
|
|
storeErrors: TestStoreFunctionsErrors{CreateAccount: store.ErrDuplicateEmail},
|
|
|
|
},
|
|
|
|
{
|
2022-07-28 01:45:09 +02:00
|
|
|
name: "unspecified account creation failure",
|
|
|
|
email: "abc@example.com",
|
|
|
|
expectedStatusCode: http.StatusInternalServerError,
|
|
|
|
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
|
|
|
expectedCallSendVerificationEmail: false,
|
|
|
|
expectedCallCreateAccount: true,
|
2022-06-19 22:41:20 +02:00
|
|
|
|
|
|
|
storeErrors: TestStoreFunctionsErrors{CreateAccount: fmt.Errorf("TestStore.CreateAccount fail")},
|
|
|
|
},
|
2022-07-28 01:45:09 +02:00
|
|
|
{
|
|
|
|
name: "fail to generate verifiy token",
|
|
|
|
email: "abc@example.com",
|
|
|
|
expectedStatusCode: http.StatusInternalServerError,
|
|
|
|
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
|
|
|
expectedCallSendVerificationEmail: false,
|
|
|
|
expectedCallCreateAccount: false,
|
|
|
|
|
|
|
|
failGenToken: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "fail to generate verification email",
|
|
|
|
email: "abc@example.com",
|
|
|
|
expectedStatusCode: http.StatusInternalServerError,
|
|
|
|
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
|
|
|
expectedCallSendVerificationEmail: true,
|
|
|
|
expectedCallCreateAccount: true,
|
|
|
|
|
|
|
|
mailError: fmt.Errorf("TestEmail.SendVerificationEmail fail"),
|
|
|
|
},
|
2022-06-19 22:41:20 +02:00
|
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
2022-07-26 16:16:44 +02:00
|
|
|
env := map[string]string{
|
2022-07-28 01:45:09 +02:00
|
|
|
"ACCOUNT_VERIFICATION_MODE": "EmailVerify",
|
2022-07-26 16:16:44 +02:00
|
|
|
}
|
|
|
|
|
2022-06-19 22:41:20 +02:00
|
|
|
// Set this up to fail according to specification
|
2022-07-28 01:45:09 +02:00
|
|
|
testAuth := TestAuth{TestNewVerifyTokenString: "abcd1234abcd1234abcd1234abcd1234", FailGenToken: tc.failGenToken}
|
|
|
|
testMail := TestMail{SendVerificationEmailError: tc.mailError}
|
|
|
|
testStore := TestStore{Errors: tc.storeErrors}
|
|
|
|
s := Server{&testAuth, &testStore, &TestEnv{env}, &testMail}
|
2022-06-19 22:41:20 +02:00
|
|
|
|
|
|
|
// Make request
|
2022-07-15 21:36:11 +02:00
|
|
|
requestBody := fmt.Sprintf(`{"email": "%s", "password": "123", "clientSaltSeed": "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"}`, tc.email)
|
2022-06-19 22:41:20 +02:00
|
|
|
req := httptest.NewRequest(http.MethodPost, PathAuthToken, bytes.NewBuffer([]byte(requestBody)))
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
2022-07-26 16:16:44 +02:00
|
|
|
s.register(w, req)
|
2022-06-19 22:41:20 +02:00
|
|
|
|
2022-06-22 17:37:03 +02:00
|
|
|
body, _ := ioutil.ReadAll(w.Body)
|
|
|
|
|
2022-06-21 17:52:03 +02:00
|
|
|
expectStatusCode(t, w, tc.expectedStatusCode)
|
2022-06-22 17:37:03 +02:00
|
|
|
expectErrorString(t, body, tc.expectedErrorString)
|
2022-07-28 01:45:09 +02:00
|
|
|
|
|
|
|
if tc.expectedCallCreateAccount && testStore.Called.CreateAccount == nil {
|
|
|
|
t.Errorf("Expected Store.CreateAccount to be called")
|
|
|
|
}
|
|
|
|
if !tc.expectedCallCreateAccount && testStore.Called.CreateAccount != nil {
|
|
|
|
t.Errorf("Expected Store.CreateAccount not to be called")
|
|
|
|
}
|
|
|
|
|
|
|
|
if tc.expectedCallSendVerificationEmail && testMail.SendVerificationEmailCall == nil {
|
|
|
|
t.Errorf("Expected Store.SendVerificationEmail to be called")
|
|
|
|
}
|
|
|
|
if !tc.expectedCallSendVerificationEmail && testMail.SendVerificationEmailCall != nil {
|
|
|
|
t.Errorf("Expected Store.SendVerificationEmail not to be called")
|
|
|
|
}
|
2022-06-19 22:41:20 +02:00
|
|
|
})
|
|
|
|
}
|
2021-12-25 02:16:58 +01:00
|
|
|
}
|
|
|
|
|
2022-07-26 16:16:44 +02:00
|
|
|
func TestServerRegisterAccountVerification(t *testing.T) {
|
|
|
|
tt := []struct {
|
|
|
|
name string
|
|
|
|
|
2022-07-28 01:45:09 +02:00
|
|
|
env map[string]string
|
|
|
|
expectSuccess bool
|
|
|
|
expectedVerified bool
|
|
|
|
expectedStatusCode int
|
|
|
|
expectedCallSendVerificationEmail bool
|
2022-07-26 16:16:44 +02:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "allow all",
|
|
|
|
|
|
|
|
env: map[string]string{
|
|
|
|
"ACCOUNT_VERIFICATION_MODE": "AllowAll",
|
|
|
|
},
|
|
|
|
|
2022-07-28 01:45:09 +02:00
|
|
|
expectedVerified: true,
|
|
|
|
expectSuccess: true,
|
|
|
|
expectedStatusCode: http.StatusCreated,
|
|
|
|
expectedCallSendVerificationEmail: false,
|
2022-07-26 16:16:44 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "whitelist allowed",
|
|
|
|
|
|
|
|
env: map[string]string{
|
|
|
|
"ACCOUNT_VERIFICATION_MODE": "Whitelist",
|
|
|
|
"ACCOUNT_WHITELIST": "abc@example.com",
|
|
|
|
},
|
|
|
|
|
2022-07-28 01:45:09 +02:00
|
|
|
expectedVerified: true,
|
|
|
|
expectSuccess: true,
|
|
|
|
expectedStatusCode: http.StatusCreated,
|
|
|
|
expectedCallSendVerificationEmail: false,
|
2022-07-26 16:16:44 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "whitelist disallowed",
|
|
|
|
|
|
|
|
env: map[string]string{
|
|
|
|
"ACCOUNT_VERIFICATION_MODE": "Whitelist",
|
|
|
|
"ACCOUNT_WHITELIST": "something-else@example.com",
|
|
|
|
},
|
|
|
|
|
2022-07-28 01:45:09 +02:00
|
|
|
expectedVerified: false,
|
|
|
|
expectSuccess: false,
|
|
|
|
expectedStatusCode: http.StatusForbidden,
|
|
|
|
expectedCallSendVerificationEmail: false,
|
2022-07-26 16:16:44 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "email verify",
|
|
|
|
|
|
|
|
env: map[string]string{
|
|
|
|
"ACCOUNT_VERIFICATION_MODE": "EmailVerify",
|
|
|
|
},
|
|
|
|
|
2022-07-28 01:45:09 +02:00
|
|
|
expectedVerified: false,
|
|
|
|
expectSuccess: true,
|
|
|
|
expectedStatusCode: http.StatusCreated,
|
|
|
|
expectedCallSendVerificationEmail: true,
|
2022-07-26 16:16:44 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range tt {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
testStore := &TestStore{}
|
2022-07-28 01:45:09 +02:00
|
|
|
testAuth := TestAuth{TestNewVerifyTokenString: "abcd1234abcd1234abcd1234abcd1234"}
|
|
|
|
testMail := TestMail{}
|
|
|
|
s := Server{&testAuth, testStore, &TestEnv{tc.env}, &testMail}
|
2022-07-26 16:16:44 +02:00
|
|
|
|
|
|
|
requestBody := []byte(`{"email": "abc@example.com", "password": "123", "clientSaltSeed": "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234" }`)
|
|
|
|
|
|
|
|
req := httptest.NewRequest(http.MethodPost, PathRegister, bytes.NewBuffer(requestBody))
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
|
|
|
s.register(w, req)
|
|
|
|
body, _ := ioutil.ReadAll(w.Body)
|
|
|
|
|
|
|
|
expectStatusCode(t, w, tc.expectedStatusCode)
|
|
|
|
|
|
|
|
if tc.expectSuccess {
|
|
|
|
if testStore.Called.CreateAccount == nil {
|
|
|
|
t.Fatalf("Expected CreateAccount to be called")
|
|
|
|
}
|
2022-07-28 01:45:09 +02:00
|
|
|
tokenPassedIn := testStore.Called.CreateAccount.VerifyToken != ""
|
|
|
|
if tc.expectedVerified && tokenPassedIn {
|
|
|
|
t.Errorf("Expected new account to be verified, thus expected verifyToken *not to be passed in* to call to CreateAccount.")
|
|
|
|
}
|
|
|
|
if !tc.expectedVerified && !tokenPassedIn {
|
|
|
|
t.Errorf("Expected new account not to be verified, thus expected verifyToken not *to be passed in* to call to CreateAccount.")
|
2022-07-26 16:16:44 +02:00
|
|
|
}
|
|
|
|
var result RegisterResponse
|
|
|
|
err := json.Unmarshal(body, &result)
|
|
|
|
|
|
|
|
if err != nil || tc.expectedVerified != result.Verified {
|
|
|
|
t.Errorf("Unexpected value in register response for `verified`. Want: %+v Got: %+v Err: %+v", tc.expectedVerified, result.Verified, err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if testStore.Called.CreateAccount != nil {
|
|
|
|
t.Errorf("Expected CreateAccount not to be called")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-28 01:45:09 +02:00
|
|
|
if tc.expectedCallSendVerificationEmail && testMail.SendVerificationEmailCall == nil {
|
|
|
|
t.Errorf("Expected Store.SendVerificationEmail to be called")
|
|
|
|
}
|
|
|
|
if !tc.expectedCallSendVerificationEmail && testMail.SendVerificationEmailCall != nil {
|
|
|
|
t.Errorf("Expected Store.SendVerificationEmail not to be called")
|
|
|
|
}
|
|
|
|
|
2022-07-26 16:16:44 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-25 02:16:58 +01:00
|
|
|
func TestServerValidateRegisterRequest(t *testing.T) {
|
2022-07-15 21:36:11 +02:00
|
|
|
registerRequest := RegisterRequest{Email: "joe@example.com", Password: "aoeu", ClientSaltSeed: "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"}
|
2022-07-11 15:42:08 +02:00
|
|
|
if registerRequest.validate() != nil {
|
2022-07-15 21:36:11 +02:00
|
|
|
t.Errorf("Expected valid RegisterRequest to successfully validate")
|
2022-06-17 22:12:20 +02:00
|
|
|
}
|
|
|
|
|
2022-07-15 21:36:11 +02:00
|
|
|
registerRequest = RegisterRequest{Email: "joe-example.com", Password: "aoeu", ClientSaltSeed: "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"}
|
2022-07-11 15:42:08 +02:00
|
|
|
err := registerRequest.validate()
|
|
|
|
if !strings.Contains(err.Error(), "email") {
|
2022-07-15 21:36:11 +02:00
|
|
|
t.Errorf("Expected RegisterRequest with invalid email to return an appropriate error")
|
2022-06-17 22:12:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Note that Golang's email address parser, which I use, will accept
|
|
|
|
// "Joe <joe@example.com>" so we need to make sure to avoid accepting it. See
|
|
|
|
// the implementation.
|
2022-07-15 21:36:11 +02:00
|
|
|
registerRequest = RegisterRequest{Email: "Joe <joe@example.com>", Password: "aoeu", ClientSaltSeed: "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"}
|
2022-07-11 15:42:08 +02:00
|
|
|
err = registerRequest.validate()
|
|
|
|
if !strings.Contains(err.Error(), "email") {
|
2022-07-15 21:36:11 +02:00
|
|
|
t.Errorf("Expected RegisterRequest with email with unexpected formatting to return an appropriate error")
|
2022-06-17 22:12:20 +02:00
|
|
|
}
|
|
|
|
|
2022-07-15 21:36:11 +02:00
|
|
|
registerRequest = RegisterRequest{Password: "aoeu", ClientSaltSeed: "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"}
|
2022-07-11 15:42:08 +02:00
|
|
|
err = registerRequest.validate()
|
|
|
|
if !strings.Contains(err.Error(), "email") {
|
2022-07-15 21:36:11 +02:00
|
|
|
t.Errorf("Expected RegisterRequest with missing email to return an appropriate error")
|
2022-06-17 22:15:27 +02:00
|
|
|
}
|
|
|
|
|
2022-07-15 21:36:11 +02:00
|
|
|
registerRequest = RegisterRequest{Email: "joe@example.com", ClientSaltSeed: "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"}
|
2022-07-11 15:42:08 +02:00
|
|
|
err = registerRequest.validate()
|
|
|
|
if !strings.Contains(err.Error(), "password") {
|
2022-07-15 21:36:11 +02:00
|
|
|
t.Errorf("Expected RegisterRequest with missing password to return an appropriate error")
|
|
|
|
}
|
|
|
|
|
|
|
|
registerRequest = RegisterRequest{Email: "joe@example.com", Password: "aoeu"}
|
|
|
|
err = registerRequest.validate()
|
|
|
|
if !strings.Contains(err.Error(), "clientSaltSeed") {
|
|
|
|
t.Errorf("Expected RegisterRequest with missing clientSaltSeed to return an appropriate error")
|
|
|
|
}
|
|
|
|
|
|
|
|
registerRequest = RegisterRequest{Email: "joe@example.com", Password: "aoeu", ClientSaltSeed: "abcd1234abcd1234abcd1234abcd1234"}
|
|
|
|
err = registerRequest.validate()
|
|
|
|
if !strings.Contains(err.Error(), "clientSaltSeed") {
|
|
|
|
t.Errorf("Expected RegisterRequest with clientSaltSeed of wrong length to return an appropriate error")
|
|
|
|
}
|
|
|
|
|
|
|
|
registerRequest = RegisterRequest{Email: "joe@example.com", Password: "aoeu", ClientSaltSeed: "xxxx1234xxxx1234xxxx1234xxxx1234xxxx1234xxxx1234xxxx1234xxxx1234"}
|
|
|
|
err = registerRequest.validate()
|
|
|
|
if !strings.Contains(err.Error(), "clientSaltSeed") {
|
|
|
|
t.Errorf("Expected RegisterRequest with clientSaltSeed with a non-hex string to return an appropriate error")
|
2022-06-17 22:12:20 +02:00
|
|
|
}
|
2021-12-25 02:16:58 +01:00
|
|
|
}
|
2022-07-26 22:36:57 +02:00
|
|
|
|
2022-07-30 02:49:00 +02:00
|
|
|
func TestServerResendVerifyEmailSuccess(t *testing.T) {
|
|
|
|
testStore := TestStore{}
|
|
|
|
testMail := TestMail{}
|
|
|
|
|
|
|
|
env := map[string]string{
|
|
|
|
"ACCOUNT_VERIFICATION_MODE": "EmailVerify",
|
|
|
|
}
|
|
|
|
s := Server{&TestAuth{}, &testStore, &TestEnv{env}, &testMail}
|
|
|
|
|
|
|
|
requestBody := []byte(`{"email": "abc@example.com"}`)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, PathVerify, bytes.NewBuffer(requestBody))
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
|
|
|
s.resendVerifyEmail(w, req)
|
|
|
|
body, _ := ioutil.ReadAll(w.Body)
|
|
|
|
|
|
|
|
expectStatusCode(t, w, http.StatusOK)
|
|
|
|
|
|
|
|
if string(body) != "{}" {
|
|
|
|
t.Errorf("Expected register response to be \"{}\": result: %+v", string(body))
|
|
|
|
}
|
|
|
|
|
|
|
|
if !testStore.Called.UpdateVerifyTokenString {
|
|
|
|
t.Errorf("Expected Store.UpdateVerifyTokenString to be called")
|
|
|
|
}
|
|
|
|
|
|
|
|
if testMail.SendVerificationEmailCall == nil {
|
|
|
|
// We're doing EmailVerify for this test.
|
|
|
|
t.Fatalf("Expected Store.SendVerificationEmail to be called")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestServerResendVerifyEmailErrors(t *testing.T) {
|
|
|
|
tt := []struct {
|
|
|
|
name string
|
|
|
|
omitEmailAddress bool
|
|
|
|
accountVerificationMode string
|
|
|
|
|
|
|
|
expectedStatusCode int
|
|
|
|
expectedErrorString string
|
|
|
|
expectedCallSendVerificationEmail bool
|
|
|
|
expectedCallUpdateVerifyTokenString bool
|
|
|
|
|
|
|
|
storeErrors TestStoreFunctionsErrors
|
|
|
|
mailError error
|
|
|
|
}{
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "wrong account verification mode",
|
|
|
|
accountVerificationMode: "Whitelist",
|
|
|
|
expectedStatusCode: http.StatusForbidden,
|
|
|
|
expectedErrorString: http.StatusText(http.StatusForbidden) + ": Account verification mode is not set to EmailVerify",
|
|
|
|
expectedCallSendVerificationEmail: false,
|
|
|
|
expectedCallUpdateVerifyTokenString: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "validation error",
|
|
|
|
accountVerificationMode: "EmailVerify",
|
|
|
|
omitEmailAddress: true,
|
|
|
|
expectedStatusCode: http.StatusBadRequest,
|
|
|
|
expectedErrorString: http.StatusText(http.StatusBadRequest) + ": Request failed validation: Invalid or missing 'email'",
|
|
|
|
expectedCallSendVerificationEmail: false,
|
|
|
|
expectedCallUpdateVerifyTokenString: false,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
name: "not found email",
|
|
|
|
accountVerificationMode: "EmailVerify",
|
|
|
|
expectedStatusCode: http.StatusUnauthorized,
|
|
|
|
expectedErrorString: http.StatusText(http.StatusUnauthorized) + ": No match for email",
|
|
|
|
storeErrors: TestStoreFunctionsErrors{UpdateVerifyTokenString: store.ErrWrongCredentials},
|
|
|
|
expectedCallSendVerificationEmail: false,
|
|
|
|
expectedCallUpdateVerifyTokenString: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "assorted db error",
|
|
|
|
accountVerificationMode: "EmailVerify",
|
|
|
|
expectedStatusCode: http.StatusInternalServerError,
|
|
|
|
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
|
|
|
storeErrors: TestStoreFunctionsErrors{UpdateVerifyTokenString: fmt.Errorf("TestStore.UpdateVerifyTokenString fail")},
|
|
|
|
expectedCallSendVerificationEmail: false,
|
|
|
|
expectedCallUpdateVerifyTokenString: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "fail to generate verification email",
|
|
|
|
accountVerificationMode: "EmailVerify",
|
|
|
|
expectedStatusCode: http.StatusInternalServerError,
|
|
|
|
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
|
|
|
expectedCallSendVerificationEmail: true,
|
|
|
|
expectedCallUpdateVerifyTokenString: true,
|
|
|
|
|
|
|
|
mailError: fmt.Errorf("TestEmail.SendVerificationEmail fail"),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
|
|
|
env := map[string]string{
|
|
|
|
"ACCOUNT_VERIFICATION_MODE": tc.accountVerificationMode,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set this up to fail according to specification
|
|
|
|
testStore := TestStore{Errors: tc.storeErrors}
|
|
|
|
testMail := TestMail{SendVerificationEmailError: tc.mailError}
|
|
|
|
s := Server{&TestAuth{}, &testStore, &TestEnv{env}, &testMail}
|
|
|
|
|
|
|
|
// Make request
|
|
|
|
var requestBody []byte
|
|
|
|
if tc.omitEmailAddress {
|
|
|
|
requestBody = []byte(`{}`)
|
|
|
|
} else {
|
|
|
|
requestBody = []byte(`{"email": "abc@example.com"}`)
|
|
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodPost, PathVerify, bytes.NewBuffer(requestBody))
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
|
|
|
s.resendVerifyEmail(w, req)
|
|
|
|
body, _ := ioutil.ReadAll(w.Body)
|
|
|
|
|
|
|
|
expectStatusCode(t, w, tc.expectedStatusCode)
|
|
|
|
expectErrorString(t, body, tc.expectedErrorString)
|
|
|
|
|
|
|
|
if tc.expectedCallUpdateVerifyTokenString && !testStore.Called.UpdateVerifyTokenString {
|
|
|
|
t.Errorf("Expected Store.UpdateVerifyTokenString to be called")
|
|
|
|
}
|
|
|
|
if !tc.expectedCallUpdateVerifyTokenString && testStore.Called.UpdateVerifyTokenString {
|
|
|
|
t.Errorf("Expected Store.UpdateVerifyTokenString not to be called")
|
|
|
|
}
|
|
|
|
|
|
|
|
if tc.expectedCallSendVerificationEmail && testMail.SendVerificationEmailCall == nil {
|
|
|
|
// We're doing EmailVerify for this test.
|
|
|
|
t.Fatalf("Expected Store.SendVerificationEmail to be called")
|
|
|
|
}
|
|
|
|
if !tc.expectedCallSendVerificationEmail && testMail.SendVerificationEmailCall != nil {
|
|
|
|
// We're doing EmailVerify for this test.
|
|
|
|
t.Fatalf("Expected Store.SendVerificationEmail not to be called")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-26 22:36:57 +02:00
|
|
|
func TestServerVerifyAccountSuccess(t *testing.T) {
|
2022-07-28 01:45:09 +02:00
|
|
|
testStore := TestStore{}
|
|
|
|
s := Server{&TestAuth{}, &testStore, &TestEnv{}, &TestMail{}}
|
2022-07-26 22:36:57 +02:00
|
|
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, PathVerify, nil)
|
|
|
|
q := req.URL.Query()
|
2022-07-28 01:45:09 +02:00
|
|
|
q.Add("verifyToken", "abcd1234abcd1234abcd1234abcd1234")
|
2022-07-26 22:36:57 +02:00
|
|
|
req.URL.RawQuery = q.Encode()
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
|
|
|
s.verify(w, req)
|
|
|
|
body, _ := ioutil.ReadAll(w.Body)
|
|
|
|
|
|
|
|
expectStatusCode(t, w, http.StatusOK)
|
|
|
|
|
2022-07-31 18:59:11 +02:00
|
|
|
if string(body) != "Your account has been verified." {
|
2022-07-26 22:36:57 +02:00
|
|
|
t.Errorf("Expected register response to be \"{}\": result: %+v", string(body))
|
|
|
|
}
|
|
|
|
|
|
|
|
if !testStore.Called.VerifyAccount {
|
|
|
|
t.Errorf("Expected Store.VerifyAccount to be called")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestServerVerifyAccountErrors(t *testing.T) {
|
|
|
|
tt := []struct {
|
|
|
|
name string
|
2022-07-28 01:45:09 +02:00
|
|
|
token string
|
2022-07-26 22:36:57 +02:00
|
|
|
expectedStatusCode int
|
2022-07-31 18:59:11 +02:00
|
|
|
expectedBody string
|
2022-07-26 22:36:57 +02:00
|
|
|
expectedCallVerifyAccount bool
|
|
|
|
|
|
|
|
storeErrors TestStoreFunctionsErrors
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "missing token",
|
|
|
|
token: "",
|
|
|
|
expectedStatusCode: http.StatusBadRequest,
|
2022-07-31 18:59:11 +02:00
|
|
|
expectedBody: "There seems to be a problem with this URL: Missing verifyToken parameter",
|
2022-07-26 22:36:57 +02:00
|
|
|
expectedCallVerifyAccount: false,
|
|
|
|
},
|
|
|
|
{
|
2022-07-29 21:52:23 +02:00
|
|
|
name: "token not found", // including expired
|
2022-07-26 22:36:57 +02:00
|
|
|
token: "abcd1234abcd1234abcd1234abcd1234",
|
|
|
|
expectedStatusCode: http.StatusForbidden,
|
2022-07-31 18:59:11 +02:00
|
|
|
expectedBody: "The verification token was not found, or it expired. Try generating a new one from your app.",
|
2022-07-26 22:36:57 +02:00
|
|
|
storeErrors: TestStoreFunctionsErrors{VerifyAccount: store.ErrNoTokenForUser},
|
|
|
|
expectedCallVerifyAccount: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "assorted db error",
|
|
|
|
token: "abcd1234abcd1234abcd1234abcd1234",
|
|
|
|
expectedStatusCode: http.StatusInternalServerError,
|
2022-07-31 18:59:11 +02:00
|
|
|
expectedBody: "Something went wrong trying to verify your account.",
|
2022-07-26 22:36:57 +02:00
|
|
|
storeErrors: TestStoreFunctionsErrors{VerifyAccount: fmt.Errorf("TestStore.VerifyAccount fail")},
|
|
|
|
expectedCallVerifyAccount: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
|
|
|
// Set this up to fail according to specification
|
2022-07-28 01:45:09 +02:00
|
|
|
testStore := TestStore{Errors: tc.storeErrors}
|
|
|
|
s := Server{&TestAuth{}, &testStore, &TestEnv{}, &TestMail{}}
|
2022-07-26 22:36:57 +02:00
|
|
|
|
|
|
|
// Make request
|
|
|
|
req := httptest.NewRequest(http.MethodGet, PathVerify, nil)
|
|
|
|
q := req.URL.Query()
|
2022-07-28 01:45:09 +02:00
|
|
|
q.Add("verifyToken", tc.token)
|
2022-07-26 22:36:57 +02:00
|
|
|
req.URL.RawQuery = q.Encode()
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
|
|
|
s.verify(w, req)
|
|
|
|
body, _ := ioutil.ReadAll(w.Body)
|
|
|
|
|
|
|
|
expectStatusCode(t, w, tc.expectedStatusCode)
|
2022-07-31 18:59:11 +02:00
|
|
|
if want, got := tc.expectedBody, strings.TrimSpace(string(body)); want != got {
|
|
|
|
t.Errorf("Body: expected `%s`, got `%s`", want, got)
|
|
|
|
}
|
2022-07-26 22:36:57 +02:00
|
|
|
|
2022-07-30 02:49:00 +02:00
|
|
|
if tc.expectedCallVerifyAccount && !testStore.Called.VerifyAccount {
|
|
|
|
t.Errorf("Expected Store.VerifyAccount to be called")
|
|
|
|
}
|
|
|
|
if !tc.expectedCallVerifyAccount && testStore.Called.VerifyAccount {
|
2022-07-26 22:36:57 +02:00
|
|
|
t.Errorf("Expected Store.VerifyAccount not to be called")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|