06/15 Phase 3 codes

This commit is contained in:
2026-06-15 14:19:14 -04:00
parent c2064b84b4
commit 60526262f0
20 changed files with 802 additions and 7 deletions
+151 -1
View File
@@ -37,6 +37,155 @@ def _csrf(html):
return re.search(r'name="csrf_token"[^>]*value="([^"]+)"', html).group(1)
def _phase3(app):
"""Phase 3: messaging, favorites, contact masking."""
from app.models.user import User
from app.models.plan import Plan
from app.models.category import Category
from app.models.listing import Listing
from app.models.messaging import Conversation, Message
from app.models.enums import TrustTier
from app.services import messaging as msvc
from app.services import favorites as fsvc
from app.services.contact import mask_body, contact_revealed, contact_density
with app.app_context():
# create a second user (buyer)
buyer = User(email="buyer@example.com", display_name="Buyer",
email_verified=True)
buyer.set_password("BuyerPass123")
buyer.tier_id = Plan.query.filter_by(slug="free").first().id
buyer.trust_tier = TrustTier.trusted # can see contact info
db.session.add(buyer); db.session.commit()
seller = User.query.filter_by(email="t@example.com").first()
fs = Category.query.filter_by(slug="for-sale").first()
listing = Listing.query.filter_by(user_id=seller.id).first()
# --- conversation create ---
conv, created = msvc.get_or_create_conversation(listing, buyer)
assert created and conv.buyer_id == buyer.id
assert conv.seller_id == seller.id
print("conversation created: ok")
# idempotent: get same conv again
conv2, created2 = msvc.get_or_create_conversation(listing, buyer)
assert conv2.id == conv.id and not created2
print("conversation idempotent: ok")
# seller can't message own listing
try:
msvc.get_or_create_conversation(listing, seller)
assert False, "should have raised"
except msvc.MessagingError:
pass
print("self-message blocked: ok")
# --- send message ---
msg1 = msvc.send_message(conv, buyer, "Hi, is this still available?")
assert msg1.sender_id == buyer.id and msg1.read_at is None
assert conv.last_message_at is not None
print("send message: ok")
# seller replies
msg2 = msvc.send_message(conv, seller, "Yes, come pick it up tomorrow.")
assert len(conv.messages) == 2
print("reply: ok")
# --- unread count ---
# buyer: 1 unread (seller's reply)
# seller: 1 unread (buyer's opening message)
unread_buyer = conv.unread_count(buyer)
unread_seller = conv.unread_count(seller)
assert unread_buyer == 1, unread_buyer
assert unread_seller == 1, unread_seller
print(f"unread count: ok (buyer={unread_buyer}, seller={unread_seller})")
# --- mark read ---
msvc.mark_conversation_read(conv, buyer)
assert conv.unread_count(buyer) == 0
print("mark read: ok")
# --- total unread ---
total = msvc.total_unread(seller)
assert total >= 1
print(f"total unread: ok (seller sees {total})")
# --- inbox ---
page = msvc.inbox(buyer)
assert any(c.id == conv.id for c in page.items)
print("inbox: ok")
# --- contact masking ---
spammy = "Call me at 703-555-0192 or email me@test.com to buy!"
assert contact_density(spammy) == 2
masked = mask_body(spammy, reveal=False)
assert "703" not in masked and "me@test.com" not in masked
revealed = mask_body(spammy, reveal=True)
assert "703-555-0192" in revealed
print("contact masking: ok")
# trusted user can reveal; new-trust cannot
assert contact_revealed(buyer) # trust_tier=trusted + email_verified
seller.trust_tier = TrustTier.new
db.session.commit()
assert not contact_revealed(seller)
print("contact reveal gating by trust tier: ok")
# --- favorites ---
now_fav = fsvc.toggle_favorite(buyer.id, listing.id)
assert now_fav
assert fsvc.is_favorited(buyer.id, listing.id)
page = fsvc.user_favorites(buyer.id)
assert any(f.listing_id == listing.id for f in page.items)
print("favorite add: ok")
now_fav = fsvc.toggle_favorite(buyer.id, listing.id)
assert not now_fav
assert not fsvc.is_favorited(buyer.id, listing.id)
print("favorite remove: ok")
# --- empty body rejected ---
try:
msvc.send_message(conv, buyer, " ")
assert False
except msvc.MessagingError:
pass
print("empty message rejected: ok")
# --- route render checks ---
import re
c = app.test_client(); B = "https://localhost"
def csrf(h): return re.search(r'name="csrf_token"[^>]*value="([^"]+)"', h).group(1)
# login as buyer
r = c.get("/auth/login", base_url=B)
tok = csrf(r.get_data(as_text=True))
c.post("/auth/login", base_url=B,
data={"csrf_token": tok, "email": "buyer@example.com",
"password": "BuyerPass123"},
headers={"Referer": B + "/auth/login"}, follow_redirects=True)
with app.app_context():
conv_id = Conversation.query.filter_by(
buyer_id=User.query.filter_by(email="buyer@example.com").first().id
).first().id
listing_id = Listing.query.first().id
for path, expect in [
("/messages", 200),
(f"/messages/{conv_id}", 200),
("/my/favorites", 200),
(f"/listings/{listing_id}/contact", 200),
]:
code = c.get(path, base_url=B).status_code
assert code == expect, f"{path} -> {code}"
print(f"{code} {path}")
print("Phase 3 route renders: ok")
def run():
app = create_app()
with app.app_context():
@@ -133,6 +282,7 @@ def run():
print("home / healthz / 404: ok")
_phase2(app)
_phase3(app)
print("\nALL SMOKE CHECKS PASSED")
@@ -241,7 +391,7 @@ def _phase2(app):
user_id=user.id, status=ListingStatus.active).count()
assert n == 1, n
assert active_status_after == active_status_before - 1
assert Listing.query.get(l2.id).status == ListingStatus.expired
assert db.session.get(Listing, l2.id).status == ListingStatus.expired
print(f"expiry sweep: ok (status active {active_status_before}->{active_status_after})")