Files
valid/valid_test.go

290 lines
6.8 KiB
Go
Raw Normal View History

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