[tests] Add tests

This commit is contained in:
Richard Huang 2020-05-14 01:23:42 -07:00
parent 3fcd9ec4fa
commit 8cde6da33e
No known key found for this signature in database
GPG key ID: FFDEF81D05C2EC94
6 changed files with 308 additions and 11 deletions

1
go.mod
View file

@ -3,6 +3,7 @@ module github.com/aeolyus/gull
go 1.14
require (
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
github.com/gorilla/mux v1.7.4
github.com/jinzhu/gorm v1.9.12
)

9
go.sum
View file

@ -1,6 +1,12 @@
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
@ -9,7 +15,9 @@ github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q=
github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw=
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@ -21,4 +29,5 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=

View file

@ -5,6 +5,7 @@ import (
"github.com/aeolyus/gull/utils"
"net/http"
valid "github.com/asaskevich/govalidator"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
@ -49,7 +50,7 @@ func (a *App) GetURL(w http.ResponseWriter, r *http.Request) {
if u.URL != "" {
http.Redirect(w, r, string(u.URL), http.StatusFound)
} else {
http.Error(w, "No such link :(", http.StatusBadRequest)
http.Error(w, "No such link :(", http.StatusNotFound)
}
}
@ -61,7 +62,7 @@ func (a *App) CreateShortURL(w http.ResponseWriter, r *http.Request) {
return
}
// Verify URL is valid
if !utils.IsValidUrl(u.URL) {
if !valid.IsRequestURL(u.URL) {
http.Error(w, "Invalid URL", http.StatusBadRequest)
return
}
@ -72,7 +73,7 @@ func (a *App) CreateShortURL(w http.ResponseWriter, r *http.Request) {
u.Alias = existingURL.Alias
} else {
// Verify alias is unique
for u.Alias == "" || !a.DB.Where("alias = ?", u.Alias).First(u).RecordNotFound() {
for u.Alias == "" || !a.DB.Where("alias = ?", u.Alias).First(existingURL).RecordNotFound() {
u.Alias = utils.RandString(6)
}
a.DB.Create(u)

246
handlers/handlers_test.go Normal file
View file

@ -0,0 +1,246 @@
package handlers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gorilla/mux"
)
// Initialize an in-memory database for testing
func setup() *App {
app := &App{}
app.Initialize("sqlite3", ":memory:")
return app
}
// Discard the in-memory database
func teardown(app *App) {
app.DB.Close()
}
func TestCreate(t *testing.T) {
tests := map[string]struct {
url string
alias string
status int
}{
"https": {url: "https://google.com", alias: "ggls", status: http.StatusCreated},
"http": {url: "http://google.com", alias: "ggl", status: http.StatusCreated},
"url already exists": {url: "http://google.com", alias: "", status: http.StatusCreated},
"alias already exists": {url: "http://asdf.com", alias: "ggl", status: http.StatusCreated},
"both url and alias exist": {url: "http://asdf.com", alias: "ggl", status: http.StatusCreated},
}
app := setup()
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
origURL := &URLEntry{
URL: tc.url,
Alias: tc.alias,
}
JSONData, _ := json.Marshal(origURL)
// Set up a new request.
req, err := http.NewRequest("POST", "/", bytes.NewBuffer(JSONData))
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
http.HandlerFunc(app.CreateShortURL).ServeHTTP(rr, req)
// Test that the status code is correct.
if status := rr.Code; status != tc.status {
t.Errorf("Status code is invalid. Expected %d. Got %d instead", tc.status, status)
}
// Alias might have been randomly generated so get from result instead
temp := &JSONRes{}
json.Unmarshal(rr.Body.Bytes(), temp)
origURL.Alias = strings.TrimPrefix(temp.ShortURL, "/s/")
// Test that the created url entry is correct.
createdURL := URLEntry{}
app.DB.Where("url = ?", origURL.URL).First(&createdURL)
if createdURL != *origURL {
t.Errorf("Created entry is invalid. Expected %+v. Got %+v instead", origURL, createdURL)
}
})
}
teardown(app)
}
func TestInvalidCreate(t *testing.T) {
tests := map[string]struct {
url string
alias string
status int
}{
"bad": {url: "https/agoogle.com", alias: "ggls", status: http.StatusBadRequest},
"no colon": {url: "http//google.com", alias: "ggl", status: http.StatusBadRequest},
"empty": {url: "", alias: "", status: http.StatusBadRequest},
"asdf": {url: "asdf", alias: "", status: http.StatusBadRequest},
}
app := setup()
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
origURL := &URLEntry{
URL: tc.url,
Alias: tc.alias,
}
JSONData, _ := json.Marshal(origURL)
// Set up a new request.
req, err := http.NewRequest("POST", "application/json", bytes.NewBuffer(JSONData))
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
http.HandlerFunc(app.CreateShortURL).ServeHTTP(rr, req)
// Test that the status code is correct.
if status := rr.Code; status != tc.status {
t.Errorf("Status code is invalid. Expected %d. Got %d instead", tc.status, status)
}
// Make sure no entries are created
createdEntry := &URLEntry{}
if !app.DB.First(createdEntry).RecordNotFound() {
t.Errorf("Should not have created an entry")
}
})
}
teardown(app)
}
func TestCorruptCreate(t *testing.T) {
app := setup()
JSONData, _ := json.Marshal([]byte("garbage data"))
// Set up a new request.
req, err := http.NewRequest("POST", "application/json", bytes.NewBuffer(JSONData))
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
http.HandlerFunc(app.CreateShortURL).ServeHTTP(rr, req)
// Test that the status code is correct.
if status := rr.Code; status != http.StatusBadRequest {
t.Errorf("Status code is invalid. Expected %d. Got %d instead", http.StatusBadRequest, status)
}
// Make sure no entries are created
createdEntry := &URLEntry{}
if !app.DB.First(createdEntry).RecordNotFound() {
t.Errorf("Should not have created an entry")
}
teardown(app)
}
func TestGetURL(t *testing.T) {
seed := []struct {
url string
alias string
}{
{url: "https://google.com", alias: "ggl"},
}
tests := map[string]struct {
url string
alias string
status int
}{
"exists": {url: "https://google.com", alias: "ggl", status: http.StatusFound},
"nonexistent": {url: "", alias: "whoami", status: http.StatusNotFound},
}
app := setup()
for _, u := range seed {
origURL := &URLEntry{
URL: u.url,
Alias: u.alias,
}
app.DB.Create(origURL)
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// Set up a new request.
req, err := http.NewRequest("GET", "/s/"+tc.alias, nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
r := mux.NewRouter()
r.HandleFunc("/s/{alias:.*}", app.GetURL).Methods("GET")
r.ServeHTTP(rr, req)
// Test that the status code is correct.
if status := rr.Code; status != tc.status {
t.Errorf("Status code is invalid. Expected %d. Got %d instead", tc.status, status)
}
if rr.HeaderMap.Get("Location") != tc.url {
t.Errorf("Created entry is invalid. Expected %+v. Got %+v instead", tc.url, rr.HeaderMap.Get("Location"))
}
})
}
teardown(app)
}
func TestListAll(t *testing.T) {
tests := []struct {
url string
alias string
}{
{url: "https://google1.com", alias: "ggl1"},
{url: "https://google2.com", alias: "ggl2"},
{url: "https://google3.com", alias: "ggl3"},
}
app := setup()
origList := make([]URLEntry, 0)
for _, tc := range tests {
origURL := &URLEntry{
URL: tc.url,
Alias: tc.alias,
}
origList = append(origList, *origURL)
app.DB.Create(origURL)
}
// Set up a new request.
req, err := http.NewRequest("GET", "/all", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
r := mux.NewRouter()
r.HandleFunc("/all", app.ListAll).Methods("GET")
r.ServeHTTP(rr, req)
lst := &[]URLEntry{}
json.Unmarshal(rr.Body.Bytes(), lst)
// Test that the status code is correct.
if status := rr.Code; status != http.StatusOK {
t.Errorf("Status code is invalid. Expected %d. Got %d instead", http.StatusOK, status)
}
// Test that the created url entries are listed correctly.
for i, createdURL := range *lst {
if createdURL != origList[i] {
t.Errorf("Created entry is invalid. Expected %+v. Got %+v instead", origList[i], createdURL)
}
}
teardown(app)
}
func TestFailDBBadDriver(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("The code did not panic")
}
}()
app := &App{}
app.Initialize("baddriver", ":memory:")
}
func TestFailDBBadURI(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("The code did not panic")
}
}()
app := &App{}
app.Initialize("sqlite3", "./garbagepath/data.db")
}

View file

@ -2,23 +2,27 @@ package utils
import (
"math/rand"
"net/url"
"strings"
"time"
)
// Charset for random string generator
const Charset = "abcdefghijklmnopqrstuvwxyz" +
// Alphanumeric charset
const CHARSET = "abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
// Tests a string to determine if it is a well-structured url or not
func IsValidUrl(str string) bool {
u, err := url.Parse(str)
return err == nil && u.Scheme != "" && u.Host != ""
// Tests whether a string is in the alphanumeric charset
func IsAlphaNum(str string) bool {
for _, r := range []rune(str) {
if !strings.ContainsRune(CHARSET, r) {
return false
}
}
return true
}
// Generate a random alphanumeric string of given length
func RandString(length int) string {
return randStringWithCharset(length, Charset)
return randStringWithCharset(length, CHARSET)
}
func randStringWithCharset(length int, charset string) string {

36
utils/utils_test.go Normal file
View file

@ -0,0 +1,36 @@
package utils
import (
"testing"
)
func TestIsAlphaNum(t *testing.T) {
tests := map[string]struct {
name string
str string
val bool
}{
"basic": {str: "hello", val: true},
"dash": {str: "-", val: false},
"period": {str: ".", val: false},
"question mark": {str: "?", val: false},
"backslash": {str: "?", val: false},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
if IsAlphaNum(tc.str) != tc.val {
t.Errorf("For '%s', expected %t. Got %t instead.", tc.str, tc.val, IsAlphaNum(tc.str))
}
})
}
}
// For the code coverage why not
func TestRandString(t *testing.T) {
str := RandString(6)
if len(str) == 6 && IsAlphaNum(str) {
return
}
t.Errorf("Seriously? How?")
}