mirror of
https://github.com/arkorty/B.Tech-Project-III.git
synced 2026-04-19 20:51:49 +00:00
init
This commit is contained in:
269
negot8/test/test_pending_flow.py
Normal file
269
negot8/test/test_pending_flow.py
Normal file
@@ -0,0 +1,269 @@
|
||||
"""
|
||||
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())
|
||||
Reference in New Issue
Block a user