Coverage for tests/test_helpers.py: 100%
92 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-05 04:36 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-05 04:36 +0000
1import pandas as pd
2import pytest
4from sankey_cashflow import (
5 is_null,
6 is_empty,
7 validate_date_string,
8 normalize_amounts,
9 df_date_filter,
10)
13class TestIsNull:
15 @pytest.mark.parametrize('value', [None, float('nan'), pd.NaT, 'None', 'NaN', 'null'])
16 def test_null_values(self, value):
17 assert is_null(value) is True
19 @pytest.mark.parametrize('value', ['', 0, 'valid_string', 123, pd.Timestamp('2023-01-01')])
20 def test_non_null_values(self, value):
21 assert is_null(value) is False
24class TestIsEmpty:
26 @pytest.mark.parametrize('value', [None, float('nan'), pd.NaT, 'None', 'NaN', 'null', ''])
27 def test_empty_values(self, value):
28 assert is_empty(value) is True
30 @pytest.mark.parametrize('value', [0, 'valid_string', 123, pd.Timestamp('2023-01-01'), [], {}, (), set()])
31 def test_non_empty_values(self, value):
32 assert is_empty(value) is False
34 def test_zero_is_empty_when_nonzero_flag_set(self):
35 assert is_empty(0, nonzero=True) is True
36 assert is_empty(0.0, nonzero=True) is True
38 def test_nonzero_number_not_empty_with_flag(self):
39 assert is_empty(1, nonzero=True) is False
40 assert is_empty(0.25, nonzero=True) is False
42 def test_nonzero_flag_does_not_affect_non_numeric(self):
43 assert is_empty('valid_string', nonzero=True) is False
46class TestValidateDateString:
48 @pytest.mark.parametrize('value', ['2023-01-01', '1901-12-31', '2099-01-01'])
49 def test_valid_yyyy_mm_dd(self, value):
50 assert validate_date_string(value) is True
52 @pytest.mark.parametrize('value', ['01/01/2023', '12/31/1901', '01/01/2099'])
53 def test_valid_mm_dd_yyyy(self, value):
54 assert validate_date_string(value) is True
56 @pytest.mark.parametrize('value', ['1899-12-31', '2100-01-01', '12/31/1899', '01/01/2100'])
57 def test_out_of_range_year(self, value):
58 assert validate_date_string(value) is False
60 @pytest.mark.parametrize('value', ['2023-00-01', '2023-13-01', '00/01/2023', '13/01/2023'])
61 def test_invalid_month(self, value):
62 assert validate_date_string(value) is False
64 @pytest.mark.parametrize('value', ['2023-01-00', '2023-01-32', '01/00/2023', '01/32/2023'])
65 def test_invalid_day(self, value):
66 assert validate_date_string(value) is False
68 @pytest.mark.parametrize('value', ['2023/01/01', '01-01-2023', '2023.01.01', '01.01.2023', 'invalid_date'])
69 def test_unrecognized_format(self, value):
70 assert validate_date_string(value) is False
72 def test_allow_empty_true(self):
73 assert validate_date_string('', allow_empty=True) is True
74 assert validate_date_string(None, allow_empty=True) is True
76 def test_allow_empty_false_raises_on_empty_string(self):
77 with pytest.raises(Exception):
78 validate_date_string('', allow_empty=False)
80 def test_allow_empty_false_raises_on_none(self):
81 with pytest.raises(Exception):
82 validate_date_string(None, allow_empty=False)
85class TestNormalizeAmounts:
87 def test_dollar_and_comma_stripped(self):
88 row = pd.Series({'Amount': '$4,000.00', 'Sales Tax': '', 'Tips': ''})
89 result = normalize_amounts(row)
90 assert result['Amount'] == 4000.0
92 def test_plain_float_string_converted(self):
93 row = pd.Series({'Amount': '40.00', 'Sales Tax': '1.50', 'Tips': ''})
94 result = normalize_amounts(row)
95 assert result['Amount'] == 40.0
96 assert result['Sales Tax'] == 1.5
98 def test_already_numeric_left_alone(self):
99 row = pd.Series({'Amount': 40.0, 'Sales Tax': 1.5, 'Tips': 0.0})
100 result = normalize_amounts(row)
101 assert result['Amount'] == 40.0
103 def test_empty_values_untouched(self):
104 row = pd.Series({'Amount': '40.00', 'Sales Tax': '', 'Tips': None})
105 result = normalize_amounts(row)
106 assert result['Sales Tax'] == ''
107 assert result['Tips'] is None
110class TestDfDateFilter:
112 @pytest.fixture
113 def dates_df(self):
114 return pd.DataFrame({
115 'Date': pd.to_datetime(['2023-01-01', '2023-02-01', '2023-03-01']),
116 'Amount': [10.0, 20.0, 30.0],
117 })
119 def test_filters_inclusive_range(self, dates_df):
120 result = df_date_filter(dates_df, pd.to_datetime('2023-01-15'), pd.to_datetime('2023-02-15'))
121 assert len(result) == 1
122 assert result.iloc[0]['Amount'] == 20.0
124 def test_no_bounds_returns_unchanged(self, dates_df):
125 result = df_date_filter(dates_df, None, None)
126 assert len(result) == 3
128 def test_only_start_bound(self, dates_df):
129 result = df_date_filter(dates_df, pd.to_datetime('2023-02-01'), None)
130 assert len(result) == 2
132 def test_only_end_bound(self, dates_df):
133 result = df_date_filter(dates_df, None, pd.to_datetime('2023-02-01'))
134 assert len(result) == 2
136 def test_empty_result_raises(self, dates_df):
137 with pytest.raises(Exception):
138 df_date_filter(dates_df, pd.to_datetime('2024-01-01'), pd.to_datetime('2024-02-01'))