
    rjM                    D   d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlZddlZddlmZmZmZ ddlmZ ej&                  j)                  d e ee      j/                         j0                  j0                               ddlmZ ddlmZmZmZ d0dZ d	d
ddd	 	 	 d1dZ!dddd	 	 	 d2dZ"d Z#d Z$d Z%d Z&d Z'd Z(d Z)d Z*d Z+d Z,d Z-d Z.d Z/d  Z0d! Z1d" Z2d# Z3d$ Z4d3d%Z5d4d&Z6d' Z7d( Z8d) Z9d* Z:d+ Z;d, Z<d- Z=d5d.Z>e?d/k(  r ej                   e>              yy)6u  
Unit tests for core/news_filter.py.

Covers:
  - Disabled / unsynced fail-open.
  - Block windows: BEFORE (45 min default) and AFTER (15 min default).
  - HIGH-only gating (MEDIUM cached but doesn't block).
  - Currency routing via ASSET_CURRENCIES (USD news blocks all USD-quoted
    assets; cross-currency only blocks the matched leg).
  - Unknown-symbol fail-open.
  - BlockingEvent payload completeness (for log forensics).
  - JSON parser: real FF schema parses correctly; malformed entries
    skipped without raising; events outside next-24h window dropped.

Sync method exercised by injecting `_upcoming` directly — no real
HTTP. One parser-level test feeds a JSON byte string through `_parse`.
    )annotationsN)datetime	timedeltatimezone)Path)config_futures)BlockingEvent	NewsEvent
NewsFilterc                     t        d|         y )Nz  ok  )print)labels    -/home/work/apex_v16/tests/test_news_filter.py_okr   &   s    	F5'
    T-      )enabledbeforeaftereventsc                    t        | ||ddd       }|r8t        |      |_        t        j                  t
        j                        |_        |S )Nhttps://example.invalid/ff.json   )r   
before_min	after_min
source_urlhttp_timeoutlogger)r   list	_upcomingr   nowr   utc_last_sync_at)r   r   r   r   nfs        r   make_filterr&   *   sH    	4
B F|#<<5Ir   USDHighz
Test Event)countryimpacttitlec                     t        | |||      S )Ndtr)   r*   r+   )r
   r-   s       r   eventr/   :   s    GF%HHr   c            	     ,   t        j                  t        j                        } t	        dt        | t        d      z   d      g      }|j                  }d} |||       }d }||u }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}x}x}}t!        d       y )NF   minutesr'   r.   r)   )r   r   MESiszZ%(py7)s
{%(py7)s = %(py2)s
{%(py2)s = %(py0)s.is_blocked
}(%(py4)s, %(py5)s)
} is %(py10)sr%   r"   py0py2py4py5py7py10assert %(py12)spy12z5disabled filter never blocks even with imminent eventr   r"   r   r#   r&   r/   r   
is_blocked
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationr   	r"   r%   @py_assert1@py_assert3@py_assert6@py_assert9@py_assert8@py_format11@py_format13s	            r   !test_disabled_filter_never_blocksrT   C   s    
,,x||
$C	Uy++U;, 
B ==,,=$,,$,,,,$,,,,,,2,,,2,,,=,,,,,,,,,,,,,,,$,,,,,,,,,,?@r   c            	        t        j                  t        j                        } t	               }|j
                  }d}||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|j                  }d} |||       }d}||u }	|	st        j                  d|	fd	||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d
t        j                         v st        j                  |       rt        j                  |       nd
t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}x}x}	}t        d       y)z1No sync ever done -> empty _upcoming -> no block.Nr6   )z5%(py2)s
{%(py2)s = %(py0)s._last_sync_at
} is %(py5)sr%   r:   r;   r=   assert %(py7)sr>   r5   r8   r"   r9   r@   rA   z/never-synced filter is fail-open (returns None))r   r"   r   r#   r&   r$   rD   rE   rF   rG   rH   rI   rJ   rK   rC   r   )r"   r%   rM   @py_assert4rN   @py_format6@py_format8rO   rP   rQ   rR   rS   s               r   test_unsynced_fail_openr[   L   sL   
,,x||
$C	B#t#t####t######2###2######t#######==,,=$,,$,,,,$,,,,,,2,,,2,,,=,,,,,,,,,,,,,,,$,,,,,,,,,,9:r   c                 V   t        j                  t        j                        } t	        | t        d      z         }t        |g      }|j                  d|       }t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndd	t        j                         v st        j                  t              rt        j                  t              nd	t        j                  |      d
z  }t        t        j                   |            d}|j"                  }d}||k(  }|st        j$                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                   |	            dx}x}}d}
|j&                  }|
|k  }d}||k  }|r|st        j$                  d||fd|
||f      t        j                  |
      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                   |            dx}
x}x}x}}|j                  }||u }|st        j$                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                   |            dx}}t)        d       y) z?USD HIGH event +30min, before_min=45 -> blocked, reason BEFORE.   r2   r.   r   r5   5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstanceblkr	   r:   py1r;   r<   NBEFORE==z.%(py2)s
{%(py2)s = %(py0)s.reason
} == %(py5)srV   rW   r>         <rl   )z7%(py1)s < %(py6)s
{%(py6)s = %(py4)s.minutes_to_event
}z7%(py6)s
{%(py6)s = %(py4)s.minutes_to_event
} < %(py8)s)rd   r<   py6py8assert %(py10)sr?   r6   )z-%(py2)s
{%(py2)s = %(py0)s.event
} is %(py4)sev)r:   r;   r<   assert %(py6)srm   z1blocks 30 min before event (within before_min=45)r   r"   r   r#   r/   r   r&   rC   ra   r	   rF   rG   rD   rH   rI   rJ   rK   reasonrE   minutes_to_eventr   )r"   rp   r%   rb   rN   @py_format5rM   rX   rY   rZ   @py_assert0@py_assert5@py_assert2@py_assert7@py_format9rR   @py_format7s                    r   *test_blocks_pre_event_within_before_windowr|   Y   sO   
,,x||
$C	#	"--	.B	RD	!B
--s
#Cc=)))))))):))):))))))c)))c))))))=)))=))))))))))::!!:!!!!:!!!!!!3!!!3!!!:!!!!!!!!!!)$$)2$))r)$r)))))2$r)))2))))))))))))$)))r)))))))999?9339;<r   c            	     (   t        j                  t        j                        } t	        t        | t        d      z         g      }|j                  }d} |||       }d}||u }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
t        j                         v st        j                  |       rt        j                  |       nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}t!        d       y)z4USD HIGH event +60min, before_min=45 -> not blocked.<   r2   r^   r_   r5   Nr6   r8   r%   r"   r9   r@   rA   z7not blocked 60 min before event (outside before_min=45)rB   rL   s	            r   )test_not_blocked_pre_event_outside_windowr   f   s    
,,x||
$C	UcIb,A&ABC	DB==,,=$,,$,,,,$,,,,,,2,,,2,,,=,,,,,,,,,,,,,,,$,,,,,,,,,,ABr   c                    t        j                  t        j                        } t	        | t        d      z
        }t        |g      }|j                  d|       }t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndd	t        j                         v st        j                  t              rt        j                  t              nd	t        j                  |      d
z  }t        t        j                   |            d}|j"                  }d}||k(  }|st        j$                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                   |	            dx}x}}d}
|
 }|j&                  }||k  }d}| }||k  }|r|st        j$                  d||fd|||f      t        j                  |
      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                   |            dx}
x}x}x}x}x}}t)        d       y)z=USD HIGH event -10min, after_min=15 -> blocked, reason AFTER.
   r2   r^   r_   r5   r`   ra   rb   r	   rc   NAFTERrf   rh   rV   rW   r>      	   rk   )z8-%(py1)s < %(py7)s
{%(py7)s = %(py5)s.minutes_to_event
}z8%(py7)s
{%(py7)s = %(py5)s.minutes_to_event
} < -%(py9)s)rd   r=   r>   py9r@   rA   z/blocks 10 min after event (within after_min=15)rr   )r"   rp   r%   rb   rN   ru   rM   rX   rY   rZ   rv   rx   rO   rQ   @py_assert10rR   rS   s                    r   *test_blocks_post_event_within_after_windowr   n   s   
,,x||
$C	#	"--	.B	RD	!B
--s
#Cc=)))))))):))):))))))c)))c))))))=)))=))))))))))::  :    :      3   3   :          *B3*%%*3%****%*****3%***B************%***********9:r   c            	     (   t        j                  t        j                        } t	        t        | t        d      z
        g      }|j                  }d} |||       }d}||u }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
t        j                         v st        j                  |       rt        j                  |       nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}t!        d       y)z3USD HIGH event -30min, after_min=15 -> not blocked.r]   r2   r^   r_   r5   Nr6   r8   r%   r"   r9   r@   rA   z5not blocked 30 min after event (outside after_min=15)rB   rL   s	            r   *test_not_blocked_post_event_outside_windowr   z   s    
,,x||
$C	UcIb,A&ABC	DB==,,=$,,$,,,,$,,,,,,2,,,2,,,=,,,,,,,,,,,,,,,$,,,,,,,,,,?@r   c                    t        j                  t        j                        } t	        t        | t        d      z         g      }|j                  d|       }g }d}||u}|}|r|j                  }d}||k(  }	|	}|slt        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }
dd|
iz  }|j                  |       |rt        j                  d	fdf      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }|j                  |       t        j                   |d      i z  }dd|iz  }t#        t        j$                  |            dx}x}x}x}x}x}	}t'        d       y)z;At exactly +before_min: still blocked (inclusive boundary).r   r2   r^   r_   r5   Nre   is notz%(py2)s is not %(py5)srb   r;   r=   %(py7)sr>   rf   z1%(py11)s
{%(py11)s = %(py9)s.reason
} == %(py14)sr   py11py14%(py16)spy16r   assert %(py19)spy19z3boundary +45 min before event is INCLUSIVE (blocks)r   r"   r   r#   r&   r/   r   rC   rs   rD   rE   rF   rG   rH   rI   append_format_booloprJ   rK   r   r"   r%   rb   rM   rX   rN   rv   r   @py_assert13@py_assert12rY   rZ   @py_format15@py_format17@py_format18@py_format20s                   r   +test_block_window_boundary_inclusive_beforer      s!   
,,x||
$C	UcIb,A&ABC	DB
--s
#C5d53d?5szz5X5zX555553d55555535553555d5555555zX555555s555s555z555X55555555555555=>r   c                    t        j                  t        j                        } t	        t        | t        d      z
        g      }|j                  d|       }g }d}||u}|}|r|j                  }d}||k(  }	|	}|slt        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }
dd|
iz  }|j                  |       |rt        j                  d	fdf      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }|j                  |       t        j                   |d      i z  }dd|iz  }t#        t        j$                  |            dx}x}x}x}x}x}	}t'        d       y)z:At exactly -after_min: still blocked (inclusive boundary).r   r2   r^   r_   r5   Nr   r   r   rb   r   r   r>   rf   r   r   r   r   r   r   r   z2boundary -15 min after event is INCLUSIVE (blocks)r   r   s                   r   *test_block_window_boundary_inclusive_afterr      s!   
,,x||
$C	UcIb,A&ABC	DB
--s
#C4d43d?4szz4W4zW444443d44444434443444d4444444zW444444s444s444z444W44444444444444<=r   c            	     *   t        j                  t        j                        } t	        t        | t        d      z   d      g      }|j                  }d} |||       }d}||u }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}t!        d       y)zDMEDIUM impact event in window -> not blocked (V14: HIGH only gates).r   r2   Mediumr.   r*   r_   r5   Nr6   r8   r%   r"   r9   r@   rA   z=MEDIUM impact event in window does NOT block (HIGH-only gate)rB   rL   s	            r   !test_medium_impact_does_not_blockr      s    
,,x||
$C	y,,X> 
B ==,,=$,,$,,,,$,,,,,,2,,,2,,,=,,,,,,,,,,,,,,,$,,,,,,,,,,GHr   c            	     *   t        j                  t        j                        } t	        t        | t        d      z   d      g      }|j                  }d} |||       }d}||u }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}t!        d       y)zADefensive: even if a Low slipped past parser, it shouldn't block.r   r2   Lowr   r_   r5   Nr6   r8   r%   r"   r9   r@   rA   z<LOW impact event in window does NOT block (defense-in-depth)rB   rL   s	            r   test_low_impact_does_not_blockr      s    
,,x||
$C	y,,U; 
B ==,,=$,,$,,,,$,,,,,,2,,,2,,,=,,,,,,,,,,,,,,,$,,,,,,,,,,FGr   c            	     *   t        j                  t        j                        } t	        t        | t        d      z   d      g      }|j                  }d} |||       }d}||u }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}t!        d       y)z8GBP HIGH event +10min for MES (USD-only) -> not blocked.r   r2   GBPr4   r_   r5   Nr6   r8   r%   r"   r9   r@   rA   z9GBP event does NOT block MES (USD-only currency exposure)rB   rL   s	            r   6test_irrelevant_currency_does_not_block_usd_only_assetr      s    
,,x||
$C	y,,e< 
B ==,,=$,,$,,,,$,,,,,,2,,,2,,,=,,,,,,,,,,,,,,,$,,,,,,,,,,CDr   c                 X   t        j                  t        j                        } t	        t        | t        d      z   d      g      }dD ]  }|j                  ||       }d}||u}|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }t        j                  | d      dz   d|iz  }t        t        j                   |            dx}} t#        d       y)z:USD HIGH event blocks MES, MGC, MCL, AND all FX (USD leg).r   r2   r'   r4   r_   )
r5   MNQMYMMGCMCL6E6B6A6J6CNr   z%(py0)s is not %(py3)srb   r:   py3z must be blocked by USD event
>assert %(py5)sr=   zDUSD event blocks ALL USD-quoted assets (indices, metals, energy, FX))r   r"   r   r#   r&   r/   r   rC   rD   rE   rF   rG   rH   rI   _format_assertmsgrJ   rK   r   )r"   r%   symrb   rx   rM   @py_format4rY   s           r   +test_usd_event_blocks_all_usd_quoted_assetsr      s    
,,x||
$C	UcIb,A&A5QR	SBP FmmC%Es$EEEs$EEEEEEsEEEsEEE$EEE3%'D EEEEEEEF NOr   c            
     @   t        j                  t        j                        } t	        t        | t        d      z   d      g      }|j                  }d} |||       }d}||u}|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}dD ]  }	|j                  } ||	|       }
d}|
|u }|s\t        j                  d|fd|
|f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dt        j                         v st        j                  |	      rt        j                  |	      nddt        j                         v st        j                  |       rt        j                  |       ndt        j                  |
      t        j                  |      dz  }t        j                   |	 d      dz   d|iz  }t        t        j                  |            dx}x}
x}} t#        d       y)z1EUR HIGH event +10min: blocks 6E, NOT MES/6B/etc.r   r2   EURr4   r_   r   Nr   )z^%(py7)s
{%(py7)s = %(py2)s
{%(py2)s = %(py0)s.is_blocked
}(%(py4)s, %(py5)s)
} is not %(py10)sr%   r"   r9   r@   rA   )	r5   r   r   r   r   r   r   r   r   r6   )zY%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.is_blocked
}(%(py3)s, %(py4)s)
} is %(py9)sr   )r:   r;   r   r<   rm   r   z must NOT be blocked by EURz
>assert %(py11)sr   z5EUR event blocks only 6E (cross-currency leg routing))r   r"   r   r#   r&   r/   r   rC   rD   rE   rF   rG   rH   rI   rJ   rK   r   r   )r"   r%   rM   rN   rO   rP   rQ   rR   rS   r   rw   ry   @py_format10@py_format12s                 r   test_eur_event_blocks_only_6er      s   
,,x||
$C	UcIb,A&A5QR	SB==//=s#/4/#4////#4//////2///2///=/////////s///s///#///4///////J T}}S}S#&S$S&$.SSS&$SSSSSSrSSSrSSS}SSSSSSSSSSSSSSSSS#SSS#SSS&SSS$SSS3%7R0SSSSSSSST?@r   c            	     (   t        j                  t        j                        } t	        t        | t        d      z         g      }|j                  }d} |||       }d}||u }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
t        j                         v st        j                  |       rt        j                  |       nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}t!        d       y)zASymbol absent from ASSET_CURRENCIES -> never blocks (onboarding).r   r2   r^   r_   ZZZ_UNKNOWNNr6   r8   r%   r"   r9   r@   rA   z1unknown symbol -> fail-open (onboarding-friendly)rB   rL   s	            r   test_unknown_symbol_fail_openr      s    
,,x||
$C	UcIb,A&ABC	DB==44=,44,4444,44444424442444=444444444444444,4444444444;<r   c                    t        j                  t        j                        } t	        | t        d      z   ddd      }t        |g      }|j                  d|       }d	}||u}|st        j                  d
|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}|j                  }|j                   }d}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            d	x}x}x}
}	|j                  }|j"                  }d}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            d	x}x}x}
}	|j                  }|j$                  }d}	||	k(  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            d	x}x}x}
}	|j                  }|j&                  }|j&                  }||k(  }
|
st        j                  d|
fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}x}
}|j(                  }t+        |t,              }
|
sddt        j                         v st        j                  t*              rt        j                  t*              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t,              rt        j                  t,              ndt        j                  |
      dz  }t        t        j                  |            d	x}}
|j.                  }d }||v }|st        j                  d!|fd"||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d#z  }d$d%|iz  }t        t        j                  |            d	x}x}}t1        d&       y	)'z<BlockingEvent must carry full diagnostics for log forensics.   r2   r'   r(   FOMC Statementr-   r_   r5   Nr   r   rb   r   assert %(py5)sr=   rf   )zH%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.event
}.title
} == %(py7)sr:   r;   r<   r>   assert %(py9)sr   )zJ%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.event
}.country
} == %(py7)s)zI%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.event
}.impact
} == %(py7)s)z]%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.event
}.dt
} == %(py8)s
{%(py8)s = %(py6)s.dt
}rp   )r:   r;   r<   rm   rn   ro   r?   z[assert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.minutes_to_event
}, %(py4)s)
}ra   float)r:   rd   r   r<   rm   )re   r   in)z.%(py2)s
{%(py2)s = %(py0)s.reason
} in %(py5)srV   rW   r>   z,BlockingEvent payload carries full forensics)r   r"   r   r#   r/   r   r&   rC   rD   rE   rF   rG   rH   rI   rJ   rK   r+   r)   r*   r.   rt   ra   r   rs   r   )r"   rp   r%   rb   rx   rM   r   rY   rN   rO   rw   rZ   r   ry   rz   rR   r{   rX   s                     r   $test_blocking_event_payload_completer      s   
,,x||
$C	2&&	
B 
RD	!B
--s
#C3d?3d33d99.9??...?.....?.......3...3...9...?...........99%9%%%%%%%%%%%%3%%%3%%%9%%%%%%%%%%%%%99%9%v%v%%%%v%%%%%%3%%%3%%%9%%%%%%v%%%%%%%99 9<< 255 <5    <5      3   3   9   <      2   2   5       **2:*E22222222:222:222222c222c222*222222E222E2222222222::,,,:,,,,,:,,,,,,,3,,,3,,,:,,,,,,,,,,,67r   c                    t        j                  t        j                        } | t	        d      z   }| t	        d      z   }| t	        d      z   }d }dd	d
 ||      dddd ||      ddd	d ||      ddd	d
 ||      ddd	d
dddd	d
dg}t        j                  |      j                  d      }t        j                  |      }|D cg c]  }|j                   }	}d}
|
|	v }|st        j                  d|fd|
|	f      t        j                  |
      dt        j                         v st        j                   |	      rt        j                  |	      nddz  }dd|iz  }t#        t        j$                  |            dx}
}d}
|
|	v }|st        j                  d|fd|
|	f      t        j                  |
      dt        j                         v st        j                   |	      rt        j                  |	      nddz  }dd|iz  }t#        t        j$                  |            dx}
}d}
|
|	v}|st        j                  d|fd|
|	f      t        j                  |
      dt        j                         v st        j                   |	      rt        j                  |	      nddz  }dd|iz  }t#        t        j$                  |            dx}
}d}
|
|	v}|st        j                  d|fd|
|	f      t        j                  |
      dt        j                         v st        j                   |	      rt        j                  |	      nddz  }dd|iz  }t#        t        j$                  |            dx}
}d}
|
|	v}|st        j                  d|fd|
|	f      t        j                  |
      dt        j                         v st        j                   |	      rt        j                  |	      nddz  }dd|iz  }t#        t        j$                  |            dx}
}d}
|
|	v}|st        j                  d|fd|
|	f      t        j                  |
      dt        j                         v st        j                   |	      rt        j                  |	      nddz  }dd|iz  }t#        t        j$                  |            dx}
}|D ]^  }|j&                  }|j(                  }d}||u}|st        j                  d |fd!||f      d"t        j                         v st        j                   |      rt        j                  |      nd"t        j                  |      t        j                  |      t        j                  |      d#z  }d$d%|iz  }t#        t        j$                  |            dx}x}x}}|j&                  }|j*                  } |       }d&}t	        |      }||k(  }|s2t        j                  d'|fd(||f      d"t        j                         v st        j                   |      rt        j                  |      nd"t        j                  |      t        j                  |      t        j                  |      d)t        j                         v st        j                   t              rt        j                  t              nd)t        j                  |      t        j                  |      d*z  }d+d,|iz  }t#        t        j$                  |            dx}x}x}x}x}}a d- }t-        ||.      }||k(  }|s7t        j                  d'|fd/||f      d0t        j                         v st        j                   |      rt        j                  |      nd0d1t        j                         v st        j                   t,              rt        j                  t,              nd1d0t        j                         v st        j                   |      rt        j                  |      nd0t        j                  |      t        j                  |      d2z  }d$d%|iz  }t#        t        j$                  |            dx}x}}t/        d3       yc c}w )4z
    Feed a JSON list with FF-shaped entries to NewsFilter._parse.
    Verify: HIGH+MEDIUM kept, LOW dropped, malformed skipped, all UTC.
    r]   r2      hours   )daysc                B    | j                  d      j                         S )Nr   microsecond)replace	isoformat)ds    r   <lambda>z/test_parse_real_ff_json_shape.<locals>.<lambda>   s    AII!I,668 r   r   r'   r(   r+   r)   r*   datezECB Rate Decisionr   r   zRandom Speechr   zOut of windowzMalformed dategarbagezNo date field)r+   r)   r*   utf-8r   )z%(py1)s in %(py3)stitles)rd   r   r   r=   N)not in)z%(py1)s not in %(py3)sr   zJ%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.dt
}.tzinfo
} is not %(py7)ser   r   r   r   rf   z%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.dt
}.utcoffset
}()
} == %(py12)s
{%(py12)s = %(py8)s(%(py10)s)
}r   r:   r;   r<   rm   rn   r?   rA   assert %(py14)sr   c                    | j                   S )Nr^   )r   s    r   r   z/test_parse_real_ff_json_shape.<locals>.<lambda>  s
    !$$ r   )key)z=%(py0)s == %(py7)s
{%(py7)s = %(py2)s(%(py3)s, key=%(py5)s)
}r   sorted)r:   r;   r   r=   r>   zIparser: HIGH+MEDIUM kept, LOW/out-of-window/malformed dropped, sorted UTC)r   r"   r   r#   r   jsondumpsencoder   _parser+   rD   rE   rI   rF   rG   rH   rJ   rK   r.   tzinfo	utcoffsetr   r   )r"   soonlatertoo_lateisopayloadrawr   r   r   rv   rx   r   rY   rM   rN   rO   rw   rZ   r   rP   @py_assert11ry   rS   r   rX   s                             r   test_parse_real_ff_json_shaper      s8   
 ,,x||
$C2&&D)"%%EYA&&H
8C #5SY	0%5SZ	1!5SY	0!5S]	4"5Y	0!5	G **W

$
$W
-Cs#F%&!agg&F&%v%%%%v%%%%%%%%%v%%%v%%%%%%%(&((((&(((((((((&(((&((((((((?&((((?&(((?((((((&(((&((((((((?&((((?&(((?((((((&(((&((((((()6))))6)))))))))6)))6)))))))(?&((((?&(((?((((((&(((&((((((( 0tt&t{{&$&{$&&&&{$&&&&&&q&&&q&&&t&&&{&&&$&&&&&&&tt/t~~/~/Q/9Q</<////<//////q///q///t///~/////////9///9///Q///<////////0 )77VF776777776777777767776777777V777V777777F777F77777777777777ST 's   bc            
     J
   t        j                  t        j                        } t        t	        d            }| t	        d      z   j                  |      j                  d      }ddd	|j                         d
g}t        j                  |      j                  d      }t        j                  |      }t        |      }d}||k(  }|st        j                  d|fd||f      dt!        j"                         v st        j$                  t              rt        j&                  t              nddt!        j"                         v st        j$                  |      rt        j&                  |      ndt        j&                  |      t        j&                  |      dz  }	dd|	iz  }
t)        t        j*                  |
            dx}x}}|d   }|j,                  }|j.                  }d}||u}|st        j                  d|fd||f      dt!        j"                         v st        j$                  |      rt        j&                  |      ndt        j&                  |      t        j&                  |      t        j&                  |      dz  }dd|iz  }t)        t        j*                  |            dx}x}x}}|j,                  }|j0                  } |       }d}t	        |      }||k(  }|s2t        j                  d|fd||f      dt!        j"                         v st        j$                  |      rt        j&                  |      ndt        j&                  |      t        j&                  |      t        j&                  |      dt!        j"                         v st        j$                  t              rt        j&                  t              ndt        j&                  |      t        j&                  |      dz  }dd|iz  }t)        t        j*                  |            dx}x}x}x}x}}t3        |j,                  | t	        d      z   z
  j5                               }d }||k  }|st        j                  d!|fd"||f      d#t!        j"                         v st        j$                  |      rt        j&                  |      nd#t        j&                  |      d$z  }t        j6                  d%| d&      d'z   d(|iz  }t)        t        j*                  |            dx}}t9        d)       y)*zCISO 8601 with explicit -04:00 offset -> converted to UTC correctly.r   r]   r2   r   r   NFPr'   r(   r   r   r   rf   )z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenr   )r:   rd   r   rm   assert %(py8)srn   Nr   r   r   r   r   r   r   r   r   r   r   r~   )rl   )z%(py0)s < %(py3)sdiff_secr   zUTC conversion drifted: sr   r=   z7parser: ISO 8601 with offset converted to UTC correctly)r   r"   r   r#   r   
astimezoner   r   r   r   r   r   r   r   rD   rE   rF   rG   rH   rI   rJ   rK   r.   r   r   abstotal_secondsr   r   )r"   edtsoon_edtr   r   r   rx   rw   rX   r{   rz   r   rM   rN   rO   rZ   r   rP   r   ry   rS   r   r  r   rY   s                            r   /test_parse_iso8601_with_offset_converted_to_utcr    s   
,,x||
$C
92&
'Ci++77<DDQRDSH5F""$ G **W

$
$W
-Cs#Fv;!;!;!33vv;!q	A44"4;;"d";d"""";d""""""1"""1"""4""";"""d"""""""44+4>>+>++y|+|++++|++++++1+++1+++4+++>+++++++++y+++y++++++|++++++++ADDC)B"778GGIJH@8b=@@@8b@@@@@@8@@@8@@@b@@@4XJa@@@@@@@ABr   c                     t        j                  ddi      j                  d      } 	 t        j                  |        t        d      # t
        $ r t        d       Y yw xY w)zGNon-list JSON (e.g. error envelope) -> ValueError so caller fails-open.errorzrate limitedr   z:parser: dict payload raises ValueError (caller fails-open)Nz'expected ValueError on non-list payload)r   r   r   r   r   
ValueErrorr   rJ   )r   s    r   !test_parse_invalid_payload_raisesr  4  s^    
**g~.
/
6
6w
?C# B
CC  HIs   A AAc            
         t        j                  | D cg c]?  }|j                  |j                  |j                  |j
                  j                         dA c}      j                  d      S c c}w )zASerialize NewsEvents back to FF JSON shape for _parse round-trip.r   r   )r   r   r+   r)   r*   r.   r   r   )r   r   s     r   _ff_payload_bytesr  C  s`    :: 	  WWahhDDNN$	
 
 vg s   AA.c           	     8    t        dddddd|       }||_        |S )z@NewsFilter wired with a cache dir and a stubbed _fetch_blocking.Tr   r   r   r   Nr   r   r   r   r   r   	cache_dir)r   _fetch_blocking)tmp_path
fetch_implr%   s      r   _make_cached_filterr  M  s/    	4
B $BIr   c           
        t        j                  t        j                        }t	        |t        d      z         ddifd}t        | |      }t        j                  }|j                  } |       } ||      }d}||k(  }	|	s2t        j                  d|	fd	||f      d
t        j                         v st        j                  t              rt        j                  t              nd
t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t!        t        j"                  |            dx}x}x}x}x}	}t        j                  }|j                  } |       } ||      }d}||k(  }	|	s2t        j                  d|	fd	||f      d
t        j                         v st        j                  t              rt        j                  t              nd
t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t!        t        j"                  |            dx}x}x}x}x}	}d   }d}||k(  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j$                  d      dz   d|iz  }t!        t        j"                  |            dx}x}}t'        d       y)z<Two sync() calls on the same UTC day -> only one HTTP fetch.r]   r2   r^   nr   c                 4     dxx   dz  cc<   t              S Nr  r   r  )callsrp   s   r   
fake_fetchz6test_cache_hit_within_same_utc_day.<locals>.fake_fetch`  s    c
a
 $$r   r   rf   z{%(py9)s
{%(py9)s = %(py2)s
{%(py2)s = %(py0)s.run
}(%(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.sync
}()
})
} == %(py12)sasyncior%   r:   r;   r   r=   r>   r   rA   r   r   Nz%(py1)s == %(py4)srd   r<   z+second sync must hit memory cache, not HTTPz
>assert %(py6)srm   z5same-day sync re-uses memory cache (single HTTP call))r   r"   r   r#   r/   r   r  r  runsyncrD   rE   rF   rG   rH   rI   rJ   rK   r   r   )r  r"   r  r%   rM   rX   rO   rQ   r   r   rS   r   rv   rN   rx   ru   r{   r  rp   s                    @@r   "test_cache_hit_within_same_utc_dayr$  [  sA   
,,x||
$C	#	"--	.B!HE% 
Xz	2B;;&rww&wy&;y!&Q&!Q&&&&!Q&&&&&&7&&&7&&&;&&&&&&r&&&r&&&w&&&y&&&!&&&Q&&&&&&&&;;&rww&wy&;y!&Q&!Q&&&&!Q&&&&&&7&&&7&&&;&&&&&&r&&&r&&&w&&&y&&&!&&&Q&&&&&&&&:II:?III:III:IIIIIIIIIIIIII?@r   c                   t        j                  t        j                        }t	        |t        d      z   d      ddifd}t        | |      }t        j                  |j                                d   }d}||k(  }|slt        j                  d	|fd
||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|j                         j!                         }	d|	 d}
| |
z  }|j"                  } |       }|sddt%        j&                         v st        j(                  |       rt        j                  |       ndt        j                  |
      t        j                  |      t        j                  |      dz  }t        t        j                  |            dx}
x}x}}d }t        | |      }t        j                  |j                               }d}||k(  }
|
st        j                  d	|
fd||f      dt%        j&                         v st        j(                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}
}|j*                  d   }|j,                  }d}||k(  }|st        j                  d	|fd||f      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}}t/        d       y)z=A fresh NewsFilter on the same day reads disk cache, no HTTP.r]   r2   zDisk-cached)r.   r+   r  r   c                 4    dxx   dz  cc<   t               S r  r  )rp   fetch_counts   r   r  z=test_cache_reload_from_disk_after_restart.<locals>.fake_fetchp  s    CA $$r   r   rf   r   r!  rq   rm   Nff_calendar_.jsonzMassert %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = (%(py0)s / %(py2)s).exists
}()
}r  )r:   r;   r=   r>   c                     t        d      )Nz*disk cache must satisfy the call, not HTTPrJ    r   r   must_not_fetchzAtest_cache_reload_from_disk_after_restart.<locals>.must_not_fetch{  s    IJJr   z%(py0)s == %(py3)sr   r   r=   z-%(py3)s
{%(py3)s = %(py1)s.title
} == %(py6)srd   r   rm   r   rn   z1post-restart sync reads disk cache; no HTTP fetch)r   r"   r   r#   r/   r   r  r  r"  r#  rD   rE   rI   rJ   rK   r   r   existsrF   rG   rH   r!   r+   r   )r  r"   r  nf1rv   rN   rx   ru   r{   todayrM   rX   rO   rZ   r-  nf2r  r   rY   rw   rz   rp   r'  s                        @@r   )test_cache_reload_from_disk_after_restartr5  k  s!   
,,x||
$C	#	"--]	CB(K% h

3CKK
s q q    q      q       HHJ  "E%eWE2<H22<2::<:<<<<<<<<H<<<H<<<2<<<:<<<<<<<<<<K
h
7CCHHJAM16MMM1MMMMMM1MMM1MMMMMMMMMM==2!!2]2!]2222!]222222!222]2222222;<r   c           
     h   t        j                  t        j                        }t	        |t        d      z         ddifd}t        | |      }t        j                  }|j                  } |       } ||      }d}||k(  }	|	s2t        j                  d|	fd	||f      d
t        j                         v st        j                  t              rt        j                  t              nd
t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t!        t        j"                  |            dx}x}x}x}x}	}d|_        | d|j'                         j)                          dz  j+                          t        j                  |j                               }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        j,                  d      dz   d|iz  }t!        t        j"                  |            dx}}d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t!        t        j"                  |            dx}x}}|j.                  }d}||u}|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }d d!|iz  }t!        t        j"                  |            dx}x}}t1        d"       y)#zDFirst fetch succeeds, second is 429: stale cache continues to serve.r]   r2   r^   r  r   c                     dxx   dz  cc<   d   dk(  rt               S t        j                  j                  dddd d       Nr  r   r     Too Many Requestshdrsfp)r  urllibr
  	HTTPError)rp   states   r   r  z<test_429_fail_open_serves_existing_cache.<locals>.fake_fetch  sQ    g!>Q$R((ll$$-s4G$ % 
 	
r   r   rf   r  r  r%   r  r   r   Nr(  r)  r.  r  r   z2must keep serving the pre-existing cache after 429r   r=   r   r   r!  rq   rm   r   )zA%(py2)s
{%(py2)s = %(py0)s._last_failed_fetch_at
} is not %(py5)srV   rW   r>   z=HTTP 429 fail-open: existing cache kept, retry-throttle armed)r   r"   r   r#   r/   r   r  r  r"  r#  rD   rE   rF   rG   rH   rI   rJ   rK   _last_fetch_dater   r   unlinkr   _last_failed_fetch_atr   )r  r"   r  r%   rM   rX   rO   rQ   r   r   rS   r   r  rx   r   rY   rv   rN   ru   r{   rZ   rp   r@  s                        @@r   (test_429_fail_open_serves_existing_cacherD    s   
,,x||
$C	#	"--	.BaLE
 
Xz	2B;;&rww&wy&;y!&Q&!Q&&&&!Q&&&&&&7&&&7&&&;&&&&&&r&&&r&&&w&&&y&&&!&&&Q&&&&&&&&B,sxxz3356e<<DDFBGGIAG16GGG1GGGGGG1GGG1GGGGGGGGGGGGG>Q>Q>Q>Q##/4/#4////#4//////2///2///#///4///////GHr   c                   d }t        | |      }t        j                  |j                               }d}||k(  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dz  }dd|iz  }t        t	        j                  |            d	x}}|j                  }d
}t        j                  }	t        j                   }
 |	|
      } |||      }d	}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |	      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |
      t	        j                  |      t	        j                  |      t	        j                  |      d
z  }dd|iz  }t        t	        j                  |            d	x}x}x}	x}
x}x}x}}t#        d       y	)z;No prior cache + 429 on first fetch -> 0 events, fail-open.c                 J    t         j                  j                  dddd d       )Nr   r9  r:  r;  r>  r
  r?  r,  r   r   r  z7test_429_with_no_cache_returns_zero.<locals>.fake_fetch  s+    ll$$-s4G$ % 
 	
r   r   rf   r.  r  r   r   r=   Nr5   r6   )z%(py14)s
{%(py14)s = %(py2)s
{%(py2)s = %(py0)s.is_blocked
}(%(py4)s, %(py12)s
{%(py12)s = %(py7)s
{%(py7)s = %(py5)s.now
}(%(py10)s
{%(py10)s = %(py8)s.utc
})
})
} is %(py17)sr%   r   r   )
r:   r;   r<   r=   r>   rn   r?   rA   r   py17r   r   z9HTTP 429 with empty cache: 0 events, gate stays fail-open)r  r  r"  r#  rD   rE   rF   rG   rH   rI   rJ   rK   rC   r   r"   r   r#   r   )r  r  r%   r  rx   rM   r   rY   rN   rO   rP   r   r   @py_assert16@py_assert15r   r   s                    r   #test_429_with_no_cache_returns_zerorK    s   

 
Xz	2BBGGIAM16MMM1MMMMMM1MMM1MMMMMMMMMM==CCCX\\C\ :C= :;CtC;tCCCC;tCCCCCC2CCC2CCC=CCCCCCCCCCCCCCCCCCCCCXCCCXCCC\CCC :CCC;CCCtCCCCCCCCCDr   c                   ddifd}t        | |      }t        j                  |j                                d   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd	|iz  }t        t	        j                  |            d
x}x}}t        j                  |j                                d   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd	|iz  }t        t	        j                  |            d
x}x}}t        j                  t        j                        t        d      z
  |_        t        j                  |j                                d   }d}||k(  }|slt	        j
                  d|fd||f      t	        j                  |      t	        j                  |      dz  }dd	|iz  }t        t	        j                  |            d
x}x}}t        d       y
)z=After a failure, the next sync() within 1h must NOT re-fetch.r  r   c                 f     dxx   dz  cc<   t         j                  j                  dddd d       r8  rG  )r@  s   r   r  zFtest_retry_throttle_skips_repeat_fetch_within_hour.<locals>.fake_fetch  s:    g!ll$$-s4G$ % 
 	
r   r   rf   r   r!  rq   rm   Nr   r   z?retry throttle: blocks re-fetch <1h after failure, allows after)r  r  r"  r#  rD   rE   rI   rJ   rK   r   r"   r   r#   r   rC  r   )	r  r  r%   rv   rN   rx   ru   r{   r@  s	           @r   2test_retry_throttle_skips_repeat_fetch_within_hourrN    sg   aLE
 
Xz	2BKK	>Q>Q>Q>QKK	>Q>Q>Q>Q'||HLL9IA<NNBKK	>Q>Q>Q>QIJr   c           
        t        j                  t        j                        }|j	                         j                         }| d| dz  }|j                         |d|t        d      z   j                         dddd	gd
}|j                  t        j                  |             d }t        | |      }t        j                  }|j                  } |       }	 ||	      }
d}|
|k(  }|s2t        j                  d|fd|
|f      dt!        j"                         v st        j$                  t              rt        j&                  t              ndt        j&                  |      dt!        j"                         v st        j$                  |      rt        j&                  |      ndt        j&                  |      t        j&                  |	      t        j&                  |
      t        j&                  |      dz  }dd|iz  }t)        t        j*                  |            dx}x}x}	x}
x}}|j,                  d   }|j.                  }d}||k(  }|st        j                  d|fd||f      t        j&                  |      t        j&                  |      t        j&                  |      dz  }dd|iz  }t)        t        j*                  |            dx}x}x}}t1        d       y)zCPre-seed a disk cache; first-ever sync must use it instead of HTTP.r(  r)  xr]   r2   r'   r(   Seededr-   )
fetched_atr   r   r   c                     t        d      )Nz"disk cache should satisfy the callr+  r,  r   r   r-  zStest_disk_cache_loaded_when_memory_empty_and_no_http_needed.<locals>.must_not_fetch  s    ABBr   r   rf   r  r  r%   r  r   r   Nr   r/  r0  r   rn   z7pre-seeded disk cache short-circuits HTTP on first sync)r   r"   r   r#   r   r   r   
write_textr   r   r  r  r"  r#  rD   rE   rF   rG   rH   rI   rJ   rK   r!   r+   r   )r  r"   r3  
cache_filer   r-  r%   rM   rX   rO   rQ   r   r   rS   r   rv   rx   rw   r{   rz   s                       r   ;test_disk_cache_loaded_when_memory_empty_and_no_http_neededrV    s   
,,x||
$CHHJ  "El5'77Jmmo2..99;
 	G $**W-.C	X~	6B;;&rww&wy&;y!&Q&!Q&&&&!Q&&&&&&7&&&7&&&;&&&&&&r&&&r&&&w&&&y&&&!&&&Q&&&&&&&&<<?,?  ,H, H,,,, H,,,?,,, ,,,H,,,,,,,ABr   c           
        d }t        dddddd|       }||_        t        j                  }|j                  } |       } ||      }d	}||k(  }|s2t        j                  d
|fd||f      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}x}x}x}}| j                  } |       }t        |      }| }|s t        j                  d      dz   dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      t        j                  |      dz  }t        t        j                  |            dx}x}x}}t!        d       y)z=sync() on a disabled filter must not call HTTP or write disk.c                     t        d      )Nzdisabled filter must not fetchr+  r,  r   r   r-  z@test_disabled_filter_skips_sync_entirely.<locals>.must_not_fetch  s    =>>r   Fr   r   r   r   Nr  r   rf   r  r  r%   r  r   r   z#disabled filter must not touch diskzf
>assert not %(py7)s
{%(py7)s = %(py0)s(%(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.iterdir
}()
})
}anyr  )r:   rd   r   r=   r>   z"disabled filter: zero side-effects)r   r  r  r"  r#  rD   rE   rF   rG   rH   rI   rJ   rK   iterdirrY  r   r   )r  r-  r%   rM   rX   rO   rQ   r   r   rS   r   rx   rz   s                r   (test_disabled_filter_skips_sync_entirelyr[    s   ?	4tx	
B (B;;&rww&wy&;y!&Q&!Q&&&&!Q&&&&&&7&&&7&&&;&&&&&&r&&&r&&&w&&&y&&&!&&&Q&&&&&&&&##M#%Ms%&M&&M&MM(MMMMMMMsMMMsMMMMMM8MMM8MMM#MMM%MMM&MMMMMM,-r   c                    t        d       t                t                t                t	                t                t                t                t                t                t                t                t                t                t                t                t!                t#                t%                t        d       y)Nztest_news_filter.pyzALL TESTS PASSEDr   )r   rT   r[   r|   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r,  r   r   mainr]    s    	
 %'.0-/.0.0/1.0%'"$:</1!#!#(*!#35%'	
r   __main__)r   strreturnNone)
r   boolr   intr   rc  r   zlist[NewsEvent] | Noner`  r   )
r.   r   r)   r_  r*   r_  r+   r_  r`  r
   )r`  bytes)r  r   r`  r   )r`  rc  )A__doc__
__future__r   builtinsrF   _pytest.assertion.rewrite	assertionrewriterD   r  r   sysurllib.errorr>  r   r   r   pathlibr   pathinsertr_  __file__resolveparentcorer   cfg_futcore.news_filterr	   r
   r   r   r&   r/   rT   r[   r|   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r  r$  r5  rD  rK  rN  rV  r[  r]  __name__exitr,  r   r   <module>rx     se  $ #     
  2 2  3tH~--/66==> ? * A A $(r26/;E  +0v#II(1IA;
=C	;A?>IHEPA=82(UVC,DA =2I6EK*C0.(0 zCHHTV r   