
    ajG7                    F   d dl mZ d dlZd dlmZmZmZ d dlmZ d dl	m
Z
 d dlmZ d dlmZ d dlmZ d d	lmZ d d
lmZmZmZ d dlmZ d dlmZ d dlmZ  G d d          Zd Zd'dZd'dZ d'dZ!d'dZ"d'dZ#d'dZ$d'dZ%d(dZ&d'd Z'd'd!Z(d'd"Z)d'd#Z*d'd$Z+d'd%Z,d'd&Z-dS ))    )annotationsN)datetime	timedeltatimezone)Decimal)main)create_crypto_asset)create_initial_holding_snapshot)create_wallet)data)CoinGeckoClient
PriceQuoterefresh_crypto_prices)build_crypto_report_context)connect_memory)apply_migrationsc                  *    e Zd ZddZddd	Zddd
ZdS )MockProviderreturnNonec                "    g | _         g | _        d S N)callsbatch_callsselfs    &tests/unit/test_crypto_price_update.py__init__zMockProvider.__init__   s    ,.
>@    CHFcoingecko_idstrcurrencyr   c                |    | j                             ||f           t          ||t          d          dd          S )N
123.456789	MockGecko2026-01-31T00:00:00+00:00)r!   r#   priceproviderprovider_timestamp)r   appendr   r   )r   r!   r#   s      r   get_crypto_pricezMockProvider.get_crypto_price   sQ    
<2333|hgVbNcNcny  Oj  k  k  k  	kr   c                v      j                             t          |          f            fd|D             S )Nc                >    i | ]}|                     |          S  )r,   ).0r!   r#   r   s     r   
<dictcomp>z2MockProvider.get_crypto_prices.<locals>.<dictcomp>   s,    nnnP\d33L(KKnnnr   )r   r+   tupler   coingecko_idsr#   s   ` `r   get_crypto_priceszMockProvider.get_crypto_prices   sF    }!5!5x @AAAnnnnn`mnnnnr   Nr   r   r    )r!   r"   r#   r"   r   r   )r#   r"   )__name__
__module____qualname__r   r,   r5   r/   r   r   r   r      sj        A A A Ak k k k ko o o o o o or   r   c            	     F   t                      } t          |            t          | dddd          }t          | ddd	          }t	          | ||t          d
          ddd           t          | ddd 	          }t	          | ||t          d          ddd           | ||fS )NzSynthetic WalletHardware Wallet	Syntheticr'   wallet_namewallet_typeplatform_providerlast_verified_atBitcoinBTCbitcoin	coin_namesymbolr!   0.01verifiedsynthetic fixtureasset_id	wallet_idquantityverification_statusnoterB   zMissing CoinMISS1)r   r   r   r	   r
   r   )connrN   btc_id
missing_ids       r   
setup_connrW       s   DTd0BPa  vA  To  p  p  pI 5W`aaaF#D6YY`agYhYh  I  Pc  vQ  R  R  R  R$T^FaefffJ#D:]deh]i]i  @J  Qd  wR  S  S  S  S##r   r   r   c                    t                      \  } }}t                      }t          | |dd          }|j        dk    sJ |j        dk    sJ |j        dk    sJ |j        dk    sJ |j        dgk    sJ |j        dgk    sJ | 	                    d|f          
                                }|J |d         d	k    sJ |d
         dk    sJ |d         dk    sJ d S )Nr    r   r)   r#   max_age_seconds   )rE   r    rE   r    z,SELECT * FROM crypto_prices WHERE asset_id=?r(   r%   price_currencyr)   r&   )rW   r   r   success_countskipped_countwarning_counterror_countr   r   executefetchone)rT   rU   _r)   resultrows         r   Ctest_refresh_crypto_prices_loads_active_assets_and_stores_chf_pricerh   +   s"    llOD&!~~H"4(U\]^^^F1$$$$1$$$$1$$$$""""$9#:::::>011111
,,Ey
Q
Q
Z
Z
\
\C???w<<'''' E))))z?k))))))r   c                 j   t                      \  } }}t                      }t          | |dd          }t          | |dd          }|j        dk    sJ |j        dk    sJ |                     d|f                                          }t          |          dk    sJ |d         d         dk    sJ d S )Nr    r   rY   r[   zISELECT * FROM alerts WHERE entity_id=? AND rule_id='missing_coingecko_id'occurrence_count   )rW   r   r   r`   rc   fetchalllen)rT   re   rV   r)   firstsecondalertss          r   Htest_missing_coingecko_id_is_skipped_and_warned_without_duplicate_alertsrq   >   s    $,,D!Z~~H!$E[\]]]E"4(U\]^^^F!####1$$$$\\ehrgtuu~~  A  AFv;;!!9'(A------r   c                     ddig } dfd	}t          ddd|| j                  }|                    d	d
          }|j        t	          d          k    sJ | dgk    sJ d         dk    sJ d S )Nnr      c                    dxx         dz  cc<   d         dk    r#t           j                            | ddd d            G d d          } |            S )Nrs   r[     rate limitedc                       e Zd Zd Zd Zd ZdS )Btest_429_backoff_is_used_by_provider.<locals>.opener.<locals>.Respc                    | S r   r/   r   s    r   	__enter__zLtest_429_backoff_is_used_by_provider.<locals>.opener.<locals>.Resp.__enter__U   s    r   c                    dS )NFr/   )r   argss     r   __exit__zKtest_429_backoff_is_used_by_provider.<locals>.opener.<locals>.Resp.__exit__V   s    eer   c                    dS )Ns8   {"bitcoin":{"chf":"12.34","last_updated_at":1700000000}}r/   r   s    r   readzGtest_429_backoff_is_used_by_provider.<locals>.opener.<locals>.Resp.readW   s    #^#^r   N)r8   r9   r:   r{   r~   r   r/   r   r   Respry   T   s+        ,,,333^^^^^r   r   urlliberror	HTTPError)urltimeoutr   r   s      r   openerz4test_429_backoff_is_used_by_provider.<locals>.openerP   s    c


a


:??,((c>4NNN	_ 	_ 	_ 	_ 	_ 	_ 	_ 	_ tvvr   rk   g      ?r[   max_retriesinitial_backoff_secondsmax_backoff_secondsr   sleeperrE   r    z12.34rt   )r   r+   r,   r(   r   )sleepsr   clientquoter   s       @r   $test_429_backoff_is_used_by_providerr   L   s    !HEF      D^_hnx~  yF  G  G  GF##Iu55E;''******dV:??????r   c                   	 t                      \  } 	}t                      }t          | |dd          }t          | |dd          }|j        dk    sJ |j        dk    sJ |j        dgk    sJ t          j        t          j	                  t          d          z
                                  }|                     d||	f           |                                  t          j        | d	          }t!          d
 |D                       }|d         dk    sJ t#          | d	          }t%          	fd|d         D                       sJ d S )Nr      rY   r[   r]   
   daysLUPDATE crypto_prices SET fetched_at=?, provider_timestamp=? WHERE asset_id=?)price_max_age_secondsc              3  2   K   | ]}|d          dk    |V  dS rH   rD   Nr/   r0   rg   s     r   	<genexpr>zNtest_fresh_cache_prevents_refetch_and_stale_price_is_marked.<locals>.<genexpr>q   0      AAs#h-5*@*@s*@*@*@*@AAr   price_qualitystalec              3  H   K   | ]}|d          dk    o|d         k    V  dS )codestale_price	entity_idNr/   r0   wrU   s     r   r   zNtest_fresh_cache_prevents_refetch_and_stale_price_is_marked.<locals>.<genexpr>t   s:      qq1qyM)Fan.Fqqqqqqr   data_quality_warnings)rW   r   r   r_   cached_countr   r   nowr   utcr   	isoformatrc   commitr   get_crypto_overviewnextr   any)
rT   re   r)   rn   ro   oldoverviewbtccontextrU   s
            @r   ;test_fresh_cache_prevents_refetch_and_stale_price_is_markedr   c   sp    llOD&!~~H!$E[_```E"4(U\`aaaF!####!####>011111<%%	r(:(:(::
E
E
G
GCLL_begjlrastttKKMMM'DIIIH
AAhAAA
A
AC7****)$dKKKGqqqqPWXoPpqqqqqqqqqqr   c                    t                      \  } }}t          | t                      dd           t          j        |           }t          j        |           }t          j        |           }t          d |D                       }|d         dk    sJ |d         sJ t          d |D                       sJ |d	         d
k    sJ |d         sJ d S )Nr    r   rY   c              3  2   K   | ]}|d          dk    |V  dS r   r/   r   s     r   r   zNtest_dashboard_reads_local_prices_without_provider_api_call.<locals>.<genexpr>   r   r   latest_price_chfz123.46market_value_chfc              3  >   K   | ]}|d          dk    |d         V  dS )rH   rD   wallet_value_chfNr/   r   s     r   r   zNtest_dashboard_reads_local_prices_without_provider_api_call.<locals>.<genexpr>   s7      VV3s8}PU?U?Us%&?U?U?U?UVVr   crypto_total_chfz0.00crypto_price_data_as_of)	rW   r   r   r   r   get_crypto_by_walletget_command_center_summaryr   r   )rT   rU   re   r   	by_walletsummaryr   s          r   ;test_dashboard_reads_local_prices_without_provider_api_callr   w   s     llOD&!$%YZ[[[['--H)$//I-d33G
AAhAAA
A
AC!"h....!""""VV)VVVVVVVV%&&0000,------r   c                    t                      \  } }t          |           }t          fd|d         D                       sJ t          | t	                      dd           t          |           }t          d |d         D                       }|d         d	k    sJ |d
         sJ t          fd|d         D                       rJ |d         d         du sJ d S )Nc              3  H   K   | ]}|d          dk    o|d         k    V  dS r   missing_pricer   Nr/   r   s     r   r   zYtest_crypto_report_uses_local_prices_and_improves_missing_price_status.<locals>.<genexpr>   s:      rrQqyO+H+&0Hrrrrrrr   r   r    r   rY   c              3  2   K   | ]}|d          dk    |V  dS r   r/   r   s     r   r   zYtest_crypto_report_uses_local_prices_and_improves_missing_price_status.<locals>.<genexpr>   s0      GGsH0F0Fs0F0F0F0FGGr   coins	price_chfr%   	value_chfc              3  H   K   | ]}|d          dk    o|d         k    V  dS r   r/   r   s     r   r   zYtest_crypto_report_uses_local_prices_and_improves_missing_price_status.<locals>.<genexpr>   s;      uuQR1V9/LAkNf4Luuuuuur   metadatalive_api_callsF)rW   r   r   r   r   r   )rT   re   beforeafterr   rU   s        @r   Ftest_crypto_report_uses_local_prices_and_improves_missing_price_statusr      s    llOD&!(..FrrrrRXYpRqrrrrrrrr$%YZ[[[['--E
GGeGnGGG
G
GC{|++++{uuuuV[\sVtuuuuuuuu-.%777777r   c           	        |dz  }|                      dt          |                     t                      }ddlm} |dz  dz  }|j                            d            ||          }t          |           t          |d	d
dd          }t          |ddd          }	t          ||	|t          d          ddd           |                                 |                     dd            t          g d          }
|                                j        }|
dk    sJ d|v sJ d|v sJ d|v sJ |                                 d S )NruntimeJARVIS_FINANCE_RUNTIME_DIRr   )connectr   zfinance.sqlite3T)parentsz
CLI Walletr<   r=   r'   r>   rC   rD   rE   rF   rI   rJ   rK   rL   z'jarvis_finance.cli.main.CoinGeckoClientc                     t                      S r   )r   )kwargss    r   <lambda>zMtest_cli_update_crypto_prices_command_uses_provider_factory.<locals>.<lambda>   s    T`TbTb r   )zupdate-crypto-pricesz--max-age-seconds0CRYPTO_PRICE_UPDATEz	updated=1z"assets_still_missing_local_price=0)setenvr"   r   jarvis_finance.storage.databaser   parentmkdirr   r   r	   r
   r   closesetattrr   
readouterrout)monkeypatchtmp_pathcapsysr   rT   r   db	file_connrN   rM   rcr   s               r   ;test_cli_update_crypto_prices_command_uses_provider_factoryr      s   "G3S\\BBBD777777	6	-	-BIOODO!!!IYi\O`t  Sn  o  o  oI"9	%^ghhhH#II`ghn`o`o  FP  Wj  }X  Y  Y  Y  YOOACbCbccc	@@@	A	AB





!C7777 C''''#/36666JJLLLLLr   rN   r"   rG   rH   r!   c          	     r    t          | |||          }t          | ||t          d          ddd           |S )NrF   2rJ   rK   r'   rL   )r	   r
   r   )rT   rN   rG   rH   r!   rM   s         r   _add_asset_with_holdingr      s`    "49VZfgggH#D8y[bcf[g[g  ~H  Ob  uP  Q  Q  Q  QOr   c                    t                      \  } }}|                     d                                          d         }t          | |ddd          }t	                      }t          | |dd	          }|j        d
k    sJ |j        dgk    sJ |                     d||f                                          d         d
k    sJ d S )N,SELECT wallet_id FROM crypto_wallets LIMIT 1rN   EthereumETHethereumrF   r    r   rY   rk   ))rE   r   r    z@SELECT COUNT(*) AS n FROM crypto_prices WHERE asset_id IN (?, ?)rs   )rW   rc   rd   r   r   r   updated_countr   )rT   rU   re   rN   eth_idr)   rf   s          r   ?test_batch_request_updates_multiple_assets_in_one_provider_callr      s     llOD&!KLLUUWWXcdI$T9
SXgqrrrF~~H"4(U\]^^^F1$$$$$D#EEEEE<<Z]cek\lmmvvxxy|}  BC  C  C  C  C  C  Cr   c                    t                      \  } }}|                     d                                          d         }t          | |ddd           t	          | t                      ddd	
           t                      }t	          | |ddd          }|j        dk    sJ |j        dgk    sJ |                     d|f                                          d         dk    sJ d S )Nr   rN   r   r   r   rF   r    r   rD   r)   r#   rZ   only_symbolT)r)   r#   rZ   only_missingr[   )r   r    z8SELECT COUNT(*) AS n FROM crypto_prices WHERE asset_id=?rs   )rW   rc   rd   r   r   r   r   r   )rT   rU   re   rN   r)   rf   s         r   :test_only_missing_refreshes_assets_without_any_fresh_pricer      s     llOD&!KLLUUWWXcdID)z%^hiiii$%Y]kpqqqq~~H"4(U\`ostttF1$$$$$:#;;;;;<<RU[T]^^ggiijmnrsssssssr   c                 @   t                      \  } }}|                     d                                          d         }t          | |ddd           t	          | t                      dd	           t          j        t          j	                  t          d
          z
                                  }|                     d|||f           |                                  t                      }t	          | |ddd          }|j        dk    sJ |j        dgk    sJ d S )Nr   rN   r   r   r   rF   r    r   rY   r   r   r   T)r)   r#   rZ   
only_staler[   r\   )rW   rc   rd   r   r   r   r   r   r   r   r   r   r   r   r   )rT   rU   re   rN   r   r)   rf   s          r   1test_only_stale_refreshes_only_stale_local_pricesr      s    llOD&!KLLUUWWXcdID)z%^hiiii$%Y]^^^^<%%	r(:(:(::
E
E
G
GCLL_begjlrastttKKMMM~~H"4(U\`mqrrrF1$$$$$9#:::::::r   c                    t                      \  } }}|                     d                                          d         }t          | |ddd           t          | |ddd	           t	                      }t          | |d
dd          }|j        dk    sJ |j        dgk    sJ t	                      }t          | |d
dd          }|j        dk    sJ t          |j        d         d                   dk    sJ d S )Nr   rN   r   r   r   rF   SolanaSOLsolanar    r   r   r[   r   )r)   r#   rZ   limit)	rW   rc   rd   r   r   r   r   r   rm   )rT   re   rN   r)   symbol_result	provider2limit_results          r   2test_only_symbol_and_limit_constrain_refresh_scoper     s   JD!QKLLUUWWXcdID)z%^hiiiiD)x\deeee~~H)$EcdrwxxxM&!++++$:#;;;;;I(	EcdlmnnnL%****y$Q'*++q000000r   c                    t                      \  } }}|                     d||f                                          d         }t          | t	                      ddd          }|j        du sJ |j        dk    sJ |j        dk    sJ |                     d	                                          d         dk    sJ |                     d||f                                          d         |k    sJ d S )
Nz:SELECT COUNT(*) AS n FROM alerts WHERE entity_id IN (?, ?)rs   r    r   T)r)   r#   rZ   dry_runr[   rk   z'SELECT COUNT(*) AS n FROM crypto_prices)rW   rc   rd   r   r   r  r   missing_local_price_count)rT   rU   rV   alerts_beforerf   s        r   ,test_dry_run_does_not_write_prices_or_alertsr
     s%   )||D&*LL!]`fhr_stt}}  AD  EM"4,..5bcmqrrrF>T!!!!1$$$$+q0000<<ABBKKMMcRVWWWWW<<TW]_iVjkkttvvwz{  @M  M  M  M  M  M  Mr   c                     g } ddidfd	}t          ddd|| j                  }|                    d	d
gd          }d         dk    sJ | ddgk    sJ d |                                D             dhk    sJ d S )Nrs   r   rt   c                j    dxx         dz  cc<   t           j                            | ddd d           )Nrs   r[   rv   rw   r   )r   r   r   s     r   r   zUtest_rate_limit_after_retries_marks_batch_stale_and_backoff_is_capped.<locals>.opener  s6    c


a


l$$S#~tTJJJr   rk   g      ?g      ?r   rE   r   r       c                    h | ]	}|j         
S r/   )quality_status)r0   r   s     r   	<setcomp>zXtest_rate_limit_after_retries_marks_batch_stale_and_backoff_is_capped.<locals>.<setcomp>  s    >>>UE >>>r   r   r   )r   r+   r5   values)r   r   r   quotesr   s       @r   Etest_rate_limit_after_retries_marks_batch_stale_and_backoff_is_cappedr     s    F!HEK K K K K K C]ajp  {A  {H  I  I  IF%%y*&=uEEF:????c4[    >>fmmoo>>>7)KKKKKKr   c                 X    G d d          } t                      \  }}}t          | |             ddd           t          | |             ddd           |                    d|f                                          }t	          |          dk    sJ |d         d	         d
k    sJ d S )Nc                      e Zd ZddZdS )Wtest_missing_local_price_alert_is_deduped_for_failed_refreshes.<locals>.MissingProviderr    c                     fd|D             S )Nc           
     :    i | ]}|t          |d dd          S )Nmissingz
not listed)r  error_message)r   )r0   cidr#   s     r   r1   z}test_missing_local_price_alert_is_deduped_for_failed_refreshes.<locals>.MissingProvider.get_crypto_prices.<locals>.<dictcomp>  sB      I  I  IsvCC4	amnnn  I  I  Ir   r/   r3   s     `r   r5   zitest_missing_local_price_alert_is_deduped_for_failed_refreshes.<locals>.MissingProvider.get_crypto_prices  s:     I  I  I  I  {H  I  I  I  Ir   Nr7   )r8   r9   r:   r5   r/   r   r   MissingProviderr    s.        	I 	I 	I 	I 	I 	Ir   r  r    r   rD   r   zOSELECT * FROM alerts WHERE entity_id=? AND rule_id='crypto_price_missing_local'r[   rj   rk   )rW   r   rc   rl   rm   )r  rT   rU   re   rp   s        r   >test_missing_local_price_alert_is_deduped_for_failed_refreshesr    s    I I I I I I I I !llOD&!$):):U\]kpqqqq$):):U\]kpqqqq\\kntmvww  A  A  C  CFv;;!!9'(A------r   r6   )rN   r"   rG   r"   rH   r"   r!   r"   ).
__future__r   urllib.errorr   r   r   r   decimalr   jarvis_finance.cli.mainr   jarvis_finance.crypto.assetsr	   jarvis_finance.crypto.holdingsr
   jarvis_finance.crypto.walletsr   jarvis_finance.dashboardr   jarvis_finance.market.providersr   r   r   'jarvis_finance.reports.crypto_inventoryr   r   r   !jarvis_finance.storage.migrationsr   r   rW   rh   rq   r   r   r   r   r   r   r   r   r   r  r
  r  r  r/   r   r   <module>r)     s   " " " " " "     2 2 2 2 2 2 2 2 2 2       ( ( ( ( ( ( < < < < < < J J J J J J 7 7 7 7 7 7 ) ) ) ) ) ) ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ O O O O O O : : : : : : > > > > > >o o o o o o o o$ $ $* * * *&. . . .   .r r r r(. . . . 8 8 8 8   6   
C 
C 
C 
Ct t t t; ; ; ; 1 1 1 1"
M 
M 
M 
ML L L L". . . . . .r   