import sqlite3
import time
from datetime import datetime, timezone
import requests
from tests.conftest import BASE_URL
from devplacepy.config import DATABASE_URL
from devplacepy.database import get_table
from devplacepy.utils import generate_uid
from devplacepy.constants import REACTION_EMOJI
_counter = [0]
AJAX = {"X-Requested-With": "fetch"}
ROCKET = REACTION_EMOJI[2]
HEART = REACTION_EMOJI[1]
def _session():
_counter[0] += 1
name = f"rxn{int(time.time() * 1000)}{_counter[0]}"
s = requests.Session()
s.post(f"{BASE_URL}/auth/signup", data={
"username": name, "email": f"{name}@t.dev",
"password": "secret123", "confirm_password": "secret123",
}, allow_redirects=True)
return s, name
def _uid(username):
return get_table("users").find_one(username=username)["uid"]
def _live_query(sql, params):
conn = sqlite3.connect(DATABASE_URL.replace("sqlite:///", "", 1))
try:
return conn.execute(sql, params).fetchone()
finally:
conn.close()
def _live_reaction_count(target_type, target_uid):
return _live_query(
"SELECT COUNT(*) FROM reactions WHERE target_type=? AND target_uid=?",
(target_type, target_uid),
)[0]
def _live_post_uid(slug):
row = _live_query("SELECT uid FROM posts WHERE slug=?", (slug,))
return row[0] if row else None
def _make_post(owner_uid):
uid = generate_uid()
get_table("posts").insert({
"uid": uid, "user_uid": owner_uid, "slug": f"{uid[:8]}-reaction-post",
"title": None, "content": "reaction target content", "topic": "random",
"project_uid": None, "image": None, "stars": 0,
"created_at": datetime.now(timezone.utc).isoformat(),
})
return uid
def _make_comment(post_uid, owner_uid):
uid = generate_uid()
get_table("comments").insert({
"uid": uid, "target_type": "post", "target_uid": post_uid, "post_uid": post_uid,
"user_uid": owner_uid, "content": "reaction target comment", "parent_uid": None,
"created_at": datetime.now(timezone.utc).isoformat(),
})
return uid
def test_add_reaction_returns_counts(app_server):
s, name = _session()
post_uid = _make_post(_uid(name))
r = s.post(f"{BASE_URL}/reactions/post/{post_uid}", data={"emoji": ROCKET}, headers=AJAX)
payload = r.json()
assert payload["counts"][ROCKET] == 1
assert ROCKET in payload["mine"]
assert get_table("reactions").count(target_type="post", target_uid=post_uid) == 1
def test_same_emoji_toggles_off(app_server):
s, name = _session()
post_uid = _make_post(_uid(name))
s.post(f"{BASE_URL}/reactions/post/{post_uid}", data={"emoji": ROCKET}, headers=AJAX)
r = s.post(f"{BASE_URL}/reactions/post/{post_uid}", data={"emoji": ROCKET}, headers=AJAX)
payload = r.json()
assert payload["counts"].get(ROCKET, 0) == 0
assert payload["mine"] == []
assert get_table("reactions").count(target_type="post", target_uid=post_uid) == 0
def test_two_distinct_emoji_both_counted(app_server):
s, name = _session()
post_uid = _make_post(_uid(name))
s.post(f"{BASE_URL}/reactions/post/{post_uid}", data={"emoji": ROCKET}, headers=AJAX)
r = s.post(f"{BASE_URL}/reactions/post/{post_uid}", data={"emoji": HEART}, headers=AJAX)
payload = r.json()
assert payload["counts"][ROCKET] == 1
assert payload["counts"][HEART] == 1
assert set(payload["mine"]) == {ROCKET, HEART}
def test_emoji_outside_palette_rejected(app_server):
s, name = _session()
post_uid = _make_post(_uid(name))
r = s.post(f"{BASE_URL}/reactions/post/{post_uid}", data={"emoji": "notanemoji"}, headers=AJAX, allow_redirects=False)
assert r.status_code == 303
assert get_table("reactions").count(target_type="post", target_uid=post_uid) == 0
def test_invalid_target_type_returns_400(app_server):
s, name = _session()
post_uid = _make_post(_uid(name))
r = s.post(f"{BASE_URL}/reactions/news/{post_uid}", data={"emoji": ROCKET}, headers=AJAX)
assert r.status_code == 400
def test_reaction_requires_login(app_server):
owner_s, owner_name = _session()
post_uid = _make_post(_uid(owner_name))
anon = requests.Session()
r = anon.post(f"{BASE_URL}/reactions/post/{post_uid}", data={"emoji": ROCKET}, headers=AJAX, allow_redirects=False)
assert r.status_code == 303
assert get_table("reactions").count(target_type="post", target_uid=post_uid) == 0
def test_react_on_comment_target(app_server):
s, name = _session()
owner_uid = _uid(name)
post_uid = _make_post(owner_uid)
comment_uid = _make_comment(post_uid, owner_uid)
r = s.post(f"{BASE_URL}/reactions/comment/{comment_uid}", data={"emoji": HEART}, headers=AJAX)
assert r.json()["counts"][HEART] == 1
assert get_table("reactions").count(target_type="comment", target_uid=comment_uid) == 1
def test_delete_post_cascades_reactions(app_server):
s, name = _session()
title = f"cascade-{int(time.time() * 1000)}"
r = s.post(f"{BASE_URL}/posts/create", data={
"content": "Post created via the server for the reaction cascade test.",
"title": title, "topic": "random",
}, allow_redirects=False)
slug = r.headers["location"].split("/posts/")[-1]
post_uid = _live_post_uid(slug)
s.post(f"{BASE_URL}/reactions/post/{post_uid}", data={"emoji": ROCKET}, headers=AJAX)
assert _live_reaction_count("post", post_uid) == 1
s.post(f"{BASE_URL}/posts/delete/{slug}", allow_redirects=False)
assert s.get(f"{BASE_URL}/posts/{slug}").status_code == 404
assert _live_reaction_count("post", post_uid) == 0