[tests] Add tests
This commit is contained in:
parent
3fcd9ec4fa
commit
8cde6da33e
6 changed files with 308 additions and 11 deletions
1
go.mod
1
go.mod
|
@ -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
9
go.sum
|
@ -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=
|
||||
|
|
|
@ -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
246
handlers/handlers_test.go
Normal 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")
|
||||
}
|
|
@ -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
36
utils/utils_test.go
Normal 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?")
|
||||
}
|
Loading…
Reference in a new issue