Refactor test system to be more extendable, add comment test
This commit is contained in:
parent
23a8fb9663
commit
e257db1542
8 changed files with 147 additions and 59 deletions
|
@ -46,3 +46,6 @@ FROM release AS operation
|
|||
|
||||
# don't run the server itself, just start up the environment and assume we'll exec things from the outside
|
||||
CMD sleep infinity
|
||||
|
||||
# Turn off the rate limiter
|
||||
ENV DBG_LIMITER_DISABLED=true
|
||||
|
|
|
@ -137,6 +137,9 @@ def get_remote_addr():
|
|||
with app.app_context():
|
||||
return request.headers.get('X-Real-IP', default='127.0.0.1')
|
||||
|
||||
app.config['RATE_LIMITER_ENABLED'] = not bool_from_string(environ.get('DBG_LIMITER_DISABLED', False))
|
||||
if not app.config['RATE_LIMITER_ENABLED']:
|
||||
print("Rate limiter disabled in debug mode!")
|
||||
limiter = Limiter(
|
||||
app,
|
||||
key_func=get_remote_addr,
|
||||
|
@ -144,6 +147,7 @@ limiter = Limiter(
|
|||
application_limits=["10/second;200/minute;5000/hour;10000/day"],
|
||||
storage_uri=environ.get("REDIS_URL", "redis://localhost"),
|
||||
auto_check=False,
|
||||
enabled=app.config['RATE_LIMITER_ENABLED'],
|
||||
)
|
||||
|
||||
Base = declarative_base()
|
||||
|
|
|
@ -261,6 +261,7 @@ def sign_up_post(v):
|
|||
|
||||
return redirect(f"/signup?{urlencode(args)}")
|
||||
|
||||
if app.config['RATE_LIMITER_ENABLED']:
|
||||
if now - int(form_timestamp) < 5:
|
||||
return signup_error("There was a problem. Please try again.")
|
||||
|
||||
|
|
4
files/tests/conftest.py
Normal file
4
files/tests/conftest.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from .fixture_accounts import accounts
|
43
files/tests/fixture_accounts.py
Normal file
43
files/tests/fixture_accounts.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
|
||||
from . import util
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from files.__main__ import app
|
||||
from functools import lru_cache
|
||||
import pytest
|
||||
from time import time, sleep
|
||||
|
||||
class AccountsFixture:
|
||||
@lru_cache(maxsize=None)
|
||||
def client_for_account(self, name = "default"):
|
||||
|
||||
client = app.test_client()
|
||||
|
||||
signup_get_response = client.get("/signup")
|
||||
assert signup_get_response.status_code == 200
|
||||
soup = BeautifulSoup(signup_get_response.text, 'html.parser')
|
||||
# these hidden input values seem to be used for anti-bot purposes and need to be submitted
|
||||
form_timestamp = next(tag for tag in soup.find_all("input") if tag.get("name") == "now").get("value")
|
||||
|
||||
username = f"test-{name}-{str(round(time()))}"
|
||||
print(f"Signing up as {username}")
|
||||
|
||||
signup_post_response = client.post("/signup", data={
|
||||
"username": username,
|
||||
"password": "password",
|
||||
"password_confirm": "password",
|
||||
"email": "",
|
||||
"formkey": util.formkey_from(signup_get_response.text),
|
||||
"now": form_timestamp
|
||||
})
|
||||
assert signup_post_response.status_code == 302
|
||||
assert "error" not in signup_post_response.location
|
||||
|
||||
return client
|
||||
|
||||
def logged_off(self):
|
||||
return app.test_client()
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def accounts():
|
||||
return AccountsFixture()
|
54
files/tests/test_basic.py
Normal file
54
files/tests/test_basic.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
|
||||
from . import fixture_accounts
|
||||
from . import util
|
||||
|
||||
def test_rules(accounts):
|
||||
response = accounts.logged_off().get("/rules")
|
||||
assert response.status_code == 200
|
||||
assert response.text.startswith("<!DOCTYPE html>")
|
||||
|
||||
|
||||
def test_post_and_comment(accounts):
|
||||
client = accounts.client_for_account()
|
||||
|
||||
# get our formkey
|
||||
submit_get_response = client.get("/submit")
|
||||
assert submit_get_response.status_code == 200
|
||||
|
||||
# make the post
|
||||
post_title = util.generate_text()
|
||||
post_body = util.generate_text()
|
||||
submit_post_response = client.post("/submit", data={
|
||||
"title": post_title,
|
||||
"body": post_body,
|
||||
"formkey": util.formkey_from(submit_get_response.text),
|
||||
})
|
||||
|
||||
assert submit_post_response.status_code == 200
|
||||
assert post_title in submit_post_response.text
|
||||
assert post_body in submit_post_response.text
|
||||
|
||||
# verify it actually got posted
|
||||
root_response = client.get("/")
|
||||
assert root_response.status_code == 200
|
||||
assert post_title in root_response.text
|
||||
assert post_body in root_response.text
|
||||
|
||||
# yank the ID out
|
||||
post = util.ItemData.from_html(submit_post_response.text)
|
||||
|
||||
# post a comment child
|
||||
comment_body = util.generate_text()
|
||||
submit_comment_response = client.post("/comment", data={
|
||||
"parent_fullname": post.id_full,
|
||||
"parent_level": 1,
|
||||
"submission": post.id,
|
||||
"body": comment_body,
|
||||
"formkey": util.formkey_from(submit_post_response.text),
|
||||
})
|
||||
assert submit_comment_response.status_code == 200
|
||||
|
||||
# verify it actually got posted
|
||||
post_response = client.get(post.url)
|
||||
assert post_response.status_code == 200
|
||||
assert comment_body in post_response.text
|
|
@ -1,57 +0,0 @@
|
|||
from bs4 import BeautifulSoup
|
||||
from time import time, sleep
|
||||
from files.__main__ import app
|
||||
|
||||
# these tests require `docker-compose up` first
|
||||
|
||||
def test_rules():
|
||||
response = app.test_client().get("/rules")
|
||||
assert response.status_code == 200
|
||||
assert response.text.startswith("<!DOCTYPE html>")
|
||||
|
||||
|
||||
def test_signup_and_post():
|
||||
print("\nTesting signup and posting flow")
|
||||
client = app.test_client()
|
||||
with client: # this keeps the session between requests, which we need
|
||||
signup_get_response = client.get("/signup")
|
||||
assert signup_get_response.status_code == 200
|
||||
soup = BeautifulSoup(signup_get_response.text, 'html.parser')
|
||||
# these hidden input values seem to be used for anti-bot purposes and need to be submitted
|
||||
formkey = next(tag for tag in soup.find_all("input") if tag.get("name") == "formkey").get("value")
|
||||
form_timestamp = next(tag for tag in soup.find_all("input") if tag.get("name") == "now").get("value")
|
||||
|
||||
sleep(5) # too-fast submissions are rejected (bot check?)
|
||||
username = "testuser" + str(round(time()))
|
||||
signup_post_response = client.post("/signup", data={
|
||||
"username": username,
|
||||
"password": "password",
|
||||
"password_confirm": "password",
|
||||
"email": "",
|
||||
"formkey": formkey,
|
||||
"now": form_timestamp
|
||||
})
|
||||
print(f"Signing up as {username}")
|
||||
assert signup_post_response.status_code == 302
|
||||
assert "error" not in signup_post_response.location
|
||||
|
||||
# we should now be logged in and able to post
|
||||
submit_get_response = client.get("/submit")
|
||||
assert submit_get_response.status_code == 200
|
||||
|
||||
submit_post_response = client.post("/submit", data={
|
||||
"title": "my_cool_post",
|
||||
"body": "hey_guys",
|
||||
})
|
||||
assert submit_post_response.status_code == 302
|
||||
post_render_result = client.get(submit_post_response.location)
|
||||
assert "my_cool_post" in post_render_result.text
|
||||
assert "hey_guys" in post_render_result.text
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
36
files/tests/util.py
Normal file
36
files/tests/util.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
|
||||
from bs4 import BeautifulSoup
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
|
||||
def formkey_from(text):
|
||||
soup = BeautifulSoup(text, 'html.parser')
|
||||
formkey = next(tag for tag in soup.find_all("input") if tag.get("name") == "formkey").get("value")
|
||||
return formkey
|
||||
|
||||
# not cryptographically secure, deal with it
|
||||
def generate_text():
|
||||
return ''.join(random.choices(string.ascii_lowercase, k=40))
|
||||
|
||||
# this is meant to be a utility class that stores post and comment references so you can use them in other calls
|
||||
# it's pretty barebones and will probably be fleshed out
|
||||
class ItemData:
|
||||
id = None
|
||||
id_full = None
|
||||
url = None
|
||||
|
||||
@staticmethod
|
||||
def from_html(text):
|
||||
soup = BeautifulSoup(text, 'html.parser')
|
||||
url = soup.find("meta", property="og:url")["content"]
|
||||
|
||||
match = re.search(r'/post/(\d+)/', url)
|
||||
if match is None:
|
||||
return None
|
||||
|
||||
result = ItemData()
|
||||
result.id = match.group(1) # this really should get yanked out of the JS, not the URL
|
||||
result.id_full = f"t2_{result.id}"
|
||||
result.url = url
|
||||
return result
|
Loading…
Add table
Add a link
Reference in a new issue