mirror of
https://github.com/arkorty/B.Tech-Project-III.git
synced 2026-04-19 12:41:48 +00:00
270 lines
11 KiB
Python
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())
|