|
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
|