github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/defaults/defaults_test.go (about)

     1  package defaults
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/bingoohuang/gg/pkg/defaults/internal/fixture"
     9  )
    10  
    11  type (
    12  	MyInt     int
    13  	MyInt8    int8
    14  	MyInt16   int16
    15  	MyInt32   int32
    16  	MyInt64   int64
    17  	MyUint    uint
    18  	MyUint8   uint8
    19  	MyUint16  uint16
    20  	MyUint32  uint32
    21  	MyUint64  uint64
    22  	MyUintptr uintptr
    23  	MyFloat32 float32
    24  	MyFloat64 float64
    25  	MyBool    bool
    26  	MyString  string
    27  	MyMap     map[string]int
    28  	MySlice   []int
    29  )
    30  
    31  type Sample struct {
    32  	Int       int           `default:"1"`
    33  	Int8      int8          `default:"8"`
    34  	Int16     int16         `default:"16"`
    35  	Int32     int32         `default:"32"`
    36  	Int64     int64         `default:"64"`
    37  	Uint      uint          `default:"1"`
    38  	Uint8     uint8         `default:"8"`
    39  	Uint16    uint16        `default:"16"`
    40  	Uint32    uint32        `default:"32"`
    41  	Uint64    uint64        `default:"64"`
    42  	Uintptr   uintptr       `default:"1"`
    43  	Float32   float32       `default:"1.32"`
    44  	Float64   float64       `default:"1.64"`
    45  	BoolTrue  bool          `default:"true"`
    46  	BoolFalse bool          `default:"false"`
    47  	String    string        `default:"hello"`
    48  	Duration  time.Duration `default:"10s"`
    49  
    50  	Struct    Struct         `default:"{}"`
    51  	StructPtr *Struct        `default:"{}"`
    52  	Map       map[string]int `default:"{}"`
    53  	Slice     []string       `default:"[]"`
    54  
    55  	MyInt       MyInt     `default:"1"`
    56  	MyInt8      MyInt8    `default:"8"`
    57  	MyInt16     MyInt16   `default:"16"`
    58  	MyInt32     MyInt32   `default:"32"`
    59  	MyInt64     MyInt64   `default:"64"`
    60  	MyUint      MyUint    `default:"1"`
    61  	MyUint8     MyUint8   `default:"8"`
    62  	MyUint16    MyUint16  `default:"16"`
    63  	MyUint32    MyUint32  `default:"32"`
    64  	MyUint64    MyUint64  `default:"64"`
    65  	MyUintptr   MyUintptr `default:"1"`
    66  	MyFloat32   MyFloat32 `default:"1.32"`
    67  	MyFloat64   MyFloat64 `default:"1.64"`
    68  	MyBoolTrue  MyBool    `default:"true"`
    69  	MyBoolFalse MyBool    `default:"false"`
    70  	MyString    MyString  `default:"hello"`
    71  	MyMap       MyMap     `default:"{}"`
    72  	MySlice     MySlice   `default:"[]"`
    73  
    74  	StructWithJSON    Struct         `default:"{\"Foo\": 123}"`
    75  	StructPtrWithJSON *Struct        `default:"{\"Foo\": 123}"`
    76  	MapWithJSON       map[string]int `default:"{\"foo\": 123}"`
    77  	SliceWithJSON     []string       `default:"[\"foo\"]"`
    78  
    79  	Empty string `default:""`
    80  
    81  	NoDefault       *string `default:"-"`
    82  	NoDefaultStruct Struct  `default:"-"`
    83  
    84  	MapWithNoTag                map[string]int
    85  	SliceWithNoTag              []string
    86  	StructPtrWithNoTag          *Struct
    87  	StructWithNoTag             Struct
    88  	DeepSliceOfStructsWithNoTag [][][]Struct
    89  
    90  	NonInitialString    string  `default:"foo"`
    91  	NonInitialSlice     []int   `default:"[123]"`
    92  	NonInitialStruct    Struct  `default:"{}"`
    93  	NonInitialStructPtr *Struct `default:"{}"`
    94  }
    95  
    96  type Struct struct {
    97  	Emmbeded `default:"{}"`
    98  
    99  	Foo         int
   100  	Bar         int
   101  	WithDefault string `default:"foo"`
   102  }
   103  
   104  func (s *Struct) SetDefaults() {
   105  	s.Bar = 456
   106  }
   107  
   108  type Emmbeded struct {
   109  	Int int `default:"1"`
   110  }
   111  
   112  func TestMustSet(t *testing.T) {
   113  	sample := &Sample{
   114  		NonInitialString:            "string",
   115  		NonInitialSlice:             []int{1, 2, 3},
   116  		NonInitialStruct:            Struct{Foo: 123},
   117  		NonInitialStructPtr:         &Struct{Foo: 123},
   118  		DeepSliceOfStructsWithNoTag: [][][]Struct{{{{Foo: 123}}}},
   119  	}
   120  
   121  	MustSet(sample)
   122  	go func() {
   123  		if err := recover(); err != nil {
   124  			t.Fatalf("it should not panic error: %v", err)
   125  		}
   126  	}()
   127  	t.Log("it works.")
   128  }
   129  
   130  func TestInit(t *testing.T) {
   131  	sample := &Sample{
   132  		NonInitialString:            "string",
   133  		NonInitialSlice:             []int{1, 2, 3},
   134  		NonInitialStruct:            Struct{Foo: 123},
   135  		NonInitialStructPtr:         &Struct{Foo: 123},
   136  		DeepSliceOfStructsWithNoTag: [][][]Struct{{{{Foo: 123}}}},
   137  	}
   138  
   139  	if err := Set(sample); err != nil {
   140  		t.Fatalf("it should not return an error: %v", err)
   141  	}
   142  
   143  	nonPtrVal := 1
   144  
   145  	if err := Set(nonPtrVal); err == nil {
   146  		t.Fatalf("it should return an error when used for a non-pointer type")
   147  	}
   148  	if err := Set(&nonPtrVal); err == nil {
   149  		t.Fatalf("it should return an error when used for a non-pointer type")
   150  	}
   151  
   152  	Set(&fixture.Sample{}) // should not panic
   153  
   154  	t.Run("scalar types", func(t *testing.T) {
   155  		if sample.Int != 1 {
   156  			t.Errorf("it should initialize int")
   157  		}
   158  		if sample.Int8 != 8 {
   159  			t.Errorf("it should initialize int8")
   160  		}
   161  		if sample.Int16 != 16 {
   162  			t.Errorf("it should initialize int16")
   163  		}
   164  		if sample.Int32 != 32 {
   165  			t.Errorf("it should initialize int32")
   166  		}
   167  		if sample.Int64 != 64 {
   168  			t.Errorf("it should initialize int64")
   169  		}
   170  		if sample.Uint != 1 {
   171  			t.Errorf("it should initialize uint")
   172  		}
   173  		if sample.Uint8 != 8 {
   174  			t.Errorf("it should initialize uint8")
   175  		}
   176  		if sample.Uint16 != 16 {
   177  			t.Errorf("it should initialize uint16")
   178  		}
   179  		if sample.Uint32 != 32 {
   180  			t.Errorf("it should initialize uint32")
   181  		}
   182  		if sample.Uint64 != 64 {
   183  			t.Errorf("it should initialize uint64")
   184  		}
   185  		if sample.Uintptr != 1 {
   186  			t.Errorf("it should initialize uintptr")
   187  		}
   188  		if sample.Float32 != 1.32 {
   189  			t.Errorf("it should initialize float32")
   190  		}
   191  		if sample.Float64 != 1.64 {
   192  			t.Errorf("it should initialize float64")
   193  		}
   194  		if sample.BoolTrue != true {
   195  			t.Errorf("it should initialize bool (true)")
   196  		}
   197  		if sample.BoolFalse != false {
   198  			t.Errorf("it should initialize bool (false)")
   199  		}
   200  		if sample.String != "hello" {
   201  			t.Errorf("it should initialize string")
   202  		}
   203  	})
   204  
   205  	t.Run("complex types", func(t *testing.T) {
   206  		if sample.StructPtr == nil {
   207  			t.Errorf("it should initialize struct pointer")
   208  		}
   209  		if sample.Map == nil {
   210  			t.Errorf("it should initialize map")
   211  		}
   212  		if sample.Slice == nil {
   213  			t.Errorf("it should initialize slice")
   214  		}
   215  	})
   216  
   217  	t.Run("aliased types", func(t *testing.T) {
   218  		if sample.MyInt != 1 {
   219  			t.Errorf("it should initialize int")
   220  		}
   221  		if sample.MyInt8 != 8 {
   222  			t.Errorf("it should initialize int8")
   223  		}
   224  		if sample.MyInt16 != 16 {
   225  			t.Errorf("it should initialize int16")
   226  		}
   227  		if sample.MyInt32 != 32 {
   228  			t.Errorf("it should initialize int32")
   229  		}
   230  		if sample.MyInt64 != 64 {
   231  			t.Errorf("it should initialize int64")
   232  		}
   233  		if sample.MyUint != 1 {
   234  			t.Errorf("it should initialize uint")
   235  		}
   236  		if sample.MyUint8 != 8 {
   237  			t.Errorf("it should initialize uint8")
   238  		}
   239  		if sample.MyUint16 != 16 {
   240  			t.Errorf("it should initialize uint16")
   241  		}
   242  		if sample.MyUint32 != 32 {
   243  			t.Errorf("it should initialize uint32")
   244  		}
   245  		if sample.MyUint64 != 64 {
   246  			t.Errorf("it should initialize uint64")
   247  		}
   248  		if sample.MyUintptr != 1 {
   249  			t.Errorf("it should initialize uintptr")
   250  		}
   251  		if sample.MyFloat32 != 1.32 {
   252  			t.Errorf("it should initialize float32")
   253  		}
   254  		if sample.MyFloat64 != 1.64 {
   255  			t.Errorf("it should initialize float64")
   256  		}
   257  		if sample.MyBoolTrue != true {
   258  			t.Errorf("it should initialize bool (true)")
   259  		}
   260  		if sample.MyBoolFalse != false {
   261  			t.Errorf("it should initialize bool (false)")
   262  		}
   263  		if sample.MyString != "hello" {
   264  			t.Errorf("it should initialize string")
   265  		}
   266  
   267  		if sample.MyMap == nil {
   268  			t.Errorf("it should initialize map")
   269  		}
   270  		if sample.MySlice == nil {
   271  			t.Errorf("it should initialize slice")
   272  		}
   273  	})
   274  
   275  	t.Run("nested", func(t *testing.T) {
   276  		if sample.Struct.WithDefault != "foo" {
   277  			t.Errorf("it should set default on inner field in struct")
   278  		}
   279  		if sample.StructPtr == nil || sample.StructPtr.WithDefault != "foo" {
   280  			t.Errorf("it should set default on inner field in struct pointer")
   281  		}
   282  		if sample.Struct.Emmbeded.Int != 1 {
   283  			t.Errorf("it should set default on an emmbeded struct")
   284  		}
   285  	})
   286  
   287  	t.Run("complex types with json", func(t *testing.T) {
   288  		if sample.StructWithJSON.Foo != 123 {
   289  			t.Errorf("it should initialize struct with json")
   290  		}
   291  		if sample.StructPtrWithJSON == nil || sample.StructPtrWithJSON.Foo != 123 {
   292  			t.Errorf("it should initialize struct pointer with json")
   293  		}
   294  		if sample.MapWithJSON["foo"] != 123 {
   295  			t.Errorf("it should initialize map with json")
   296  		}
   297  		if len(sample.SliceWithJSON) == 0 || sample.SliceWithJSON[0] != "foo" {
   298  			t.Errorf("it should initialize slice with json")
   299  		}
   300  
   301  		t.Run("invalid json", func(t *testing.T) {
   302  			if err := Set(&struct {
   303  				I []int `default:"[!]"`
   304  			}{}); err == nil {
   305  				t.Errorf("it should return error")
   306  			}
   307  
   308  			if err := Set(&struct {
   309  				I map[string]int `default:"{1}"`
   310  			}{}); err == nil {
   311  				t.Errorf("it should return error")
   312  			}
   313  
   314  			if err := Set(&struct {
   315  				S struct {
   316  					I []int
   317  				} `default:"{!}"`
   318  			}{}); err == nil {
   319  				t.Errorf("it should return error")
   320  			}
   321  
   322  			if err := Set(&struct {
   323  				S struct {
   324  					I []int `default:"[!]"`
   325  				}
   326  			}{}); err == nil {
   327  				t.Errorf("it should return error")
   328  			}
   329  		})
   330  	})
   331  
   332  	t.Run("Setter interface", func(t *testing.T) {
   333  		if sample.Struct.Bar != 456 {
   334  			t.Errorf("it should initialize struct")
   335  		}
   336  		if sample.StructPtr == nil || sample.StructPtr.Bar != 456 {
   337  			t.Errorf("it should initialize struct pointer")
   338  		}
   339  	})
   340  
   341  	t.Run("non-initial value", func(t *testing.T) {
   342  		if sample.NonInitialString != "string" {
   343  			t.Errorf("it should not override non-initial value")
   344  		}
   345  		if !reflect.DeepEqual(sample.NonInitialSlice, []int{1, 2, 3}) {
   346  			t.Errorf("it should not override non-initial value")
   347  		}
   348  		if !reflect.DeepEqual(sample.NonInitialStruct, Struct{Emmbeded: Emmbeded{Int: 1}, Foo: 123, Bar: 456, WithDefault: "foo"}) {
   349  			t.Errorf("it should not override non-initial value but set defaults for fields")
   350  		}
   351  		if !reflect.DeepEqual(sample.NonInitialStructPtr, &Struct{Emmbeded: Emmbeded{Int: 1}, Foo: 123, Bar: 456, WithDefault: "foo"}) {
   352  			t.Errorf("it should not override non-initial value but set defaults for fields")
   353  		}
   354  	})
   355  
   356  	t.Run("no tag", func(t *testing.T) {
   357  		if sample.MapWithNoTag != nil {
   358  			t.Errorf("it should not initialize pointer type (map)")
   359  		}
   360  		if sample.SliceWithNoTag != nil {
   361  			t.Errorf("it should not initialize pointer type (slice)")
   362  		}
   363  		if sample.StructPtrWithNoTag != nil {
   364  			t.Errorf("it should not initialize pointer type (struct)")
   365  		}
   366  		if sample.StructWithNoTag.WithDefault != "foo" {
   367  			t.Errorf("it should automatically recurse into a struct even without a tag")
   368  		}
   369  		if !reflect.DeepEqual(sample.DeepSliceOfStructsWithNoTag, [][][]Struct{{{{Emmbeded: Emmbeded{Int: 1}, Foo: 123, Bar: 456, WithDefault: "foo"}}}}) {
   370  			t.Errorf("it should automatically recurse into a slice of structs even without a tag")
   371  		}
   372  	})
   373  
   374  	t.Run("opt-out", func(t *testing.T) {
   375  		if sample.NoDefault != nil {
   376  			t.Errorf("it should not be set")
   377  		}
   378  		if sample.NoDefaultStruct.WithDefault != "" {
   379  			t.Errorf("it should not initialize a struct with default values")
   380  		}
   381  	})
   382  }
   383  
   384  func TestCanUpdate(t *testing.T) {
   385  	type st struct{ Int int }
   386  
   387  	var myStructPtr *st
   388  
   389  	pairs := map[interface{}]bool{
   390  		0:            true,
   391  		123:          false,
   392  		float64(0):   true,
   393  		float64(123): false,
   394  		"":           true,
   395  		"string":     false,
   396  		false:        true,
   397  		true:         false,
   398  		st{}:         true,
   399  		st{Int: 123}: false,
   400  		myStructPtr:  true,
   401  		&st{}:        false,
   402  	}
   403  	for input, expect := range pairs {
   404  		typ := reflect.TypeOf(input).String()
   405  		output := CanUpdate(input)
   406  		if output != expect {
   407  			t.Errorf("CanUpdate(%+v) returns %v, expected %v, type:%s", input, output, expect, typ)
   408  		}
   409  	}
   410  }