Coverage for tests/test_data_row.py: 100%

65 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-05 04:36 +0000

1import pandas as pd 

2import pytest 

3 

4from sankey_cashflow import DataRow 

5 

6 

7@pytest.fixture 

8def valid_row(): 

9 return [ 

10 pd.to_datetime('2023-01-01'), 

11 'Groceries', 

12 'Grocery Store', 

13 'food,essentials', 

14 'Monthly groceries', 

15 'Income', 

16 'Groceries', 

17 'computed', 

18 0, 

19 100.0, 

20 5.0, 

21 2.0, 

22 'Expense' 

23 ] 

24 

25 

26class TestValidate: 

27 

28 def test_validate_valid_data(self, valid_row): 

29 assert DataRow.validate(valid_row, include_classifications=True) == valid_row 

30 

31 def test_validate_invalid_amount_raises(self, valid_row): 

32 invalid_row = list(valid_row) 

33 invalid_row[9] = 'invalid_amount' 

34 with pytest.raises(Exception): 

35 DataRow.validate(invalid_row, include_classifications=True) 

36 

37 def test_validate_non_nullable_field_raises(self, valid_row): 

38 invalid_row = list(valid_row) 

39 invalid_row[0] = None # Date is required, non-nullable 

40 with pytest.raises(Exception): 

41 DataRow.validate(invalid_row, include_classifications=True) 

42 

43 def test_validate_wrong_column_count_raises(self, valid_row): 

44 with pytest.raises(Exception): 

45 DataRow.validate(valid_row[:-1], include_classifications=True) 

46 

47 def test_validate_disallowed_type_value_raises(self, valid_row): 

48 invalid_row = list(valid_row) 

49 invalid_row[7] = 'not_a_real_type' 

50 with pytest.raises(Exception): 

51 DataRow.validate(invalid_row, include_classifications=True) 

52 

53 def test_validate_header_only_correct_order(self): 

54 header = ['Date', 'Category', 'Description', 'Tags', 'Comments', 'Source', 'Target', 

55 'Type', 'Distribution', 'Amount', 'Sales Tax', 'Tips'] 

56 result, error = DataRow.validate(header, header_only=True) 

57 assert result is True 

58 assert error is None 

59 

60 def test_validate_header_only_wrong_order(self): 

61 header = ['Category', 'Date', 'Description', 'Tags', 'Comments', 'Source', 'Target', 

62 'Type', 'Distribution', 'Amount', 'Sales Tax', 'Tips'] 

63 result, error = DataRow.validate(header, header_only=True) 

64 assert result is False 

65 assert error is not None 

66 

67 

68class TestCreate: 

69 

70 def test_create_matches_manually_built_row(self, valid_row): 

71 created = DataRow.create( 

72 date=pd.to_datetime('2023-01-01'), 

73 category_name='Groceries', 

74 source='Income', 

75 target='Groceries', 

76 amount=100.0, 

77 description='Grocery Store', 

78 sales_tax=5.0, 

79 tips=2.0, 

80 comment='Monthly groceries', 

81 tags='food,essentials', 

82 row_type='computed', 

83 distribution=0, 

84 classification='Expense' 

85 ) 

86 assert created == valid_row 

87 

88 def test_create_coerces_string_amount(self): 

89 created = DataRow.create( 

90 date=pd.to_datetime('2023-01-01'), category_name='Groceries', 

91 source='Income', target='Groceries', amount='100.0' 

92 ) 

93 assert created[9] == 100.0 

94 

95 def test_create_unparseable_amount_defaults_to_zero(self): 

96 created = DataRow.create( 

97 date=pd.to_datetime('2023-01-01'), category_name='Groceries', 

98 source='Income', target='Groceries', amount='not_a_number' 

99 ) 

100 assert created[9] == 0 

101 

102 def test_create_null_tips_defaults_to_zero(self): 

103 created = DataRow.create( 

104 date=pd.to_datetime('2023-01-01'), category_name='Groceries', 

105 source='Income', target='Groceries', amount=100.0, tips=None 

106 ) 

107 assert created[11] == 0 

108 

109 def test_create_null_distribution_defaults_to_zero(self): 

110 created = DataRow.create( 

111 date=pd.to_datetime('2023-01-01'), category_name='Groceries', 

112 source='Income', target='Groceries', amount=100.0, distribution=None 

113 ) 

114 assert created[8] == 0 

115 

116 def test_create_null_sales_tax_defaults_to_zero(self): 

117 created = DataRow.create( 

118 date=pd.to_datetime('2023-01-01'), category_name='Groceries', 

119 source='Income', target='Groceries', amount=100.0, sales_tax=None 

120 ) 

121 assert created[10] == 0 

122 

123 

124class TestTagMatches: 

125 

126 def test_returns_matching_tags(self): 

127 assert DataRow.tag_matches('food,essentials', ['food', 'luxury']) == ['food'] 

128 

129 def test_no_matches_returns_empty_list(self): 

130 assert DataRow.tag_matches('food,essentials', ['luxury']) == [] 

131 

132 def test_no_search_tags_returns_none(self): 

133 assert DataRow.tag_matches('food,essentials', None) is None 

134 

135 def test_no_row_tags_returns_none(self): 

136 assert DataRow.tag_matches(None, ['food']) is None