github.com/shoshinnikita/budget-manager@v0.7.1-0.20220131195411-8c46ff1c6778/internal/web/pages/statistics/cost_intervals_test.go (about) 1 package statistics 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/require" 7 8 "github.com/ShoshinNikita/budget-manager/internal/db" 9 "github.com/ShoshinNikita/budget-manager/internal/pkg/money" 10 ) 11 12 func TestCalculateCostIntervals(t *testing.T) { 13 t.Parallel() 14 15 m := money.FromInt 16 17 tests := []struct { 18 spends []db.Spend 19 intervals int 20 // 21 want []CostInterval 22 }{ 23 { 24 spends: []db.Spend{}, 25 intervals: 5, 26 // 27 want: nil, 28 }, 29 { 30 spends: []db.Spend{ 31 {Cost: m(20)}, 32 {Cost: m(30)}, 33 {Cost: m(35)}, 34 {Cost: m(17)}, 35 }, 36 intervals: 5, 37 // 38 want: []CostInterval{ 39 {From: m(17), To: m(21) - 1, Count: 2, Total: m(37)}, 40 {From: m(21), To: m(25) - 1, Count: 0, Total: 0}, 41 {From: m(25), To: m(29) - 1, Count: 0, Total: 0}, 42 {From: m(29), To: m(33) - 1, Count: 1, Total: m(30)}, 43 {From: m(33), To: m(35), Count: 1, Total: m(35)}, 44 }, 45 }, 46 // Too many intervals 47 { 48 spends: []db.Spend{ 49 {Cost: m(1)}, 50 {Cost: m(2)}, 51 {Cost: m(3)}, 52 {Cost: m(4)}, 53 }, 54 intervals: 10, 55 // 56 want: []CostInterval{ 57 {From: m(1), To: m(2) - 1, Count: 1, Total: m(1)}, 58 {From: m(2), To: m(3) - 1, Count: 1, Total: m(2)}, 59 {From: m(3), To: m(4), Count: 2, Total: m(7)}, 60 }, 61 }, 62 { 63 spends: []db.Spend{ 64 {Cost: m(20)}, 65 {Cost: m(30)}, 66 {Cost: m(35)}, 67 {Cost: m(17)}, 68 }, 69 intervals: 1, 70 // 71 want: []CostInterval{ 72 {From: m(17), To: m(35), Count: 4, Total: m(102)}, 73 }, 74 }, 75 { 76 spends: []db.Spend{ 77 {Cost: m(20)}, 78 {Cost: m(30)}, 79 {Cost: m(35)}, 80 {Cost: m(17)}, 81 }, 82 intervals: 10, 83 // 84 want: []CostInterval{ 85 {From: m(17), To: m(20) - 1, Count: 1, Total: m(17)}, 86 {From: m(20), To: m(23) - 1, Count: 1, Total: m(20)}, 87 {From: m(23), To: m(26) - 1, Count: 0, Total: 0}, 88 {From: m(26), To: m(29) - 1, Count: 0, Total: 0}, 89 {From: m(29), To: m(32) - 1, Count: 1, Total: m(30)}, 90 {From: m(32), To: m(35), Count: 1, Total: m(35)}, 91 }, 92 }, 93 } 94 for _, tt := range tests { 95 tt := tt 96 t.Run("", func(t *testing.T) { 97 got := CalculateCostIntervals(tt.spends, tt.intervals) 98 require.Equal(t, tt.want, got) 99 }) 100 } 101 } 102 103 func TestPrepareIntervals(t *testing.T) { 104 t.Parallel() 105 106 m := money.FromInt 107 108 tests := []struct { 109 costs []money.Money 110 intervals int 111 // 112 want []CostInterval 113 }{ 114 { 115 costs: []money.Money{m(1), m(2), m(3), m(4), m(5), m(6), m(7)}, 116 intervals: 2, 117 // 118 want: []CostInterval{ 119 {From: m(1), To: m(4) - 1}, 120 {From: m(4), To: m(7)}, 121 }, 122 }, 123 { 124 costs: []money.Money{m(1), m(2), m(3), m(4), m(5), m(6), m(7)}, 125 intervals: 3, 126 // 127 want: []CostInterval{ 128 {From: m(1), To: m(3) - 1}, 129 {From: m(3), To: m(5) - 1}, 130 {From: m(5), To: m(7)}, 131 }, 132 }, 133 { 134 costs: []money.Money{ 135 // p5 136 m(1), 137 // 138 m(2), m(3), m(4), m(5), m(6), m(7), m(8), m(9), m(10), m(11), m(12), m(13), m(14), m(15), m(16), 139 m(17), m(18), m(19), m(20), m(21), m(22), m(23), m(24), m(25), m(26), m(27), m(28), m(29), 140 // p95 141 m(30), 142 }, 143 intervals: 2, 144 // 145 want: []CostInterval{ 146 {From: m(2), To: m(16) - 1}, 147 {From: m(16), To: m(29)}, 148 }, 149 }, 150 { 151 costs: []money.Money{ 152 // p5 153 m(1), m(2), 154 // 155 m(3), m(4), m(5), m(6), m(7), m(8), m(9), m(10), m(11), m(12), m(13), m(14), m(15), m(16), m(17), 156 m(18), m(19), m(20), m(21), m(22), m(23), m(24), m(25), m(26), m(27), m(28), m(29), m(30), m(31), 157 m(32), m(33), m(34), m(35), m(36), m(37), m(38), m(39), m(40), m(41), m(42), m(43), m(44), m(45), 158 m(46), m(47), m(48), m(49), m(50), m(51), m(52), m(53), m(54), m(55), m(56), m(57), 159 // p95 160 m(58), m(59), m(60), 161 }, 162 intervals: 2, 163 // 164 want: []CostInterval{ 165 {From: m(3), To: m(30) - 1}, 166 {From: m(30), To: m(57)}, 167 }, 168 }, 169 } 170 for _, tt := range tests { 171 tt := tt 172 t.Run("", func(t *testing.T) { 173 got := prepareIntervals(tt.costs, tt.intervals) 174 require.Equal(t, tt.want, got) 175 }) 176 } 177 } 178 179 func TestGetPercentileValue(t *testing.T) { 180 t.Parallel() 181 182 type test struct { 183 p int 184 want money.Money 185 } 186 tests := []struct { 187 data []money.Money 188 checks []test 189 }{ 190 // Tests from https://en.wikipedia.org/wiki/Percentile#Worked_examples_of_the_nearest-rank_method 191 { 192 data: []money.Money{15, 20, 35, 40, 50}, 193 checks: []test{ 194 {p: 5, want: 15}, 195 {p: 30, want: 20}, 196 {p: 40, want: 20}, 197 {p: 50, want: 35}, 198 {p: 100, want: 50}, 199 }, 200 }, 201 { 202 data: []money.Money{3, 6, 7, 8, 8, 10, 13, 15, 16, 20}, 203 checks: []test{ 204 {p: 25, want: 7}, 205 {p: 50, want: 8}, 206 {p: 75, want: 15}, 207 {p: 100, want: 20}, 208 }, 209 }, 210 { 211 data: []money.Money{3, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20}, 212 checks: []test{ 213 {p: 25, want: 7}, 214 {p: 50, want: 9}, 215 {p: 75, want: 15}, 216 {p: 100, want: 20}, 217 }, 218 }, 219 // Edge cases 220 { 221 data: []money.Money{1, 2, 3, 4, 5}, 222 checks: []test{ 223 {p: -2, want: 1}, 224 {p: 0, want: 1}, 225 {p: 200, want: 5}, 226 }, 227 }, 228 } 229 for _, tt := range tests { 230 tt := tt 231 t.Run("", func(t *testing.T) { 232 for _, check := range tt.checks { 233 got := getPercentileValue(tt.data, check.p) 234 require.Equal(t, check.want, got) 235 } 236 }) 237 } 238 }