aboutsummaryrefslogtreecommitdiffstats
path: root/tests/api/test_eval.py
blob: 41bdd358ede49eb1c649edad2d3dffdedb61cc44 (plain) (blame)
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
from tests.api import SnekAPITestCase


class TestEvalResource(SnekAPITestCase):
    PATH = "/eval"

    def test_post_valid_200(self):
        body = {"args": ["-c", "print('output')"]}
        result = self.simulate_post(self.PATH, json=body)

        self.assertEqual(result.status_code, 200)
        self.assertEqual("output", result.json["stdout"])
        self.assertEqual(0, result.json["returncode"])

    def test_post_invalid_schema_400(self):
        body = {"stuff": "foo"}
        result = self.simulate_post(self.PATH, json=body)

        self.assertEqual(result.status_code, 400)

        expected = {
            "title": "Request data failed validation",
            "description": "'args' is a required property",
        }

        self.assertEqual(expected, result.json)

    def test_post_invalid_data_400(self):
        bodies = ({"args": 400}, {"args": [], "files": [215]})
        expects = ["400 is not of type 'array'", "215 is not of type 'object'"]
        for body, expected in zip(bodies, expects):
            with self.subTest():
                result = self.simulate_post(self.PATH, json=body)

                self.assertEqual(result.status_code, 400)

                expected_json = {
                    "title": "Request data failed validation",
                    "description": expected,
                }
                self.assertEqual(expected_json, result.json)

    def test_files_path(self):
        """Normal paths should work with 200."""
        test_paths = [
            "file.txt",
            "./file.jpg",
            "path/to/file",
            "folder/../hm",
            "folder/./to/./somewhere",
            "traversal/but/../not/beyond/../root",
        ]
        for path in test_paths:
            with self.subTest(path=path):
                body = {"args": ["test.py"], "files": [{"path": path}]}
                result = self.simulate_post(self.PATH, json=body)
                self.assertEqual(result.status_code, 200)
                self.assertEqual("output", result.json["stdout"])
                self.assertEqual(0, result.json["returncode"])

    def test_files_illegal_path_traversal(self):
        """Traversal beyond root should be denied with 400 error."""
        test_paths = [
            "../secrets",
            "../../dir",
            "dir/../../secrets",
            "dir/var/../../../file",
        ]
        for path in test_paths:
            with self.subTest(path=path):
                body = {"args": ["test.py"], "files": [{"path": path}]}
                result = self.simulate_post(self.PATH, json=body)
                self.assertEqual(result.status_code, 400)
                expected = {
                    "title": "Request file path failed validation",
                    "description": f"File path '{path}' may not traverse beyond root",
                }
                self.assertEqual(expected, result.json)

    def test_files_illegal_path_absolute(self):
        """Absolute file paths should 400-error at json schema validation stage."""
        test_paths = [
            "/",
            "/etc",
            "/etc/vars/secrets",
            "/absolute",
            "/file.bin",
        ]
        for path in test_paths:
            with self.subTest(path=path):
                body = {"args": ["test.py"], "files": [{"path": path}]}
                result = self.simulate_post(self.PATH, json=body)
                self.assertEqual(result.status_code, 400)
                self.assertEqual("Request data failed validation", result.json["title"])
                self.assertIn("does not match", result.json["description"])

    def test_post_invalid_content_type_415(self):
        body = "{'input': 'foo'}"
        headers = {"Content-Type": "application/xml"}
        result = self.simulate_post(self.PATH, body=body, headers=headers)

        self.assertEqual(result.status_code, 415)

        expected = {
            "title": "415 Unsupported Media Type",
            "description": "application/xml is an unsupported media type.",
        }

        self.assertEqual(expected, result.json)

    def test_disallowed_method_405(self):
        result = self.simulate_get(self.PATH)
        self.assertEqual(result.status_code, 405)

    def test_options_allow_post_only(self):
        result = self.simulate_options(self.PATH)
        self.assertEqual(result.status_code, 200)
        self.assertEqual(result.headers.get("Allow"), "POST")