"""app.core.handlers.calculator 单元测试""" import pytest import pandas as pd import numpy as np from app.core.handlers.calculator import DataCalculator @pytest.fixture def sample_df(): return pd.DataFrame({ 'price': [10.0, 20.0, 30.0], 'quantity': [2, 5, 10], 'name': ['A', 'B', 'C'], }) class TestMultiply: def test_basic_multiply(self, sample_df): calc = DataCalculator() calc.add_rule('multiply', source_column='price', target_column='total', factor=2) result = calc.calculate(sample_df) assert list(result['total']) == [20.0, 40.0, 60.0] def test_multiply_missing_source(self, sample_df): calc = DataCalculator() calc.add_rule('multiply', source_column='nonexistent', target_column='total', factor=2) result = calc.calculate(sample_df) assert 'total' not in result.columns def test_multiply_default_factor(self, sample_df): calc = DataCalculator() calc.add_rule('multiply', source_column='price', target_column='copy', factor=1) result = calc.calculate(sample_df) assert list(result['copy']) == [10.0, 20.0, 30.0] def test_convenience_method(self, sample_df): calc = DataCalculator() calc.multiply('price', 'total', 3) result = calc.calculate(sample_df) assert list(result['total']) == [30.0, 60.0, 90.0] class TestDivide: def test_basic_divide(self, sample_df): calc = DataCalculator() calc.add_rule('divide', source_column='price', target_column='half', divisor=2) result = calc.calculate(sample_df) assert list(result['half']) == [5.0, 10.0, 15.0] def test_divide_by_zero(self, sample_df): calc = DataCalculator() calc.add_rule('divide', source_column='price', target_column='half', divisor=0) result = calc.calculate(sample_df) assert 'half' not in result.columns def test_divide_missing_source(self, sample_df): calc = DataCalculator() calc.add_rule('divide', source_column='nonexistent', target_column='x', divisor=2) result = calc.calculate(sample_df) assert 'x' not in result.columns class TestAdd: def test_add_columns(self): df = pd.DataFrame({'a': [1, 2, 3], 'b': [10, 20, 30]}) calc = DataCalculator() calc.add_rule('add', columns=['a', 'b'], target_column='sum') result = calc.calculate(df) assert list(result['sum']) == [11, 22, 33] def test_add_constant(self): df = pd.DataFrame({'a': [1, 2, 3]}) calc = DataCalculator() calc.add_rule('add', target_column='a', constant=100) result = calc.calculate(df) assert list(result['a']) == [101, 102, 103] def test_add_columns_with_constant(self): df = pd.DataFrame({'a': [1, 2], 'b': [3, 4]}) calc = DataCalculator() calc.add_rule('add', columns=['a', 'b'], target_column='total', constant=10) result = calc.calculate(df) assert list(result['total']) == [14, 16] def test_add_string_column(self): df = pd.DataFrame({'a': [1, 2]}) calc = DataCalculator() calc.add_rule('add', columns='a', target_column='total') result = calc.calculate(df) assert list(result['total']) == [1, 2] class TestSubtract: def test_subtract_two_columns(self): df = pd.DataFrame({'income': [100, 200], 'cost': [30, 80]}) calc = DataCalculator() calc.add_rule('subtract', minuend='income', subtrahend='cost', target_column='profit') result = calc.calculate(df) assert list(result['profit']) == [70, 120] def test_subtract_constant(self): df = pd.DataFrame({'price': [100, 200]}) calc = DataCalculator() calc.add_rule('subtract', minuend='price', target_column='discounted', constant=10) result = calc.calculate(df) assert list(result['discounted']) == [90, 190] def test_subtract_missing_minuend(self): df = pd.DataFrame({'a': [1, 2]}) calc = DataCalculator() calc.add_rule('subtract', minuend='nonexistent', target_column='x', constant=1) result = calc.calculate(df) assert 'x' not in result.columns class TestFormula: def test_basic_formula(self, sample_df): calc = DataCalculator() calc.add_rule('formula', formula='price * quantity', target_column='total') result = calc.calculate(sample_df) assert list(result['total']) == [20.0, 100.0, 300.0] def test_invalid_formula(self, sample_df): calc = DataCalculator() calc.add_rule('formula', formula='nonexistent + 1', target_column='x') result = calc.calculate(sample_df) # formula fails, original df returned assert 'x' not in result.columns def test_formula_missing_target(self, sample_df): calc = DataCalculator() calc.add_rule('formula', formula='price * 2') result = calc.calculate(sample_df) # no target_column, nothing happens assert list(result['price']) == [10.0, 20.0, 30.0] class TestRound: def test_round_specific_columns(self): df = pd.DataFrame({'a': [1.234, 2.567], 'b': [3.1, 4.9]}) calc = DataCalculator() calc.add_rule('round', columns=['a'], decimals=1) result = calc.calculate(df) assert list(result['a']) == [1.2, 2.6] assert list(result['b']) == [3.1, 4.9] # unchanged def test_round_all_numeric(self): df = pd.DataFrame({'a': [1.234, 2.567], 'b': [3.111, 4.999]}) calc = DataCalculator() calc.add_rule('round', decimals=0) result = calc.calculate(df) assert list(result['a']) == [1.0, 3.0] assert list(result['b']) == [3.0, 5.0] def test_round_string_column_skipped(self): df = pd.DataFrame({'name': ['a', 'b'], 'val': [1.5, 2.5]}) calc = DataCalculator() calc.add_rule('round', columns=['name', 'val'], decimals=0) result = calc.calculate(df) assert list(result['val']) == [2.0, 2.0] class TestSum: def test_sum_columns_to_target(self): df = pd.DataFrame({'a': [1, 2], 'b': [3, 4], 'c': [5, 6]}) calc = DataCalculator() calc.add_rule('sum', columns=['a', 'b'], target_column='total') result = calc.calculate(df) assert list(result['total']) == [4, 6] def test_sum_missing_columns(self): df = pd.DataFrame({'a': [1, 2]}) calc = DataCalculator() calc.add_rule('sum', columns=['a', 'missing'], target_column='total') result = calc.calculate(df) assert list(result['total']) == [1, 2] class TestChaining: def test_multiple_rules(self, sample_df): calc = DataCalculator() calc.add_rule('multiply', source_column='price', target_column='total', factor=2) calc.add_rule('add', columns=['total', 'quantity'], target_column='grand') result = calc.calculate(sample_df) assert list(result['total']) == [20.0, 40.0, 60.0] assert list(result['grand']) == [22.0, 45.0, 70.0] def test_chaining_convenience(self, sample_df): calc = DataCalculator() calc.multiply('price', 'total', 2).round_columns('total', 0) result = calc.calculate(sample_df) assert list(result['total']) == [20.0, 40.0, 60.0] class TestEdgeCases: def test_empty_dataframe(self): df = pd.DataFrame({'a': pd.Series([], dtype=float)}) calc = DataCalculator() calc.add_rule('multiply', source_column='a', target_column='b', factor=2) result = calc.calculate(df) assert len(result) == 0 def test_no_rules(self, sample_df): calc = DataCalculator() result = calc.calculate(sample_df) assert list(result['price']) == [10.0, 20.0, 30.0] def test_unknown_rule_type(self, sample_df): calc = DataCalculator() calc.add_rule('unknown_op', source_column='price', target_column='x') result = calc.calculate(sample_df) # unknown rule is skipped, df unchanged assert list(result['price']) == [10.0, 20.0, 30.0] def test_rule_failure_continues(self, sample_df): calc = DataCalculator() calc.add_rule('formula', formula='nonexistent + 1', target_column='x') calc.add_rule('multiply', source_column='price', target_column='y', factor=2) result = calc.calculate(sample_df) assert list(result['y']) == [20.0, 40.0, 60.0]