aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomasz Kramkowski <tomasz@kramkow.ski>2023-03-27 18:59:54 +0100
committerTomasz Kramkowski <tomasz@kramkow.ski>2023-03-27 19:01:56 +0100
commit9d893cb55ecdad2d2c4aa5ff9262b16e4f4caec2 (patch)
tree09e00d17cef81846b141eb6e1758f70bd791a3d4
parente6e6e3bb6f0f68aa60bdab50309401cb5e27fe9e (diff)
downloadpaste-9d893cb55ecdad2d2c4aa5ff9262b16e4f4caec2.tar.gz
paste-9d893cb55ecdad2d2c4aa5ff9262b16e4f4caec2.tar.xz
paste-9d893cb55ecdad2d2c4aa5ff9262b16e4f4caec2.zip
functional tests
-rw-r--r--poetry.lock86
-rw-r--r--pyproject.toml7
-rw-r--r--tests/test_application.py240
3 files changed, 332 insertions, 1 deletions
diff --git a/poetry.lock b/poetry.lock
index 0a66966..a75bab0 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -20,6 +20,25 @@ tests = ["attrs[tests-no-zope]", "zope.interface"]
tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"]
[[package]]
+name = "beautifulsoup4"
+version = "4.12.0"
+description = "Screen-scraping library"
+category = "dev"
+optional = false
+python-versions = ">=3.6.0"
+files = [
+ {file = "beautifulsoup4-4.12.0-py3-none-any.whl", hash = "sha256:2130a5ad7f513200fae61a17abb5e338ca980fa28c439c0571014bc0217e9591"},
+ {file = "beautifulsoup4-4.12.0.tar.gz", hash = "sha256:c5fceeaec29d09c84970e47c65f2f0efe57872f7cff494c9691a26ec0ff13234"},
+]
+
+[package.dependencies]
+soupsieve = ">1.2"
+
+[package.extras]
+html5lib = ["html5lib"]
+lxml = ["lxml"]
+
+[[package]]
name = "black"
version = "23.1.0"
description = "The uncompromising code formatter."
@@ -285,6 +304,18 @@ testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-202
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
+name = "soupsieve"
+version = "2.4"
+description = "A modern CSS selector implementation for Beautiful Soup."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "soupsieve-2.4-py3-none-any.whl", hash = "sha256:49e5368c2cda80ee7e84da9dbe3e110b70a4575f196efb74e51b94549d921955"},
+ {file = "soupsieve-2.4.tar.gz", hash = "sha256:e28dba9ca6c7c00173e34e4ba57448f0688bb681b7c5e8bf4971daafc093d69a"},
+]
+
+[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
@@ -319,7 +350,60 @@ files = [
{file = "vermin-1.5.1-py2.py3-none-any.whl", hash = "sha256:420995de564ac0c31e2157220259d7ac82556e8fa69c112d8005b78c14b0caf5"},
]
+[[package]]
+name = "waitress"
+version = "2.1.2"
+description = "Waitress WSGI server"
+category = "dev"
+optional = false
+python-versions = ">=3.7.0"
+files = [
+ {file = "waitress-2.1.2-py3-none-any.whl", hash = "sha256:7500c9625927c8ec60f54377d590f67b30c8e70ef4b8894214ac6e4cad233d2a"},
+ {file = "waitress-2.1.2.tar.gz", hash = "sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba"},
+]
+
+[package.extras]
+docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"]
+testing = ["coverage (>=5.0)", "pytest", "pytest-cover"]
+
+[[package]]
+name = "webob"
+version = "1.8.7"
+description = "WSGI request and response object"
+category = "dev"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*"
+files = [
+ {file = "WebOb-1.8.7-py2.py3-none-any.whl", hash = "sha256:73aae30359291c14fa3b956f8b5ca31960e420c28c1bec002547fb04928cf89b"},
+ {file = "WebOb-1.8.7.tar.gz", hash = "sha256:b64ef5141be559cfade448f044fa45c2260351edcb6a8ef6b7e00c7dcef0c323"},
+]
+
+[package.extras]
+docs = ["Sphinx (>=1.7.5)", "pylons-sphinx-themes"]
+testing = ["coverage", "pytest (>=3.1.0)", "pytest-cov", "pytest-xdist"]
+
+[[package]]
+name = "webtest"
+version = "3.0.0"
+description = "Helper to test WSGI applications"
+category = "dev"
+optional = false
+python-versions = ">=3.6, <4"
+files = [
+ {file = "WebTest-3.0.0-py3-none-any.whl", hash = "sha256:2a001a9efa40d2a7e5d9cd8d1527c75f41814eb6afce2c3d207402547b1e5ead"},
+ {file = "WebTest-3.0.0.tar.gz", hash = "sha256:54bd969725838d9861a9fa27f8d971f79d275d94ae255f5c501f53bb6d9929eb"},
+]
+
+[package.dependencies]
+beautifulsoup4 = "*"
+waitress = ">=0.8.5"
+WebOb = ">=1.2"
+
+[package.extras]
+docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.8)"]
+tests = ["PasteDeploy", "WSGIProxy2", "coverage", "pyquery", "pytest", "pytest-cov"]
+
[metadata]
lock-version = "2.0"
python-versions = "^3.9"
-content-hash = "77af368711793c4ad45130892553cfd105662c3947b72c66145b5121b480928e"
+content-hash = "1c78b39f184bd07745be4d68bec51c8f3b2fa80d32a0ac6cc02cb78ef09df46c"
diff --git a/pyproject.toml b/pyproject.toml
index dced527..72bd94b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -15,6 +15,13 @@ black = "^23.1.0"
vermin = "^1.5.1"
pyright = "^1.1.0"
pytest = "^7"
+webtest = "^3.0.0"
+
+[tool.pytest.ini_options]
+filterwarnings = [
+ "ignore:'cgi' is deprecated and slated for removal in Python 3.13:DeprecationWarning"
+]
+
[build-system]
requires = ["poetry-core"]
diff --git a/tests/test_application.py b/tests/test_application.py
new file mode 100644
index 0000000..a7d6861
--- /dev/null
+++ b/tests/test_application.py
@@ -0,0 +1,240 @@
+from base64 import b64decode, b64encode
+
+import pytest
+from webtest import TestApp
+
+import paste.db
+from paste import __main__, application
+
+DB = "file::memory:?cache=shared"
+
+
+@pytest.fixture
+def db():
+ with paste.db.connect(DB) as d:
+ yield d
+
+
+@pytest.fixture
+def app(db):
+ _ = db
+ app = TestApp(application, extra_environ={"HTTP_HOST": "localhost", "PASTE_DB": DB})
+ yield app
+
+
+@pytest.fixture
+def token(db):
+ return b64encode(__main__.generate_token(db)).decode()
+
+
+@pytest.mark.parametrize("method", ["put", "post", "delete"])
+def test_without_apikey(app, method):
+ res = getattr(app, method)(
+ "/test_key",
+ "Hello, World!",
+ headers={"Content-Type": "text/plain"},
+ expect_errors=True,
+ )
+ assert res.status == "401 Unauthorized"
+ assert res.headers["WWW-Authenticate"] == "APIKey"
+
+
+@pytest.mark.parametrize("method", ["put", "post", "delete"])
+def test_malformed_authorization_header(app, method):
+ res = getattr(app, method)(
+ "/test_key",
+ "Hello, World!",
+ headers={"Content-Type": "text/plain", "Authorization": "malformed"},
+ expect_errors=True,
+ )
+ assert res.status == "401 Unauthorized"
+ assert res.headers["WWW-Authenticate"] == "APIKey"
+
+
+@pytest.mark.parametrize("method", ["put", "post", "delete"])
+def test_malformed_apikey(app, method):
+ res = getattr(app, method)(
+ "/test_key" "Hello, World!",
+ headers={"Content-Type": "text/plain", "Authorization": "APIKey malformed"},
+ expect_errors=True,
+ )
+ assert res.status == "401 Unauthorized"
+ assert res.headers["WWW-Authenticate"] == "APIKey"
+
+
+@pytest.mark.parametrize("method", ["put", "post", "delete"])
+def test_invalid_apikey(app, method, token):
+ invalid = b64encode(bytes(b ^ 13 for b in b64decode(token))).decode()
+ res = getattr(app, method)(
+ "/test_key" "Hello, World!",
+ headers={"Content-Type": "text/plain", "Authorization": f"APIKey {invalid}"},
+ expect_errors=True,
+ )
+ assert res.status == "401 Unauthorized"
+ assert res.headers["WWW-Authenticate"] == "APIKey"
+
+
+def test_put(app, token):
+ res = app.put(
+ "/test_key",
+ "Hello, World!",
+ headers={"Content-Type": "text/plain", "Authorization": f"APIKey {token}"},
+ )
+ assert res.status == "201 Created"
+ assert res.headers["Location"] == res.request.url
+
+
+def test_put_twice(app, token):
+ res = app.put(
+ "/test_key",
+ "Hello, World!",
+ headers={"Content-Type": "text/plain", "Authorization": f"APIKey {token}"},
+ )
+ assert res.status == "201 Created"
+ assert res.headers["Location"] == res.request.url
+ res = app.put(
+ "/test_key",
+ "Hello, World!",
+ headers={"Content-Type": "text/plain", "Authorization": f"APIKey {token}"},
+ )
+ assert res.status == "204 No Content"
+ assert res.headers["Location"] == res.request.url
+
+
+@pytest.mark.parametrize("method", ["get", "head", "delete"])
+def test_method_nonexistent_fails(app, method, token):
+ headers = {}
+ if method == "delete":
+ headers = {"Authorization": f"APIKey {token}"}
+ res = getattr(app, method)("/test_key", headers=headers, expect_errors=True)
+ assert res.status == "404 Not Found"
+
+
+def test_put_then_get_then_head(app, token):
+ BODY = "Hello, World!"
+ res = app.put(
+ "/test_key",
+ BODY,
+ headers={"Content-Type": "text/plain", "Authorization": f"APIKey {token}"},
+ )
+ assert res.status == "201 Created"
+ assert res.headers["Location"] == res.request.url
+ etag = res.headers["ETag"]
+ res = app.get("/test_key")
+ assert res.status == "200 OK"
+ assert res.headers["ETag"] == etag
+ assert res.text == BODY
+ res = app.head("/test_key")
+ assert res.status == "200 OK"
+ assert res.headers["ETag"] == etag
+ assert res.text == ""
+
+
+def test_if_none_match(app, token):
+ res = app.put(
+ "/test_key",
+ "Hello, World!",
+ headers={"Content-Type": "text/plain", "Authorization": f"APIKey {token}"},
+ )
+ assert res.status == "201 Created"
+ assert res.headers["Location"] == res.request.url
+ etag = res.headers["ETag"]
+ res = app.get("/test_key", headers={"If-None-Match": etag})
+ assert res.status == "304 Not Modified"
+ assert res.headers["ETag"] == etag
+
+
+def test_if_none_match_other_etag(app, token):
+ BODY = "Hello, World!"
+ res = app.put(
+ "/test_key",
+ BODY,
+ headers={"Content-Type": "text/plain", "Authorization": f"APIKey {token}"},
+ )
+ assert res.status == "201 Created"
+ assert res.headers["Location"] == res.request.url
+ etag = res.headers["ETag"]
+ res = app.get("/test_key", headers={"If-None-Match": '"not a real etag"'})
+ assert res.status == "200 OK"
+ assert res.headers["ETag"] == etag
+ assert res.text == BODY
+
+
+def test_if_none_match_malformed_etag(app, token):
+ res = app.put(
+ "/test_key",
+ "Hello, World!",
+ headers={"Content-Type": "text/plain", "Authorization": f"APIKey {token}"},
+ )
+ assert res.status == "201 Created"
+ assert res.headers["Location"] == res.request.url
+ etag = res.headers["ETag"]
+ res = app.get(
+ "/test_key", headers={"If-None-Match": "malformed"}, expect_errors=True
+ )
+ assert res.status == "400 Bad Request"
+
+
+@pytest.mark.parametrize(
+ "etags",
+ [
+ [None, "a"],
+ ["a", None],
+ [None, "a", "b"],
+ ["a", None, "b"],
+ ["a", "b", None],
+ ],
+)
+def test_if_none_match_list(app, etags, token):
+ res = app.put(
+ "/test_key",
+ "Hello, World!",
+ headers={"Content-Type": "text/plain", "Authorization": f"APIKey {token}"},
+ )
+ assert res.status == "201 Created"
+ assert res.headers["Location"] == res.request.url
+ etag = res.headers["ETag"]
+ etags_str = ", ".join(f'"{e}"' if e else etag for e in etags)
+ res = app.get("/test_key", headers={"If-None-Match": etags_str})
+ assert res.status == "304 Not Modified"
+ assert res.headers["ETag"] == etag
+
+
+def test_put_update(app, token):
+ res = app.put(
+ "/test_key",
+ "Hello, World!",
+ headers={"Content-Type": "text/plain", "Authorization": f"APIKey {token}"},
+ )
+ assert res.status == "201 Created"
+ assert res.headers["Location"] == res.request.url
+ etag = res.headers["ETag"]
+ res = app.put(
+ "/test_key",
+ "Hello, Updated World!",
+ headers={"Content-Type": "text/plain", "Authorization": f"APIKey {token}"},
+ )
+ assert res.status == "204 No Content"
+ assert res.headers["ETag"] != etag
+ res = app.get("/test_key")
+ assert res.status == "200 OK"
+ assert res.text == "Hello, Updated World!"
+
+
+def test_delete(app, token):
+ res = app.put(
+ "/test_key",
+ "Hello, World!",
+ headers={"Content-Type": "text/plain", "Authorization": f"APIKey {token}"},
+ )
+ assert res.status == "201 Created"
+ assert res.headers["Location"] == res.request.url
+ etag = res.headers["ETag"]
+ res = app.delete("/test_key", expect_errors=True)
+ assert res.status == "401 Unauthorized"
+ res = app.delete("/test_key", headers={"Authorization": f"APIKey {token}"})
+ assert res.status == "204 No Content"
+ res = app.delete(
+ "/test_key", headers={"Authorization": f"APIKey {token}"}, expect_errors=True
+ )
+ assert res.status == "404 Not Found"