| """ |
| Tests for calculator tool (safe mathematical evaluation) |
| Author: @mangubee |
| Date: 2026-01-02 |
| |
| Tests cover: |
| - Basic arithmetic operations |
| - Mathematical functions |
| - Safety checks (no code execution, no imports, etc.) |
| - Timeout protection |
| - Complexity limits |
| - Error handling |
| """ |
|
|
| import pytest |
| from src.tools.calculator import safe_eval |
|
|
|
|
| |
| |
| |
|
|
| def test_addition(): |
| """Test basic addition""" |
| result = safe_eval("2 + 3") |
| assert result["result"] == 5 |
| assert result["success"] is True |
|
|
|
|
| def test_subtraction(): |
| """Test basic subtraction""" |
| result = safe_eval("10 - 4") |
| assert result["result"] == 6 |
|
|
|
|
| def test_multiplication(): |
| """Test basic multiplication""" |
| result = safe_eval("6 * 7") |
| assert result["result"] == 42 |
|
|
|
|
| def test_division(): |
| """Test basic division""" |
| result = safe_eval("15 / 3") |
| assert result["result"] == 5.0 |
|
|
|
|
| def test_floor_division(): |
| """Test floor division""" |
| result = safe_eval("17 // 5") |
| assert result["result"] == 3 |
|
|
|
|
| def test_modulo(): |
| """Test modulo operation""" |
| result = safe_eval("17 % 5") |
| assert result["result"] == 2 |
|
|
|
|
| def test_exponentiation(): |
| """Test exponentiation""" |
| result = safe_eval("2 ** 8") |
| assert result["result"] == 256 |
|
|
|
|
| def test_negative_numbers(): |
| """Test negative numbers""" |
| result = safe_eval("-5 + 3") |
| assert result["result"] == -2 |
|
|
|
|
| def test_complex_expression(): |
| """Test complex arithmetic expression""" |
| result = safe_eval("(2 + 3) * 4 - 10 / 2") |
| assert result["result"] == 15.0 |
|
|
|
|
| |
| |
| |
|
|
| def test_sqrt(): |
| """Test square root function""" |
| result = safe_eval("sqrt(16)") |
| assert result["result"] == 4.0 |
|
|
|
|
| def test_abs(): |
| """Test absolute value""" |
| result = safe_eval("abs(-42)") |
| assert result["result"] == 42 |
|
|
|
|
| def test_round(): |
| """Test rounding""" |
| result = safe_eval("round(3.7)") |
| assert result["result"] == 4 |
|
|
|
|
| def test_min(): |
| """Test min function""" |
| result = safe_eval("min(5, 2, 8, 1)") |
| assert result["result"] == 1 |
|
|
|
|
| def test_max(): |
| """Test max function""" |
| result = safe_eval("max(5, 2, 8, 1)") |
| assert result["result"] == 8 |
|
|
|
|
| def test_trigonometric(): |
| """Test trigonometric functions""" |
| result = safe_eval("sin(0)") |
| assert result["result"] == 0.0 |
|
|
| result = safe_eval("cos(0)") |
| assert result["result"] == 1.0 |
|
|
|
|
| def test_logarithm(): |
| """Test logarithmic functions""" |
| result = safe_eval("log10(100)") |
| assert result["result"] == 2.0 |
|
|
|
|
| def test_constants(): |
| """Test mathematical constants""" |
| result = safe_eval("pi") |
| assert abs(result["result"] - 3.14159) < 0.001 |
|
|
| result = safe_eval("e") |
| assert abs(result["result"] - 2.71828) < 0.001 |
|
|
|
|
| def test_factorial(): |
| """Test factorial function""" |
| result = safe_eval("factorial(5)") |
| assert result["result"] == 120 |
|
|
|
|
| def test_nested_functions(): |
| """Test nested function calls""" |
| result = safe_eval("sqrt(abs(-16))") |
| assert result["result"] == 4.0 |
|
|
|
|
| |
| |
| |
|
|
| def test_no_import(): |
| """Test that imports are blocked""" |
| with pytest.raises(SyntaxError): |
| safe_eval("import os") |
|
|
|
|
| def test_no_exec(): |
| """Test that exec is blocked""" |
| with pytest.raises((ValueError, SyntaxError)): |
| safe_eval("exec('print(1)')") |
|
|
|
|
| def test_no_eval(): |
| """Test that eval is blocked""" |
| with pytest.raises((ValueError, SyntaxError)): |
| safe_eval("eval('1+1')") |
|
|
|
|
| def test_no_lambda(): |
| """Test that lambda is blocked""" |
| with pytest.raises((ValueError, SyntaxError)): |
| safe_eval("lambda x: x + 1") |
|
|
|
|
| def test_no_attribute_access(): |
| """Test that attribute access is blocked""" |
| with pytest.raises(ValueError): |
| safe_eval("(1).__class__") |
|
|
|
|
| def test_no_list_comprehension(): |
| """Test that list comprehensions are blocked""" |
| with pytest.raises(ValueError): |
| safe_eval("[x for x in range(10)]") |
|
|
|
|
| def test_no_dict_access(): |
| """Test that dict operations are blocked""" |
| with pytest.raises((ValueError, SyntaxError)): |
| safe_eval("{'a': 1}") |
|
|
|
|
| def test_no_undefined_names(): |
| """Test that undefined variable names are blocked""" |
| with pytest.raises(ValueError, match="Undefined name"): |
| safe_eval("undefined_variable + 1") |
|
|
|
|
| def test_no_dangerous_functions(): |
| """Test that dangerous functions are blocked""" |
| with pytest.raises(ValueError, match="Unsupported function"): |
| safe_eval("open('file.txt')") |
|
|
|
|
| |
| |
| |
|
|
| def test_division_by_zero(): |
| """Test division by zero raises error""" |
| with pytest.raises(ZeroDivisionError): |
| safe_eval("10 / 0") |
|
|
|
|
| def test_invalid_syntax(): |
| """Test invalid syntax raises error""" |
| with pytest.raises(SyntaxError): |
| safe_eval("2 +* 3") |
|
|
|
|
| def test_empty_expression(): |
| """Test empty expression returns graceful error dict""" |
| result = safe_eval("") |
| assert result["success"] is False |
| assert "Empty expression" in result["error"] |
| assert result["result"] is None |
|
|
|
|
| def test_too_long_expression(): |
| """Test expression length limit returns graceful error dict""" |
| long_expr = "1 + " * 300 + "1" |
| result = safe_eval(long_expr) |
| assert result["success"] is False |
| assert "too long" in result["error"] |
| assert result["result"] is None |
|
|
|
|
| def test_huge_exponent(): |
| """Test that huge exponents are blocked""" |
| with pytest.raises(ValueError, match="Exponent too large"): |
| safe_eval("2 ** 10000") |
|
|
|
|
| def test_sqrt_negative(): |
| """Test sqrt of negative number raises error""" |
| with pytest.raises(ValueError): |
| safe_eval("sqrt(-1)") |
|
|
|
|
| def test_factorial_negative(): |
| """Test factorial of negative number raises error""" |
| with pytest.raises(ValueError): |
| safe_eval("factorial(-5)") |
|
|
|
|
| |
| |
| |
|
|
| def test_whitespace_handling(): |
| """Test that whitespace is handled correctly""" |
| result = safe_eval(" 2 + 3 ") |
| assert result["result"] == 5 |
|
|
|
|
| def test_floating_point(): |
| """Test floating point arithmetic""" |
| result = safe_eval("3.14 * 2") |
| assert abs(result["result"] - 6.28) < 0.01 |
|
|
|
|
| def test_very_small_numbers(): |
| """Test very small numbers""" |
| result = safe_eval("0.0001 + 0.0002") |
| assert abs(result["result"] - 0.0003) < 0.00001 |
|
|
|
|
| def test_scientific_notation(): |
| """Test scientific notation""" |
| result = safe_eval("1e3 + 2e2") |
| assert result["result"] == 1200.0 |
|
|
|
|
| def test_parentheses_precedence(): |
| """Test that parentheses affect precedence correctly""" |
| result1 = safe_eval("2 + 3 * 4") |
| assert result1["result"] == 14 |
|
|
| result2 = safe_eval("(2 + 3) * 4") |
| assert result2["result"] == 20 |
|
|
|
|
| def test_multiple_operations(): |
| """Test chaining multiple operations""" |
| result = safe_eval("10 + 20 - 5 * 2 / 2 + 3") |
| assert result["result"] == 28.0 |
|
|