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  }