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

282 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
test_pdf_generator.py — Tests for negoT8 Deal Agreement PDF generation
Run from the project root:
cd /path/to/negot8
python test/test_pdf_generator.py
What this tests:
1. Freelance deal PDF — rich details (budget, scope, timeline, payment)
2. Marketplace (buy/sell) PDF — item, price, delivery
3. Minimal data — all optional fields absent; should still produce a valid PDF
4. Blockchain proof attached — real-looking proof dict
5. Mock blockchain proof — mock=True path
6. File is actually written to /tmp and is non-empty
7. Temp-file cleanup helper works
"""
import asyncio
import os
import sys
# ── Make sure backend/ is on the path ────────────────────────────────────────
BACKEND = os.path.join(os.path.dirname(__file__), "..", "backend")
sys.path.insert(0, os.path.abspath(BACKEND))
from tools.pdf_generator import generate_deal_pdf
# ─────────────────────────────────────────────────────────────────────────────
# Shared fixtures
# ─────────────────────────────────────────────────────────────────────────────
USER_A = {"id": 111111111, "name": "Alice Sharma", "username": "alice_s"}
USER_B = {"id": 222222222, "name": "Bob Chatterjee", "username": "bobchat"}
REAL_PROOF = {
"success": True,
"mock": False,
"tx_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"block_number": 87654321,
"agreement_hash": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
"explorer_url": "https://amoy.polygonscan.com/tx/0xabcdef1234567890",
"gas_used": 42000,
}
MOCK_PROOF = {
"success": True,
"mock": True,
"tx_hash": "0xMOCKTX1234567890",
"block_number": 0,
"agreement_hash": "0xMOCKHASH1234567890",
"explorer_url": "",
"gas_used": 0,
}
# ─────────────────────────────────────────────────────────────────────────────
# Test helpers
# ─────────────────────────────────────────────────────────────────────────────
def _check_pdf(path: str, label: str):
"""Assert the file exists, is non-empty, and starts with the PDF magic bytes."""
assert os.path.exists(path), f"[{label}] PDF file not found at {path}"
size = os.path.getsize(path)
assert size > 500, f"[{label}] PDF suspiciously small: {size} bytes"
with open(path, "rb") as f:
magic = f.read(4)
assert magic == b"%PDF", f"[{label}] File does not start with PDF magic bytes: {magic}"
print(f" ✅ [{label}] PDF OK — {size:,} bytes → {path}")
def _cleanup(path: str):
try:
os.remove(path)
except OSError:
pass
# ─────────────────────────────────────────────────────────────────────────────
# Individual tests
# ─────────────────────────────────────────────────────────────────────────────
async def test_freelance_full():
"""Freelance deal with full details + real blockchain proof."""
final_proposal = {
"summary": "Alice will build the dashboard in 3 weeks for Rs. 45,000 with 50% upfront.",
"details": {
"budget": "45000",
"timeline": "3 weeks",
"scope": ["Admin dashboard", "REST API integration", "Mobile responsive UI"],
"payment_schedule": "50% upfront (Rs. 22,500) + 50% on delivery",
"upfront": "22500",
"ip_ownership": "Full transfer to client on final payment",
},
}
preferences_a = {
"goal": "Build a dashboard",
"raw_details": {
"role": "freelancer", "skill": "React + FastAPI",
"rate": "1500", "hours": "30", "upfront_minimum": "50",
},
}
preferences_b = {
"goal": "Hire a developer",
"raw_details": {
"role": "client", "budget": "45000",
"required_features": ["Admin dashboard", "API", "Mobile UI"],
},
}
path = await generate_deal_pdf(
negotiation_id = "freelance_test_001",
feature_type = "freelance",
final_proposal = final_proposal,
user_a = USER_A,
user_b = USER_B,
rounds_taken = 4,
sat_a = 85.0,
sat_b = 78.0,
preferences_a = preferences_a,
preferences_b = preferences_b,
blockchain_proof = REAL_PROOF,
)
_check_pdf(path, "freelance_full")
_cleanup(path)
async def test_marketplace_full():
"""Buy/sell deal with full details + mock blockchain proof."""
final_proposal = {
"summary": "iPhone 14 sold for Rs. 52,000. Pickup at Andheri station on Saturday.",
"details": {
"agreed_price": "52000",
"delivery": "Pickup — Andheri West Metro station, Saturday 4 PM",
"condition": "Used — 6 months old, no scratches",
"market_price": "55000",
},
}
preferences_a = {
"goal": "Sell iPhone 14",
"raw_details": {
"role": "seller", "item": "iPhone 14 128GB Black",
"asking_price": "55000", "minimum_price": "50000",
},
}
preferences_b = {
"goal": "Buy iPhone 14",
"raw_details": {
"role": "buyer", "item": "iPhone 14",
"max_budget": "54000", "offer_price": "49000",
},
}
path = await generate_deal_pdf(
negotiation_id = "marketplace_test_001",
feature_type = "marketplace",
final_proposal = final_proposal,
user_a = USER_A,
user_b = USER_B,
rounds_taken = 3,
sat_a = 72.0,
sat_b = 88.0,
preferences_a = preferences_a,
preferences_b = preferences_b,
blockchain_proof = MOCK_PROOF,
)
_check_pdf(path, "marketplace_full")
_cleanup(path)
async def test_minimal_data():
"""Both feature_type is unknown and all optional fields are empty — should not crash."""
path = await generate_deal_pdf(
negotiation_id = "minimal_test_001",
feature_type = "generic",
final_proposal = {"summary": "Parties agreed to share expenses equally."},
user_a = {"id": 1, "name": "", "username": "userA"},
user_b = {"id": 2, "name": "", "username": "userB"},
rounds_taken = 1,
sat_a = 60.0,
sat_b = 60.0,
blockchain_proof = None,
)
_check_pdf(path, "minimal_data")
_cleanup(path)
async def test_no_blockchain_proof():
"""Freelance deal where blockchain proof hasn't been registered yet."""
final_proposal = {
"summary": "React Native app, Rs. 80,000, 6 weeks.",
"details": {
"budget": "80000",
"timeline": "6 weeks",
"scope": ["React Native app", "Backend API"],
},
}
path = await generate_deal_pdf(
negotiation_id = "noproof_test_001",
feature_type = "freelance",
final_proposal = final_proposal,
user_a = USER_A,
user_b = USER_B,
rounds_taken = 5,
sat_a = 90.0,
sat_b = 70.0,
blockchain_proof = None,
)
_check_pdf(path, "no_blockchain_proof")
_cleanup(path)
async def test_unicode_safe():
"""
Ensure the PDF builder doesn't crash on characters outside Latin-1
(Rs. symbol ₹, em-dashes, etc.).
"""
final_proposal = {
"summary": "₹45,000 deal — React dashboard — agreed ✓",
"details": {
"budget": "₹45,000",
"timeline": "3 weeks confirmed",
"scope": ["Dashboard • Admin panel", "API • REST"],
},
}
path = await generate_deal_pdf(
negotiation_id = "unicode_test_001",
feature_type = "freelance",
final_proposal = final_proposal,
user_a = {"id": 1, "name": "Anirbán Bāsak", "username": "anirban"},
user_b = {"id": 2, "name": "Rāhul Gupta", "username": "rahul"},
rounds_taken = 2,
sat_a = 88.0,
sat_b = 82.0,
blockchain_proof = MOCK_PROOF,
)
_check_pdf(path, "unicode_safe")
_cleanup(path)
# ─────────────────────────────────────────────────────────────────────────────
# Runner
# ─────────────────────────────────────────────────────────────────────────────
TESTS = [
("Freelance full details + real blockchain proof", test_freelance_full),
("Marketplace (buy/sell) + mock proof", test_marketplace_full),
("Minimal / sparse data — no crash", test_minimal_data),
("No blockchain proof yet", test_no_blockchain_proof),
("Unicode / special chars — Latin-1 safety", test_unicode_safe),
]
async def main():
print("\n🧪 negoT8 — PDF Generator Tests")
print("=" * 52)
passed = 0
failed = 0
for name, fn in TESTS:
print(f"\n{name}")
try:
await fn()
passed += 1
except Exception as exc:
import traceback
print(f" ❌ FAILED: {exc}")
traceback.print_exc()
failed += 1
print("\n" + "=" * 52)
print(f"Results: {passed} passed | {failed} failed | {len(TESTS)} total")
if failed == 0:
print("✅ All PDF tests passed!\n")
else:
print("❌ Some tests failed — see output above.\n")
sys.exit(1)
if __name__ == "__main__":
asyncio.run(main())