
    pio                    H   d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
mZmZ ddlmZmZ ddlmZ ddlmZmZ ddlmZ ddlZ ej0                  d	      Z ej4                  ej6                  d
       e G d d             Ze G d d             Z G d de      Z G d de      Zd#dZ  G d de      Z! G d de      Z" G d de      Z#d$d%dZ$	 	 	 d&	 	 	 	 	 	 	 	 	 	 	 d'dZ%d(dZ&d)d*dZ' G d d e      Z(d+d!Z)e*d"k(  r ejV                   e)              yy),u*  
Email Agent — Programmable Email Inboxes for E2E Testing
==========================================================
Provides AI agents with temporary email inboxes for end-to-end testing.
Supports MailSlurp, AgentMail, and Inbucket (self-hosted) backends
via a unified async interface.

Usage:
    provider = get_email_provider()
    inbox = await provider.create_inbox()
    email = await wait_for_email(provider, inbox.id, subject_contains="Verify")
    link = extract_verification_link(email.html)

Run self-test:
    python testing/email_agent.py
    )annotationsN)ABCabstractmethod)	dataclassfield)
HTMLParser)ListOptional)urlparseemail_agentu&   %(levelname)s %(name)s — %(message)s)levelformatc                  Z    e Zd ZU dZded<   ded<   ded<   dZded<    ee	      Zd
ed<   y)InboxzA temporary email inbox.stridemail_addressproviderNOptional[str]
created_atdefault_factorydictextra)	__name__
__module____qualname____doc____annotations__r   r   r   r        ,/mnt/e/genesis-system/testing/email_agent.pyr   r   ,   s.    "GM $J$-E4-r!   r   c                      e Zd ZU dZded<   ded<   ded<   ded<   ded<   d	ed
<   dZd	ed<   dZd	ed<    ee      Z	ded<   y)Emailz&An email message received in an inbox.r   r   inbox_idsubjectfrom_address	List[str]tor   received_atNhtmltextr   r   r   )
r   r   r   r   r   r+   r,   r   r   r   r    r!   r"   r$   r$   6   sH    0GMLMD-D--E4-r!   r$   c                  v    e Zd ZdZed	d
d       Zedd       Zedd       Zedd       Zedd       Z	d	ddZ
y)EmailProviderz.Abstract base class for email inbox providers.Nc                   K   yw)z+Create a new temporary inbox and return it.Nr    selfnames     r"   create_inboxzEmailProvider.create_inboxK         	   c                   K   yw)zAReturn all emails currently in the inbox (summary, no full body).Nr    r1   r%   s     r"   
get_emailszEmailProvider.get_emailsP   r4   r5   c                   K   yw)z9Fetch full content (HTML + text body) for a single email.Nr    r1   r%   email_ids      r"   get_email_contentzEmailProvider.get_email_contentU   r4   r5   c                   K   yw)z3Return all hyperlinks found in the email HTML body.Nr    r:   s      r"   extract_linkszEmailProvider.extract_linksZ   r4   r5   c                   K   yw)z?Delete the inbox and all its messages. Returns True on success.Nr    r7   s     r"   delete_inboxzEmailProvider.delete_inbox_   r4   r5   c                   g d}g d}t        j                  |      }t        j                  |      }t        j                  dd      }| d| | | | | | d| | |d    | | g}t        j                  |      }|xs d}	| d	|	 S )
zIGenerate a realistic-looking human email address for the provider domain.)jamesemmaoliveravawilliamsophianoahisabellaliammiaethan	charlottemasonameliajacobharpermichaelevelynjackabigail)smithjohnsonwilliamsjonesbrowndavismillerwilsonmooretaylorandersonthomasjacksonwhiteharrismartinthompsongarciamartinezrobinson
   c   ._r   	gmail.com@)randomchoicerandint)
r1   domainfirst_names
last_namesfirstlastsuffix
separatorslocaltarget_domains
             r"   create_human_emailz EmailProvider.create_human_emaild   s    




 k*}}Z(B'qvh/E74&1Iqvh/E!H:dVF81LN
j)-+-))r!   Nr2   r   returnr   r%   r   r   zList[Email]r%   r   r;   r   r   r$   r%   r   r;   r   r   r(   r%   r   r   boolrs   r   r   r   )r   r   r   r   r   r3   r8   r<   r>   r@   r|   r    r!   r"   r.   r.   H   sl    8         *r!   r.   c                  ,     e Zd ZdZd fdZddZ xZS )_LinkExtractorz2Minimal HTML parser that collects all href values.c                0    t         |           g | _        y r}   )super__init__links)r1   	__class__s    r"   r   z_LinkExtractor.__init__   s     "
r!   c                l    |dk(  r/|D ])  \  }}|dk(  s|s| j                   j                  |       + y y )Nahref)r   append)r1   tagattrsattrvalues        r"   handle_starttagz_LinkExtractor.handle_starttag   s<    #:$ -e6>eJJ%%e,- r!   r   None)r   r   r   listr   r   )r   r   r   r   r   r   __classcell__r   s   @r"   r   r      s    <#-r!   r   c                P    t               }|j                  |        |j                  S r}   )r   feedr   )r+   parsers     r"   _extract_links_from_htmlr      s     F
KK<<r!   c                  f     e Zd ZdZdZdddZddZdddZddZddZ	ddZ
dd	Zdd fd
Z xZS )MailSlurpProvideruv   
    MailSlurp (https://mailslurp.com) — cloud temporary email service.
    Requires MAILSLURP_API_KEY env var.
    zhttps://api.mailslurp.comc                d    |xs t         j                  d   | _        | j                  dd| _        y )NMAILSLURP_API_KEYapplication/json)z	x-api-keyContent-Typeosenvironapi_key_headersr1   r   s     r"   r   zMailSlurpProvider.__init__   s+    A"**-@"A.
r!   c                D    t        j                  | j                  d      S N   )headerstimeouthttpxAsyncClientr   r1   s    r"   _clientzMailSlurpProvider._client         CCr!   c                  K   i }|r||d<   | j                         4 d {   }|j                  | j                   d|       d {   }|j                          |j	                         }d d d       d {    t
        j                  dd           t        |d   |d   d|j                  d      	      S 7 7 u7 G# 1 d {  7  sw Y   WxY ww)
Nr2   z/createInboxjsonz[MailSlurp] Created inbox: emailAddressr   	mailslurp	createdAtr   r   r   r   )	r   postBASE_URLraise_for_statusr   loggerinfor   getr1   r2   payloadclientrespdatas         r"   r3   zMailSlurpProvider.create_inbox   s     "GFO<<> 	 	Vl%C'RRD!!#99;D	 	
 	1$~2F1GHIDz~. xx,	
 	
	R	 	 	 	sW   CB<C$CB>	$C-C8C 9AC>C CCC	CCc                  K   | j                         4 d {   }|j                  | j                   d|dd       d {   }|j                          |j	                         }d d d       d {    D cg c]U  }t        |d   ||j                  dd      |j                  dd      |j                  d	g       |j                  d
            W c}S 7 7 7 i# 1 d {  7  sw Y   yxY wc c}w w)Nz/getEmailsForInboxASC)inboxIdsort)paramsr   r&    fromr)   r   r   r%   r&   r'   r)   r*   r   r   r   r   r   r$   r1   r%   r   r   itemsitems         r"   r8   zMailSlurpProvider.get_emails   s     <<> 	  	 V==/!34#+U; $  D !!#IIKE	  	 " 

  :!B/!XXfb188D"% HH[1

 
	
	 	  	  	  	 

sc   C8CC8'CC$C'C82C3C8;AC3C8CC8C0$C'%C0,C8c                .  K   | j                         4 d {   }|j                  | j                   d|        d {   }|j                          |j	                         }d d d       d {    t        d   ||j                  dd      |j                  dd      |j                  dg       |j                  d      |j                  d      |j                  d	      xr |j                  d      
      S 7 7 7 # 1 d {  7  sw Y   xY ww)Nz/emails/r   r&   r   r   r)   r   bodybodyMD5r   r%   r&   r'   r)   r*   r+   r,   r   )r1   r%   r;   r   r   r   s         r"   r<   z#MailSlurpProvider.get_email_content   s     <<> 	 	Vt}}oXhZ$HIID!!#99;D	 	
 DzHHY+&"-xxb!-&!)$9&)9	
 		
	I	 	 	 	sV   DC:D$D C< $D $D/C>0BD<D >D DD	DDc                   K   | j                  ||       d {   }|j                  sg S t        |j                        S 7 'wr}   r<   r+   r   r1   r%   r;   emails       r"   r>   zMailSlurpProvider.extract_links   <     ,,Xx@@zzI'

33 A   AA (Ac                H  K   | j                         4 d {   }|j                  | j                   d|        d {   }d d d       d {    t        j	                  d| dj
                          |j
                  dv S 7 o7 I7 ;# 1 d {  7  sw Y   KxY ww)N	/inboxes/z[MailSlurp] Deleted inbox :       r   deleter   r   r   status_coder1   r%   r   r   s       r"   r@   zMailSlurpProvider.delete_inbox        <<> 	N 	NV$--	('LMMD	N 	N0
"T=M=M<NOP:--	NM	N 	N 	N 	NU   B"BB"$BB	 BB"B8B"	BB"BBBB"c                *    t         |   |xs d      S )Nmailslurp.comr   r|   r1   rs   r   s     r"   r|   z$MailSlurpProvider.create_human_email   s    w)&*CODDr!   r}   r   r   r   r   r   zhttpx.AsyncClientr~   r   r   r   r   r   r   r   r   r   r   r   r   r3   r8   r<   r>   r@   r|   r   r   s   @r"   r   r      s?    
 +H
D
$
*
"4.E Er!   r   c                  f     e Zd ZdZdZdddZddZdddZddZddZ	ddZ
dd	Zdd fd
Z xZS )AgentMailProvideruz   
    AgentMail (https://agentmail.to) — programmable inboxes for AI agents.
    Requires AGENTMAIL_API_KEY env var.
    zhttps://api.agentmail.to/v0c                j    |xs t         j                  d   | _        d| j                   dd| _        y )NAGENTMAIL_API_KEYzBearer r   )Authorizationr   r   r   s     r"   r   zAgentMailProvider.__init__   s2    A"**-@"A&t||n5.
r!   c                D    t        j                  | j                  d      S r   r   r   s    r"   r   zAgentMailProvider._client  r   r!   c                  K   i }|r#|j                         j                  dd      |d<   | j                         4 d {   }|j                  | j                   d|       d {   }|j                          |j                         }d d d       d {    t        j                  dd           t        |d   |d   d	|j                  d
            S 7 7 u7 G# 1 d {  7  sw Y   WxY ww)N rm   usernamez/inboxesr   z[AgentMail] Created inbox: addressr%   	agentmailr   r   )lowerreplacer   r   r   r   r   r   r   r   r   r   s         r"   r3   zAgentMailProvider.create_inbox  s     "&**,"6"6sC"@GJ<<> 	 	Vh%?gNND!!#99;D	 	
 	1$y/1BCDJy/ xx-	
 	
	N	 	 	 	sX   <C5CC5$C &C'$C C5CAC5C C5 C2&C)'C2.C5c                  K   | j                         4 d {   }|j                  | j                   d| d       d {   }|j                          |j	                         }d d d       d {    t        t              r|j                  d|      n|}|D cg c]  }t        |d   ||j                  dd      |j                  dd      t        |j                  d      t              r|j                  dg       n|j                  dd      g|j                  d	      
       c}S 7 7 7 # 1 d {  7  sw Y   xY wc c}w w)Nr   z	/messagesmessages
message_idr&   r   r   r)   r*   r   )	r   r   r   r   r   
isinstancer   r$   r   )r1   r%   r   r   r   r   msgs          r"   r8   zAgentMailProvider.get_emails  s'    <<> 	 	Vt}}oYxj	$RSSD!!#99;D	 	
 2<D$1G488J-T  

  |$!	2. WWVR0(23774=$(G3774$cggVZ\^N_M`GGM2

 
	
	S	 	 	 	

sc   ED,E%D3 D/$D3%E0D11,EBE)E/D31E3E9D<:EEc                8  K   | j                         4 d {   }|j                  | j                   d| d|        d {   }|j                          |j	                         }d d d       d {    j                  dg       }t        |t              r|g}t        |d   ||j                  dd      |j                  dd      ||j                  d      |j                  d	      |j                  d
            S 7 7 7 # 1 d {  7  sw Y   xY ww)Nr   z
/messages/r)   r   r&   r   r   r*   r+   r,   r   )r   r   r   r   r   r  r   r$   )r1   r%   r;   r   r   r   to_fields          r"   r<   z#AgentMailProvider.get_email_content-  s    <<> 	 	V==/8*JxjI D !!#99;D	 	 88D"%h$ zHL!HHY+&"-/&!&!	
 		
		 	 	 	sW   DC?D'DD$D'D2D3BDDDDDDDc                   K   | j                  ||       d {   }|j                  sg S t        |j                        S 7 'wr}   r   r   s       r"   r>   zAgentMailProvider.extract_linksD  r   r   c                H  K   | j                         4 d {   }|j                  | j                   d|        d {   }d d d       d {    t        j	                  d| dj
                          |j
                  dv S 7 o7 I7 ;# 1 d {  7  sw Y   KxY ww)Nr   z[AgentMail] Deleted inbox r   r   r   r   s       r"   r@   zAgentMailProvider.delete_inboxJ  r   r   c                *    t         |   |xs d      S )Nzagentmail.tor   r   s     r"   r|   z$AgentMailProvider.create_human_emailP  s    w)&*BNCCr!   r}   r   r   r~   r   r   r   r   r   r   r   s   @r"   r   r      s?    
 -H
D
$
&
.4.D Dr!   r   c                  j     e Zd ZdZdddZddZddZdddZddZddZ	ddZ
dd	Zdd fd
Z xZS )InbucketProvideru   
    Inbucket (https://inbucket.org) — self-hosted disposable email server.
    Connects to Inbucket REST API.
    Env vars: INBUCKET_URL (default http://localhost:9000)
    c                    |xs  t         j                  j                  dd      }|j                  d      | _        t        | j                        j                  xs d| _        y )NINBUCKET_URLhttp://localhost:9000/	localhost)r   r   r   rstripbase_urlr   hostname_domain)r1   r  raws      r"   r   zInbucketProvider.__init___  sF    Q"**..9PQ

3.77F;r!   c                .    t        j                  d      S )N   r   )r   r   r   s    r"   r   zInbucketProvider._clientd  s      ,,r!   c                $    | d| j                    S )Nro   )r  r0   s     r"   _make_inbox_addressz$InbucketProvider._make_inbox_addressg  s    q''r!   c                   K   |xs$ dt        j                         j                  d d  }| j                  |      }t        j                  d|        t        ||d      S w)Nagent_   z[Inbucket] Using inbox: inbucket)r   r   r   )uuiduuid4hexr  r   r   r   )r1   r2   rz   r   s       r"   r3   zInbucketProvider.create_inboxj  sg      8&!1!1#2!6 78007.}o>?'
 	
s   A A"c                @  K   | j                         4 d {   }|j                  | j                   d|        d {   }|j                  dk(  rg cd d d       d {    S |j	                          |j                         xs g }d d d       d {    D cg c]V  }t        |d   ||j                  dd      |j                  dd      |j                  dd      g|j                  d      	      X c}S 7 7 7 7 l# 1 d {  7  sw Y   |xY wc c}w w)
N/api/v1/mailbox/i  r   r&   r   r   mailboxdater   )r   r   r  r   r   r   r$   r   s         r"   r8   zInbucketProvider.get_emailsw  s    <<> 	& 	&Vt}}o5EhZ$PQQD3&	& 	& 	& !!#IIK%2E	& 	&  

  :!B/!XXfb1HHY+, HHV,

 
	
	&Q	& 	& 	& 	& 	&

s   DC<D$DC> DD D !D&$D
DDDAD9D>D DDD
DDDc                .  K   | j                         4 d {   }|j                  | j                   d| d|        d {   }|j                          |j	                         }d d d       d {    j                  di       }t        |||j                  dd      |j                  dd      |j                  dd      g|j                  d      |j                  d	      |j                  d
            S 7 7 7 # 1 d {  7  sw Y   xY ww)Nr!  r  r   r&   r   r   r"  r#  r+   r,   r   )r   r   r  r   r   r$   )r1   r%   r;   r   r   r   r   s          r"   r<   z"InbucketProvider.get_email_content  s    <<> 	 	V==/!1(1XJG D !!#99;D	 	 xx#HHY+&"-B'((&!&!	
 		
		 	 	 	sW   DC:D'D C<$D 'D2C>3BD<D >D DD	DDc                   K   | j                  ||       d {   }|j                  sg S t        |j                        S 7 'wr}   r   r   s       r"   r>   zInbucketProvider.extract_links  r   r   c                H  K   | j                         4 d {   }|j                  | j                   d|        d {   }d d d       d {    t        j	                  d| dj
                          |j
                  dv S 7 o7 I7 ;# 1 d {  7  sw Y   KxY ww)Nr!  z[Inbucket] Deleted mailbox r   r   )r   r   r  r   r   r   r   s       r"   r@   zInbucketProvider.delete_inbox  s     <<> 	U 	UV$--8H
'STTD	U 	U1(2d>N>N=OPQ:--	UT	U 	U 	U 	Ur   c                >    t         |   |xs | j                        S r}   )r   r|   r  r   s     r"   r|   z#InbucketProvider.create_human_email  s    w)&*@DLLAAr!   r}   )r  r   r   r   r   )r2   r   r   r   r~   r   r   r   r   r   )r   r   r   r   r   r   r  r3   r8   r<   r>   r@   r|   r   r   s   @r"   r	  r	  X  s=    G
-(

(
(4.B Br!   r	  c                   | xs dj                         j                         }|dk(  s!|s>t        j                  j	                  d      rt
        j                  d       t               S |dk(  s!|s>t        j                  j	                  d      rt
        j                  d       t               S |dk(  s|sEt        j                  j	                  d	d
      }t
        j                  d| d       t        |      S t        d| d      )u  
    Return an EmailProvider instance.

    Auto-detection order (when provider_name is None):
      1. MAILSLURP_API_KEY  → MailSlurpProvider
      2. AGENTMAIL_API_KEY  → AgentMailProvider
      3. INBUCKET_URL       → InbucketProvider
      4. fallback           → InbucketProvider (localhost:9000)

    Args:
        provider_name: One of "mailslurp", "agentmail", "inbucket".
                       If None, auto-detects from environment.
    r   r   r   zEmailProvider: MailSlurpr   r   zEmailProvider: AgentMailr  r  r  zEmailProvider: Inbucket ()r  zUnknown provider: z-. Choose from: mailslurp, agentmail, inbucket)r   stripr   r   r   r   r   r   r   r	  
ValueError)provider_namer2   urls      r"   get_email_providerr/    s     R&&(..0D{4BJJNN;N,O./ ""{4BJJNN;N,O./ ""zjjnn^-DE/uA67--

]- .6 	6 r!   c           	       K   t        j                         |z   }d}t        j                         |k  r!|dz  }| j                  |       d{   }|D ]~  }|&| j                  ||j                         d{   c S |j                         |j                  xs dj                         v sZ| j                  ||j                         d{   c S  |t        j                         z
  }	t        j                  d| dt        |       d|	dd	       t        j                  t        ||	             d{    t        j                         |k  r!t        d
|d| d| d      7 7 7 7 9w)aC  
    Poll an inbox until a matching email arrives or timeout elapses.

    Args:
        provider: An EmailProvider instance.
        inbox_id: The inbox to poll.
        subject_contains: Optional substring to match against email subjects
                          (case-insensitive). If None, returns the first email.
        timeout: Maximum seconds to wait before raising TimeoutError.
        poll_interval: Seconds between polls.

    Returns:
        The first Email matching the filter.

    Raises:
        TimeoutError: If no matching email arrives within timeout.
    r      Nr   zwait_for_email attempt z: no match yet (z	 emails, z.0fzs remaining)z#No email matching subject_contains=z arrived in inbox z within s)time	monotonicr8   r<   r   r   r&   r   debuglenasynciosleepminTimeoutError)
r   r%   subject_containsr   poll_intervaldeadlineattemptemailssummary	remainings
             r"   wait_for_emailrB    sm    0 ~~')HG
..
X
%1**844 	NG'%77'**MMM%%'GOO,Ar+H+H+JJ%77'**MMM	N t~~//	%gY /F}Ii_LB	
 mmCy9:::! ..
X
%$ 
-.>-A B$:XgYa	9 ! 5
 NM 	;sU   AE3E**E38E-94E3.E3E/A)E37E18E3E3-E3/E31E3c                J   t        j                  dt         j                        }t        j                  dt         j                        }t        j                  dt         j                  t         j                  z        }|j	                  |       D ]l  }|j                  d      |j                  d      }}|j                  |      s|j                  |      sH|j                         }|j                  d      sj|c S  |j	                  |       D ]H  }|j                  d      j                         }|j                  d      s4|j                  |      sF|c S  |j	                  |       D ]6  }|j                  d      j                         }|j                  d      s4|c S  y)a^  
    Parse email HTML and return the first verification/confirmation URL found.

    Looks for links whose text or href contains keywords like:
    verify, confirm, activate, reset, click here, get started, etc.

    Args:
        email_html: Raw HTML string of the email body.

    Returns:
        The first matching URL, or None if not found.
    zGverif|confirm|activat|reset|click.here|get.started|validate|unsubscribezhref=["\']([^"\']+)["\']z0<a\b[^>]*href=["\']([^"\']+)["\'][^>]*>(.*?)</a>r1     httpN)	recompile
IGNORECASEDOTALLfinditergroupsearchr+  
startswith)	
email_htmlVERIFICATION_KEYWORDSURL_PATTERNanchor_patternmatchr   	link_textr.  
href_matchs	            r"   extract_verification_linkrU    sf    JJR
 **#
K ZZ;
		!N  ((4 ++a.%++a.i ''-1F1M1Mi1X**,C~~f%
 "**:6 
"((*??6"'<'C'CD'IK "**:6 
"((*??6"K
 r!   c                8    t               }|j                  |       S )ur  
    Generate a realistic human-style email address on the given domain.

    Examples:
        create_human_email()              → "emma.johnson45@gmail.com"
        create_human_email("yahoo.com")  → "jsmith72@yahoo.com"

    This is a standalone helper; individual providers also expose
    this via EmailProvider.create_human_email() using their own domain.
    )_NullProviderr|   )rs   r   s     r"   r|   r|   >  s     H&&v..r!   c                  0    e Zd ZdZddZd Zd Zd Zd Zy)	rW  zBInternal stub to reuse create_human_email without a real provider.Nc                   K   t         wr}   NotImplementedErrorr0   s     r"   r3   z_NullProvider.create_inboxP       !!   	c                   K   t         wr}   rZ  r7   s     r"   r8   z_NullProvider.get_emailsS  r\  r]  c                   K   t         wr}   rZ  r:   s      r"   r<   z_NullProvider.get_email_contentV  r\  r]  c                   K   t         wr}   rZ  r:   s      r"   r>   z_NullProvider.extract_linksY  r\  r]  c                   K   t         wr}   rZ  r7   s     r"   r@   z_NullProvider.delete_inbox\  r\  r]  r}   )	r   r   r   r   r3   r8   r<   r>   r@   r    r!   r"   rW  rW  M  s    L"""""r!   rW  c            	       K   t        d       d} d}t        d       dD ]y  }t        |      }d|v r||v s
J d|        |j                  d      d   j                  dd      j                  d	d      j	                         s
J d
|        t        d|        { t        d       | dz  } t        d       d}t        |      }t        |      dk(  sJ dt        |              d|v sJ t        d|        t        d       | dz  } t        d       d}t        |      }|dk(  s
J d|        t        d|        t        d       | dz  } t        d       d}t        |      }	|	dk(  s
J d|	        t        d|	        t        d       | dz  } t        d       t        d      }
|

J d|
        t        d        t        d       | dz  } t        d!       d"D ]"  }t        j                  j                  |d        $ t               }t        |t              sJ d#t        |              t        d$t        |      j                   d%       t        d       | dz  } t        d&       t        d'      }t        |t              sJ t        d(t        |      j                          t        d       | dz  } t        d)       t        d*+      }|j                         }d,|v s
J d-|        t        d.|        t         j#                  t               }d/|_        i |_        |j                         }d0|v s
J d1|        t        d2|        t        d       | dz  } t        d3        G d4 d5t              } |       }	 t)        |d6d7d89       d {    t        d:       |dz  }t        d<       t-        d=d>d?d@dAgdBdCD       G fdEdFt              } |       }t)        |d>dGdHI       d {   }|j.                  d=k(  sJ dG|j0                  j3                         v sJ t        dJ|j0                          t        d       | dz  } t        dK       t        j                  j5                  dLdM      }	 t7        j8                  dN      4 d {   }|j5                  | dO       d {   }d d d       d {    j:                  dPk(  r0t        dQ| dR|j=                                 t        d       | dz  } nt        dS|j:                   dT       t        dW        t        dX|  dY| dZ       |rt        d[       nt        d\       t        dW d]       y 7 # t*        $ r)}t        d;|        t        d       | dz  } Y d }~d }~ww xY w7 7 
7 7 # 1 d {  7  sw Y   xY w# t>        $ r}t        dU| dV       Y d }~d }~ww xY ww)^Nz
=== Email Agent Self-Test ===
r   zTest 1: create_human_email())rn   z	yahoo.comr   ro   zBad address: rl   r   rm   zNon-alphanum local part: z  z	  PASSED
r1  z"Test 2: _extract_links_from_html()z
    <html><body>
      <a href="https://example.com/verify?token=abc123">Verify your email</a>
      <a href="https://example.com/home">Home</a>
      <a href="mailto:support@example.com">Contact</a>
    </body></html>
       zExpected 3 links, got z'https://example.com/verify?token=abc123z  Links found: z#Test 3: extract_verification_link()z
    <html><body>
      <p>Click below to verify your account:</p>
      <a href="https://app.example.com/verify?token=xyz789&user=42">Verify your email</a>
      <a href="https://app.example.com/home">Back to site</a>
    </body></html>
    z3https://app.example.com/verify?token=xyz789&user=42zWrong link: z  Extracted: zATest 4: extract_verification_link() fallback (no keyword in text)zq
    <html><body>
      <a href="https://accounts.example.com/confirm/abc">Click here</a>
    </body></html>
    z(https://accounts.example.com/confirm/abczWrong: z=Test 5: extract_verification_link() returns None for no linksz'<html><body>No links here</body></html>zExpected None, got: z  Result: Nonez+Test 6: get_email_provider() auto-detection)r   r   zExpected InbucketProvider, got z  Auto-detected: u    (no API keys set → Inbucket)z&Test 7: get_email_provider('inbucket')r  z  Got: z,Test 8: Provider.create_human_email() domainzhttp://mail.internal:9000r*  zmail.internalzExpected mail.internal domain: z  Inbucket address: dummyr   zExpected mailslurp.com: z  MailSlurp address: z=Test 9: wait_for_email() raises TimeoutError when inbox emptyc                      e Zd Zd Zd Zd Zy)&_run_self_test.<locals>._EmptyProviderc                     d| _         d| _        y Nr  r  r  r  r   s    r"   r   z/_run_self_test.<locals>._EmptyProvider.__init__      3DM&DLr!   c                   K   g S wr}   r    r7   s     r"   r8   z1_run_self_test.<locals>._EmptyProvider.get_emails  s     Is   c                   K   t         wr}   rZ  r:   s      r"   r<   z8_run_self_test.<locals>._EmptyProvider.get_email_content  s     %%r]  Nr   r   r   r   r8   r<   r    r!   r"   _EmptyProviderrf    s    	'		&r!   rn  
test_inboxrD  g      ?)r   r<  u,     FAILED — should have raised TimeoutErrorz   Got TimeoutError as expected: z0Test 10: wait_for_email() returns matching emaile001box1zPlease verify your accountznoreply@app.comzagent@test.comz2026-02-27T00:00:00Zz1<a href='https://app.com/verify?t=abc'>Verify</a>)r   r%   r&   r'   r)   r*   r+   c                  (    e Zd Zd Z fdZ fdZy)*_run_self_test.<locals>._PopulatedProviderc                     d| _         d| _        y rh  ri  r   s    r"   r   z3_run_self_test.<locals>._PopulatedProvider.__init__  rj  r!   c                   K   gS wr}   r    )r1   r%   sample_emails     r"   r8   z5_run_self_test.<locals>._PopulatedProvider.get_emails  s      >!s   c                   K   S wr}   r    )r1   r%   r;   rv  s      r"   r<   z<_run_self_test.<locals>._PopulatedProvider.get_email_content  s     s   Nrm  )rv  s   r"   _PopulatedProviderrs    s    	'	"	 r!   rx  verify   )r;  r   z  Received: z?Test 11: InbucketProvider connectivity (skipped if not running)r  r  r  z/api/v1/statusr   z  Inbucket LIVE at r   z  Inbucket responded u    — skippedz  Inbucket not reachable (u   ) — skipped (not a failure)
z(========================================z	Results: z	 passed, z failedzSOME TESTS FAILEDzALL TESTS PASSED
) printr|   splitr   isalnumr   r6  rU  r   r   popr/  r  r	  typer   r   __new__r   r   rB  r:  r$   r   r&   r   r   r   r   r   r   	Exception)passedfailedrs   addrsample_htmlr   verification_htmllink
plain_htmllink2resultvarr   p
inbucket_pmailslurp_pms_addrrn  emptyerx  r  result_emailinbucket_urlr   r   excrv  s                              @r"   _run_self_testr  d  s\    	
-.FF 

()= !&)d{v~Etf/EE-zz#q!))#r2::3CKKM 	/'v.	/M4&k 
,
aKF 

./K %[1Eu:?A4SZLAA?4===	OE7
#$	,
aKF 

/0 %%67DHH 
tfH	M$
 !	,
aKF 

MNJ
 &j1E>>Q'%@QQ>	M%
!"	,
aKF 

IJ&'PQF>:1&::>	
	,
aKF 

78 : "


sD!" "#Hh 01 ;
)$x.)9:;1	d8n5566U
VW	,
aKF 

23:&Aa)***	GDG$$%
&'	,
aKF 

89!+FGJ((*Dd"L&EdV$LL"	 
'(#++,=>K!KK,,.Gg%K)A''KK%	!'
+,	,
aKF 

IJ	&) 	& EUL!3OOO<=! 

<=,&*@L	 - 	  
C'Vh L ??f$$$|++113333	L--.
/0	,
aKF 

KL::>>.2IJL
Q$$Q/ 	E 	E6|nN$CDDD	E 	Es"'~R		}EF,aKF)$*:*:);<HI
 
XJ	IfXYvhg
67!" !	XJb/A 	P  045l!<	ED	E 	E 	E 	E  Q*3%/NOPPQs   M>WU% U"U% )AW1V2BW9V9 VV9 V$/V 0V$4V9 ?V" AV9 AW"U% %	V.VWVWV9  V$"V9 $V6*V-+V62V9 9	WWWWW__main__)r+   r   r   r(   r}   )r-  r   r   r.   )N<   g      @)r   r.   r%   r   r;  r   r   intr<  floatr   r$   )rN  r   r   r   )rn   )rs   r   r   r   r   ),r   
__future__r   r7  loggingr   rp   rF  r3  r  abcr   r   dataclassesr   r   html.parserr   typingr	   r
   urllib.parser   r   	getLoggerr   basicConfigINFOr   r$   r.   r   r   r   r   r	  r/  rB  rU  r|   rW  r  r   runr    r!   r"   <module>r     so  " #   	  	   # ( " ! ! 			=	)   ',,/W X . . . 
. 
. 
."1*C 1*p-Z -WE WE|[D [DDTB} TBv T '+000 $0 	0
 0 0f.b/"M ".{| zGKK ! r!   