File size: 4,404 Bytes
abd4352
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"""
tests/unit/test_python_sandbox.py
Tests for RestrictedPython-based Pandas execution sandbox.
"""

import pandas as pd
import pytest
from sandbox.python_sandbox import validate_python, run_pandas


@pytest.fixture
def df():
    return pd.DataFrame({
        "product": ["Widget", "Gadget", "Widget", "Doohickey"],
        "amount": [99.99, 149.99, 99.99, 49.99],
        "region": ["North", "South", "North", "East"],
    })


# ── validate_python ───────────────────────────────────────────────────────────

@pytest.mark.unit
class TestValidatePython:
    def test_safe_code_passes(self):
        code = "result = df.groupby('product')['amount'].sum().to_frame()"
        ok, err = validate_python(code)
        assert ok, err

    def test_blocks_open(self):
        ok, err = validate_python("open('/etc/passwd')")
        assert not ok
        assert "open" in err

    def test_blocks_os_import(self):
        ok, err = validate_python("import os\nresult = df")
        assert not ok

    def test_blocks_subprocess(self):
        ok, err = validate_python("import subprocess\nresult = df")
        assert not ok

    def test_blocks_dunder_attr(self):
        ok, err = validate_python("df.__class__.__bases__")
        assert not ok

    def test_blocks_exec(self):
        ok, err = validate_python("exec('import os')")
        assert not ok

    def test_blocks_eval(self):
        ok, err = validate_python("eval('1+1')")
        assert not ok

    def test_allows_pandas_import(self):
        ok, err = validate_python("import pandas as pd\nresult = df")
        assert ok

    def test_allows_numpy_import(self):
        ok, err = validate_python("import numpy as np\nresult = df")
        assert ok

    def test_blocks_requests_import(self):
        ok, err = validate_python("import requests\nresult = df")
        assert not ok

    def test_syntax_error(self):
        ok, err = validate_python("def broken(:\n  pass")
        assert not ok


# ── run_pandas ────────────────────────────────────────────────────────────────

@pytest.mark.unit
class TestRunPandas:
    def test_basic_groupby(self, df):
        code = "result = df.groupby('product')['amount'].sum().to_frame()"
        out = run_pandas(code, df)
        assert isinstance(out, pd.DataFrame)
        assert "amount" in out.columns

    def test_filter(self, df):
        code = "result = df[df['region'] == 'North'].copy()"
        out = run_pandas(code, df)
        assert len(out) == 2

    def test_new_column(self, df):
        code = "result = df.copy()\nresult['doubled'] = result['amount'] * 2"
        out = run_pandas(code, df)
        assert "doubled" in out.columns

    def test_series_result_coerced_to_dataframe(self, df):
        code = "result = df['amount']"
        out = run_pandas(code, df)
        assert isinstance(out, pd.DataFrame)

    def test_missing_result_raises(self, df):
        with pytest.raises(ValueError, match="result"):
            run_pandas("x = 1", df)

    def test_non_dataframe_result_raises(self, df):
        with pytest.raises(TypeError):
            run_pandas("result = 42", df)

    def test_safety_block_raises_permission_error(self, df):
        with pytest.raises(PermissionError, match="SAFETY_BLOCK"):
            run_pandas("import os\nresult = df", df)

    def test_augmented_assignment(self, df):
        # RestrictedPython disallows augmented assignment on subscripts (result['col'] += x)
        # Use explicit assignment instead β€” the sandbox enforces this restriction correctly
        code = "result = df.copy()\nresult['amount'] = result['amount'] + 10"
        out = run_pandas(code, df)
        assert out["amount"].min() > 50

    def test_sort_and_limit(self, df):
        code = "result = df.sort_values('amount', ascending=False).head(2)"
        out = run_pandas(code, df)
        assert len(out) == 2
        assert out.iloc[0]["amount"] == pytest.approx(149.99)

    def test_pivot_table(self, df):
        code = """
result = df.pivot_table(
    index='region', values='amount', aggfunc='sum'
).reset_index()
"""
        out = run_pandas(code, df)
        assert "region" in out.columns