package types import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // ---------------------------------------------------------------------------- // Coin tests func TestCoin(t *testing.T) { require.Panics(t, func() { NewInt64Coin("a", -1) }) require.Panics(t, func() { NewCoin("a", NewInt(-1)) }) require.Panics(t, func() { NewInt64Coin("Atom", 10) }) require.Panics(t, func() { NewCoin("Atom", NewInt(10)) }) require.Equal(t, NewInt(5), NewInt64Coin("a", 5).Amount) require.Equal(t, NewInt(5), NewCoin("a", NewInt(5)).Amount) } func TestSameDenomAsCoin(t *testing.T) { cases := []struct { inputOne Coin inputTwo Coin expected bool }{ {NewInt64Coin("a", 1), NewInt64Coin("a", 1), true}, {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, {NewInt64Coin("steak", 1), NewInt64Coin("steak", 10), true}, } for tcIndex, tc := range cases { res := tc.inputOne.SameDenomAs(tc.inputTwo) require.Equal(t, tc.expected, res, "coin denominations didn't match, tc #%d", tcIndex) } } func TestIsEqualCoin(t *testing.T) { cases := []struct { inputOne Coin inputTwo Coin expected bool }{ {NewInt64Coin("a", 1), NewInt64Coin("a", 1), true}, {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, {NewInt64Coin("steak", 1), NewInt64Coin("steak", 10), false}, } for tcIndex, tc := range cases { res := tc.inputOne.IsEqual(tc.inputTwo) require.Equal(t, tc.expected, res, "coin equality relation is incorrect, tc #%d", tcIndex) } } func TestPlusCoin(t *testing.T) { cases := []struct { inputOne Coin inputTwo Coin expected Coin shouldPanic bool }{ {NewInt64Coin("a", 1), NewInt64Coin("a", 1), NewInt64Coin("a", 2), false}, {NewInt64Coin("a", 1), NewInt64Coin("a", 0), NewInt64Coin("a", 1), false}, {NewInt64Coin("a", 1), NewInt64Coin("b", 1), NewInt64Coin("a", 1), true}, } for tcIndex, tc := range cases { if tc.shouldPanic { require.Panics(t, func() { tc.inputOne.Plus(tc.inputTwo) }) } else { res := tc.inputOne.Plus(tc.inputTwo) require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex) } } } func TestMinusCoin(t *testing.T) { cases := []struct { inputOne Coin inputTwo Coin expected Coin shouldPanic bool }{ {NewInt64Coin("a", 1), NewInt64Coin("b", 1), NewInt64Coin("a", 1), true}, {NewInt64Coin("a", 10), NewInt64Coin("a", 1), NewInt64Coin("a", 9), false}, {NewInt64Coin("a", 5), NewInt64Coin("a", 3), NewInt64Coin("a", 2), false}, {NewInt64Coin("a", 5), NewInt64Coin("a", 0), NewInt64Coin("a", 5), false}, {NewInt64Coin("a", 1), NewInt64Coin("a", 5), Coin{}, true}, } for tcIndex, tc := range cases { if tc.shouldPanic { require.Panics(t, func() { tc.inputOne.Minus(tc.inputTwo) }) } else { res := tc.inputOne.Minus(tc.inputTwo) require.Equal(t, tc.expected, res, "difference of coins is incorrect, tc #%d", tcIndex) } } tc := struct { inputOne Coin inputTwo Coin expected int64 }{NewInt64Coin("a", 1), NewInt64Coin("a", 1), 0} res := tc.inputOne.Minus(tc.inputTwo) require.Equal(t, tc.expected, res.Amount.Int64()) } func TestIsGTECoin(t *testing.T) { cases := []struct { inputOne Coin inputTwo Coin expected bool }{ {NewInt64Coin("a", 1), NewInt64Coin("a", 1), true}, {NewInt64Coin("a", 2), NewInt64Coin("a", 1), true}, {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, } for tcIndex, tc := range cases { res := tc.inputOne.IsGTE(tc.inputTwo) require.Equal(t, tc.expected, res, "coin GTE relation is incorrect, tc #%d", tcIndex) } } func TestIsLTCoin(t *testing.T) { cases := []struct { inputOne Coin inputTwo Coin expected bool }{ {NewInt64Coin("a", 1), NewInt64Coin("a", 1), false}, {NewInt64Coin("a", 2), NewInt64Coin("a", 1), false}, {NewInt64Coin("a", 0), NewInt64Coin("b", 1), false}, {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, {NewInt64Coin("a", 1), NewInt64Coin("a", 1), false}, {NewInt64Coin("a", 1), NewInt64Coin("a", 2), true}, } for tcIndex, tc := range cases { res := tc.inputOne.IsLT(tc.inputTwo) require.Equal(t, tc.expected, res, "coin LT relation is incorrect, tc #%d", tcIndex) } } func TestCoinIsZero(t *testing.T) { coin := NewInt64Coin("a", 0) res := coin.IsZero() require.True(t, res) coin = NewInt64Coin("a", 1) res = coin.IsZero() require.False(t, res) } // ---------------------------------------------------------------------------- // Coins tests func TestIsZeroCoins(t *testing.T) { cases := []struct { inputOne Coins expected bool }{ {Coins{}, true}, {Coins{NewInt64Coin("a", 0)}, true}, {Coins{NewInt64Coin("a", 0), NewInt64Coin("b", 0)}, true}, {Coins{NewInt64Coin("a", 1)}, false}, {Coins{NewInt64Coin("a", 0), NewInt64Coin("b", 1)}, false}, } for _, tc := range cases { res := tc.inputOne.IsZero() require.Equal(t, tc.expected, res) } } func TestEqualCoins(t *testing.T) { cases := []struct { inputOne Coins inputTwo Coins expected bool }{ {Coins{}, Coins{}, true}, {Coins{NewInt64Coin("a", 0)}, Coins{NewInt64Coin("a", 0)}, true}, {Coins{NewInt64Coin("a", 0), NewInt64Coin("b", 1)}, Coins{NewInt64Coin("a", 0), NewInt64Coin("b", 1)}, true}, {Coins{NewInt64Coin("a", 0)}, Coins{NewInt64Coin("b", 0)}, false}, {Coins{NewInt64Coin("a", 0)}, Coins{NewInt64Coin("a", 1)}, false}, {Coins{NewInt64Coin("a", 0)}, Coins{NewInt64Coin("a", 0), NewInt64Coin("b", 1)}, false}, {Coins{NewInt64Coin("a", 0), NewInt64Coin("b", 1)}, Coins{NewInt64Coin("b", 1), NewInt64Coin("a", 0)}, true}, } for tcnum, tc := range cases { res := tc.inputOne.IsEqual(tc.inputTwo) require.Equal(t, tc.expected, res, "Equality is differed from expected. tc #%d, expected %b, actual %b.", tcnum, tc.expected, res) } } func TestPlusCoins(t *testing.T) { zero := NewInt(0) one := NewInt(1) two := NewInt(2) cases := []struct { inputOne Coins inputTwo Coins expected Coins }{ {Coins{{"a", one}, {"b", one}}, Coins{{"a", one}, {"b", one}}, Coins{{"a", two}, {"b", two}}}, {Coins{{"a", zero}, {"b", one}}, Coins{{"a", zero}, {"b", zero}}, Coins{{"b", one}}}, {Coins{{"a", two}}, Coins{{"b", zero}}, Coins{{"a", two}}}, {Coins{{"a", one}}, Coins{{"a", one}, {"b", two}}, Coins{{"a", two}, {"b", two}}}, {Coins{{"a", zero}, {"b", zero}}, Coins{{"a", zero}, {"b", zero}}, Coins(nil)}, } for tcIndex, tc := range cases { res := tc.inputOne.Plus(tc.inputTwo) assert.True(t, res.IsValid()) require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex) } } func TestMinusCoins(t *testing.T) { zero := NewInt(0) one := NewInt(1) two := NewInt(2) testCases := []struct { inputOne Coins inputTwo Coins expected Coins shouldPanic bool }{ {Coins{{"a", two}}, Coins{{"a", one}, {"b", two}}, Coins{{"a", one}, {"b", two}}, true}, {Coins{{"a", two}}, Coins{{"b", zero}}, Coins{{"a", two}}, false}, {Coins{{"a", one}}, Coins{{"b", zero}}, Coins{{"a", one}}, false}, {Coins{{"a", one}, {"b", one}}, Coins{{"a", one}}, Coins{{"b", one}}, false}, {Coins{{"a", one}, {"b", one}}, Coins{{"a", two}}, Coins{}, true}, } for i, tc := range testCases { if tc.shouldPanic { require.Panics(t, func() { tc.inputOne.Minus(tc.inputTwo) }) } else { res := tc.inputOne.Minus(tc.inputTwo) assert.True(t, res.IsValid()) require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", i) } } } func TestCoins(t *testing.T) { good := Coins{ {"gas", NewInt(1)}, {"mineral", NewInt(1)}, {"tree", NewInt(1)}, } mixedCase := Coins{ {"gAs", NewInt(1)}, {"MineraL", NewInt(1)}, {"TREE", NewInt(1)}, } empty := Coins{ {"gold", NewInt(0)}, } null := Coins{} badSort1 := Coins{ {"tree", NewInt(1)}, {"gas", NewInt(1)}, {"mineral", NewInt(1)}, } // both are after the first one, but the second and third are in the wrong order badSort2 := Coins{ {"gas", NewInt(1)}, {"tree", NewInt(1)}, {"mineral", NewInt(1)}, } badAmt := Coins{ {"gas", NewInt(1)}, {"tree", NewInt(0)}, {"mineral", NewInt(1)}, } dup := Coins{ {"gas", NewInt(1)}, {"gas", NewInt(1)}, {"mineral", NewInt(1)}, } neg := Coins{ {"gas", NewInt(-1)}, {"mineral", NewInt(1)}, } assert.True(t, good.IsValid(), "Coins are valid") assert.False(t, mixedCase.IsValid(), "Coins denoms contain upper case characters") assert.True(t, good.IsPositive(), "Expected coins to be positive: %v", good) assert.False(t, null.IsPositive(), "Expected coins to not be positive: %v", null) assert.True(t, good.IsAllGTE(empty), "Expected %v to be >= %v", good, empty) assert.False(t, good.IsAllLT(empty), "Expected %v to be < %v", good, empty) assert.True(t, empty.IsAllLT(good), "Expected %v to be < %v", empty, good) assert.False(t, badSort1.IsValid(), "Coins are not sorted") assert.False(t, badSort2.IsValid(), "Coins are not sorted") assert.False(t, badAmt.IsValid(), "Coins cannot include 0 amounts") assert.False(t, dup.IsValid(), "Duplicate coin") assert.False(t, neg.IsValid(), "Negative first-denom coin") } func TestCoinsGT(t *testing.T) { one := NewInt(1) two := NewInt(2) assert.False(t, Coins{}.IsAllGT(Coins{})) assert.True(t, Coins{{"a", one}}.IsAllGT(Coins{})) assert.False(t, Coins{{"a", one}}.IsAllGT(Coins{{"a", one}})) assert.False(t, Coins{{"a", one}}.IsAllGT(Coins{{"b", one}})) assert.True(t, Coins{{"a", one}, {"b", one}}.IsAllGT(Coins{{"b", one}})) assert.False(t, Coins{{"a", one}, {"b", one}}.IsAllGT(Coins{{"b", two}})) } func TestCoinsGTE(t *testing.T) { one := NewInt(1) two := NewInt(2) assert.True(t, Coins{}.IsAllGTE(Coins{})) assert.True(t, Coins{{"a", one}}.IsAllGTE(Coins{})) assert.True(t, Coins{{"a", one}}.IsAllGTE(Coins{{"a", one}})) assert.False(t, Coins{{"a", one}}.IsAllGTE(Coins{{"b", one}})) assert.True(t, Coins{{"a", one}, {"b", one}}.IsAllGTE(Coins{{"b", one}})) assert.False(t, Coins{{"a", one}, {"b", one}}.IsAllGTE(Coins{{"b", two}})) } func TestCoinsLT(t *testing.T) { one := NewInt(1) two := NewInt(2) assert.False(t, Coins{}.IsAllLT(Coins{})) assert.False(t, Coins{{"a", one}}.IsAllLT(Coins{})) assert.False(t, Coins{{"a", one}}.IsAllLT(Coins{{"a", one}})) assert.False(t, Coins{{"a", one}}.IsAllLT(Coins{{"b", one}})) assert.False(t, Coins{{"a", one}, {"b", one}}.IsAllLT(Coins{{"b", one}})) assert.False(t, Coins{{"a", one}, {"b", one}}.IsAllLT(Coins{{"b", two}})) assert.False(t, Coins{{"a", one}, {"b", one}}.IsAllLT(Coins{{"a", one}, {"b", one}})) assert.True(t, Coins{{"a", one}, {"b", one}}.IsAllLT(Coins{{"a", one}, {"b", two}})) assert.True(t, Coins{}.IsAllLT(Coins{{"a", one}})) } func TestCoinsLTE(t *testing.T) { one := NewInt(1) two := NewInt(2) assert.True(t, Coins{}.IsAllLTE(Coins{})) assert.False(t, Coins{{"a", one}}.IsAllLTE(Coins{})) assert.True(t, Coins{{"a", one}}.IsAllLTE(Coins{{"a", one}})) assert.False(t, Coins{{"a", one}}.IsAllLTE(Coins{{"b", one}})) assert.False(t, Coins{{"a", one}, {"b", one}}.IsAllLTE(Coins{{"b", one}})) assert.False(t, Coins{{"a", one}, {"b", one}}.IsAllLTE(Coins{{"b", two}})) assert.True(t, Coins{{"a", one}, {"b", one}}.IsAllLTE(Coins{{"a", one}, {"b", one}})) assert.True(t, Coins{{"a", one}, {"b", one}}.IsAllLTE(Coins{{"a", one}, {"b", two}})) assert.True(t, Coins{}.IsAllLTE(Coins{{"a", one}})) } func TestParse(t *testing.T) { one := NewInt(1) cases := []struct { input string valid bool // if false, we expect an error on parse expected Coins // if valid is true, make sure this is returned }{ {"", true, nil}, {"1foo", true, Coins{{"foo", one}}}, {"10bar", true, Coins{{"bar", NewInt(10)}}}, {"99bar,1foo", true, Coins{{"bar", NewInt(99)}, {"foo", one}}}, {"98 bar , 1 foo ", true, Coins{{"bar", NewInt(98)}, {"foo", one}}}, {" 55\t \t bling\n", true, Coins{{"bling", NewInt(55)}}}, {"2foo, 97 bar", true, Coins{{"bar", NewInt(97)}, {"foo", NewInt(2)}}}, {"5 mycoin,", false, nil}, // no empty coins in a list {"2 3foo, 97 bar", false, nil}, // 3foo is invalid coin name {"11me coin, 12you coin", false, nil}, // no spaces in coin names {"1.2btc", false, nil}, // amount must be integer {"5foo-bar", false, nil}, // once more, only letters in coin name } for tcIndex, tc := range cases { res, err := ParseCoins(tc.input) if !tc.valid { require.NotNil(t, err, "%s: %#v. tc #%d", tc.input, res, tcIndex) } else if assert.Nil(t, err, "%s: %+v", tc.input, err) { require.Equal(t, tc.expected, res, "coin parsing was incorrect, tc #%d", tcIndex) } } } func TestSortCoins(t *testing.T) { good := Coins{ NewInt64Coin("gas", 1), NewInt64Coin("mineral", 1), NewInt64Coin("tree", 1), } empty := Coins{ NewInt64Coin("gold", 0), } badSort1 := Coins{ NewInt64Coin("tree", 1), NewInt64Coin("gas", 1), NewInt64Coin("mineral", 1), } badSort2 := Coins{ // both are after the first one, but the second and third are in the wrong order NewInt64Coin("gas", 1), NewInt64Coin("tree", 1), NewInt64Coin("mineral", 1), } badAmt := Coins{ NewInt64Coin("gas", 1), NewInt64Coin("tree", 0), NewInt64Coin("mineral", 1), } dup := Coins{ NewInt64Coin("gas", 1), NewInt64Coin("gas", 1), NewInt64Coin("mineral", 1), } cases := []struct { coins Coins before, after bool // valid before/after sort }{ {good, true, true}, {empty, false, false}, {badSort1, false, true}, {badSort2, false, true}, {badAmt, false, false}, {dup, false, false}, } for tcIndex, tc := range cases { require.Equal(t, tc.before, tc.coins.IsValid(), "coin validity is incorrect before sorting, tc #%d", tcIndex) tc.coins.Sort() require.Equal(t, tc.after, tc.coins.IsValid(), "coin validity is incorrect after sorting, tc #%d", tcIndex) } } func TestAmountOf(t *testing.T) { case0 := Coins{} case1 := Coins{ NewInt64Coin("", 0), } case2 := Coins{ NewInt64Coin(" ", 0), } case3 := Coins{ NewInt64Coin("gold", 0), } case4 := Coins{ NewInt64Coin("gas", 1), NewInt64Coin("mineral", 1), NewInt64Coin("tree", 1), } case5 := Coins{ NewInt64Coin("mineral", 1), NewInt64Coin("tree", 1), } case6 := Coins{ NewInt64Coin("", 6), } case7 := Coins{ NewInt64Coin(" ", 7), } case8 := Coins{ NewInt64Coin("gas", 8), } cases := []struct { coins Coins amountOf int64 amountOfSpace int64 amountOfGAS int64 amountOfMINERAL int64 amountOfTREE int64 }{ {case0, 0, 0, 0, 0, 0}, {case1, 0, 0, 0, 0, 0}, {case2, 0, 0, 0, 0, 0}, {case3, 0, 0, 0, 0, 0}, {case4, 0, 0, 1, 1, 1}, {case5, 0, 0, 0, 1, 1}, {case6, 6, 0, 0, 0, 0}, {case7, 0, 7, 0, 0, 0}, {case8, 0, 0, 8, 0, 0}, } for _, tc := range cases { assert.Equal(t, NewInt(tc.amountOf), tc.coins.AmountOf("")) assert.Equal(t, NewInt(tc.amountOfSpace), tc.coins.AmountOf(" ")) assert.Equal(t, NewInt(tc.amountOfGAS), tc.coins.AmountOf("gas")) assert.Equal(t, NewInt(tc.amountOfMINERAL), tc.coins.AmountOf("mineral")) assert.Equal(t, NewInt(tc.amountOfTREE), tc.coins.AmountOf("tree")) } assert.Panics(t, func() { cases[0].coins.AmountOf("Invalid") }) }