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