diff --git a/poetry.lock b/poetry.lock index 8a25b39..c467141 100644 --- a/poetry.lock +++ b/poetry.lock @@ -28,6 +28,21 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +[[package]] +name = "beautifulsoup4" +version = "4.9.3" +description = "Screen-scraping library" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +soupsieve = {version = ">1.2", markers = "python_version >= \"3.0\""} + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "black" version = "21.6b0" @@ -50,6 +65,17 @@ d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] python2 = ["typed-ast (>=1.4.2)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "bs4" +version = "0.0.1" +description = "Dummy package for Beautiful Soup" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +beautifulsoup4 = "*" + [[package]] name = "click" version = "8.0.1" @@ -260,6 +286,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "soupsieve" +version = "2.2.1" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "sqlalchemy" version = "1.4.18" @@ -353,7 +387,7 @@ locale = ["Babel (>=1.3)"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "493d96d9f3aa7056057b41877a76b5d4c4bcbd7f0a3c2864e4221024547ded87" +content-hash = "8fbeb9ceb3dfa728518390f2220db31a5530cb0a8d3b97e8c613990c1e6af9b1" [metadata.files] appdirs = [ @@ -368,10 +402,18 @@ attrs = [ {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] +beautifulsoup4 = [ + {file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"}, + {file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"}, + {file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"}, +] black = [ {file = "black-21.6b0-py3-none-any.whl", hash = "sha256:dfb8c5a069012b2ab1e972e7b908f5fb42b6bbabcba0a788b86dc05067c7d9c7"}, {file = "black-21.6b0.tar.gz", hash = "sha256:dc132348a88d103016726fe360cb9ede02cecf99b76e3660ce6c596be132ce04"}, ] +bs4 = [ + {file = "bs4-0.0.1.tar.gz", hash = "sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"}, +] click = [ {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, @@ -586,6 +628,10 @@ regex = [ {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, ] +soupsieve = [ + {file = "soupsieve-2.2.1-py3-none-any.whl", hash = "sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b"}, + {file = "soupsieve-2.2.1.tar.gz", hash = "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc"}, +] sqlalchemy = [ {file = "SQLAlchemy-1.4.18-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:d76abceeb6f7c564fdbc304b1ce17ec59664ca7ed0fe6dbc6fc6a960c91370e3"}, {file = "SQLAlchemy-1.4.18-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4cdc91bb3ee5b10e24ec59303131b791f3f82caa4dd8b36064d1918b0f4d0de4"}, diff --git a/pyproject.toml b/pyproject.toml index 5bd1698..9fb73e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ SQLAlchemy = "^1.4.12" pytest = "^5.2" black = "^21.5b2" mypy = "^0.812" +bs4 = "^0.0.1" [tool.poetry.scripts] amanuensis-cli = "amanuensis.cli:main" diff --git a/tests/test_auth.py b/tests/test_auth.py new file mode 100644 index 0000000..dc9a392 --- /dev/null +++ b/tests/test_auth.py @@ -0,0 +1,56 @@ +import os +from urllib.parse import urlsplit + +from bs4 import BeautifulSoup +from flask import Flask + +from amanuensis.db import User + + +def test_auth_circuit(app: Flask, make): + """Test the user login/logout path.""" + username: str = f"user_{os.urandom(8).hex()}" + ub: bytes = username.encode("utf8") + user: User = make.user(username=username, password=username) + + with app.test_client() as client: + # User should not be logged in + response = client.get("/home/") + assert response.status_code == 200 + assert ub not in response.data + + # The login page exists + response = client.get("/auth/login/") + assert response.status_code == 200 + assert ub not in response.data + assert b"Username" in response.data + assert b"Username" in response.data + assert b"csrf_token" in response.data + + # Get the csrf token for logging in + soup = BeautifulSoup(response.data, features="html.parser") + csrf_token = soup.find(id="csrf_token")["value"] + assert csrf_token + + # Log the user in + response = client.post( + "/auth/login/", + data={"username": username, "password": username, "csrf_token": csrf_token}, + ) + assert 300 <= response.status_code <= 399 + assert urlsplit(response.location).path == "/home/" + + # Confirm that the user is logged in + response = client.get("/home/") + assert response.status_code == 200 + assert ub in response.data + + # Log the user out + response = client.get("/auth/logout/") + assert 300 <= response.status_code <= 399 + assert urlsplit(response.location).path == "/home/" + + # Confirm the user is logged out + response = client.get("/home/") + assert response.status_code == 200 + assert ub not in response.data