import hashlib import pytest from httpx import AsyncClient from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.contact_lead import ContactLead from app.models.member import MemberVerificationCode pytestmark = pytest.mark.asyncio async def test_public_contact_submission_creates_lead(client: AsyncClient, db_session: AsyncSession): response = await client.post( "/api/contact", json={ "name": "Jamie Smith", "email": "jamie@example.com", "phone": "021 333 4444", "services": ["Pack Walks", "Puppy Visits"], "petName": "Buddy", "petBreed": "Shih Tzu", "location": "Ponsonby", "serviceAreaStatus": "in_area", "message": "Interested in a meet and greet.", }, ) assert response.status_code == 201 body = response.json() assert body["status"] == "invite" assert body["email"] == "jamie@example.com" result = await db_session.execute(select(ContactLead).where(ContactLead.email == "jamie@example.com")) lead = result.scalars().first() assert lead is not None assert lead.requested_services == "Pack Walks, Puppy Visits" assert lead.pet_name == "Buddy" async def test_admin_can_invite_lead_into_onboarding(client: AsyncClient, admin_token: str): create_response = await client.post( "/api/contact", json={ "name": "Jamie Smith", "email": "jamie@example.com", "phone": "021 333 4444", "services": ["Pack Walks"], "petName": "Buddy", "petBreed": "Shih Tzu", "location": "Ponsonby", "message": "Interested in a meet and greet.", }, ) lead_id = create_response.json()["id"] invite_response = await client.post( f"/api/v1/admin/leads/{lead_id}/invite", headers={"Authorization": f"Bearer {admin_token}"}, json={"send_email": False}, ) assert invite_response.status_code == 200 body = invite_response.json() assert body["lead"]["status"] == "invited" assert body["member_status"] == "invited" assert body["member_id"] members_response = await client.get( "/api/v1/admin/members", headers={"Authorization": f"Bearer {admin_token}"}, ) assert any(member["email"] == "jamie@example.com" for member in members_response.json()) async def test_lead_becomes_converted_after_member_activation( client: AsyncClient, admin_token: str, db_session: AsyncSession, ): create_response = await client.post( "/api/contact", json={ "name": "Jamie Smith", "email": "jamie@example.com", "services": ["Pack Walks"], "petName": "Buddy", }, ) lead_id = create_response.json()["id"] invite_response = await client.post( f"/api/v1/admin/leads/{lead_id}/invite", headers={"Authorization": f"Bearer {admin_token}"}, json={"send_email": False}, ) member_id = invite_response.json()["member_id"] await client.post("/api/v1/members/claim/request", json={"email": "jamie@example.com"}) await db_session.execute( MemberVerificationCode.__table__.update() .values(code_hash=hashlib.sha256("AABBCC".encode()).hexdigest()) ) await db_session.commit() await client.post( "/api/v1/members/claim/complete", json={"email": "jamie@example.com", "code": "AABBCC", "password": "NewPass99!"}, ) await client.post( "/api/v1/members/auth/login", json={"email": "jamie@example.com", "password": "NewPass99!"}, ) await db_session.execute( MemberVerificationCode.__table__.update() .where(MemberVerificationCode.purpose == "login_2fa") .values(code_hash=hashlib.sha256("112233".encode()).hexdigest()) ) await db_session.commit() verify_response = await client.post( "/api/v1/members/auth/login/verify", json={"email": "jamie@example.com", "code": "112233"}, ) access_token = verify_response.json()["access_token"] await client.put( "/api/v1/members/onboarding", headers={"Authorization": f"Bearer {access_token}"}, json={"onboarding_data": {"dog_name": "Buddy"}, "complete_onboarding": True}, ) await client.post( "/api/v1/members/onboarding/contract", headers={"Authorization": f"Bearer {access_token}"}, json={"signer_name": "Jamie Smith", "agreed": True}, ) activate_response = await client.post( f"/api/v1/admin/members/{member_id}/activate", headers={"Authorization": f"Bearer {admin_token}"}, ) assert activate_response.status_code == 200 leads_response = await client.get( "/api/v1/admin/leads", headers={"Authorization": f"Bearer {admin_token}"}, ) matching_lead = next(lead for lead in leads_response.json() if lead["id"] == lead_id) assert matching_lead["status"] == "converted"