Files
copykar/tests/test_repository.py
T

79 lines
3.5 KiB
Python

"""Integration tests for the repository layer. Requires a reachable Postgres."""
import asyncio
import sys
sys.path.insert(0, "/app")
from db.database import init_db, close_db_pool
from db.repository import Repository
TEST_SOURCE_ID = -1009999000001
TEST_TARGET_ID = -1009999000002
# Deliberately hostile title: quotes and a backslash must survive the JSONB round-trip.
TEST_TARGET_TITLE = 'News "Daily" \\ Channel'
async def _cleanup(repo: Repository):
pool = await repo._get_pool()
async with pool.acquire() as conn:
await conn.execute("DELETE FROM posts WHERE source_channel_id = $1;", TEST_SOURCE_ID)
await conn.execute("DELETE FROM sources WHERE channel_id = $1;", TEST_SOURCE_ID)
await conn.execute("DELETE FROM targets WHERE channel_id = $1;", TEST_TARGET_ID)
async def run_tests():
await init_db()
repo = Repository()
await _cleanup(repo)
# 1. Sources are looked up by their Telegram channel_id, not the surrogate row id.
await repo.add_source(TEST_SOURCE_ID, "Source Tech", "source_tech")
source = await repo.get_source_by_channel_id(TEST_SOURCE_ID)
assert source is not None, "get_source_by_channel_id returned None for a registered source"
assert source.channel_id == TEST_SOURCE_ID
assert source.title == "Source Tech"
assert await repo.get_source_by_channel_id(-1000000000000) is None
# 2. Deduplication lookup by content hash.
post_id = await repo.create_raw_post(
source_channel_id=TEST_SOURCE_ID,
source_message_id=101,
raw_text="Breaking news: AI update released!",
content_hash="hash_12345",
)
assert post_id is not None
dup = await repo.find_duplicate_post("hash_12345")
assert dup is not None and dup.id == post_id, "find_duplicate_post did not match a stored hash"
assert await repo.find_duplicate_post("no_such_hash") is None
# Re-inserting the same source message is rejected by the unique constraint.
assert await repo.create_raw_post(TEST_SOURCE_ID, 101, "dupe") is None
# 3. Queueing records the target without prematurely marking the post published.
target_id = await repo.add_target(TEST_TARGET_ID, TEST_TARGET_TITLE, "target_chan", post_interval_min=15)
await repo.record_post_queued_to_target(post_id, target_id, TEST_TARGET_TITLE)
post = await repo.get_post_by_id(post_id)
assert post.status == "pending_review", f"queueing must not publish, got {post.status}"
assert len(post.published_to) == 1, f"expected 1 queue entry, got {post.published_to}"
assert post.published_to[0]["target_title"] == TEST_TARGET_TITLE, "title was mangled in JSONB"
assert post.published_to[0]["published_at"] is None
# 4. Publishing flips status and stamps a real timestamp on the existing entry.
await repo.record_post_published_to_target(post_id, target_id, TEST_TARGET_TITLE)
post = await repo.get_post_by_id(post_id)
assert post.status == "published", f"expected published, got {post.status}"
assert len(post.published_to) == 1, f"publishing must not duplicate the entry, got {post.published_to}"
stamped = post.published_to[0]["published_at"]
assert stamped and "class" not in str(stamped), f"published_at is not a timestamp: {stamped!r}"
# 5. Counting by status must not require loading every row.
assert await repo.count_posts_by_status("published") >= 1
await _cleanup(repo)
await close_db_pool()
print("All repository tests passed successfully!")
if __name__ == "__main__":
asyncio.run(run_tests())