github.com/shoshinnikita/budget-manager@v0.7.1-0.20220131195411-8c46ff1c6778/internal/pkg/money/money_test.go (about)

     1  package money_test
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"math"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	. "github.com/ShoshinNikita/budget-manager/internal/pkg/money"
    13  )
    14  
    15  func TestConvertMoney(t *testing.T) {
    16  	t.Run("int to int", testConvertMoney_IntToInt)
    17  	t.Run("float to float", testConvertMoney_FloatToFloat)
    18  	t.Run("int to float", testConvertMoney_IntToFloat)
    19  	t.Run("float to int", testConvertMoney_FloatToInt)
    20  }
    21  
    22  func testConvertMoney_IntToInt(t *testing.T) {
    23  	t.Parallel()
    24  
    25  	require := require.New(t)
    26  
    27  	tests := []struct {
    28  		in        int64
    29  		res       Money
    30  		converted int64
    31  	}{
    32  		{in: -20, res: Money(-2000), converted: -20},
    33  		{in: 0, res: Money(0), converted: 0},
    34  		{in: 15, res: Money(1500), converted: 15},
    35  		{in: 1000000, res: Money(100000000), converted: 1000000},
    36  	}
    37  
    38  	for _, tt := range tests {
    39  		res := FromInt(tt.in)
    40  		require.Equal(tt.res, res)
    41  		require.Equal(tt.converted, res.Int())
    42  	}
    43  }
    44  
    45  func testConvertMoney_FloatToFloat(t *testing.T) {
    46  	t.Parallel()
    47  
    48  	require := require.New(t)
    49  
    50  	tests := []struct {
    51  		in        float64
    52  		res       Money
    53  		converted float64
    54  	}{
    55  		{in: -20.50, res: Money(-2050), converted: -20.50},
    56  		{in: 0, res: Money(0), converted: 0},
    57  		{in: 0.75, res: Money(75), converted: 0.75},
    58  		{in: 15.30, res: Money(1530), converted: 15.30},
    59  		{in: 1000000.87, res: Money(100000087), converted: 1000000.87},
    60  		{in: 69.99, res: Money(6999), converted: 69.99},
    61  		{in: 17.83, res: Money(1783), converted: 17.83},
    62  	}
    63  
    64  	for _, tt := range tests {
    65  		res := FromFloat(tt.in)
    66  		require.Equal(tt.res, res)
    67  		require.Equal(tt.converted, res.Float())
    68  	}
    69  }
    70  
    71  func testConvertMoney_IntToFloat(t *testing.T) {
    72  	t.Parallel()
    73  
    74  	require := require.New(t)
    75  
    76  	tests := []struct {
    77  		in        int64
    78  		res       Money
    79  		converted float64
    80  	}{
    81  		{in: -20, res: Money(-2000), converted: -20},
    82  		{in: 0, res: Money(0), converted: 0},
    83  		{in: 15, res: Money(1500), converted: 15},
    84  		{in: 1000000, res: Money(100000000), converted: 1000000},
    85  	}
    86  
    87  	for _, tt := range tests {
    88  		res := FromInt(tt.in)
    89  		require.Equal(tt.res, res)
    90  		require.Equal(tt.converted, res.Float())
    91  	}
    92  }
    93  
    94  func testConvertMoney_FloatToInt(t *testing.T) {
    95  	t.Parallel()
    96  
    97  	require := require.New(t)
    98  
    99  	tests := []struct {
   100  		in        float64
   101  		res       Money
   102  		converted int64
   103  	}{
   104  		{in: -20.5, res: Money(-2050), converted: -20},
   105  		{in: 0.30, res: Money(30), converted: 0},
   106  		{in: 15, res: Money(1500), converted: 15},
   107  		{in: 1000000.87, res: Money(100000087), converted: 1000000},
   108  	}
   109  
   110  	for _, tt := range tests {
   111  		res := FromFloat(tt.in)
   112  		require.Equal(tt.res, res)
   113  		require.Equal(tt.converted, res.Int())
   114  	}
   115  }
   116  
   117  func TestAdd(t *testing.T) {
   118  	t.Parallel()
   119  
   120  	require := require.New(t)
   121  
   122  	tests := []struct {
   123  		origin int64
   124  
   125  		add Money
   126  		res Money
   127  	}{
   128  		{origin: -150, add: Money(2000), res: Money(-13000)},
   129  		{origin: 0, add: Money(2000), res: Money(2000)},
   130  		{origin: 150, add: Money(2000), res: Money(17000)},
   131  		// Add negative
   132  		{origin: 150, add: Money(-2000), res: Money(13000)},
   133  	}
   134  
   135  	for _, tt := range tests {
   136  		origin := FromInt(tt.origin)
   137  
   138  		res := origin.Add(tt.add)
   139  		require.Equal(tt.res, res)
   140  	}
   141  }
   142  
   143  func TestSub(t *testing.T) {
   144  	t.Parallel()
   145  
   146  	require := require.New(t)
   147  
   148  	tests := []struct {
   149  		origin int64
   150  
   151  		sub Money
   152  		res Money
   153  	}{
   154  		{origin: -150, sub: Money(2000), res: Money(-17000)},
   155  		{origin: 0, sub: Money(2000), res: Money(-2000)},
   156  		{origin: 150, sub: Money(2000), res: Money(13000)},
   157  		// Sub negative
   158  		{origin: 150, sub: Money(-2000), res: Money(17000)},
   159  	}
   160  
   161  	for _, tt := range tests {
   162  		origin := FromInt(tt.origin)
   163  
   164  		res := origin.Sub(tt.sub)
   165  		require.Equal(tt.res, res)
   166  	}
   167  }
   168  
   169  func TestDivide(t *testing.T) {
   170  	t.Parallel()
   171  
   172  	t.Run("common divides", func(t *testing.T) {
   173  		require := require.New(t)
   174  
   175  		tests := []struct {
   176  			origin   Money
   177  			n        int64
   178  			res      Money
   179  			resInt   int64
   180  			resFloat float64
   181  		}{
   182  			{origin: FromInt(1500), n: 1, res: Money(150000), resInt: 1500, resFloat: 1500},
   183  			{origin: FromInt(1500), n: 5, res: Money(30000), resInt: 300, resFloat: 300},
   184  			{origin: FromInt(1500), n: 7, res: Money(21428), resInt: 214, resFloat: 214.28},
   185  		}
   186  
   187  		for _, tt := range tests {
   188  			res := tt.origin.Div(tt.n)
   189  			require.Equal(tt.res, res)
   190  			require.Equal(tt.resInt, res.Int())
   191  			require.Equal(tt.resFloat, res.Float())
   192  		}
   193  	})
   194  
   195  	// Special case
   196  	t.Run("divide by zero", func(t *testing.T) {
   197  		require := require.New(t)
   198  
   199  		defer func() {
   200  			r := recover()
   201  			require.NotNil(r)
   202  		}()
   203  
   204  		FromFloat(120.5).Div(0)
   205  	})
   206  }
   207  
   208  func TestRound(t *testing.T) {
   209  	t.Parallel()
   210  
   211  	tests := []float64{
   212  		1, 1.4, 1.5, 1.6,
   213  		0,
   214  		-1, -1.4, -1.5, -1.6,
   215  	}
   216  	for _, tt := range tests {
   217  		wantRound := int64(math.Round(tt))
   218  		gotRound := FromFloat(tt).Round().Int()
   219  
   220  		assert.Equalf(t, wantRound, gotRound, "test: round %f", tt)
   221  
   222  		wantCeil := int64(math.Ceil(tt))
   223  		gotCeil := FromFloat(tt).Ceil().Int()
   224  
   225  		assert.Equalf(t, wantCeil, gotCeil, "test: ceil %f", tt)
   226  
   227  		wantFloor := int64(math.Floor(tt))
   228  		gotFloor := FromFloat(tt).Floor().Int()
   229  
   230  		assert.Equalf(t, wantFloor, gotFloor, "test: floor %f", tt)
   231  	}
   232  }
   233  
   234  func TestJSON(t *testing.T) {
   235  	t.Run("Marshal", testJSON_Marshal)
   236  	t.Run("Unmarshal", testJSON_Unmarshal)
   237  }
   238  
   239  func testJSON_Marshal(t *testing.T) {
   240  	t.Parallel()
   241  
   242  	type testStruct struct {
   243  		Money Money `json:"money"`
   244  	}
   245  
   246  	require := require.New(t)
   247  
   248  	tests := []struct {
   249  		input testStruct
   250  		want  string
   251  	}{
   252  		{
   253  			input: testStruct{Money: FromInt(357)},
   254  			want:  `{"money":357.00}`,
   255  		},
   256  		{
   257  			input: testStruct{Money: FromFloat(154.30)},
   258  			want:  `{"money":154.30}`,
   259  		},
   260  		{
   261  			input: testStruct{Money: FromFloat(0.07)},
   262  			want:  `{"money":0.07}`,
   263  		},
   264  		{
   265  			input: testStruct{Money: FromFloat(15.073)},
   266  			want:  `{"money":15.07}`,
   267  		},
   268  		{
   269  			input: testStruct{Money: FromFloat(15.078)},
   270  			want:  `{"money":15.07}`,
   271  		},
   272  	}
   273  
   274  	for _, tt := range tests {
   275  		data, err := json.Marshal(tt.input)
   276  		require.Nil(err)
   277  		require.Equal(tt.want, string(data))
   278  	}
   279  }
   280  
   281  func testJSON_Unmarshal(t *testing.T) {
   282  	t.Parallel()
   283  
   284  	type testStruct struct {
   285  		Money Money `json:"money"`
   286  	}
   287  
   288  	require := require.New(t)
   289  
   290  	tests := []struct {
   291  		input string
   292  		want  testStruct
   293  	}{
   294  		{
   295  			input: `{"money":357}`,
   296  			want:  testStruct{Money: FromInt(357)},
   297  		},
   298  		{
   299  			input: `{"money":154.30}`,
   300  			want:  testStruct{Money: FromFloat(154.30)},
   301  		},
   302  		{
   303  			input: `{"money":0.07}`,
   304  			want:  testStruct{Money: FromFloat(0.07)},
   305  		},
   306  		{
   307  			input: `{"money":"test"}`,
   308  			want:  testStruct{Money: FromInt(0)},
   309  		},
   310  	}
   311  
   312  	for _, tt := range tests {
   313  		var res testStruct
   314  
   315  		// Ignore errors
   316  		_ = json.Unmarshal([]byte(tt.input), &res)
   317  		require.Equal(tt.want, res)
   318  	}
   319  }
   320  
   321  func TestFormat(t *testing.T) {
   322  	t.Parallel()
   323  
   324  	require := require.New(t)
   325  
   326  	tests := []struct {
   327  		input  Money
   328  		format string // default format is '%v'
   329  		want   string
   330  	}{
   331  		{input: FromInt(357), want: "357.00"},
   332  		{input: FromInt(-357), want: "-357.00"},
   333  		{input: FromFloat(154.30), want: "154.30"},
   334  		{input: FromFloat(-154.30), want: "-154.30"},
   335  		{input: FromFloat(0.07), want: "0.07"},
   336  		{input: FromFloat(15.073), want: "15.07"},
   337  		{input: FromFloat(15.078), want: "15.07"},
   338  		// Check grouping
   339  		{input: FromInt(1_500), want: "1 500.00"},
   340  		{input: FromInt(-1_500), want: "-1 500.00"},
   341  		{input: FromInt(15_000), want: "15 000.00"},
   342  		{input: FromInt(-15_000), want: "-15 000.00"},
   343  		{input: FromInt(150_000), want: "150 000.00"},
   344  		{input: FromInt(-150_000), want: "-150 000.00"},
   345  		{input: FromFloat(1_500_000.05), want: "1 500 000.05"},
   346  		{input: FromFloat(-1_500_000.05), want: "-1 500 000.05"},
   347  		{input: FromFloat(15_000_000.05), want: "15 000 000.05"},
   348  		{input: FromFloat(-15_000_000.05), want: "-15 000 000.05"},
   349  		{input: FromFloat(150_000_000.05), want: "150 000 000.05"},
   350  		{input: FromFloat(-150_000_000.05), want: "-150 000 000.05"},
   351  		{input: FromFloat(1_500_000_000.05), want: "1 500 000 000.05"},
   352  		{input: FromFloat(-1_500_000_000.05), want: "-1 500 000 000.05"},
   353  		{input: FromFloat(15_000_000_000.05), want: "15 000 000 000.05"},
   354  		{input: FromFloat(-15_000_000_000.05), want: "-15 000 000 000.05"},
   355  		{input: FromFloat(150_000_000_000.00), want: "150 000 000 000.00"},
   356  		{input: FromFloat(-150_000_000_000.00), want: "-150 000 000 000.00"},
   357  		{input: FromInt(1_500_000_000_000), want: "1 500 000 000 000.00"},
   358  		{input: FromInt(-1_500_000_000_000), want: "-1 500 000 000 000.00"},
   359  		// Check formats
   360  		{input: FromFloat(0.05), format: "%d", want: "0"},
   361  		{input: FromFloat(-0.05), format: "%d", want: "0"},
   362  		{input: FromFloat(0.05), format: "%f", want: "0.05"},
   363  		{input: FromFloat(-0.05), format: "%f", want: "-0.05"},
   364  		{input: FromFloat(1_500.25), format: "%d", want: "1500"},
   365  		{input: FromFloat(-1_500.25), format: "%d", want: "-1500"},
   366  		{input: FromFloat(1_500.25), format: "%f", want: "1500.25"},
   367  		{input: FromFloat(-1_500.25), format: "%f", want: "-1500.25"},
   368  	}
   369  
   370  	for _, tt := range tests {
   371  		var s string
   372  		if tt.format == "" {
   373  			tt.format = "%v"
   374  		}
   375  
   376  		s = fmt.Sprintf(tt.format, tt.input)
   377  		require.Equal(tt.want, s)
   378  	}
   379  }