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

413 lines
20 KiB
Python

"""
Milestone 5 — Full Feature Test Suite
Run from backend/ directory:
cd backend && python ../test/test_milestone5_all.py
Tests all 8 feature modules locally (no Telegram) against live Gemini + Tavily APIs.
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/backend")
import asyncio
import json
PASS = ""
FAIL = ""
SKIP = ""
results = {}
# ─── Test helpers ─────────────────────────────────────────────────────────────
def check(name: str, condition: bool, detail: str = ""):
icon = PASS if condition else FAIL
results[name] = condition
print(f" {icon} {name}" + (f": {detail}" if detail else ""))
# ─── 0. Database init ─────────────────────────────────────────────────────────
async def test_database():
print("\n── Test 0: Database Init ──")
from database import init_db
try:
await init_db()
check("init_db runs", True)
except Exception as e:
check("init_db runs", False, str(e))
from database import (
create_user, get_user, create_negotiation,
add_participant, get_rounds, store_analytics, get_analytics
)
await create_user(99999, "testuser", "Test User")
user = await get_user(99999)
check("create_user + get_user", user is not None)
neg_id = await create_negotiation("expenses", 99999)
check("create_negotiation", len(neg_id) > 0, f"id={neg_id}")
await add_participant(neg_id, 99999, {"goal": "test"}, "balanced")
check("add_participant", True)
rows = await get_rounds(neg_id)
check("get_rounds returns list", isinstance(rows, list))
await store_analytics({
"negotiation_id": neg_id,
"satisfaction_timeline": "[]",
"concession_log": "[]",
"fairness_score": 80.0,
"total_concessions_a": 2,
"total_concessions_b": 1,
})
analytics = await get_analytics(neg_id)
check("store + get analytics", analytics is not None and analytics.get("fairness_score") == 80.0)
# ─── 1. Feature context fetching ─────────────────────────────────────────────
async def test_feature_contexts():
print("\n── Test 1: Feature Context Fetching ──")
from features.base_feature import get_feature
PREFS = {
"scheduling": (
{"raw_details": {"available_windows": ["Monday 10am-12pm", "Wednesday 3-5pm"], "location": "Bandra"}, "goal": "Schedule a coffee meeting", "constraints": [], "preferences": [], "tone": "friendly"},
{"raw_details": {"available_windows": ["Monday 11am-1pm", "Friday 2-4pm"]}, "goal": "Find a time to meet", "constraints": [], "preferences": [], "tone": "friendly"},
),
"expenses": (
{"raw_details": {"expenses": [{"name": "Hotel", "amount": 12000}, {"name": "Fuel", "amount": 3000}], "upi_id": "rahul@paytm"}, "goal": "Split trip expenses", "constraints": [], "preferences": [], "tone": "friendly"},
{"raw_details": {"expenses": [{"name": "Dinner", "amount": 2000}]}, "goal": "Fair expense split", "constraints": [], "preferences": [], "tone": "friendly"},
),
"collaborative": (
{"raw_details": {"cuisine": "Italian", "location": "Bandra", "budget": 800}, "goal": "Pick dinner spot", "constraints": [], "preferences": [], "tone": "friendly"},
{"raw_details": {"cuisine": "Chinese", "location": "Bandra", "budget": 600}, "goal": "Restaurant for tonight", "constraints": [], "preferences": [], "tone": "friendly"},
),
"marketplace": (
{"raw_details": {"item": "PS5 with 2 controllers", "asking_price": 35000, "minimum_price": 30000, "role": "seller", "upi_id": "seller@upi"}, "goal": "Sell PS5", "constraints": [], "preferences": [], "tone": "firm"},
{"raw_details": {"item": "PS5", "budget": 28000, "role": "buyer"}, "goal": "Buy PS5", "constraints": [], "preferences": [], "tone": "friendly"},
),
"freelance": (
{"raw_details": {"skill": "React developer", "rate": 1500, "hours": 40, "upfront_minimum": "50"}, "goal": "Freelance React project", "constraints": [{"value": "minimum rate ₹1500/hr", "hard": True}], "preferences": [], "tone": "professional"},
{"raw_details": {"budget": 40000, "project_type": "web app", "required_features": ["auth", "dashboard", "API"], "role": "client"}, "goal": "Build web app in budget", "constraints": [{"value": "budget max ₹40000", "hard": True}], "preferences": [], "tone": "professional"},
),
"roommate": (
{"raw_details": {"decision_type": "wifi plan", "city": "Mumbai", "budget": 600}, "goal": "Pick WiFi plan", "constraints": [], "preferences": [], "tone": "friendly"},
{"raw_details": {"decision_type": "wifi plan", "city": "Mumbai", "budget": 700}, "goal": "Fast internet within budget", "constraints": [], "preferences": [], "tone": "friendly"},
),
"trip": (
{"raw_details": {"available_dates": ["March 15-17", "March 22-24"], "budget": 5000, "destination_preference": "beach", "origin": "Mumbai"}, "goal": "Weekend beach trip", "constraints": [], "preferences": [], "tone": "excited"},
{"raw_details": {"available_dates": ["March 15-17", "April 5-7"], "budget": 4000, "destination_preference": "hills", "origin": "Mumbai"}, "goal": "Weekend getaway", "constraints": [], "preferences": [], "tone": "friendly"},
),
"conflict": (
{"raw_details": {"conflict_type": "parking spot", "position": "I need the spot Mon-Wed", "relationship_importance": "high"}, "goal": "Resolve parking dispute", "constraints": [], "preferences": [], "tone": "firm"},
{"raw_details": {"conflict_type": "parking spot", "position": "I need the spot Tue-Thu", "relationship_importance": "high"}, "goal": "Fair parking arrangement", "constraints": [], "preferences": [], "tone": "friendly"},
),
}
for feature_type, (prefs_a, prefs_b) in PREFS.items():
try:
feat = get_feature(feature_type)
ctx = await feat.get_context(prefs_a, prefs_b)
check(f"{feature_type}.get_context", len(ctx) > 50, f"{len(ctx)} chars")
except Exception as e:
check(f"{feature_type}.get_context", False, str(e))
# ─── 2. Full negotiation for each feature (live Gemini) ──────────────────────
async def test_negotiation_feature(feature_type: str, prefs_a: dict, prefs_b: dict, check_fn=None):
"""Run a full negotiation for one feature and verify the result."""
from agents.negotiation import run_negotiation
from features.base_feature import get_feature
import database as db
feat = get_feature(feature_type)
try:
feature_context = await feat.get_context(prefs_a, prefs_b)
except Exception:
feature_context = ""
neg_id = await db.create_negotiation(feature_type, 99901)
await db.add_participant(neg_id, 99901, prefs_a)
await db.add_participant(neg_id, 99902, prefs_b)
resolution = await run_negotiation(
negotiation_id=neg_id,
preferences_a=prefs_a,
preferences_b=prefs_b,
user_a_id=99901,
user_b_id=99902,
feature_type=feature_type,
personality_a="balanced",
personality_b="balanced",
feature_context=feature_context,
)
check(f"{feature_type} negotiation completes", resolution is not None)
check(f"{feature_type} has status", resolution.get("status") in ("resolved", "escalated"),
resolution.get("status"))
check(f"{feature_type} has rounds_taken", isinstance(resolution.get("rounds_taken"), int),
str(resolution.get("rounds_taken")))
check(f"{feature_type} has final_proposal", isinstance(resolution.get("final_proposal"), dict))
# Feature-specific checks
if check_fn:
check_fn(resolution, prefs_a, prefs_b)
# Verify format_resolution produces non-empty string
try:
formatted = feat.format_resolution(resolution, prefs_a, prefs_b)
check(f"{feature_type} format_resolution non-empty", len(formatted) > 30, f"{len(formatted)} chars")
print(f"\n 📄 Formatted resolution preview:\n {formatted[:200].replace(chr(10), chr(10)+' ')}\n")
except Exception as e:
check(f"{feature_type} format_resolution", False, str(e))
return resolution
async def test_all_negotiations():
print("\n── Test 2: Full Negotiations (live Gemini + Tavily) ──")
print(" (This may take 2-4 minutes due to API rate limits)\n")
from database import init_db
await init_db()
# ── Feature 1: Scheduling ──
print(" [1/8] Scheduling...")
await test_negotiation_feature(
"scheduling",
{"goal": "Schedule a coffee meeting", "constraints": [], "preferences": [],
"tone": "friendly", "feature_type": "scheduling",
"raw_details": {"available_windows": ["Monday 10am-12pm", "Wednesday 3-5pm"], "location": "Bandra", "duration": "1 hour"}},
{"goal": "Coffee meeting next week", "constraints": [], "preferences": [],
"tone": "friendly", "feature_type": "scheduling",
"raw_details": {"available_windows": ["Monday 11am-1pm", "Wednesday 4-6pm"]}},
)
# ── Feature 2: Expenses ──
print(" [2/8] Expenses...")
def check_expense_math(resolution, prefs_a, prefs_b):
# Verify the resolution doesn't hallucinate wrong math
final = resolution.get("final_proposal", {})
details = final.get("details", {})
# At minimum, the proposal should exist
check("expenses has details", bool(details), str(details)[:80])
await test_negotiation_feature(
"expenses",
{"goal": "Split Goa trip expenses fairly", "constraints": [], "preferences": [],
"tone": "friendly", "feature_type": "expenses",
"raw_details": {"expenses": [{"name": "Hotel", "amount": 12000}, {"name": "Fuel", "amount": 3000}], "upi_id": "rahul@paytm"}},
{"goal": "Fair expense split", "constraints": [], "preferences": [],
"tone": "friendly", "feature_type": "expenses",
"raw_details": {"expenses": [{"name": "Dinner", "amount": 2000}]}},
check_fn=check_expense_math,
)
# ── Feature 3: Collaborative ──
print(" [3/8] Collaborative (uses Tavily)...")
def check_collaborative(resolution, prefs_a, prefs_b):
final = resolution.get("final_proposal", {})
summary = final.get("summary", "")
check("collaborative has venue recommendation", bool(summary), summary[:60])
await test_negotiation_feature(
"collaborative",
{"goal": "Pick a restaurant for dinner", "constraints": [], "preferences": [],
"tone": "friendly", "feature_type": "collaborative",
"raw_details": {"cuisine": "Italian", "location": "Bandra Mumbai", "budget": 800}},
{"goal": "Dinner somewhere nice", "constraints": [], "preferences": [],
"tone": "friendly", "feature_type": "collaborative",
"raw_details": {"cuisine": "Chinese", "location": "Bandra Mumbai", "budget": 600}},
check_fn=check_collaborative,
)
# ── Feature 4: Marketplace ──
print(" [4/8] Marketplace (uses Tavily)...")
def check_marketplace(resolution, prefs_a, prefs_b):
final = resolution.get("final_proposal", {})
details = final.get("details", {})
price = details.get("agreed_price") or details.get("price") or details.get("final_price") or ""
reasoning = resolution.get("summary", "")
check("marketplace has price", bool(price) or bool(reasoning), f"price={price}")
await test_negotiation_feature(
"marketplace",
{"goal": "Sell my PS5", "constraints": [{"value": "minimum ₹30000", "hard": True}],
"preferences": [], "tone": "firm", "feature_type": "marketplace",
"raw_details": {"item": "PS5 with 2 controllers", "asking_price": 35000, "minimum_price": 30000, "role": "seller", "upi_id": "seller@upi"}},
{"goal": "Buy a PS5 in budget", "constraints": [{"value": "budget max ₹28000", "hard": True}],
"preferences": [], "tone": "friendly", "feature_type": "marketplace",
"raw_details": {"item": "PS5", "budget": 28000, "role": "buyer", "max_budget": 28000}},
check_fn=check_marketplace,
)
# ── Feature 5: Freelance ──
print(" [5/8] Freelance (uses Tavily + Calculator)...")
def check_freelance(resolution, prefs_a, prefs_b):
final = resolution.get("final_proposal", {})
details = final.get("details", {})
budget = details.get("budget") or details.get("agreed_budget") or details.get("price") or ""
check("freelance has budget in proposal", bool(budget) or bool(details), f"budget={budget}")
await test_negotiation_feature(
"freelance",
{"goal": "Get paid fairly for React project", "constraints": [{"value": "min rate ₹1500/hr", "hard": True}],
"preferences": [], "tone": "professional", "feature_type": "freelance",
"raw_details": {"skill": "React developer", "rate": 1500, "hours": 40, "upfront_minimum": "50"}},
{"goal": "Build web app within ₹40k budget", "constraints": [{"value": "budget max ₹40000", "hard": True}],
"preferences": [], "tone": "professional", "feature_type": "freelance",
"raw_details": {"budget": 40000, "project_type": "web app", "required_features": ["auth", "dashboard", "API"], "role": "client"}},
check_fn=check_freelance,
)
# ── Feature 6: Roommate ──
print(" [6/8] Roommate (uses Tavily for real plans)...")
def check_roommate(resolution, prefs_a, prefs_b):
final = resolution.get("final_proposal", {})
summary = final.get("summary", "")
check("roommate has decision", bool(summary), summary[:60])
await test_negotiation_feature(
"roommate",
{"goal": "Pick a shared WiFi plan", "constraints": [], "preferences": [],
"tone": "friendly", "feature_type": "roommate",
"raw_details": {"decision_type": "wifi plan", "city": "Mumbai", "budget": 600}},
{"goal": "Fast internet within budget", "constraints": [], "preferences": [],
"tone": "friendly", "feature_type": "roommate",
"raw_details": {"decision_type": "wifi plan", "city": "Mumbai", "budget": 700}},
check_fn=check_roommate,
)
# ── Feature 7: Conflict ──
print(" [7/8] Conflict Resolution...")
await test_negotiation_feature(
"conflict",
{"goal": "Resolve parking spot dispute", "constraints": [], "preferences": [],
"tone": "firm", "feature_type": "conflict",
"raw_details": {"conflict_type": "parking spot", "position": "I need Mon-Wed", "relationship_importance": "high"}},
{"goal": "Fair parking arrangement", "constraints": [], "preferences": [],
"tone": "friendly", "feature_type": "conflict",
"raw_details": {"conflict_type": "parking spot", "position": "I need Tue-Thu", "relationship_importance": "high"}},
)
# ── Feature 8: Trip (Group Negotiation) ──
print(" [8/8] Trip Planning (group negotiation with 3 preferences)...")
from features.trip import run_group_negotiation
from database import init_db as _init
all_prefs = [
{"goal": "Beach weekend trip", "constraints": [], "preferences": [],
"tone": "excited", "feature_type": "trip",
"raw_details": {"available_dates": ["March 15-17", "March 22-24"], "budget": 5000, "destination_preference": "beach", "origin": "Mumbai"}},
{"goal": "Weekend trip with friends", "constraints": [], "preferences": [],
"tone": "friendly", "feature_type": "trip",
"raw_details": {"available_dates": ["March 15-17", "April 5-7"], "budget": 4000, "destination_preference": "hills", "origin": "Mumbai"}},
{"goal": "Budget getaway", "constraints": [], "preferences": [],
"tone": "friendly", "feature_type": "trip",
"raw_details": {"available_dates": ["March 15-17", "March 29-31"], "budget": 3500, "destination_preference": "any", "origin": "Mumbai"}},
]
from database import create_negotiation, add_participant
trip_neg_id = await create_negotiation("trip", 99901)
for i, p in enumerate(all_prefs):
await add_participant(trip_neg_id, 99901 + i, p)
trip_resolution = {"status": None}
async def on_trip_round(data):
print(f" Round {data['round_number']}: {data['action']} | avg_sat={data['satisfaction_score']:.0f}")
async def on_trip_resolve(data):
trip_resolution["status"] = data.get("status")
trip_resolution["summary"] = data.get("summary", "")
trip_resolution["data"] = data
await run_group_negotiation(
negotiation_id=trip_neg_id,
all_preferences=all_prefs,
all_user_ids=[99901, 99902, 99903],
feature_type="trip",
personalities=["balanced", "empathetic", "balanced"],
on_round_update=on_trip_round,
on_resolution=on_trip_resolve,
)
check("trip group negotiation completes", trip_resolution["status"] in ("resolved", "escalated"),
trip_resolution.get("status"))
check("trip has summary", bool(trip_resolution.get("summary")))
from features.trip import TripFeature
if trip_resolution.get("data"):
try:
formatted = TripFeature().format_resolution(trip_resolution["data"], all_prefs[0], all_prefs[1])
check("trip format_resolution", len(formatted) > 30, f"{len(formatted)} chars")
except Exception as e:
check("trip format_resolution", False, str(e))
# ─── 3. Dispatcher test ───────────────────────────────────────────────────────
async def test_dispatcher():
print("\n── Test 3: Feature Dispatcher ──")
from features.base_feature import get_feature
from features.scheduling import SchedulingFeature
from features.expenses import ExpensesFeature
from features.collaborative import CollaborativeFeature
from features.marketplace import MarketplaceFeature
from features.freelance import FreelanceFeature
from features.roommate import RoommateFeature
from features.trip import TripFeature
from features.conflict import ConflictFeature
from features.generic import GenericFeature
for feature_type, expected_cls in [
("scheduling", SchedulingFeature), ("expenses", ExpensesFeature),
("collaborative", CollaborativeFeature), ("marketplace", MarketplaceFeature),
("freelance", FreelanceFeature), ("roommate", RoommateFeature),
("trip", TripFeature), ("conflict", ConflictFeature),
("generic", GenericFeature), ("unknown_xyz", GenericFeature),
]:
feat = get_feature(feature_type)
check(f"get_feature('{feature_type}')", isinstance(feat, expected_cls))
# ─── Summary ─────────────────────────────────────────────────────────────────
async def main():
print("=" * 60)
print(" negoT8 — Milestone 5 Test Suite")
print("=" * 60)
await test_database()
await test_dispatcher()
await test_feature_contexts()
# Full negotiations use live Gemini + Tavily — skip if you want a fast run
await test_all_negotiations()
print("\n" + "=" * 60)
total = len(results)
passed = sum(1 for v in results.values() if v)
failed = total - passed
print(f" RESULTS: {passed}/{total} passed" + (f" | {failed} FAILED" if failed else " ✅ All passed!"))
print("=" * 60)
if failed:
print("\nFailed tests:")
for name, status in results.items():
if not status:
print(f"{name}")
sys.exit(1)
if __name__ == "__main__":
asyncio.run(main())