db: extend schema for categories, channel profiles, and error logs
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"""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())
|
||||
Reference in New Issue
Block a user