290 lines
6.8 KiB
Go
290 lines
6.8 KiB
Go
|
|
package valid
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"code.nochebuena.dev/go/xerrors"
|
||
|
|
playground "github.com/go-playground/validator/v10"
|
||
|
|
)
|
||
|
|
|
||
|
|
type testUser struct {
|
||
|
|
Name string `validate:"required"`
|
||
|
|
Email string `validate:"required,email"`
|
||
|
|
Age int `validate:"min=18,max=120"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------
|
||
|
|
// Constructor tests
|
||
|
|
// ---------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestNew_Defaults(t *testing.T) {
|
||
|
|
v := New()
|
||
|
|
if v == nil {
|
||
|
|
t.Fatal("New() returned nil")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestNew_WithMessageProvider(t *testing.T) {
|
||
|
|
called := false
|
||
|
|
mp := &testMP{fn: func(field, tag, param string) string {
|
||
|
|
called = true
|
||
|
|
return "custom: " + field
|
||
|
|
}}
|
||
|
|
|
||
|
|
v := New(WithMessageProvider(mp))
|
||
|
|
_ = v.Struct(testUser{}) // triggers validation failure (missing Name/Email)
|
||
|
|
if !called {
|
||
|
|
t.Error("custom MessageProvider was not called")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------
|
||
|
|
// Struct validation tests
|
||
|
|
// ---------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestValidator_Struct_Valid(t *testing.T) {
|
||
|
|
v := New()
|
||
|
|
u := testUser{Name: "Alice", Email: "alice@example.com", Age: 25}
|
||
|
|
if err := v.Struct(u); err != nil {
|
||
|
|
t.Errorf("expected nil, got %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestValidator_Struct_MissingRequired(t *testing.T) {
|
||
|
|
v := New()
|
||
|
|
u := testUser{Email: "a@b.com", Age: 20} // Name is missing
|
||
|
|
err := v.Struct(u)
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("expected error, got nil")
|
||
|
|
}
|
||
|
|
|
||
|
|
var xe *xerrors.Err
|
||
|
|
if !errors.As(err, &xe) {
|
||
|
|
t.Fatalf("expected *xerrors.Err, got %T", err)
|
||
|
|
}
|
||
|
|
if xe.Code() != xerrors.ErrInvalidInput {
|
||
|
|
t.Errorf("code = %s, want %s", xe.Code(), xerrors.ErrInvalidInput)
|
||
|
|
}
|
||
|
|
if xe.Fields()["field"] != "Name" {
|
||
|
|
t.Errorf("field = %v, want Name", xe.Fields()["field"])
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestValidator_Struct_InvalidEmail(t *testing.T) {
|
||
|
|
v := New()
|
||
|
|
u := testUser{Name: "Bob", Email: "not-an-email", Age: 25}
|
||
|
|
err := v.Struct(u)
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("expected error, got nil")
|
||
|
|
}
|
||
|
|
|
||
|
|
var xe *xerrors.Err
|
||
|
|
if !errors.As(err, &xe) {
|
||
|
|
t.Fatalf("expected *xerrors.Err, got %T", err)
|
||
|
|
}
|
||
|
|
if xe.Fields()["tag"] != "email" {
|
||
|
|
t.Errorf("tag = %v, want email", xe.Fields()["tag"])
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestValidator_Struct_BelowMin(t *testing.T) {
|
||
|
|
v := New()
|
||
|
|
u := testUser{Name: "Carol", Email: "carol@example.com", Age: 10}
|
||
|
|
err := v.Struct(u)
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("expected error, got nil")
|
||
|
|
}
|
||
|
|
|
||
|
|
var xe *xerrors.Err
|
||
|
|
if !errors.As(err, &xe) {
|
||
|
|
t.Fatalf("expected *xerrors.Err, got %T", err)
|
||
|
|
}
|
||
|
|
if xe.Code() != xerrors.ErrInvalidInput {
|
||
|
|
t.Errorf("code = %s, want %s", xe.Code(), xerrors.ErrInvalidInput)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestValidator_Struct_AboveMax(t *testing.T) {
|
||
|
|
v := New()
|
||
|
|
u := testUser{Name: "Dave", Email: "dave@example.com", Age: 200}
|
||
|
|
err := v.Struct(u)
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("expected error, got nil")
|
||
|
|
}
|
||
|
|
|
||
|
|
var xe *xerrors.Err
|
||
|
|
if !errors.As(err, &xe) {
|
||
|
|
t.Fatalf("expected *xerrors.Err, got %T", err)
|
||
|
|
}
|
||
|
|
if xe.Code() != xerrors.ErrInvalidInput {
|
||
|
|
t.Errorf("code = %s, want %s", xe.Code(), xerrors.ErrInvalidInput)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestValidator_Struct_NotAStruct(t *testing.T) {
|
||
|
|
v := New()
|
||
|
|
err := v.Struct("not a struct")
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("expected error, got nil")
|
||
|
|
}
|
||
|
|
|
||
|
|
var xe *xerrors.Err
|
||
|
|
if !errors.As(err, &xe) {
|
||
|
|
t.Fatalf("expected *xerrors.Err, got %T", err)
|
||
|
|
}
|
||
|
|
if xe.Code() != xerrors.ErrInternal {
|
||
|
|
t.Errorf("code = %s, want %s", xe.Code(), xerrors.ErrInternal)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestValidator_Struct_ErrorCode(t *testing.T) {
|
||
|
|
v := New()
|
||
|
|
u := testUser{} // all fields fail
|
||
|
|
err := v.Struct(u)
|
||
|
|
|
||
|
|
var xe *xerrors.Err
|
||
|
|
if !errors.As(err, &xe) {
|
||
|
|
t.Fatalf("errors.As failed: %T", err)
|
||
|
|
}
|
||
|
|
if xe.Code() != xerrors.ErrInvalidInput {
|
||
|
|
t.Errorf("Code() = %s, want %s", xe.Code(), xerrors.ErrInvalidInput)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestValidator_Struct_Fields(t *testing.T) {
|
||
|
|
v := New()
|
||
|
|
u := testUser{} // missing Name triggers required
|
||
|
|
err := v.Struct(u)
|
||
|
|
|
||
|
|
var xe *xerrors.Err
|
||
|
|
errors.As(err, &xe)
|
||
|
|
fields := xe.Fields()
|
||
|
|
if _, ok := fields["field"]; !ok {
|
||
|
|
t.Error("Fields() missing 'field' key")
|
||
|
|
}
|
||
|
|
if _, ok := fields["tag"]; !ok {
|
||
|
|
t.Error("Fields() missing 'tag' key")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestValidator_Struct_Unwrap(t *testing.T) {
|
||
|
|
v := New()
|
||
|
|
u := testUser{} // triggers validation error
|
||
|
|
err := v.Struct(u)
|
||
|
|
|
||
|
|
var xe *xerrors.Err
|
||
|
|
errors.As(err, &xe)
|
||
|
|
|
||
|
|
// The wrapped error must be a playground.ValidationErrors.
|
||
|
|
cause := errors.Unwrap(xe)
|
||
|
|
if cause == nil {
|
||
|
|
t.Fatal("Unwrap returned nil")
|
||
|
|
}
|
||
|
|
var ve playground.ValidationErrors
|
||
|
|
if !errors.As(cause, &ve) {
|
||
|
|
t.Errorf("Unwrap should return validator.ValidationErrors, got %T", cause)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestValidator_Struct_SpanishMessages(t *testing.T) {
|
||
|
|
v := New(WithMessageProvider(SpanishMessages))
|
||
|
|
u := testUser{Email: "a@b.com", Age: 20} // missing Name
|
||
|
|
|
||
|
|
err := v.Struct(u)
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("expected error")
|
||
|
|
}
|
||
|
|
|
||
|
|
if !strings.Contains(err.Error(), "obligatorio") {
|
||
|
|
t.Errorf("expected Spanish message, got: %s", err.Error())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestValidator_Struct_EnglishMessage_Required(t *testing.T) {
|
||
|
|
v := New()
|
||
|
|
u := testUser{Email: "a@b.com", Age: 20} // missing Name
|
||
|
|
|
||
|
|
err := v.Struct(u)
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("expected error")
|
||
|
|
}
|
||
|
|
|
||
|
|
if !strings.Contains(err.Error(), "required") {
|
||
|
|
t.Errorf("expected English 'required' message, got: %s", err.Error())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------
|
||
|
|
// MessageProvider tests
|
||
|
|
// ---------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestDefaultMessages_AllTags(t *testing.T) {
|
||
|
|
mp := DefaultMessages
|
||
|
|
tags := []struct {
|
||
|
|
tag string
|
||
|
|
param string
|
||
|
|
}{
|
||
|
|
{"required", ""},
|
||
|
|
{"email", ""},
|
||
|
|
{"min", "5"},
|
||
|
|
{"max", "100"},
|
||
|
|
{"unknown_rule", ""},
|
||
|
|
}
|
||
|
|
for _, tt := range tags {
|
||
|
|
msg := mp.Message("Field", tt.tag, tt.param)
|
||
|
|
if msg == "" {
|
||
|
|
t.Errorf("DefaultMessages.Message(%q) returned empty string", tt.tag)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSpanishMessages_AllTags(t *testing.T) {
|
||
|
|
mp := SpanishMessages
|
||
|
|
tags := []struct {
|
||
|
|
tag string
|
||
|
|
param string
|
||
|
|
}{
|
||
|
|
{"required", ""},
|
||
|
|
{"email", ""},
|
||
|
|
{"min", "5"},
|
||
|
|
{"max", "100"},
|
||
|
|
{"unknown_rule", ""},
|
||
|
|
}
|
||
|
|
for _, tt := range tags {
|
||
|
|
msg := mp.Message("Campo", tt.tag, tt.param)
|
||
|
|
if msg == "" {
|
||
|
|
t.Errorf("SpanishMessages.Message(%q) returned empty string", tt.tag)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWithMessageProvider_CustomImpl(t *testing.T) {
|
||
|
|
mp := &testMP{fn: func(field, tag, param string) string {
|
||
|
|
return "CUSTOM:" + field + ":" + tag
|
||
|
|
}}
|
||
|
|
v := New(WithMessageProvider(mp))
|
||
|
|
u := testUser{Email: "a@b.com", Age: 20} // missing Name
|
||
|
|
|
||
|
|
err := v.Struct(u)
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("expected error")
|
||
|
|
}
|
||
|
|
if !strings.Contains(err.Error(), "CUSTOM:") {
|
||
|
|
t.Errorf("expected custom message prefix, got: %s", err.Error())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------
|
||
|
|
// Helpers
|
||
|
|
// ---------------------------------------------------------------
|
||
|
|
|
||
|
|
type testMP struct {
|
||
|
|
fn func(field, tag, param string) string
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *testMP) Message(field, tag, param string) string {
|
||
|
|
return m.fn(field, tag, param)
|
||
|
|
}
|