| Server IP : 3.147.158.171 / Your IP : 216.73.216.88 Web Server : Apache/2.4.67 (Amazon Linux) OpenSSL/3.5.5 System : Linux ip-172-31-2-178.us-east-2.compute.internal 6.1.172-216.329.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Wed May 20 06:31:34 UTC 2026 x86_64 User : ec2-user ( 1000) PHP Version : 8.4.21 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /tsai/repo/api/tests/unit/ |
Upload File : |
"""Tests for cron article-selection scoring in app.api.agents.comments."""
from collections import Counter
from datetime import datetime, timedelta, timezone
import pytest
from app.api.agents.comments import (
DECAY_DAYS,
REPLY_DECAY_HOURS,
_article_age_days,
_article_score,
_comment_age_hours,
_comment_score,
_count_replies_per_comment,
_is_fact_check_comment,
_is_self_comment,
_pick_article_weighted,
_pick_reply_target,
)
from app.appTypes import Character
def _article(article_id: int, age_days: float) -> dict:
published = datetime.now(timezone.utc) - timedelta(days=age_days)
return {
'id': article_id,
'date_gmt': published.strftime('%Y-%m-%dT%H:%M:%S'),
'title': {'rendered': f'Article {article_id}'},
}
def test_age_days_parses_iso_naive_as_utc():
article = _article(1, 0.5)
age = _article_age_days(article)
assert age is not None
assert 0.4 < age < 0.6
def test_age_days_returns_none_when_missing():
assert _article_age_days({}) is None
assert _article_age_days({'date_gmt': 'not-a-date'}) is None
def test_fresh_zero_comments_scores_close_to_one():
article = _article(1, 0)
score = _article_score(article, {1: 0})
assert score > 0.99
def test_old_zero_comment_beats_fresh_saturated():
fresh_saturated = _article(1, 0)
old_zero = _article(2, DECAY_DAYS) # ~0.5 recency, 1.0 under-commented = 0.5
counts = {1: 5, 2: 0}
assert _article_score(old_zero, counts) > _article_score(fresh_saturated, counts)
def test_score_zero_when_date_missing():
assert _article_score({'id': 1}, {1: 0}) == 0.0
def test_picker_returns_none_for_empty_pool():
assert _pick_article_weighted([], {}) is None
def test_picker_falls_back_to_uniform_when_all_weights_zero():
bad = [{'id': 1}, {'id': 2}, {'id': 3}] # no date_gmt -> all zero weight
picked = _pick_article_weighted(bad, {})
assert picked in bad
def test_picker_favors_higher_scores_over_many_trials():
fresh_zero = _article(1, 0)
old_saturated = _article(2, 30)
pool = [fresh_zero, old_saturated]
counts = {1: 0, 2: 8}
import random as _random
_random.seed(42)
tally = Counter()
for _ in range(2000):
picked = _pick_article_weighted(pool, counts)
tally[picked['id']] += 1
# fresh+0 should dominate old+saturated by a wide margin
assert tally[1] > tally[2] * 20
def test_picker_spreads_when_scores_close():
a = _article(1, 0)
b = _article(2, 0)
pool = [a, b]
counts = {1: 0, 2: 0}
import random as _random
_random.seed(7)
tally = Counter()
for _ in range(2000):
picked = _pick_article_weighted(pool, counts)
tally[picked['id']] += 1
# equal scores should produce ~50/50; allow generous slack
assert 800 < tally[1] < 1200
assert 800 < tally[2] < 1200
# --------------------------------------------------------------------
# Reply-target selection
# --------------------------------------------------------------------
def _comment(
comment_id: int,
age_hours: float,
author_name: str = 'Alice',
parent: int = 0,
) -> dict:
published = datetime.now(timezone.utc) - timedelta(hours=age_hours)
return {
'id': comment_id,
'parent': parent,
'date_gmt': published.strftime('%Y-%m-%dT%H:%M:%S'),
'author_name': author_name,
'content': {'rendered': f'<p>comment {comment_id}</p>'},
}
def _character(name: str = 'Sarah') -> Character:
return Character(
name=name,
email=f'{name.lower()}@example.test',
description='test character',
enabled=True,
date='2025-01-01',
)
def test_is_fact_check_comment_detects_prefix_case_insensitive():
assert _is_fact_check_comment({'author_name': 'Fact-Check (via OpenAI gpt-4)'})
assert _is_fact_check_comment({'author_name': 'fact-check (via Anthropic claude)'})
assert not _is_fact_check_comment({'author_name': 'Sarah'})
assert not _is_fact_check_comment({})
def test_is_self_comment_case_insensitive():
char = _character('Sarah')
assert _is_self_comment({'author_name': 'Sarah'}, char)
assert _is_self_comment({'author_name': 'sarah'}, char)
assert not _is_self_comment({'author_name': 'Bob'}, char)
assert not _is_self_comment({}, char)
def test_count_replies_per_comment():
comments = [
_comment(1, 1, parent=0),
_comment(2, 1, parent=1),
_comment(3, 1, parent=1),
_comment(4, 1, parent=2),
_comment(5, 1, parent=0),
]
counts = _count_replies_per_comment(comments)
assert counts == {1: 2, 2: 1}
def test_comment_age_hours_parses():
c = _comment(1, age_hours=2.0)
age = _comment_age_hours(c)
assert age is not None
assert 1.9 < age < 2.1
def test_fresh_top_level_zero_replies_scores_close_to_one():
c = _comment(1, age_hours=0, parent=0)
assert _comment_score(c, {}) > 0.99
def test_nested_penalized_vs_top_level():
top = _comment(1, age_hours=0, parent=0)
nested = _comment(2, age_hours=0, parent=99)
assert _comment_score(top, {}) > _comment_score(nested, {}) * 1.9
def test_underreplied_penalty():
c = _comment(1, age_hours=0, parent=0)
no_replies = _comment_score(c, {})
saturated = _comment_score(c, {1: 3})
assert no_replies > saturated * 3.5 # 1.0 vs 0.25
def test_old_top_level_zero_beats_fresh_top_level_saturated():
fresh_sat = _comment(1, age_hours=0, parent=0)
old_zero = _comment(2, age_hours=REPLY_DECAY_HOURS, parent=0)
counts = {1: 5, 2: 0}
assert _comment_score(old_zero, counts) > _comment_score(fresh_sat, counts)
def test_pick_reply_target_returns_none_when_all_filtered():
char = _character('Sarah')
comments = [
_comment(1, 1, author_name='Sarah'),
_comment(2, 1, author_name='Fact-Check (via OpenAI gpt-4)'),
]
assert _pick_reply_target(comments, char) is None
def test_pick_reply_target_excludes_self_and_fact_check():
char = _character('Sarah')
comments = [
_comment(1, 1, author_name='Sarah'),
_comment(2, 1, author_name='Fact-Check (via OpenAI gpt-4)'),
_comment(3, 1, author_name='Bob'),
]
import random as _random
_random.seed(0)
for _ in range(50):
picked = _pick_reply_target(comments, char)
assert picked is not None
assert picked['id'] == 3
def test_pick_reply_target_favors_fresh_top_level_zero_over_old_nested_replied():
char = _character('Sarah')
fresh_top = _comment(1, age_hours=0, author_name='Bob', parent=0)
old_nested = _comment(2, age_hours=24 * 7, author_name='Carol', parent=1)
# add filler replies under old_nested to inflate its reply count
fillers = [_comment(100 + i, 1, author_name='X', parent=2) for i in range(5)]
pool = [fresh_top, old_nested] + fillers
import random as _random
_random.seed(123)
tally = Counter()
for _ in range(2000):
picked = _pick_reply_target(pool, char)
tally[picked['id']] += 1
# fresh+top+0 should dominate old+nested+saturated by a wide margin
assert tally[1] > tally[2] * 20