WyR/tests/test_app.py

252 lines
7.7 KiB
Python

import os
import tempfile
import unittest
from datetime import date
from werkzeug.security import generate_password_hash
from app import create_app
from app.database import get_db, init_db
class AppTestCase(unittest.TestCase):
def setUp(self):
self.db_fd, self.db_path = tempfile.mkstemp()
self.app = create_app(
{
"TESTING": True,
"DATABASE": self.db_path,
"SECRET_KEY": "test-secret",
}
)
with self.app.app_context():
init_db()
db = get_db()
db.execute(
"""
INSERT INTO admins (username, password_hash)
VALUES (?, ?)
""",
("admin", generate_password_hash("password")),
)
db.commit()
self.client = self.app.test_client()
def tearDown(self):
os.close(self.db_fd)
os.unlink(self.db_path)
def seed_question(self, question_date=None):
if question_date is None:
question_date = date.today().isoformat()
with self.app.app_context():
db = get_db()
db.execute(
"""
INSERT INTO questions (question, option_a, option_b, date)
VALUES (?, ?, ?, ?)
""",
("Would you rather test A or test B?", "Test A", "Test B", question_date),
)
db.commit()
def login(self):
return self.client.post(
"/admin/login",
data={"username": "admin", "password": "password"},
follow_redirects=True,
)
def test_public_empty_state(self):
response = self.client.get("/")
self.assertEqual(response.status_code, 200)
self.assertIn(b"No question is scheduled yet.", response.data)
def test_voting_flow_blocks_duplicate_vote(self):
self.seed_question()
first = self.client.post("/vote", data={"qid": "1", "vote": "A"})
second = self.client.post("/vote", data={"qid": "1", "vote": "A"})
self.assertEqual(first.status_code, 302)
self.assertEqual(second.status_code, 302)
with self.app.app_context():
count = get_db().execute("SELECT COUNT(*) FROM votes").fetchone()[0]
self.assertEqual(count, 1)
def test_new_browser_can_vote_without_cookie(self):
self.seed_question()
first = self.client.post("/vote", data={"qid": "1", "vote": "A"})
other_client = self.app.test_client()
second = other_client.post("/vote", data={"qid": "1", "vote": "B"})
self.assertEqual(first.status_code, 302)
self.assertEqual(second.status_code, 302)
with self.app.app_context():
count = get_db().execute("SELECT COUNT(*) FROM votes").fetchone()[0]
self.assertEqual(count, 2)
def test_invalid_vote_does_not_set_cookie(self):
response = self.client.post("/vote", data={"qid": "", "vote": "A"})
self.assertEqual(response.status_code, 302)
self.assertNotIn("wyr_daily_vote", response.headers.get("Set-Cookie", ""))
def test_non_today_vote_does_not_set_cookie(self):
self.seed_question("2030-01-01")
response = self.client.post("/vote", data={"qid": "1", "vote": "A"})
self.assertEqual(response.status_code, 302)
self.assertNotIn("wyr_daily_vote", response.headers.get("Set-Cookie", ""))
def test_homepage_uses_todays_question_only(self):
self.seed_question("2030-01-01")
response = self.client.get("/")
self.assertIn(b"No question is scheduled yet.", response.data)
self.seed_question()
response = self.client.get("/")
self.assertIn(b"Would you rather test A or test B?", response.data)
def test_vote_rejects_non_today_question(self):
self.seed_question("2030-01-01")
response = self.client.post("/vote", data={"qid": "1", "vote": "A"})
self.assertEqual(response.status_code, 302)
with self.app.app_context():
count = get_db().execute("SELECT COUNT(*) FROM votes").fetchone()[0]
self.assertEqual(count, 0)
def test_editing_todays_question_clears_votes_and_allows_new_vote(self):
self.seed_question()
self.client.post("/vote", data={"qid": "1", "vote": "A"})
self.login()
response = self.client.post(
"/admin/question/edit/1",
data={
"date": date.today().isoformat(),
"question": "Would you rather retest A or retest B?",
"option_a": "Retest A",
"option_b": "Retest B",
},
)
self.assertEqual(response.status_code, 302)
with self.app.app_context():
count_after_edit = (
get_db().execute("SELECT COUNT(*) FROM votes").fetchone()[0]
)
self.assertEqual(count_after_edit, 0)
self.client.post("/vote", data={"qid": "1", "vote": "B"})
with self.app.app_context():
db = get_db()
total = db.execute("SELECT COUNT(*) FROM votes").fetchone()[0]
vote_b = db.execute(
"SELECT COUNT(*) FROM votes WHERE vote = 'B'",
).fetchone()[0]
self.assertEqual(total, 1)
self.assertEqual(vote_b, 1)
def test_admin_login_success_and_failure(self):
failed = self.client.post(
"/admin/login",
data={"username": "admin", "password": "wrong"},
)
self.assertIn(b"Invalid username or password.", failed.data)
success = self.login()
self.assertIn(b"Questions", success.data)
def test_protected_route_redirects_to_login(self):
response = self.client.get("/admin/dashboard")
self.assertEqual(response.status_code, 302)
self.assertIn("/admin/login", response.headers["Location"])
def test_question_create_duplicate_date_failure(self):
self.seed_question()
self.login()
response = self.client.post(
"/admin/question/new",
data={
"date": date.today().isoformat(),
"question": "Another question?",
"option_a": "A",
"option_b": "B",
},
)
self.assertIn(b"Date already has a question.", response.data)
def test_admin_can_change_credentials(self):
self.login()
response = self.client.post(
"/admin/account",
data={
"username": "newadmin",
"current_password": "password",
"new_password": "new-password",
"confirm_password": "new-password",
},
follow_redirects=True,
)
self.assertIn(b"Admin credentials updated.", response.data)
self.client.get("/admin/logout")
old_login = self.client.post(
"/admin/login",
data={"username": "admin", "password": "password"},
)
self.assertIn(b"Invalid username or password.", old_login.data)
new_login = self.client.post(
"/admin/login",
data={"username": "newadmin", "password": "new-password"},
follow_redirects=True,
)
self.assertIn(b"Questions", new_login.data)
def test_admin_account_requires_current_password(self):
self.login()
response = self.client.post(
"/admin/account",
data={
"username": "newadmin",
"current_password": "wrong",
"new_password": "new-password",
"confirm_password": "new-password",
},
)
self.assertIn(b"Current password is incorrect.", response.data)
if __name__ == "__main__":
unittest.main()