github.com/hzck/speedroute@v0.0.0-20201115191102-403b7d0e443f/main_test.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "net/http" 8 "net/http/httptest" 9 "os" 10 "regexp" 11 "strings" 12 "testing" 13 "time" 14 15 model "github.com/hzck/speedroute/model" 16 ) 17 18 var app App 19 20 func TestMain(m *testing.M) { 21 app.InitConfigFile() 22 app.InitDB() 23 app.InitRoutes() 24 code := m.Run() 25 os.Exit(code) 26 } 27 28 func TestCreateAccount(t *testing.T) { 29 username := "valid_username_7" 30 password := "val!dP@s5word" 31 32 req := createPostRequestForCreateAccount(username, password) 33 34 response := executeRequest(req) 35 defer clearAccountInDB(username) 36 37 checkResponseCode(t, http.StatusCreated, response.Code) 38 39 var a model.Account 40 query := "SELECT * FROM account WHERE username=$1" 41 err := app.Dbpool.QueryRow(context.Background(), query, username).Scan(&a.ID, &a.Username, &a.Password, &a.Created, &a.LastUpdated) 42 if err != nil { 43 panic(err) 44 } 45 if a.ID <= 0 { 46 t.Errorf("ID field is not valid") 47 } 48 if a.Username != username { 49 t.Errorf("Username '%s' is not the expected '%s'", a.Username, username) 50 } 51 match, _ := regexp.MatchString("^\\$argon2id\\$v=19\\$m=65536,t=8,p=1\\$.{22}\\$.{43}$", a.Password) 52 if !match { 53 t.Errorf("Password field is not set") 54 } 55 if a.Created.IsZero() { 56 t.Errorf("Created field is not set") 57 } 58 if a.LastUpdated.IsZero() { 59 t.Errorf("LastUpdated field is not set") 60 } 61 } 62 63 func TestCreateAccountInvalidJSON(t *testing.T) { 64 req := createPostRequestForCreateAccount("\",{invalidjson", "val!dP@s5word") 65 response := executeRequest(req) 66 checkResponseCode(t, http.StatusBadRequest, response.Code) 67 } 68 69 func TestCreateAccountUsernameNotPopulated(t *testing.T) { 70 req := createPostRequestForCreateAccount("", "val!dP@s5word") 71 response := executeRequest(req) 72 checkResponseCode(t, http.StatusBadRequest, response.Code) 73 } 74 75 func TestCreateAccountPasswordNotPopulated(t *testing.T) { 76 req := createPostRequestForCreateAccount("passwordnotpopulated", "") 77 response := executeRequest(req) 78 checkResponseCode(t, http.StatusBadRequest, response.Code) 79 } 80 81 func TestCreateAccountDuplicateUsername(t *testing.T) { 82 username := "duplicate" 83 createAccountInDB(username) 84 defer clearAccountInDB(username) 85 req := createPostRequestForCreateAccount(username, "val!dP@s5word") 86 response := executeRequest(req) 87 checkResponseCode(t, http.StatusConflict, response.Code) 88 } 89 90 func TestCreateAccountLowerCaseUsername(t *testing.T) { 91 username := "CaMeLcAsE_8" 92 createAccountInDB(strings.ToLower(username)) 93 defer clearAccountInDB(strings.ToLower(username)) 94 req := createPostRequestForCreateAccount(username, "val!dP@s5word") 95 response := executeRequest(req) 96 checkResponseCode(t, http.StatusConflict, response.Code) 97 } 98 99 func TestCreateAccountUsernameTooShort(t *testing.T) { 100 req := createPostRequestForCreateAccount("1", "val!dP@s5word") 101 response := executeRequest(req) 102 checkResponseCode(t, http.StatusBadRequest, response.Code) 103 } 104 105 func TestCreateAccountUsernameTooLong(t *testing.T) { 106 req := createPostRequestForCreateAccount("this_user_is_31_characters_long", "val!dP@s5word") 107 response := executeRequest(req) 108 checkResponseCode(t, http.StatusBadRequest, response.Code) 109 } 110 111 func TestCreateAccountPasswordTooShort(t *testing.T) { 112 req := createPostRequestForCreateAccount("shortpassword", "tooshrt") 113 response := executeRequest(req) 114 checkResponseCode(t, http.StatusBadRequest, response.Code) 115 } 116 117 func TestCreateAccountInvalidCharacters(t *testing.T) { 118 // Note: Not all invalid characters are tested for obvious reasons 119 invalidChars := `!"#ยค%&/()=?<>|[]{}+-.,;:^*` 120 for _, ch := range invalidChars { 121 req := createPostRequestForCreateAccount(fmt.Sprintf("username_invalid%c", ch), "val!dP@s5word") 122 response := executeRequest(req) 123 checkResponseCode(t, http.StatusBadRequest, response.Code) 124 } 125 } 126 127 func createPostRequestForCreateAccount(username, password string) *http.Request { 128 usernameJSON := "" 129 if username != "" { 130 usernameJSON = `"username":"` + username + `"` 131 } 132 passwordJSON := "" 133 if password != "" { 134 passwordJSON = `"password":"` + password + `"` 135 } 136 commaJSON := "" 137 if usernameJSON != "" && passwordJSON != "" { 138 commaJSON = "," 139 } 140 var jsonStr = []byte("{" + usernameJSON + commaJSON + passwordJSON + "}") 141 req, err := http.NewRequest("POST", "/account", bytes.NewBuffer(jsonStr)) 142 if err != nil { 143 panic(err) 144 } 145 req.Header.Set("Content-Type", "application/json") 146 return req 147 } 148 149 func executeRequest(req *http.Request) *httptest.ResponseRecorder { 150 rr := httptest.NewRecorder() 151 app.Router.ServeHTTP(rr, req) 152 return rr 153 } 154 155 func checkResponseCode(t *testing.T, expected, actual int) { 156 if expected != actual { 157 t.Errorf("Expected response code %d. Got %d\n", expected, actual) 158 } 159 } 160 161 func createAccountInDB(username string) { 162 query := "INSERT INTO account (username, password, created, last_updated) VALUES ($1, $2, $3, $3)" 163 _, err := app.Dbpool.Exec(context.Background(), query, username, "password", time.Now()) 164 if err != nil { 165 panic(err) 166 } 167 } 168 169 func clearAccountInDB(username string) { 170 query := "DELETE FROM account WHERE username=$1" 171 _, err := app.Dbpool.Exec(context.Background(), query, username) 172 if err != nil { 173 panic(err) 174 } 175 }