بهبود هندلر شروع، انعطافپذیری دستورات و افزایش پایداری دیتابیس
This commit is contained in:
+74
-29
@@ -70,16 +70,26 @@ class Repository:
|
||||
return [SourceChannel(**dict(r)) for r in rows]
|
||||
|
||||
async def get_source_by_channel_id(self, channel_id: int) -> Optional[SourceChannel]:
|
||||
if not isinstance(channel_id, int) or not (-9223372036854775808 <= channel_id <= 9223372036854775807):
|
||||
return None
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
row = await conn.fetchrow("SELECT * FROM sources WHERE channel_id = $1;", channel_id)
|
||||
return SourceChannel(**dict(row)) if row else None
|
||||
try:
|
||||
row = await conn.fetchrow("SELECT * FROM sources WHERE channel_id = $1;", channel_id)
|
||||
return SourceChannel(**dict(row)) if row else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def get_source_by_id(self, source_id: int) -> Optional[SourceChannel]:
|
||||
if not isinstance(source_id, int) or not (1 <= source_id <= 2147483647):
|
||||
return None
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
row = await conn.fetchrow("SELECT * FROM sources WHERE id = $1;", source_id)
|
||||
return SourceChannel(**dict(row)) if row else None
|
||||
try:
|
||||
row = await conn.fetchrow("SELECT * FROM sources WHERE id = $1;", source_id)
|
||||
return SourceChannel(**dict(row)) if row else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def update_source_context_count(self, source_id: int, count: int) -> None:
|
||||
pool = await self._get_pool()
|
||||
@@ -133,10 +143,15 @@ class Repository:
|
||||
return [_parse_source_website_row(r) for r in rows]
|
||||
|
||||
async def get_source_website_by_id(self, site_id: int) -> Optional[SourceWebsite]:
|
||||
if not isinstance(site_id, int) or not (1 <= site_id <= 2147483647):
|
||||
return None
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
row = await conn.fetchrow("SELECT * FROM source_websites WHERE id = $1;", site_id)
|
||||
return _parse_source_website_row(row) if row else None
|
||||
try:
|
||||
row = await conn.fetchrow("SELECT * FROM source_websites WHERE id = $1;", site_id)
|
||||
return _parse_source_website_row(row) if row else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def get_source_websites_by_category(self, category_id: Optional[int]) -> List[SourceWebsite]:
|
||||
pool = await self._get_pool()
|
||||
@@ -363,10 +378,15 @@ class Repository:
|
||||
return [TargetChannel(**dict(r)) for r in rows]
|
||||
|
||||
async def get_target_by_id(self, target_id: int) -> Optional[TargetChannel]:
|
||||
if not isinstance(target_id, int) or not (1 <= target_id <= 2147483647):
|
||||
return None
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
row = await conn.fetchrow("SELECT * FROM targets WHERE id = $1;", target_id)
|
||||
return TargetChannel(**dict(row)) if row else None
|
||||
try:
|
||||
row = await conn.fetchrow("SELECT * FROM targets WHERE id = $1;", target_id)
|
||||
return TargetChannel(**dict(row)) if row else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def set_target_auto_sources(self, target_id: int, source_channel_ids: List[int]) -> None:
|
||||
"""Replace the set of source channels auto-routed into this target."""
|
||||
@@ -459,10 +479,15 @@ class Repository:
|
||||
return None
|
||||
|
||||
async def get_post_by_id(self, post_id: int) -> Optional[Post]:
|
||||
if not isinstance(post_id, int) or not (1 <= post_id <= 9223372036854775807):
|
||||
return None
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
row = await conn.fetchrow("SELECT * FROM posts WHERE id = $1;", post_id)
|
||||
return _parse_post_row(row) if row else None
|
||||
try:
|
||||
row = await conn.fetchrow("SELECT * FROM posts WHERE id = $1;", post_id)
|
||||
return _parse_post_row(row) if row else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def get_recent_source_posts(
|
||||
self,
|
||||
@@ -862,21 +887,26 @@ class Repository:
|
||||
return [AIProviderProfile(**dict(r)) for r in rows]
|
||||
|
||||
async def get_provider_profile_by_id(self, profile_id: int) -> Optional[AIProviderProfile]:
|
||||
if not isinstance(profile_id, int) or not (1 <= profile_id <= 2147483647):
|
||||
return None
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT p.id, p.name, p.provider_type, p.model, p.base_url, p.api_key, p.reasoning_effort, p.is_active,
|
||||
p.fallback_provider_id, p.supports_vision,
|
||||
fb.name as fallback_provider_name,
|
||||
to_char(p.created_at, 'YYYY-MM-DD HH24:MI:SS') as created_at
|
||||
FROM ai_providers p
|
||||
LEFT JOIN ai_providers fb ON p.fallback_provider_id = fb.id
|
||||
WHERE p.id = $1;
|
||||
""",
|
||||
profile_id
|
||||
)
|
||||
return AIProviderProfile(**dict(row)) if row else None
|
||||
try:
|
||||
row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT p.id, p.name, p.provider_type, p.model, p.base_url, p.api_key, p.reasoning_effort, p.is_active,
|
||||
p.fallback_provider_id, p.supports_vision,
|
||||
fb.name as fallback_provider_name,
|
||||
to_char(p.created_at, 'YYYY-MM-DD HH24:MI:SS') as created_at
|
||||
FROM ai_providers p
|
||||
LEFT JOIN ai_providers fb ON p.fallback_provider_id = fb.id
|
||||
WHERE p.id = $1;
|
||||
""",
|
||||
profile_id
|
||||
)
|
||||
return AIProviderProfile(**dict(row)) if row else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def get_active_provider_profile(self) -> Optional[AIProviderProfile]:
|
||||
pool = await self._get_pool()
|
||||
@@ -983,10 +1013,15 @@ class Repository:
|
||||
return [ChannelCategory(**dict(r)) for r in rows]
|
||||
|
||||
async def get_category_by_id(self, cat_id: int) -> Optional[ChannelCategory]:
|
||||
if not isinstance(cat_id, int) or not (1 <= cat_id <= 2147483647):
|
||||
return None
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
row = await conn.fetchrow("SELECT * FROM channel_categories WHERE id = $1;", cat_id)
|
||||
return ChannelCategory(**dict(row)) if row else None
|
||||
try:
|
||||
row = await conn.fetchrow("SELECT * FROM channel_categories WHERE id = $1;", cat_id)
|
||||
return ChannelCategory(**dict(row)) if row else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def update_category(self, cat_id: int, name: Optional[str] = None, description: Optional[str] = None, cat_type: Optional[str] = None) -> None:
|
||||
pool = await self._get_pool()
|
||||
@@ -1092,16 +1127,26 @@ class Repository:
|
||||
return [_parse_admin_channel_row(r) for r in rows]
|
||||
|
||||
async def get_admin_channel_by_id(self, admin_id: int) -> Optional[AdminReviewChannel]:
|
||||
if not isinstance(admin_id, int) or not (1 <= admin_id <= 2147483647):
|
||||
return None
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
row = await conn.fetchrow("SELECT * FROM admin_channels WHERE id = $1;", admin_id)
|
||||
return _parse_admin_channel_row(row) if row else None
|
||||
try:
|
||||
row = await conn.fetchrow("SELECT * FROM admin_channels WHERE id = $1;", admin_id)
|
||||
return _parse_admin_channel_row(row) if row else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def get_admin_channel_by_telegram_id(self, channel_id: int) -> Optional[AdminReviewChannel]:
|
||||
if not isinstance(channel_id, int) or not (-9223372036854775808 <= channel_id <= 9223372036854775807):
|
||||
return None
|
||||
pool = await self._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
row = await conn.fetchrow("SELECT * FROM admin_channels WHERE channel_id = $1;", channel_id)
|
||||
return _parse_admin_channel_row(row) if row else None
|
||||
try:
|
||||
row = await conn.fetchrow("SELECT * FROM admin_channels WHERE channel_id = $1;", channel_id)
|
||||
return _parse_admin_channel_row(row) if row else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def get_default_admin_channel(self) -> Optional[AdminReviewChannel]:
|
||||
pool = await self._get_pool()
|
||||
|
||||
+87
-63
@@ -654,24 +654,31 @@ class AdminBotService:
|
||||
|
||||
tag_line = f"🏷 <b>موضوع:</b> #{post.subject}\n\n" if getattr(post, "subject", None) else ""
|
||||
|
||||
if -999999999 <= post.source_channel_id <= -900000000:
|
||||
site_id = abs(post.source_channel_id + 900000000)
|
||||
site = await self.repo.get_source_website_by_id(site_id)
|
||||
if site and site.name:
|
||||
source_label = f"🌐 وبسایت <b>{site.name}</b>"
|
||||
elif site and site.url:
|
||||
source_label = f"🌐 وبسایت <b>{site.url}</b>"
|
||||
source_label = "کانال"
|
||||
try:
|
||||
if post.source_channel_id is not None and -999999999 <= post.source_channel_id <= -900000000:
|
||||
site_id = abs(post.source_channel_id + 900000000)
|
||||
site = await self.repo.get_source_website_by_id(site_id)
|
||||
if site and site.name:
|
||||
source_label = f"🌐 وبسایت <b>{site.name}</b>"
|
||||
elif site and site.url:
|
||||
source_label = f"🌐 وبسایت <b>{site.url}</b>"
|
||||
else:
|
||||
source_label = f"🌐 وبسایت <code>#{site_id}</code>"
|
||||
elif post.source_channel_id is not None:
|
||||
src = await self.repo.get_source_by_channel_id(post.source_channel_id)
|
||||
if src and src.title:
|
||||
user_part = f" (@{src.username})" if src.username else ""
|
||||
source_label = f"📢 کانال <b>{src.title}</b>{user_part}"
|
||||
elif src and src.username:
|
||||
source_label = f"📢 کانال @<b>{src.username}</b>"
|
||||
else:
|
||||
source_label = f"کانال <code>{post.source_channel_id}</code>"
|
||||
else:
|
||||
source_label = f"🌐 وبسایت <code>#{site_id}</code>"
|
||||
else:
|
||||
src = await self.repo.get_source_by_channel_id(post.source_channel_id)
|
||||
if src and src.title:
|
||||
user_part = f" (@{src.username})" if src.username else ""
|
||||
source_label = f"📢 کانال <b>{src.title}</b>{user_part}"
|
||||
elif src and src.username:
|
||||
source_label = f"📢 کانال @<b>{src.username}</b>"
|
||||
else:
|
||||
source_label = f"کانال <code>{post.source_channel_id}</code>"
|
||||
source_label = "کانال نامشخص"
|
||||
except Exception as e:
|
||||
logger.debug(f"Error formatting source label for post {post.id}: {e}")
|
||||
source_label = f"شناسه <code>{post.source_channel_id}</code>"
|
||||
|
||||
caption = (
|
||||
f"📥 <b>پست جدید از {source_label}:</b>\n\n"
|
||||
@@ -685,19 +692,22 @@ class AdminBotService:
|
||||
|
||||
async def get_effective_review_channel_id(self, post: Post) -> Optional[int]:
|
||||
"""Determine which Admin Review Channel this post should be sent to."""
|
||||
if -999999999 <= post.source_channel_id <= -900000000:
|
||||
site_id = abs(post.source_channel_id + 900000000)
|
||||
site = await self.repo.get_source_website_by_id(site_id)
|
||||
if site and site.admin_channel_id:
|
||||
return site.admin_channel_id
|
||||
else:
|
||||
src = await self.repo.get_source_by_channel_id(post.source_channel_id)
|
||||
if src and src.admin_channel_id:
|
||||
return src.admin_channel_id
|
||||
try:
|
||||
if post.source_channel_id is not None and -999999999 <= post.source_channel_id <= -900000000:
|
||||
site_id = abs(post.source_channel_id + 900000000)
|
||||
site = await self.repo.get_source_website_by_id(site_id)
|
||||
if site and site.admin_channel_id:
|
||||
return site.admin_channel_id
|
||||
elif post.source_channel_id is not None:
|
||||
src = await self.repo.get_source_by_channel_id(post.source_channel_id)
|
||||
if src and src.admin_channel_id:
|
||||
return src.admin_channel_id
|
||||
|
||||
default_adm = await self.repo.get_default_admin_channel()
|
||||
if default_adm and default_adm.channel_id:
|
||||
return default_adm.channel_id
|
||||
default_adm = await self.repo.get_default_admin_channel()
|
||||
if default_adm and default_adm.channel_id:
|
||||
return default_adm.channel_id
|
||||
except Exception as e:
|
||||
logger.error(f"Error resolving review channel for post {post.id}: {e}")
|
||||
|
||||
return self.review_channel_id or None
|
||||
|
||||
@@ -1707,30 +1717,38 @@ class AdminBotService:
|
||||
|
||||
def _register_handlers(self):
|
||||
# --- Start / Menu ---
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/start|/menu|منو|منوی اصلی|🏠 منوی اصلی)$"))
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/start(@\w+)?|/srart(@\w+)?|/strt(@\w+)?|/starrt(@\w+)?|/menu(@\w+)?|شروع|منو|منوی اصلی|🏠 منوی اصلی)(\s.*)?$"))
|
||||
async def cmd_start(event: events.NewMessage.Event):
|
||||
if not self.is_admin(event.sender_id):
|
||||
await event.reply(f"⛔ دسترسی غیرمجاز. شناسه عددی شما: <code>{event.sender_id}</code>", parse_mode="html")
|
||||
return
|
||||
|
||||
self.user_states.pop(event.sender_id, None)
|
||||
try:
|
||||
self.user_states.pop(event.sender_id, None)
|
||||
|
||||
userbot_status = "🔴 قطع / نیاز به لاگین"
|
||||
if self.collector and self.collector.client.is_connected() and await self.collector.client.is_user_authorized():
|
||||
me = await self.collector.client.get_me()
|
||||
userbot_status = f"🟢 آنلاین ({me.first_name})"
|
||||
userbot_status = "🔴 قطع / نیاز به لاگین"
|
||||
if self.collector and self.collector.client.is_connected() and await self.collector.client.is_user_authorized():
|
||||
me = await self.collector.client.get_me()
|
||||
userbot_status = f"🟢 آنلاین ({me.first_name})"
|
||||
|
||||
def_adm = await self.repo.get_default_admin_channel()
|
||||
adm_label = f"<b>{def_adm.title}</b> (<code>{def_adm.channel_id}</code>)" if (def_adm and def_adm.title) else (f"<code>{self.review_channel_id}</code>" if self.review_channel_id else "تعریفنشده")
|
||||
def_adm = await self.repo.get_default_admin_channel()
|
||||
adm_label = f"<b>{def_adm.title}</b> (<code>{def_adm.channel_id}</code>)" if (def_adm and def_adm.title) else (f"<code>{self.review_channel_id}</code>" if self.review_channel_id else "تعریفنشده")
|
||||
|
||||
welcome_text = (
|
||||
"👋 <b>به پنل مدیریت سیستم هوشمند کپیکار خوش آمدید!</b>\n\n"
|
||||
f"• 🤖 <b>وضعیت ربات جمعآوریکننده:</b> {userbot_status}\n"
|
||||
f"• 📋 <b>کانال نظارت ادمین:</b> {adm_label}\n\n"
|
||||
"برای مدیریت کانالها از دکمههای زیر استفاده کنید:"
|
||||
)
|
||||
menu = await self.get_menu()
|
||||
await event.reply(welcome_text, parse_mode="html", buttons=menu)
|
||||
welcome_text = (
|
||||
"👋 <b>به پنل مدیریت سیستم هوشمند کپیکار خوش آمدید!</b>\n\n"
|
||||
f"• 🤖 <b>وضعیت ربات جمعآوریکننده:</b> {userbot_status}\n"
|
||||
f"• 📋 <b>کانال نظارت ادمین:</b> {adm_label}\n\n"
|
||||
"برای مدیریت کانالها از دکمههای زیر استفاده کنید:"
|
||||
)
|
||||
menu = await self.get_menu()
|
||||
await event.reply(welcome_text, parse_mode="html", buttons=menu)
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing cmd_start: {e}", exc_info=True)
|
||||
try:
|
||||
menu = await self.get_menu()
|
||||
await event.reply("👋 <b>به پنل مدیریت سیستم هوشمند کپیکار خوش آمدید!</b>", parse_mode="html", buttons=menu)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# --- 5 Primary Category Hubs: Copy, AI, Bots, System, Monitor ---
|
||||
@self.client.on(events.NewMessage(pattern=r"(?i)^(/copy|📋.*|.*copy.*|.*محتوا.*)$"))
|
||||
@@ -2032,25 +2050,31 @@ class AdminBotService:
|
||||
return
|
||||
|
||||
text = (event.raw_text or "").strip()
|
||||
first_word = text.split()[0].lower() if text else ""
|
||||
|
||||
# Ignore main menu commands
|
||||
if text in ["/start", "/menu", "منو", "منوی اصلی", "🏠 منوی اصلی",
|
||||
"/copy", "📋 مدیریت محتوا (Copy)", "📋 مدیریت محتوا", "📋 copy (محتوا)", "📋 Copy (محتوا)", "📋 کپی", "Copy",
|
||||
"/ai", "🧠 هوش مصنوعی (AI)", "🧠 هوش مصنوعی", "🧠 ai (هوش مصنوعی)", "🧠 AI (هوش مصنوعی)", "🧠 تنظیمات AI", "🧠 تنظیمات و لاگهای AI", "AI",
|
||||
"/bots", "🤖 کانالها و وب (Bots)", "🤖 کانالها و وب", "🤖 bots (کانالها و رباتها)", "🤖 Bots (کانالها و رباتها)", "🤖 کانالها و رباتها", "Bots",
|
||||
"/system", "⚙️ مدیریت سیستم (System)", "⚙️ مدیریت سیستم", "⚙️ system (سیستم)", "⚙️ System (سیستم)", "System",
|
||||
"/monitor", "📊 مانیتورینگ (Monitor)", "📊 مانیتورینگ", "📊 monitor (مانیتورینگ)", "📊 Monitor (مانیتورینگ)", "Monitor",
|
||||
"/stats", "/report", "📊 آمار ناوگان", "📊 آمار و وضعیت ناوگان", "📊 گزارش و آمار سیستم",
|
||||
"/sources", "📡 کانالهای مبدا", "/targets", "🎯 کانالهای مقصد",
|
||||
"/websites", "/sites", "🌐 وبسایتهای مبدا",
|
||||
"/request_code", "🔑 کد لاگین", "🔑 درخواست کد لاگین",
|
||||
"/pending", "📨 پستهای بررسینشده", "📨 ارسال پستهای بررسینشده",
|
||||
"/errors", "/err", "⚠️ خطاهای سیستم",
|
||||
"/categories", "/cats", "📂 دستهبندیها", "📂 دستهبندی کانالها",
|
||||
"/add", "➕ افزودن مبدا / مقصد", "➕ افزودن کانال مبدا", "➕ افزودن کانال مقصد",
|
||||
"/pause", "/stop", "🛑 توقف سیستم", "🛑 توقف اضطراری سیستم",
|
||||
"/resume", "/start_fleet", "▶️ راهاندازی سیستم", "▶️ راهاندازی و ادامه سیستم",
|
||||
"/help", "❓ راهنمای سیستم"]:
|
||||
# Ignore main menu commands and start variants
|
||||
if (
|
||||
re.match(r"(?i)^(/start(@\w+)?|/srart(@\w+)?|/strt(@\w+)?|/starrt(@\w+)?|/menu(@\w+)?|شروع|منو|منوی اصلی|🏠 منوی اصلی)$", first_word)
|
||||
or text in [
|
||||
"/start", "/menu", "منو", "منوی اصلی", "🏠 منوی اصلی", "شروع",
|
||||
"/copy", "📋 مدیریت محتوا (Copy)", "📋 مدیریت محتوا", "📋 copy (محتوا)", "📋 Copy (محتوا)", "📋 کپی", "Copy",
|
||||
"/ai", "🧠 هوش مصنوعی (AI)", "🧠 هوش مصنوعی", "🧠 ai (هوش مصنوعی)", "🧠 AI (هوش مصنوعی)", "🧠 تنظیمات AI", "🧠 تنظیمات و لاگهای AI", "AI",
|
||||
"/bots", "🤖 کانالها و وب (Bots)", "🤖 کانالها و وب", "🤖 bots (کانالها و رباتها)", "🤖 Bots (کانالها و رباتها)", "🤖 کانالها و رباتها", "Bots",
|
||||
"/system", "⚙️ مدیریت سیستم (System)", "⚙️ مدیریت سیستم", "⚙️ تنظیمات و سیستم", "⚙️ system (سیستم)", "⚙️ System (سیستم)", "System",
|
||||
"/monitor", "📊 مانیتورینگ (Monitor)", "📊 مانیتورینگ", "📊 monitor (مانیتورینگ)", "📊 Monitor (مانیتورینگ)", "Monitor",
|
||||
"/stats", "/report", "📊 آمار ناوگان", "📊 آمار و وضعیت ناوگان", "📊 گزارش و آمار سیستم",
|
||||
"/sources", "📡 کانالهای مبدا", "📡 مبداها", "/targets", "🎯 کانالهای مقصد", "🎯 مقصدها",
|
||||
"/websites", "/sites", "🌐 وبسایتهای مبدا", "🌐 وبسایتها",
|
||||
"/request_code", "🔑 کد لاگین", "🔑 درخواست کد لاگین", "🔑 لاگین یوزربات",
|
||||
"/pending", "📨 پستهای بررسینشده", "📨 ارسال پستهای بررسینشده", "📨 بررسینشده",
|
||||
"/errors", "/err", "⚠️ خطاهای سیستم",
|
||||
"/categories", "/cats", "📂 دستهبندیها", "📂 دستهبندی کانالها",
|
||||
"/add", "➕ افزودن مبدا / مقصد", "➕ افزودن کانال مبدا", "➕ افزودن کانال مقصد", "➕ افزودن مبدا", "➕ افزودن مقصد", "➕ افزودن وبسایت",
|
||||
"/pause", "/stop", "🛑 توقف سیستم", "🛑 توقف اضطراری سیستم",
|
||||
"/resume", "/start_fleet", "▶️ راهاندازی سیستم", "▶️ راهاندازی و ادامه سیستم",
|
||||
"/help", "❓ راهنمای سیستم", "❓ راهنما"
|
||||
]
|
||||
):
|
||||
return
|
||||
|
||||
# Cancel command
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import re
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from db.repository import Repository
|
||||
from db.models import Post, SourceWebsite, SourceChannel
|
||||
from services.admin_bot import AdminBotService
|
||||
|
||||
def test_start_command_regex():
|
||||
pattern = r"(?i)^(/start(@\w+)?|/srart(@\w+)?|/strt(@\w+)?|/starrt(@\w+)?|/menu(@\w+)?|شروع|منو|منوی اصلی|🏠 منوی اصلی)(\s.*)?$"
|
||||
|
||||
valid_inputs = [
|
||||
"/start",
|
||||
"/START",
|
||||
"/start@copykar_2026_bot",
|
||||
"/start ",
|
||||
"/srart",
|
||||
"/srart@copykar_2026_bot",
|
||||
"/strt",
|
||||
"/starrt",
|
||||
"/menu",
|
||||
"/menu@copykar_2026_bot",
|
||||
"منو",
|
||||
"منوی اصلی",
|
||||
"🏠 منوی اصلی",
|
||||
"شروع",
|
||||
"/start something",
|
||||
]
|
||||
for inp in valid_inputs:
|
||||
assert re.match(pattern, inp), f"Failed to match: {inp}"
|
||||
|
||||
invalid_inputs = [
|
||||
"/start123",
|
||||
"سلام",
|
||||
"/settings",
|
||||
"test",
|
||||
]
|
||||
for inp in invalid_inputs:
|
||||
assert not re.match(pattern, inp), f"Should not match: {inp}"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_repository_safe_bounds_without_db():
|
||||
repo = Repository()
|
||||
# Out of range integer bounds should return None without error
|
||||
assert await repo.get_source_website_by_id(-1) is None
|
||||
assert await repo.get_source_website_by_id(0) is None
|
||||
assert await repo.get_source_website_by_id(99999999999999) is None
|
||||
assert await repo.get_source_by_id(-5) is None
|
||||
assert await repo.get_target_by_id(-10) is None
|
||||
assert await repo.get_category_by_id(-1) is None
|
||||
assert await repo.get_admin_channel_by_id(0) is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_bot_safe_caption_formatting():
|
||||
repo = MagicMock()
|
||||
repo.get_source_website_by_id = AsyncMock(side_effect=Exception("Database connection down"))
|
||||
repo.get_source_by_channel_id = AsyncMock(side_effect=Exception("Database connection down"))
|
||||
bot = AdminBotService(repo=repo)
|
||||
|
||||
post = Post(
|
||||
id=1,
|
||||
source_channel_id=-900000001,
|
||||
source_message_id=10,
|
||||
raw_text="متن تستی",
|
||||
status="raw"
|
||||
)
|
||||
caption = await bot._format_raw_post_caption(post)
|
||||
assert "متن تستی" in caption
|
||||
assert "شناسه <code>-900000001</code>" in caption
|
||||
Reference in New Issue
Block a user