Validate package tests

This commit is contained in:
anthdm 2024-06-23 08:40:24 +02:00
parent 597c53d4ed
commit e97af35676
6 changed files with 176 additions and 22 deletions

2
Makefile Normal file
View file

@ -0,0 +1,2 @@
test:
@go test -v ./...

View file

@ -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
View file

@ -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=

View file

@ -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.

View file

@ -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
}

View file

@ -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) {