Files
B.Tech-Project-III/negot8/test/test_pending_flow.py
2026-04-05 00:43:23 +05:30

270 lines
11 KiB
Python

"""
test_pending_flow.py — Success test for Step 5: /pending counterparty flow
Tests (no live Telegram connection needed):
✅ 1. pending_coordinations dict is populated when User A coordinates
✅ 2. /pending lookup by counterparty username returns correct entries
✅ 3. Personality is stored in the pending entry
✅ 4. Accepting a request marks it as "accepted" and stores neg_id in user_data
✅ 5. Declining a request removes it from pending_coordinations
✅ 6. receive_counterparty_preferences persists BOTH participants in DB
✅ 7. Both participants have personality_used saved in participants table
✅ 8. Negotiation is created in DB with correct feature_type
✅ 9. Bot module imports cleanly (all handlers registered)
✅ 10. send_to_user silently fails when no bots are registered
Run from project root:
cd e:\\negot8
python test/test_pending_flow.py
"""
import sys
import os
import asyncio
import json
# ─── Path setup ───
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BACKEND = os.path.join(ROOT, "backend")
BOTS_DIR = os.path.join(BACKEND, "telegram-bots")
sys.path.insert(0, BACKEND)
sys.path.insert(0, BOTS_DIR)
# ════════════════════════════════════════════════
# Helpers
# ════════════════════════════════════════════════
PASS = ""
FAIL = ""
results: list[tuple[str, str, str]] = [] # (status, name, detail)
def record(ok: bool, name: str, detail: str = ""):
status = PASS if ok else FAIL
results.append((status, name, detail))
print(f" {status} {name}" + (f" [{detail}]" if detail else ""))
# ════════════════════════════════════════════════
# Test 1 — Module import & handler registration
# ════════════════════════════════════════════════
def test_module_import():
print("\n── Test 1: Bot module imports cleanly ──")
try:
import bot # noqa: F401
record(True, "bot.py imports without errors")
except Exception as e:
record(False, "bot.py import", str(e))
return None
try:
from bot import (
pending_coordinations, bot_apps, register_bot, send_to_user,
pending_command, accept_pending_callback,
receive_counterparty_preferences, run_negotiation_with_telegram_updates,
create_bot, AWAITING_PREFERENCES, AWAITING_COUNTERPARTY_PREFS,
)
record(True, "All new symbols exported from bot.py")
return True
except ImportError as e:
record(False, "Symbol import check", str(e))
return False
# ════════════════════════════════════════════════
# Test 2 — pending_coordinations dict logic
# ════════════════════════════════════════════════
async def test_pending_dict_logic():
print("\n── Test 2: pending_coordinations dict logic ──")
import bot
# Reset shared dict for a clean test
bot.pending_coordinations.clear()
NEG_ID = "test1234"
PREFS_A = {"feature_type": "scheduling", "goal": "coffee next week",
"constraints": [], "preferences": []}
PERSONALITY = "aggressive"
# Simulate User A calling /coordinate and storing in dict (mirrors receive_preferences)
bot.pending_coordinations[NEG_ID] = {
"negotiation_id": NEG_ID,
"counterparty_username": "bob", # lowercase username
"initiator_id": 111,
"initiator_name": "Alice",
"feature_type": "scheduling",
"preferences_a": PREFS_A,
"personality_a": PERSONALITY,
"status": "pending",
}
# /pending lookup: User B (bob) checks for their requests
username_b = "bob"
matching = {
nid: d for nid, d in bot.pending_coordinations.items()
if d.get("counterparty_username", "").lower() == username_b
and d.get("status") == "pending"
}
record(len(matching) == 1, "/pending finds the request by counterparty username",
f"found {len(matching)} entry(ies)")
# Personality is stored
entry = matching[NEG_ID]
record(entry.get("personality_a") == PERSONALITY, "Personality_a stored in pending dict",
entry.get("personality_a"))
# Accept: mark as accepted, simulating accept_pending_callback
bot.pending_coordinations[NEG_ID]["status"] = "accepted"
still_pending = {
nid: d for nid, d in bot.pending_coordinations.items()
if d.get("counterparty_username", "").lower() == username_b
and d.get("status") == "pending"
}
record(len(still_pending) == 0,
"After accept, /pending no longer shows the same request")
# Decline: should remove entry
bot.pending_coordinations["test9999"] = {
"negotiation_id": "test9999",
"counterparty_username": "carol",
"initiator_id": 222,
"initiator_name": "Dave",
"feature_type": "expenses",
"preferences_a": {},
"personality_a": "balanced",
"status": "pending",
}
bot.pending_coordinations.pop("test9999", None)
record("test9999" not in bot.pending_coordinations, "Declined request removed from dict")
# ════════════════════════════════════════════════
# Test 3 — Database: both participants + personality persisted
# ════════════════════════════════════════════════
async def test_db_persistence():
print("\n── Test 3: DB persistence of both participants ──")
import database as db
await db.init_db()
NEG_ID = "pendtest1"
USER_A = 10001
USER_B = 10002
PREFS_A = {"feature_type": "scheduling", "goal": "coffee meeting",
"constraints": [], "preferences": []}
PREFS_B = {"feature_type": "scheduling", "goal": "afternoon slot",
"constraints": [], "preferences": []}
# Ensure users exist
await db.create_user(USER_A, "alice_test", "Alice")
await db.create_user(USER_B, "bob_test", "Bob")
# Create negotiation + add both participants (mirrors the full flow)
await db.create_negotiation.__wrapped__(NEG_ID, "scheduling", USER_A) \
if hasattr(db.create_negotiation, "__wrapped__") else None
# Use raw DB insert for test isolation
import aiosqlite
from config import DATABASE_PATH
async with aiosqlite.connect(DATABASE_PATH) as conn:
await conn.execute(
"INSERT OR REPLACE INTO negotiations (id, feature_type, initiator_id) VALUES (?, ?, ?)",
(NEG_ID, "scheduling", USER_A)
)
await conn.commit()
await db.add_participant(NEG_ID, USER_A, PREFS_A, personality_used="aggressive")
await db.add_participant(NEG_ID, USER_B, PREFS_B, personality_used="empathetic")
participants = await db.get_participants(NEG_ID)
record(len(participants) == 2, "Both participants stored in DB", f"count={len(participants)}")
personalities = {dict(p)["user_id"]: dict(p)["personality_used"] for p in participants}
record(personalities.get(USER_A) == "aggressive",
"User A personality_used='aggressive' persisted", str(personalities.get(USER_A)))
record(personalities.get(USER_B) == "empathetic",
"User B personality_used='empathetic' persisted", str(personalities.get(USER_B)))
# Cleanup
import aiosqlite
from config import DATABASE_PATH
async with aiosqlite.connect(DATABASE_PATH) as conn:
await conn.execute("DELETE FROM participants WHERE negotiation_id = ?", (NEG_ID,))
await conn.execute("DELETE FROM negotiations WHERE id = ?", (NEG_ID,))
await conn.commit()
# ════════════════════════════════════════════════
# Test 4 — send_to_user fails gracefully when no bots registered
# ════════════════════════════════════════════════
async def test_send_to_user_graceful():
print("\n── Test 4: send_to_user graceful failure ──")
import bot
bot.bot_apps.clear() # No bots registered
result = await bot.send_to_user(99999, "test message")
record(result is False, "send_to_user returns False when no bots are registered")
# ════════════════════════════════════════════════
# Test 5 — Conversation state constants are correct
# ════════════════════════════════════════════════
def test_conversation_states():
print("\n── Test 5: ConversationHandler state constants ──")
from bot import AWAITING_PREFERENCES, AWAITING_COUNTERPARTY_PREFS
record(AWAITING_PREFERENCES == 1,
"AWAITING_PREFERENCES == 1", str(AWAITING_PREFERENCES))
record(AWAITING_COUNTERPARTY_PREFS == 2,
"AWAITING_COUNTERPARTY_PREFS == 2", str(AWAITING_COUNTERPARTY_PREFS))
record(AWAITING_PREFERENCES != AWAITING_COUNTERPARTY_PREFS,
"States are distinct (no collision)")
# ════════════════════════════════════════════════
# Main runner
# ════════════════════════════════════════════════
async def run_all():
ok = test_module_import()
if ok is False:
print("\n⛔ Aborting — bot.py failed to import. Fix import errors first.")
return
await test_pending_dict_logic()
await test_db_persistence()
await test_send_to_user_graceful()
test_conversation_states()
# ── Summary ──
passed = sum(1 for s, _, _ in results if s == PASS)
total = len(results)
print("\n" + "=" * 60)
print(f" PENDING FLOW TESTS: {passed}/{total} passed")
print("=" * 60)
if passed == total:
print("\n🏆 ALL TESTS PASSED — /pending counterparty flow is ready!\n")
print("Next steps (live Telegram test):")
print(" 1. Run: python test/bot_runner_test.py")
print(" 2. Phone A: /coordinate @PhoneB_username")
print(" 3. Phone A: describe your preferences")
print(" 4. Phone B: /pending → tap Accept")
print(" 5. Phone B: describe their preferences")
print(" 6. Watch agents negotiate live on both phones! 🤖↔️🤖\n")
else:
failed = [(name, detail) for s, name, detail in results if s == FAIL]
print(f"\n⚠️ {total - passed} test(s) failed:")
for name, detail in failed:
print(f"{name}: {detail}")
print()
if __name__ == "__main__":
asyncio.run(run_all())