|
|
""" |
|
|
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 |
|
|
|