Validate package tests
This commit is contained in:
parent
597c53d4ed
commit
e97af35676
6 changed files with 176 additions and 22 deletions
2
Makefile
Normal file
2
Makefile
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
test:
|
||||||
|
@go test -v ./...
|
|
@ -3,17 +3,14 @@ package event
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEventSubscribeEmit(t *testing.T) {
|
func TestEventSubscribeEmit(t *testing.T) {
|
||||||
var (
|
expect := 1
|
||||||
expect = 1
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
wg = sync.WaitGroup{}
|
Subscribe("foo.a", func(_ context.Context, event any) {
|
||||||
)
|
defer cancel()
|
||||||
wg.Add(1)
|
|
||||||
Subscribe("foo.bar", func(_ context.Context, event any) {
|
|
||||||
value, ok := event.(int)
|
value, ok := event.(int)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("expected int got %v", reflect.TypeOf(event))
|
t.Errorf("expected int got %v", reflect.TypeOf(event))
|
||||||
|
@ -21,16 +18,15 @@ func TestEventSubscribeEmit(t *testing.T) {
|
||||||
if value != 1 {
|
if value != 1 {
|
||||||
t.Errorf("expected %d got %d", expect, value)
|
t.Errorf("expected %d got %d", expect, value)
|
||||||
}
|
}
|
||||||
wg.Done()
|
|
||||||
})
|
})
|
||||||
Emit("foo.bar", expect)
|
Emit("foo.a", expect)
|
||||||
wg.Wait()
|
<-ctx.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnsubscribe(t *testing.T) {
|
func TestUnsubscribe(t *testing.T) {
|
||||||
sub := Subscribe("foo.bar", func(_ context.Context, _ any) {})
|
sub := Subscribe("foo.b", func(_ context.Context, _ any) {})
|
||||||
Unsubscribe(sub)
|
Unsubscribe(sub)
|
||||||
if _, ok := stream.subs["foo.bar"]; ok {
|
if _, ok := stream.subs["foo.b"]; ok {
|
||||||
t.Errorf("expected topic foo.bar to be deleted")
|
t.Errorf("expected topic foo.bar to be deleted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -1,8 +1,20 @@
|
||||||
|
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
|
||||||
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
|
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
|
github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
|
||||||
github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
emailRegex = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
|
emailRegex = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
|
||||||
urlRegex = regexp.MustCompile(`^(http(s)?://)?([\da-z\.-]+)\.([a-z\.]{2,6})([/\w \.-]*)*/?$`)
|
urlRegex = regexp.MustCompile(`^(https?:\/\/)?(www\.)?([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,}(\/[a-zA-Z0-9\-._~:\/?#\[\]@!$&'()*+,;=]*)?$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// RuleSet holds the state of a single rule.
|
// RuleSet holds the state of a single rule.
|
||||||
|
|
|
@ -39,7 +39,7 @@ func (e Errors) Has(field string) bool {
|
||||||
// Schema represents a validation schema.
|
// Schema represents a validation schema.
|
||||||
type Schema map[string][]RuleSet
|
type Schema map[string][]RuleSet
|
||||||
|
|
||||||
// Merge merges the two given schemas returning a new Schema.
|
// Merge merges the two given schemas, returning a new Schema.
|
||||||
func Merge(schema, other Schema) Schema {
|
func Merge(schema, other Schema) Schema {
|
||||||
newSchema := Schema{}
|
newSchema := Schema{}
|
||||||
maps.Copy(newSchema, schema)
|
maps.Copy(newSchema, schema)
|
||||||
|
@ -75,9 +75,14 @@ func Request(r *http.Request, data any, schema Schema) (Errors, bool) {
|
||||||
func validate(data any, schema Schema, errors Errors) (Errors, bool) {
|
func validate(data any, schema Schema, errors Errors) (Errors, bool) {
|
||||||
ok := true
|
ok := true
|
||||||
for fieldName, ruleSets := range schema {
|
for fieldName, ruleSets := range schema {
|
||||||
// Uppercase the field name so we never check un-exported fields
|
// Uppercase the field name so we never check un-exported fields.
|
||||||
fieldName = string(unicode.ToUpper(rune(fieldName[0]))) + fieldName[1:]
|
// But we need to watch out for member fields that are uppercased by
|
||||||
fieldValue := getFieldValueByName(data, fieldName)
|
// the user. For example (URL, ID, ...)
|
||||||
|
if !isUppercase(fieldName) {
|
||||||
|
fieldName = string(unicode.ToUpper(rune(fieldName[0]))) + fieldName[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldValue := getFieldAndTagByName(data, fieldName)
|
||||||
for _, set := range ruleSets {
|
for _, set := range ruleSets {
|
||||||
set.FieldValue = fieldValue
|
set.FieldValue = fieldValue
|
||||||
set.FieldName = fieldName
|
set.FieldName = fieldName
|
||||||
|
@ -98,7 +103,7 @@ func validate(data any, schema Schema, errors Errors) (Errors, bool) {
|
||||||
return errors, ok
|
return errors, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFieldValueByName(v any, name string) any {
|
func getFieldAndTagByName(v any, name string) any {
|
||||||
val := reflect.ValueOf(v)
|
val := reflect.ValueOf(v)
|
||||||
if val.Kind() == reflect.Ptr {
|
if val.Kind() == reflect.Ptr {
|
||||||
val = val.Elem()
|
val = val.Elem()
|
||||||
|
@ -168,3 +173,12 @@ func parseRequest(r *http.Request, v any) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isUppercase(s string) bool {
|
||||||
|
for _, ch := range s {
|
||||||
|
if !unicode.IsUpper(rune(ch)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@ package validate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -28,6 +31,65 @@ var testSchema = Schema{
|
||||||
"username": Rules(Required),
|
"username": Rules(Required),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateRequest(t *testing.T) {
|
||||||
|
var (
|
||||||
|
email = "foo@bar.com"
|
||||||
|
password = "superHunter123@"
|
||||||
|
firstName = "Anthony"
|
||||||
|
website = "http://foo.com"
|
||||||
|
randomNumber = 123
|
||||||
|
randomFloat = 9.999
|
||||||
|
)
|
||||||
|
formValues := url.Values{}
|
||||||
|
formValues.Set("email", email)
|
||||||
|
formValues.Set("password", password)
|
||||||
|
formValues.Set("firstName", firstName)
|
||||||
|
formValues.Set("url", website)
|
||||||
|
formValues.Set("brandom", fmt.Sprint(randomNumber))
|
||||||
|
formValues.Set("arandom", fmt.Sprint(randomFloat))
|
||||||
|
encodedValues := formValues.Encode()
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "http://foo.com", strings.NewReader(encodedValues))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
type SignupData struct {
|
||||||
|
Email string `form:"email"`
|
||||||
|
Password string `form:"password"`
|
||||||
|
FirstName string `form:"firstName"`
|
||||||
|
URL string `form:"url"`
|
||||||
|
ARandomRenamedNumber int `form:"brandom"`
|
||||||
|
ARandomRenamedFloat float64 `form:"arandom"`
|
||||||
|
}
|
||||||
|
|
||||||
|
schema := Schema{
|
||||||
|
"Email": Rules(Email),
|
||||||
|
"Password": Rules(
|
||||||
|
Required,
|
||||||
|
ContainsDigit,
|
||||||
|
ContainsUpper,
|
||||||
|
ContainsSpecial,
|
||||||
|
Min(7),
|
||||||
|
),
|
||||||
|
"FirstName": Rules(Min(3), Max(50)),
|
||||||
|
"URL": Rules(URL),
|
||||||
|
"ARandomRenamedNumber": Rules(GT(100), LT(124)),
|
||||||
|
"ARandomRenamedFloat": Rules(GT(9.0), LT(10.1)),
|
||||||
|
}
|
||||||
|
|
||||||
|
var data SignupData
|
||||||
|
errors, ok := Request(req, &data, schema)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Empty(t, errors)
|
||||||
|
|
||||||
|
assert.Equal(t, data.Email, email)
|
||||||
|
assert.Equal(t, data.Password, password)
|
||||||
|
assert.Equal(t, data.FirstName, firstName)
|
||||||
|
assert.Equal(t, data.URL, website)
|
||||||
|
assert.Equal(t, data.ARandomRenamedNumber, randomNumber)
|
||||||
|
assert.Equal(t, data.ARandomRenamedFloat, randomFloat)
|
||||||
|
}
|
||||||
|
|
||||||
func TestTime(t *testing.T) {
|
func TestTime(t *testing.T) {
|
||||||
type Foo struct {
|
type Foo struct {
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
|
@ -48,21 +110,89 @@ func TestTime(t *testing.T) {
|
||||||
|
|
||||||
func TestURL(t *testing.T) {
|
func TestURL(t *testing.T) {
|
||||||
type Foo struct {
|
type Foo struct {
|
||||||
URL string
|
URL string `v:"URL"`
|
||||||
}
|
}
|
||||||
foo := Foo{
|
foo := Foo{
|
||||||
URL: "not an url",
|
URL: "not an url",
|
||||||
}
|
}
|
||||||
schema := Schema{
|
schema := Schema{
|
||||||
"url": Rules(URL),
|
"URL": Rules(URL),
|
||||||
}
|
}
|
||||||
errors, ok := Validate(foo, schema)
|
errors, ok := Validate(foo, schema)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
|
assert.NotEmpty(t, errors)
|
||||||
|
|
||||||
foo.URL = "www.user.com"
|
validURLS := []string{
|
||||||
|
"http://google.com",
|
||||||
|
"http://www.google.com",
|
||||||
|
"https://www.google.com",
|
||||||
|
"https://www.google.com",
|
||||||
|
"www.google.com",
|
||||||
|
"https://book.com/sales",
|
||||||
|
"app.book.com",
|
||||||
|
"app.book.com/signup",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, url := range validURLS {
|
||||||
|
foo.URL = url
|
||||||
|
errors, ok = Validate(foo, schema)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Empty(t, errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainsUpper(t *testing.T) {
|
||||||
|
type Foo struct {
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
foo := Foo{"hunter"}
|
||||||
|
schema := Schema{
|
||||||
|
"Password": Rules(ContainsUpper),
|
||||||
|
}
|
||||||
|
errors, ok := Validate(foo, schema)
|
||||||
|
assert.False(t, ok)
|
||||||
|
assert.NotEmpty(t, errors)
|
||||||
|
|
||||||
|
foo.Password = "Hunter"
|
||||||
errors, ok = Validate(foo, schema)
|
errors, ok = Validate(foo, schema)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
fmt.Println(errors)
|
assert.Empty(t, errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainsDigit(t *testing.T) {
|
||||||
|
type Foo struct {
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
foo := Foo{"hunter"}
|
||||||
|
schema := Schema{
|
||||||
|
"Password": Rules(ContainsDigit),
|
||||||
|
}
|
||||||
|
errors, ok := Validate(foo, schema)
|
||||||
|
assert.False(t, ok)
|
||||||
|
assert.NotEmpty(t, errors)
|
||||||
|
|
||||||
|
foo.Password = "Hunter1"
|
||||||
|
errors, ok = Validate(foo, schema)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Empty(t, errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainsSpecial(t *testing.T) {
|
||||||
|
type Foo struct {
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
foo := Foo{"hunter"}
|
||||||
|
schema := Schema{
|
||||||
|
"Password": Rules(ContainsSpecial),
|
||||||
|
}
|
||||||
|
errors, ok := Validate(foo, schema)
|
||||||
|
assert.False(t, ok)
|
||||||
|
assert.NotEmpty(t, errors)
|
||||||
|
|
||||||
|
foo.Password = "Hunter@"
|
||||||
|
errors, ok = Validate(foo, schema)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Empty(t, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRuleIn(t *testing.T) {
|
func TestRuleIn(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue