diff options
| author | 2019-06-02 23:28:09 +0200 | |
|---|---|---|
| committer | 2019-06-02 23:28:09 +0200 | |
| commit | 77c7fc3e30069259bb5160048b152962da586350 (patch) | |
| tree | 0585661f636f8c4f17e2fa763de93801208337b9 | |
| parent | Merge branch 'master' into revitalisation (diff) | |
| parent | Revise docstrings for SnekAPI & EvalResource (diff) | |
Merge pull request #23 from python-discord/falcon
Replace Flask with Falcon
| -rw-r--r-- | .coveragerc | 14 | ||||
| -rw-r--r-- | Pipfile | 5 | ||||
| -rw-r--r-- | Pipfile.lock | 173 | ||||
| -rw-r--r-- | snekbox/api/__init__.py | 3 | ||||
| -rw-r--r-- | snekbox/api/app.py | 3 | ||||
| -rw-r--r-- | snekbox/api/middleware/__init__.py | 3 | ||||
| -rw-r--r-- | snekbox/api/middleware/logger.py | 11 | ||||
| -rw-r--r-- | snekbox/api/resources/__init__.py | 3 | ||||
| -rw-r--r-- | snekbox/api/resources/eval.py | 74 | ||||
| -rw-r--r-- | snekbox/api/snekapi.py | 27 | ||||
| -rw-r--r-- | snekbox/site/snekapp.py | 35 | ||||
| -rw-r--r-- | snekbox/site/templates/index.html | 14 | ||||
| -rw-r--r-- | snekbox/site/templates/result.html | 9 | ||||
| -rw-r--r-- | tests/__init__.py | 1 | ||||
| -rw-r--r-- | tests/api/__init__.py | 17 | ||||
| -rw-r--r-- | tests/api/test_eval.py | 49 | 
16 files changed, 278 insertions, 163 deletions
| diff --git a/.coveragerc b/.coveragerc index c1e718b..d1877d4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,10 +1,6 @@  [run] -omit = .venv/*, -       tests/*, -       snekweb.py -       config.py - -[report] -exclude_lines = return jsonify, -                raise RuntimeError, -                return +branch = True +omit = +    .venv/*, +    snekbox/api/app.py, +    tests/* @@ -4,8 +4,9 @@ verify_ssl = true  name = "pypi"  [packages] -flask = "*" +falcon = "*"  gunicorn = "*" +jsonschema = "*"  [dev-packages]  pytest = "*" @@ -29,7 +30,7 @@ lint = "flake8"  precommit = "pre-commit install"  test = "pytest tests --cov . --cov-report term-missing -v"  report = "pytest tests --cov . --cov-report=html" -snekbox = "gunicorn -w 2 -b 0.0.0.0:8060 snekbox.site.snekapp:app" +snekbox = "gunicorn -w 2 -b 0.0.0.0:8060 snekbox.api.app"  buildbox = "docker build -t pythondiscord/snekbox:latest -f docker/Dockerfile ."  pushbox = "docker push pythondiscord/snekbox:latest"  buildboxbase = "docker build -t pythondiscord/snekbox-base:latest -f docker/base.Dockerfile ." diff --git a/Pipfile.lock b/Pipfile.lock index 466a42b..e0c08a0 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@  {      "_meta": {          "hash": { -            "sha256": "814185e2e1b964ab58af9a9df416ace7b5b416475d828ec9b31a9dfecb5693e1" +            "sha256": "8dda3bfb1f2f9109b882225e4c55e3a561e25a7d80845889b4ffe5ad250cc86e"          },          "pipfile-spec": 6,          "requires": { @@ -16,20 +16,32 @@          ]      },      "default": { -        "click": { +        "attrs": {              "hashes": [ -                "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", -                "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" +                "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", +                "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"              ], -            "version": "==7.0" +            "version": "==19.1.0"          }, -        "flask": { -            "hashes": [ -                "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", -                "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" +        "falcon": { +            "hashes": [ +                "sha256:18157af2a4fc3feedf2b5dcc6196f448639acf01c68bc33d4d5a04c3ef87f494", +                "sha256:24adcd2b29a8ffa9d552dc79638cd21736a3fb04eda7d102c6cebafdaadb88ad", +                "sha256:54f2cb4b687035b2a03206dbfc538055cc48b59a953187b0458aa1b574d47b53", +                "sha256:59d1e8c993b9a37ea06df9d72cf907a46cc8063b30717cdac2f34d1658b6f936", +                "sha256:733033ec80c896e30a43ab3e776856096836787197a44eb21022320a61311983", +                "sha256:74cf1d18207381c665b9e6292d65100ce146d958707793174b03869dc6e614f4", +                "sha256:95bf6ce986c1119aef12c9b348f4dee9c6dcc58391bdd0bc2b0bf353c2b15986", +                "sha256:9712975adcf8c6e12876239085ad757b8fdeba223d46d23daef82b47658f83a9", +                "sha256:a5ebb22a04c9cc65081938ee7651b4e3b4d2a28522ea8ec04c7bdd2b3e9e8cd8", +                "sha256:aa184895d1ad4573fbfaaf803563d02f019ebdf4790e41cc568a330607eae439", +                "sha256:e3782b7b92fefd46a6ad1fd8fe63fe6c6f1b7740a95ca56957f48d1aee34b357", +                "sha256:e9efa0791b5d9f9dd9689015ea6bce0a27fcd5ecbcd30e6d940bffa4f7f03389", +                "sha256:eea593cf466b9c126ce667f6d30503624ef24459f118c75594a69353b6c3d5fc", +                "sha256:f93351459f110b4c1ee28556aef9a791832df6f910bea7b3f616109d534df06b"              ],              "index": "pypi", -            "version": "==1.0.2" +            "version": "==2.0.0"          },          "gunicorn": {              "hashes": [ @@ -39,68 +51,36 @@              "index": "pypi",              "version": "==19.9.0"          }, -        "itsdangerous": { -            "hashes": [ -                "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", -                "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" -            ], -            "version": "==1.1.0" -        }, -        "jinja2": { -            "hashes": [ -                "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", -                "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" -            ], -            "version": "==2.10" -        }, -        "markupsafe": { -            "hashes": [ -                "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", -                "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", -                "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", -                "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", -                "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", -                "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", -                "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", -                "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", -                "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", -                "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", -                "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", -                "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", -                "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", -                "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", -                "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", -                "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", -                "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", -                "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", -                "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", -                "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", -                "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", -                "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", -                "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", -                "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", -                "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", -                "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", -                "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", -                "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" -            ], -            "version": "==1.1.1" -        }, -        "werkzeug": { -            "hashes": [ -                "sha256:96da23fa8ccecbc3ae832a83df5c722c11547d021637faacb0bec4dd2f4666c8", -                "sha256:ca5c2dcd367d6c0df87185b9082929d255358f5391923269335782b213d52655" -            ], -            "version": "==0.15.1" +        "jsonschema": { +            "hashes": [ +                "sha256:0c0a81564f181de3212efa2d17de1910f8732fa1b71c42266d983cd74304e20d", +                "sha256:a5f6559964a3851f59040d3b961de5e68e70971afb88ba519d27e6a039efff1a" +            ], +            "index": "pypi", +            "version": "==3.0.1" +        }, +        "pyrsistent": { +            "hashes": [ +                "sha256:16692ee739d42cf5e39cef8d27649a8c1fdb7aa99887098f1460057c5eb75c3a", +                "sha256:1b304bab4d25fbe2b5bf32034d0d904bfc870f7f4ed9dccec6ae388978f0ef6f" +            ], +            "version": "==0.15.2" +        }, +        "six": { +            "hashes": [ +                "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", +                "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" +            ], +            "version": "==1.12.0"          }      },      "develop": {          "aspy.yaml": {              "hashes": [ -                "sha256:ae249074803e8b957c83fdd82a99160d0d6d26dff9ba81ba608b42eebd7d8cd3", -                "sha256:c7390d79f58eb9157406966201abf26da0d56c07e0ff0deadc39c8f4dbc13482" +                "sha256:463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc", +                "sha256:e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"              ], -            "version": "==1.2.0" +            "version": "==1.3.0"          },          "atomicwrites": {              "hashes": [ @@ -118,10 +98,10 @@          },          "cfgv": {              "hashes": [ -                "sha256:39f8475d8eca48639f900daffa3f8bd2f60a31d989df41a9f81c5ad1779a66eb", -                "sha256:a6a4366d32799a6bfb6f577ebe113b27ba8d1bae43cb57133b1472c1c3dae227" +                "sha256:32edbe09de6f4521224b87822103a8c16a614d31a894735f7a5b3bcf0eb3c37e", +                "sha256:3bd31385cd2bebddbba8012200aaf15aa208539f1b33973759b4d02fc2148da5"              ], -            "version": "==1.5.0" +            "version": "==2.0.0"          },          "coverage": {              "hashes": [ @@ -238,17 +218,17 @@          },          "identify": {              "hashes": [ -                "sha256:244e7864ef59f0c7c50c6db73f58564151d91345cd9b76ed793458953578cadd", -                "sha256:8ff062f90ad4b09cfe79b5dfb7a12e40f19d2e68a5c9598a49be45f16aba7171" +                "sha256:432c548d6138cb57a3d8f62f079a025a29b8ae34a50dd3b496bbf661818f2bc0", +                "sha256:d4401d60bf1938aa3074a352a5cc9044107edf11a6fedd3a1db172c141619b81"              ], -            "version": "==1.4.1" +            "version": "==1.4.3"          },          "importlib-metadata": {              "hashes": [ -                "sha256:a17ce1a8c7bff1e8674cb12c992375d8d0800c9190177ecf0ad93e0097224095", -                "sha256:b50191ead8c70adfa12495fba19ce6d75f2e0275c14c5a7beb653d6799b512bd" +                "sha256:027cfc6524613de726789072f95d2e4cc64dd1dee8096d42d13f2ead5bd302f5", +                "sha256:0d05199e1f0b1a8707a1b9c46476d4a49807fb56cb1b0737db1d37feb42fe31d"              ], -            "version": "==0.8" +            "version": "==0.15"          },          "junit-xml": {              "hashes": [ @@ -279,18 +259,18 @@          },          "pluggy": {              "hashes": [ -                "sha256:19ecf9ce9db2fce065a7a0586e07cfb4ac8614fe96edf628a264b1c70116cf8f", -                "sha256:84d306a647cc805219916e62aab89caa97a33a1dd8c342e87a37f91073cd4746" +                "sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", +                "sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"              ], -            "version": "==0.9.0" +            "version": "==0.12.0"          },          "pre-commit": {              "hashes": [ -                "sha256:d3d69c63ae7b7584c4b51446b0b583d454548f9df92575b2fe93a68ec800c4d3", -                "sha256:fc512f129b9526e35e80d656a16a31c198f584c4fce3a5c739045b5140584917" +                "sha256:6ca409d1f22d444af427fb023a33ca8b69625d508a50e1b7eaabd59247c93043", +                "sha256:94dd519597f5bff06a4b0df194a79c524b78f4b1534c1ce63241a9d4fb23b926"              ],              "index": "pypi", -            "version": "==1.14.4" +            "version": "==1.16.1"          },          "py": {              "hashes": [ @@ -323,19 +303,19 @@          },          "pytest": {              "hashes": [ -                "sha256:592eaa2c33fae68c7d75aacf042efc9f77b27c08a6224a4f59beab8d9a420523", -                "sha256:ad3ad5c450284819ecde191a654c09b0ec72257a2c711b9633d677c71c9850c4" +                "sha256:1a8aa4fa958f8f451ac5441f3ac130d9fc86ea38780dd2715e6d5c5882700b24", +                "sha256:b8bf138592384bd4e87338cb0f256bf5f615398a649d4bd83915f0e4047a5ca6"              ],              "index": "pypi", -            "version": "==4.3.1" +            "version": "==4.5.0"          },          "pytest-cov": {              "hashes": [ -                "sha256:0ab664b25c6aa9716cbf203b17ddb301932383046082c081b9848a0edf5add33", -                "sha256:230ef817450ab0699c6cc3c9c8f7a829c34674456f2ed8df1fe1d39780f7c87f" +                "sha256:2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6", +                "sha256:e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a"              ],              "index": "pypi", -            "version": "==2.6.1" +            "version": "==2.7.1"          },          "pytest-dependency": {              "hashes": [ @@ -383,17 +363,24 @@          },          "virtualenv": {              "hashes": [ -                "sha256:6aebaf4dd2568a0094225ebbca987859e369e3e5c22dc7d52e5406d504890417", -                "sha256:984d7e607b0a5d1329425dd8845bd971b957424b5ba664729fab51ab8c11bc39" +                "sha256:99acaf1e35c7ccf9763db9ba2accbca2f4254d61d1912c5ee364f9cc4a8942a0", +                "sha256:fe51cdbf04e5d8152af06c075404745a7419de27495a83f0d72518ad50be3ce8" +            ], +            "version": "==16.6.0" +        }, +        "wcwidth": { +            "hashes": [ +                "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", +                "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"              ], -            "version": "==16.4.3" +            "version": "==0.1.7"          },          "zipp": {              "hashes": [ -                "sha256:55ca87266c38af6658b84db8cfb7343cdb0bf275f93c7afaea0d8e7a209c7478", -                "sha256:682b3e1c62b7026afe24eadf6be579fb45fec54c07ea218bded8092af07a68c4" +                "sha256:8c1019c6aad13642199fbe458275ad6a84907634cc9f0989877ccc4a2840139d", +                "sha256:ca943a7e809cc12257001ccfb99e3563da9af99d52f261725e96dfe0f9275bc3"              ], -            "version": "==0.3.3" +            "version": "==0.5.1"          }      }  } diff --git a/snekbox/api/__init__.py b/snekbox/api/__init__.py new file mode 100644 index 0000000..d106f26 --- /dev/null +++ b/snekbox/api/__init__.py @@ -0,0 +1,3 @@ +from .snekapi import SnekAPI + +__all__ = ("SnekAPI",) diff --git a/snekbox/api/app.py b/snekbox/api/app.py new file mode 100644 index 0000000..c71e246 --- /dev/null +++ b/snekbox/api/app.py @@ -0,0 +1,3 @@ +from . import SnekAPI + +application = SnekAPI() diff --git a/snekbox/api/middleware/__init__.py b/snekbox/api/middleware/__init__.py new file mode 100644 index 0000000..ba97ca6 --- /dev/null +++ b/snekbox/api/middleware/__init__.py @@ -0,0 +1,3 @@ +from .logger import LoggingMiddleware + +__all__ = ("LoggingMiddleware",) diff --git a/snekbox/api/middleware/logger.py b/snekbox/api/middleware/logger.py new file mode 100644 index 0000000..393fc64 --- /dev/null +++ b/snekbox/api/middleware/logger.py @@ -0,0 +1,11 @@ +import logging + +log = logging.getLogger("snekbox.api") + + +class LoggingMiddleware: +    """Log basic information about responses.""" + +    def process_response(self, req, resp, resource, req_succeeded): +        """Log the method, route, and status of a response.""" +        log.info(f"{req.method} {req.relative_uri} {resp.status}") diff --git a/snekbox/api/resources/__init__.py b/snekbox/api/resources/__init__.py new file mode 100644 index 0000000..fe422b8 --- /dev/null +++ b/snekbox/api/resources/__init__.py @@ -0,0 +1,3 @@ +from .eval import EvalResource + +__all__ = ("EvalResource",) diff --git a/snekbox/api/resources/eval.py b/snekbox/api/resources/eval.py new file mode 100644 index 0000000..b2f4260 --- /dev/null +++ b/snekbox/api/resources/eval.py @@ -0,0 +1,74 @@ +import logging + +import falcon +from falcon.media.validators.jsonschema import validate + +from snekbox.nsjail import NsJail + +log = logging.getLogger(__name__) + + +class EvalResource: +    """ +    Evaluation of Python code. + +    Supported methods: + +    - POST /eval +        Evaluate Python code and return the result +    """ + +    REQ_SCHEMA = { +        "type": "object", +        "properties": { +            "input": { +                "type": "string" +            } +        }, +        "required": [ +            "input" +        ] +    } + +    def __init__(self): +        self.nsjail = NsJail() + +    @validate(REQ_SCHEMA) +    def on_post(self, req, resp): +        """ +        Evaluate Python code and return the result. + +        Request body: + +        >>> { +        ...     "input": "print(1 + 1)" +        ... } + +        Response format: + +        >>> { +        ...     "input": "print(1 + 1)", +        ...     "output": "2\\n" +        ... } + +        Status codes: + +        - 200 +            Successful evaluation; not indicative that the input code itself works +        - 400 +           Input's JSON schema is invalid +        - 415 +            Unsupported content type; only application/JSON is supported +        """ +        code = req.media["input"] + +        try: +            output = self.nsjail.python3(code) +        except Exception: +            log.exception("An exception occurred while trying to process the request") +            raise falcon.HTTPInternalServerError + +        resp.media = { +            "input": code, +            "output": output +        } diff --git a/snekbox/api/snekapi.py b/snekbox/api/snekapi.py new file mode 100644 index 0000000..849e7d6 --- /dev/null +++ b/snekbox/api/snekapi.py @@ -0,0 +1,27 @@ +import falcon + +from .middleware import LoggingMiddleware +from .resources import EvalResource + + +class SnekAPI(falcon.API): +    """ +    The main entry point to the snekbox JSON API. + +    Routes: + +    - /eval +        Evaluation of Python code + +    Error response format: + +    >>> { +    ...     "title": "Unsupported media type", +    ...     "description": "application/xml is an unsupported media type." +    ... } +    """ + +    def __init__(self, *args, **kwargs): +        super().__init__(middleware=[LoggingMiddleware()], *args, **kwargs) + +        self.add_route("/eval", EvalResource()) diff --git a/snekbox/site/snekapp.py b/snekbox/site/snekapp.py deleted file mode 100644 index ef96148..0000000 --- a/snekbox/site/snekapp.py +++ /dev/null @@ -1,35 +0,0 @@ -from flask import Flask, jsonify, render_template, request - -from snekbox.nsjail import NsJail - -nsjail = NsJail() - -# Load app -app = Flask(__name__) -app.use_reloader = False - -# Logging -log = app.logger - - [email protected]('/') -def index(): -    """Return a page with a form for inputting code to be executed.""" -    return render_template('index.html') - - [email protected]('/result', methods=["POST", "GET"]) -def result(): -    """Execute code and return a page displaying the results.""" -    if request.method == "POST": -        code = request.form["Code"] -        output = nsjail.python3(code) -        return render_template('result.html', code=code, result=output) - - [email protected]('/input', methods=["POST"]) -def code_input(): -    """Execute code and return the results.""" -    body = request.get_json() -    output = nsjail.python3(body["code"]) -    return jsonify(input=body["code"], output=output) diff --git a/snekbox/site/templates/index.html b/snekbox/site/templates/index.html deleted file mode 100644 index 41980d1..0000000 --- a/snekbox/site/templates/index.html +++ /dev/null @@ -1,14 +0,0 @@ -<!DOCTYPE html> -<html> -    <meta charset="utf-8" /> -    <title>snekboxweb</title> -    <body> -        <form action="/result" method="POST"> -            <p>Code:<br><textarea name="Code" rows="20" cols="80"> -def sum(a,b): -    return a+b -print( sum(1,2) )</textarea></p> -            <p><input type="submit" value="Run"></p> -        </form> -    </body> -</html> diff --git a/snekbox/site/templates/result.html b/snekbox/site/templates/result.html deleted file mode 100644 index e339605..0000000 --- a/snekbox/site/templates/result.html +++ /dev/null @@ -1,9 +0,0 @@ -<!DOCTYPE html> -<html> -    <meta charset="utf-8" /> -    <title>snekboxweb</title> -    <body> -        <p>Code Evaluated:<br><pre>{{ code }}</pre></p> -        <p>Results:<br><pre>{{ result }}</pre></p> -    </body> -</html> diff --git a/tests/__init__.py b/tests/__init__.py index 792d600..e69de29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +0,0 @@ -# diff --git a/tests/api/__init__.py b/tests/api/__init__.py new file mode 100644 index 0000000..fd4679a --- /dev/null +++ b/tests/api/__init__.py @@ -0,0 +1,17 @@ +from unittest import mock + +from falcon import testing + +from snekbox.api import SnekAPI + + +class SnekAPITestCase(testing.TestCase): +    def setUp(self): +        super().setUp() + +        self.patcher = mock.patch("snekbox.api.resources.eval.NsJail", autospec=True) +        self.mock_nsjail = self.patcher.start() +        self.mock_nsjail.return_value.python3.return_value = "test output" +        self.addCleanup(self.patcher.stop) + +        self.app = SnekAPI() diff --git a/tests/api/test_eval.py b/tests/api/test_eval.py new file mode 100644 index 0000000..a5b83fd --- /dev/null +++ b/tests/api/test_eval.py @@ -0,0 +1,49 @@ +from tests.api import SnekAPITestCase + + +class TestEvalResource(SnekAPITestCase): +    PATH = "/eval" + +    def test_post_valid_200(self): +        body = {"input": "foo"} +        result = self.simulate_post(self.PATH, json=body) + +        self.assertEqual(result.status_code, 200) +        self.assertEqual(body["input"], result.json["input"]) +        self.assertEqual("test output", result.json["output"]) + +    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": "'input' is a required property" +        } + +        self.assertEqual(expected, result.json) + +    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": "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") | 
