goyave.dev/goyave/v5@v5.0.0-rc9.0.20240517145003-d3f977d0b9f3/util/typeutil/typeutil_test.go (about)

     1  package typeutil
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/samber/lo"
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  )
    10  
    11  func TestConvert(t *testing.T) {
    12  	type Nested struct {
    13  		C uint `json:"c"`
    14  	}
    15  
    16  	type Promoted struct {
    17  		P string `json:"p"`
    18  	}
    19  
    20  	type TestStruct struct {
    21  		Promoted
    22  		A      string   `json:"a"`
    23  		D      []string `json:"d"`
    24  		B      float64  `json:"b"`
    25  		Nested Nested   `json:"nested"`
    26  	}
    27  
    28  	cases := []struct {
    29  		value   any
    30  		want    any
    31  		wantErr bool
    32  	}{
    33  		{
    34  			value:   map[string]any{"p": "p", "a": "hello", "b": 0.3, "d": []string{"world"}, "c": 456, "nested": map[string]any{"c": 123}},
    35  			want:    &TestStruct{Promoted: Promoted{P: "p"}, A: "hello", B: 0.3, D: []string{"world"}, Nested: Nested{C: 123}},
    36  			wantErr: false,
    37  		},
    38  		{value: &TestStruct{A: "hello"}, want: &TestStruct{A: "hello"}, wantErr: false},
    39  		{value: struct{}{}, want: &TestStruct{}, wantErr: false},
    40  		{value: "string", want: &TestStruct{}, wantErr: true},
    41  		{value: 'a', want: &TestStruct{}, wantErr: true},
    42  		{value: 2, want: &TestStruct{}, wantErr: true},
    43  		{value: 2.5, want: &TestStruct{}, wantErr: true},
    44  		{value: []string{"string"}, want: &TestStruct{}, wantErr: true},
    45  		{value: map[string]any{"a": 1}, want: &TestStruct{}, wantErr: true},
    46  		{value: true, want: &TestStruct{}, wantErr: true},
    47  		{value: nil, want: (*TestStruct)(nil), wantErr: false},
    48  	}
    49  
    50  	for _, c := range cases {
    51  		c := c
    52  		t.Run("TestStruct", func(t *testing.T) {
    53  			res, err := Convert[*TestStruct](c.value)
    54  			assert.Equal(t, c.want, res)
    55  			if c.wantErr {
    56  				require.Error(t, err)
    57  			} else {
    58  				require.NoError(t, err)
    59  			}
    60  
    61  			assert.Equal(t, c.want, res)
    62  		})
    63  	}
    64  
    65  	t.Run("string", func(t *testing.T) {
    66  		res, err := Convert[string]("hello")
    67  		assert.Equal(t, "hello", res)
    68  		require.NoError(t, err)
    69  	})
    70  	t.Run("int", func(t *testing.T) {
    71  		res, err := Convert[int](123)
    72  		assert.Equal(t, 123, res)
    73  		require.NoError(t, err)
    74  	})
    75  	t.Run("float", func(t *testing.T) {
    76  		res, err := Convert[float64](0.3)
    77  		assert.InEpsilon(t, 0.3, res, 0)
    78  		require.NoError(t, err)
    79  	})
    80  	t.Run("bool", func(t *testing.T) {
    81  		res, err := Convert[bool](true)
    82  		assert.True(t, res)
    83  		require.NoError(t, err)
    84  	})
    85  	t.Run("mismatching types", func(t *testing.T) {
    86  		res, err := Convert[bool]("true")
    87  		assert.False(t, res)
    88  		require.Error(t, err)
    89  	})
    90  	t.Run("[]string", func(t *testing.T) {
    91  		res, err := Convert[[]string]([]string{"a", "b", "c"})
    92  		assert.Equal(t, []string{"a", "b", "c"}, res)
    93  		require.NoError(t, err)
    94  	})
    95  	t.Run("[]any", func(t *testing.T) {
    96  		res, err := Convert[[]any]([]string{"a", "4", "c"})
    97  		assert.Equal(t, []any{"a", "4", "c"}, res)
    98  		require.NoError(t, err)
    99  
   100  		res, err = Convert[[]any]([]any{"a", 4, 4.0, true, []any{"a", "b"}})
   101  		assert.Equal(t, []any{"a", 4, 4.0, true, []any{"a", "b"}}, res)
   102  		require.NoError(t, err)
   103  	})
   104  }
   105  
   106  func TestMustConvert(t *testing.T) {
   107  	assert.InEpsilon(t, 0.3, MustConvert[float64](0.3), 0)
   108  
   109  	assert.Panics(t, func() {
   110  		MustConvert[float64]("0.3")
   111  	})
   112  }
   113  
   114  func TestCopy(t *testing.T) {
   115  	type Nested struct {
   116  		C uint `json:"c"`
   117  	}
   118  
   119  	type Promoted struct {
   120  		P string `json:"p"`
   121  	}
   122  
   123  	type TestStruct struct {
   124  		Promoted
   125  		Undefined    Undefined[string]    `json:"undefined"`
   126  		UndefinedPtr Undefined[*string]   `json:"undefinedPtr"`
   127  		A            string               `json:"a"`
   128  		Ptr          *string              `json:"ptr"`
   129  		D            []string             `json:"d"`
   130  		B            float64              `json:"b"`
   131  		Scanner      Undefined[testInt64] `json:"scanner"`
   132  		Nested       Nested               `json:"nested"`
   133  	}
   134  
   135  	cases := []struct {
   136  		model     *TestStruct
   137  		dto       any
   138  		want      *TestStruct
   139  		desc      string
   140  		wantPanic bool
   141  	}{
   142  		{
   143  			desc: "base",
   144  			model: &TestStruct{
   145  				A: "test",
   146  				D: []string{"test1", "test2"},
   147  				B: 1,
   148  			},
   149  			dto: struct {
   150  				A string
   151  				D []string
   152  			}{A: "override", D: []string{"override1", "override2"}},
   153  			want: &TestStruct{
   154  				A: "override",
   155  				D: []string{"override1", "override2"},
   156  				B: 1,
   157  			},
   158  		},
   159  		{
   160  			desc:  "base_at_zero",
   161  			model: &TestStruct{},
   162  			dto: struct {
   163  				B float64
   164  			}{B: 1.234},
   165  			want: &TestStruct{
   166  				B: 1.234,
   167  			},
   168  		},
   169  		{
   170  			desc: "promoted",
   171  			model: &TestStruct{
   172  				A: "test",
   173  				Promoted: Promoted{
   174  					P: "promoted",
   175  				},
   176  			},
   177  			dto: struct {
   178  				A string
   179  				P string
   180  			}{A: "override", P: "promoted override"},
   181  			want: &TestStruct{
   182  				A: "override",
   183  				Promoted: Promoted{
   184  					P: "promoted override",
   185  				},
   186  			},
   187  		},
   188  		{
   189  			desc: "promoted_dto",
   190  			model: &TestStruct{
   191  				A: "test",
   192  				Promoted: Promoted{
   193  					P: "promoted",
   194  				},
   195  			},
   196  			dto: struct {
   197  				A        string
   198  				Promoted struct {
   199  					P string
   200  				}
   201  			}{A: "override", Promoted: struct {
   202  				P string
   203  			}{
   204  				P: "promoted override",
   205  			}},
   206  			want: &TestStruct{
   207  				A: "override",
   208  				Promoted: Promoted{
   209  					P: "promoted override",
   210  				},
   211  			},
   212  		},
   213  		{
   214  			desc: "ignore_empty",
   215  			model: &TestStruct{
   216  				A: "test",
   217  				D: []string{"test1", "test2"},
   218  				B: 0,
   219  			},
   220  			dto: struct {
   221  				Ptr Undefined[*string]
   222  				A   string
   223  				D   []string
   224  				B   float64
   225  			}{A: "", B: 0, D: nil, Ptr: Undefined[*string]{}},
   226  			want: &TestStruct{
   227  				A: "test",
   228  				D: []string{"test1", "test2"},
   229  				B: 0,
   230  			},
   231  		},
   232  		{
   233  			desc: "deep",
   234  			model: &TestStruct{
   235  				Nested: Nested{
   236  					C: 2,
   237  				},
   238  			},
   239  			dto: struct {
   240  				C      uint
   241  				Nested struct {
   242  					C uint
   243  				}
   244  			}{C: 3, Nested: struct{ C uint }{C: 4}},
   245  			want: &TestStruct{
   246  				Nested: Nested{
   247  					C: 4,
   248  				},
   249  			},
   250  		},
   251  		{
   252  			desc: "undefined_field_zero_value",
   253  			model: &TestStruct{
   254  				B: 1,
   255  			},
   256  			dto: struct{ B Undefined[float64] }{B: NewUndefined(0.0)},
   257  			want: &TestStruct{
   258  				B: 0,
   259  			},
   260  		},
   261  		{
   262  			desc: "undefined_field",
   263  			model: &TestStruct{
   264  				B: 1,
   265  			},
   266  			dto: struct{ B Undefined[float64] }{B: NewUndefined(1.234)},
   267  			want: &TestStruct{
   268  				B: 1.234,
   269  			},
   270  		},
   271  		{
   272  			desc:  "undefined_slice",
   273  			model: &TestStruct{},
   274  			dto:   struct{ D Undefined[[]string] }{D: NewUndefined([]string{"a", "b", "c"})},
   275  			want: &TestStruct{
   276  				D: []string{"a", "b", "c"},
   277  			},
   278  		},
   279  		{
   280  			desc:  "undefined_struct",
   281  			model: &TestStruct{},
   282  			dto:   struct{ Nested Undefined[struct{ C uint }] }{Nested: NewUndefined(struct{ C uint }{C: 4})},
   283  			want: &TestStruct{
   284  				Nested: Nested{
   285  					C: 4,
   286  				},
   287  			},
   288  		},
   289  		{
   290  			desc: "undefined_nil",
   291  			model: &TestStruct{
   292  				A:   "not nil",
   293  				Ptr: lo.ToPtr("not nil"),
   294  			},
   295  			dto: struct {
   296  				A   Undefined[*string]
   297  				Ptr Undefined[*string]
   298  			}{
   299  				A:   NewUndefined[*string](nil),
   300  				Ptr: NewUndefined[*string](nil),
   301  			},
   302  			want: &TestStruct{
   303  				A:   "not nil",
   304  				Ptr: nil,
   305  			},
   306  		},
   307  		{
   308  			desc: "undefined_to_undefined",
   309  			model: &TestStruct{
   310  				Undefined: NewUndefined("value"),
   311  			},
   312  			dto: struct {
   313  				Undefined Undefined[string]
   314  			}{
   315  				Undefined: NewUndefined("override"),
   316  			},
   317  			want: &TestStruct{
   318  				Undefined: NewUndefined("override"),
   319  			},
   320  		},
   321  		{
   322  			desc: "undefined_ptr_to_undefined",
   323  			model: &TestStruct{
   324  				Undefined: NewUndefined("value"),
   325  			},
   326  			dto: struct {
   327  				Undefined Undefined[*string]
   328  			}{
   329  				Undefined: NewUndefined(lo.ToPtr("override")),
   330  			},
   331  			want: &TestStruct{
   332  				Undefined: NewUndefined("override"),
   333  			},
   334  		},
   335  		{
   336  			desc: "ptr_to_undefined",
   337  			model: &TestStruct{
   338  				Undefined: NewUndefined("value"),
   339  			},
   340  			dto: struct {
   341  				Undefined *string
   342  			}{
   343  				Undefined: lo.ToPtr("override"),
   344  			},
   345  			want: &TestStruct{
   346  				Undefined: NewUndefined("override"),
   347  			},
   348  		},
   349  		{
   350  			desc: "undefined_ptr_to_undefined",
   351  			model: &TestStruct{
   352  				Undefined: NewUndefined("value"),
   353  			},
   354  			dto: struct {
   355  				Undefined Undefined[*string]
   356  			}{
   357  				Undefined: NewUndefined(lo.ToPtr("override")),
   358  			},
   359  			want: &TestStruct{
   360  				Undefined: NewUndefined("override"),
   361  			},
   362  		},
   363  		{
   364  			desc: "undefined_to_undefined_incompatible_types",
   365  			model: &TestStruct{
   366  				Undefined: NewUndefined("value"),
   367  			},
   368  			dto: struct {
   369  				Undefined Undefined[int]
   370  			}{
   371  				Undefined: NewUndefined(123),
   372  			},
   373  			want: &TestStruct{
   374  				Undefined: NewUndefined("value"), // The value has not been overridden because of incompatible types
   375  			},
   376  		},
   377  		{
   378  			desc: "scanner_undefined",
   379  			model: &TestStruct{
   380  				Scanner: NewUndefined(testInt64{Val: 123}),
   381  			},
   382  			dto: struct {
   383  				Scanner int64
   384  			}{
   385  				Scanner: 456,
   386  			},
   387  			want: &TestStruct{
   388  				Scanner: NewUndefined(testInt64{Val: 456}),
   389  			},
   390  		},
   391  		{
   392  			desc: "undefined_scanner_incompatible",
   393  			model: &TestStruct{
   394  				Scanner: NewUndefined(testInt64{Val: 123}),
   395  			},
   396  			dto: struct {
   397  				Scanner string
   398  			}{
   399  				Scanner: "456",
   400  			},
   401  			want: &TestStruct{
   402  				Scanner: NewUndefined(testInt64{Val: 123}),
   403  			},
   404  		},
   405  	}
   406  
   407  	for _, c := range cases {
   408  		c := c
   409  		t.Run(c.desc, func(t *testing.T) {
   410  			if c.wantPanic {
   411  				assert.Panics(t, func() {
   412  					Copy(c.model, c.dto)
   413  				})
   414  				return
   415  			}
   416  			res := Copy(c.model, c.dto)
   417  			assert.Equal(t, c.want, res)
   418  		})
   419  	}
   420  }