
    mjE                        d dl Z d dlmZ d dlmZ d dlZd dlmZ d dlm	Z	 d dl
mZ d dlmZmZ d dlmZ d d	lmZ d
 Zd Zd Zd ZdddZd Zd Z G d d          Z G d d          Zd Zd Zd Zd Zd Z d Z!d Z"d Z#d Z$d Z%dS )     N)
Connection)cast)
TestClient)get_db)
create_app)confirm_policypreview_policy)connect_memory)apply_migrationsc                  @    t                      } t          |            | S N)r
   r   conns    .tests/unit/test_portfolio_policy_foundation.pydbr      s     DTK    c                  ^    ddddd ddddd	d
ddddd	dgd}|                     |            |S )NCHFz
2026-07-23longz	Long termz100.0020cash605070asset_class
target_pct	lower_pct	upper_pctequity4030)base_currencyeffective_fromhorizon	objectiveliquidity_reservemonthly_contributionmax_crypto_pctallocations)update)changesbases     r   payloadr.      sq    & ! (  &"!!	   ("!!	 
 D. 	KKKr   c                 f     t          d          } fd|j        t          <   t          |          S )Ntest
write_modec                       S r    r   s   r   <lambda>zclient_for.<locals>.<lambda>3       t r   )r   dependency_overridesr   r   )r   apps   ` r   
client_forr9   1   s2    

'
'
'C'3|||CV$c??r   c                     |                      d|          }|j        dk    sJ |                                d         du sJ |                                S )N/api/portfolio/policy/previewjson   validT)poststatus_coder=   )clientdataresponses      r   previewrE   7   sX    {{:{FFH3&&&&==??7#t++++==??r   identifiersc                |    |pt          | |          }|                     di ||d         |d         dd          S )N/api/portfolio/policy/confirm
preview_idconfirmation_idTrJ   rK   confirmr<   )rE   r@   )rB   rC   rG   s      r   rM   rM   >   sd    6!6!6K;;'

%l3*+<=	
 
 
    r   c                  ^   t                      } t          |           }t                      }| j        }t	          ||          }|d                             d          sJ |d                             d          sJ | j        |k    sJ t          |||          }t          |||          }|j        dk    sJ |j        dk    sJ |                                i |                                ddik    sJ | 	                    d	          
                                d
         dk    sJ d S )NrJ   zpolicy-preview-rK   zpolicy-confirm-rF   r>   
idempotentT'SELECT COUNT(*) FROM portfolio_policiesr      )r   r9   r.   total_changesrE   
startswithrM   rA   r=   executefetchone)r   rB   rC   beforerG   firstseconds          r   =test_preview_is_read_only_and_same_confirmation_is_idempotentrY   K   sD   44DF99DF&$''K|$//0ABBBBB()445FGGGGG''''FDk:::EVT{;;;F####$$$$;;==@uzz||@\4@@@@@@<<ABBKKMMaPTUUUUUUUr   c                  z   t                      } t          |           }t                      }t          ||          }t	          |||          j        dk    sJ t	          ||i |ddi          }|j        dk    sJ d|                                d         v sJ t          d	          }t          ||          }t	          |||d         |d
         d          }|j        dk    sJ d|                                d         v sJ |                     d                                          d         dk    sJ d S )NrF   r>   rJ   zpolicy-preview-wrong  VorschaudetailChangedr&   rK   )rJ   rK      verändertem InhaltrP   r   rQ   )	r   r9   r.   rE   rM   rA   r=   rT   rU   )r   rB   originalrG   wrong_previewchangedchanged_previewconflicts           r   @test_confirmation_id_is_bound_to_preview_and_payload_fail_closedrf   ]   sv   44DFyyH&(++K68===ISPPPPI{IL2HII  M
 $++++++--h77777	***Gfg..O),7*+<=
 
  H 3&&&& HMMOOH$=====<<ABBKKMMaPTUUUUUUUr   c                       e Zd Zd ZdS )_EmptyCursorc                     d S r   r4   selfs    r   rU   z_EmptyCursor.fetchone|   s    tr   N)__name__
__module____qualname__rU   r4   r   r   rh   rh   {   s#            r   rh   c                   ,    e Zd ZdZd ZddZd Zd ZdS )	_RaceConnectionzAHide the first lookup to force the post-IntegrityError race path.c                 "    || _         d| _        d S )NT)r   hide_confirmation_lookup)rk   r   s     r   __init__z_RaceConnection.__init__   s    	(,%%%r   r4   c                 x    | j         rd|v rd| _         t                      S | j                            ||          S )Nz/FROM portfolio_policies WHERE confirmation_id=?F)rr   rh   r   rT   )rk   sql
parameterss      r   rT   z_RaceConnection.execute   sB    )	"ASHH,1D)>>!y  j111r   c                 4    | j                                         S r   )r   commitrj   s    r   rx   z_RaceConnection.commit   s    y!!!r   c                 4    | j                                         S r   )r   rollbackrj   s    r   rz   z_RaceConnection.rollback   s    y!!###r   N)r4   )rl   rm   rn   __doc__rs   rT   rx   rz   r4   r   r   rp   rp      s[        KK- - -2 2 2 2" " "$ $ $ $ $r   rp   c            	         t                      } t          |           }t                      }t          ||          }t	          |||                                          }i ||ddi}t          t          t          t          |                     |          }|i |ddik    sJ | 
                    d                                          d         dk    sJ | 
                    d                                          d         dk    sJ t          d	
          }t          ||          }i ||d         |d         dd}	t          j        t          d          5  t          t          t          t          |                     |	           d d d            n# 1 swxY w Y   | 
                    d                                          d         dk    sJ | 
                    d                                          d         dk    sJ d S )NrF   rM   TrO   rP   r   rQ   zSELECT COUNT(*) FROM audit_logzChanged during racer_   rJ   rK   rL   r`   match)r   r9   r.   rE   rM   r=   r   r   r   rp   rT   rU   pytestraises
ValueError)
r   rB   ra   rG   rW   requestretriedrc   rd   conflicting_requests
             r   8test_integrity_error_race_path_rechecks_request_identityr      s[   44DFyyH&(++KFH+>>>CCEEE::[:)T::GT*od.C.CDDgNNG33d333333<<ABBKKMMaPTUUUUU<<899BBDDQG1LLLL 5666Gfg..O
%l3&'89	   
z)>	?	?	? 
 
_T22335H	
 	
 	

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 <<ABBKKMMaPTUUUUU<<899BBDDQG1LLLLLLs   1FF #F c                     t                      } t          |           }t          d          }t          d          }t          ||                                          }t          ||                                          }t          ||                                          }|d         |d         |d         gg dk    sJ t          |d         |d         |d         h          dk    sJ |                     d                                          }|d	         d
         J |d         d
         |d	         d         k    sJ |d         d
         |d         d         k    sJ d |D             g dk    sJ d |D             g dk    sJ d S )NAr_   Bversion)rQ         	policy_idr   zwSELECT policy_id, version, previous_policy_id, is_active, objective
           FROM portfolio_policies ORDER BY versionr   previous_policy_idrQ   r   c                     g | ]
}|d          S )	is_activer4   .0rows     r   
<listcomp>z^test_new_confirmation_allows_same_content_and_a_b_a_creates_three_versions.<locals>.<listcomp>       ---C---r   )r   r   rQ   c                     g | ]
}|d          S r_   r4   r   s     r   r   z^test_new_confirmation_allows_same_content_and_a_b_a_creates_three_versions.<locals>.<listcomp>   r   r   )r   r   r   )r   r9   r.   rM   r=   lenrT   fetchall)r   rB   policy_apolicy_brW   rX   thirdrowss           r   Jtest_new_confirmation_allows_same_content_and_a_b_a_creates_three_versionsr      s   44DF%%%H%%%HFH%%**,,EVX&&++--FFH%%**,,E)fY/y1ABiiiOOOOk"F;$7{9KLMMQRRRRR<<	7  hjj 	 7'(0007'(DGK,@@@@@7'(DGK,@@@@@-----::::-----@@@@@@r   c                  :   t                      } t          |           }t          |t                                                                }|                     d|d         f                                          }t	          j        |d                   }|d         |d         k    sJ |d         dk    sJ |d         d	k    sJ t          |          d
dhk    sJ |d	d dk    sJ |d         }dD ]}||vsJ 	t          j
        t          d          5  |                     d|d         f           d d d            n# 1 swxY w Y   t          j
        t          d          5  |                     d|d         f           d d d            d S # 1 swxY w Y   d S )Nz_SELECT entity_id, action, confirmed, new_values_json
           FROM audit_log WHERE audit_id=?audit_idnew_values_json	entity_idr   actionportfolio_policy_confirmed	confirmedrQ   r   r   )r   r   )	r   r   
benchmarksr&   rE   confirmationr.   hashr(   	immutabler}   zCUPDATE portfolio_policies SET objective='changed' WHERE policy_id=?zcannot be deletedz:DELETE FROM portfolio_policy_allocations WHERE policy_id=?)r   r9   rM   r.   r=   rT   rU   loadssetr   r   	Exception)r   rB   r   auditdetails
serialized	forbiddens          r   ?test_audit_has_exact_allowlist_and_history_content_is_immutabler      s\   44DF		**//11ILL	.	:	   hjj	 

 j0122G;!77777?:::::""""w<<I';<<<<<!4@@@@@@()J + +	 
*****	y	4	4	4 
 
Q{#%	
 	
 	

 
 
 
 
 
 
 
 
 
 
 
 
 
 

 
y(;	<	<	< 
 
H{#%	
 	
 	

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
s$   D??EE%FFFc            	         t                      } t          |           }t          d ddddddig          }t          ||          }|j        dk    sJ |                     d	|                                d
         f                                          }|d         J |d         dk    sJ |d         dk    sJ t          d          dft          d          dft          d          dft          d          dft          dddg          dft          dd ig          dfg}|D ]x\  }}|                    d|          }|j        dk    sJ |                                d         d u sJ |d!	                    |                                d"                   v sJ yt          | t          d#                    }	|	d         d u sJ d$d!	                    |	d"                   v sJ d S )%Nz0.100100z0.01	referenceSPI)r'   r(   min_transaction_amountmax_single_position_pctrebalance_tolerance_pctr   r>   z{SELECT liquidity_reserve, monthly_contribution, min_transaction_amount
           FROM portfolio_policies WHERE policy_id=?r   r'   r(   r   z-0.01)r'   z-1)r(   z100.01)r)   100 Prozent101)r   r   )r   weight)r   Gewichter   Referenzr;   r<   r?   F; errorsg?zexakter Decimal-Text)r   r9   r.   rM   rA   rT   r=   rU   r@   joinr	   )
r   rB   r?   r   r   casesinvalidexpectedrD   direct_floats
             r   5test_decimal_null_percentage_and_benchmark_validationr      s^   44DF#" % & %()  E &&I C''''
,,	8			+	&(  hjj	 
 "#+++%&&0000'(C//// 
7	+	+	+-@A	d	+	+	+-CD		)	)	)=9		/	/	/1JK	5EBBC	D	D	DjQ	h-.	/	/	/<E # @ @;;>W;MM#s****}}w'50000499X]]__X%>???????!$S(I(I(IJJL E))))!TYY|H/E%F%FFFFFFFr   c            	         t                      } t          |           }dddddgdfdddddgdfdddddgd	fdd
ddddddddgdfg}|D ]\  }}|                    dt          |                    }|j        dk    sJ |                                d         du sJ |d                    |                                d                   v sJ d S )Nr   r   r   r   r   r   Bandgrenzenz99.9z	exakt 100r   cryptor"   zCrypto-Zielallokationr;   )r*   r<   r>   r?   Fr   r   )r   r9   r@   r.   rA   r=   r   )r   rB   r   r*   r   rD   s         r   7test_allocation_percentages_bounds_total_and_crypto_capr     sU   44DF $5sY^__`	

 $5u[`aab	

 $6Z_``a	
 !'d[`aa (3]bcc $	
E* "' @ @X;;+[111  
 
 #s****}}w'50000499X]]__X%>???????@ @r   c                     t                      } |                     d           t          |            d |                     d          D             }ddh|k    sJ |                     d                                          }t          |          ddd d d	k    sJ d
 |                     d          D             }d|v sJ d S )Na  
        CREATE TABLE portfolio_policies (
          policy_id TEXT PRIMARY KEY, version INTEGER NOT NULL UNIQUE,
          is_active INTEGER NOT NULL, effective_from TEXT NOT NULL,
          previous_policy_id TEXT, base_currency TEXT NOT NULL, horizon TEXT,
          objective TEXT, liquidity_reserve TEXT, monthly_contribution TEXT,
          max_single_position_pct TEXT, max_crypto_pct TEXT,
          rebalance_tolerance_pct TEXT, min_transaction_amount TEXT,
          benchmarks_json TEXT NOT NULL DEFAULT '[]',
          restrictions_json TEXT NOT NULL DEFAULT '[]',
          request_fingerprint TEXT NOT NULL UNIQUE, audit_id TEXT NOT NULL,
          created_at TEXT NOT NULL
        );
        INSERT INTO portfolio_policies(
          policy_id, version, is_active, effective_from, base_currency,
          request_fingerprint, audit_id, created_at
        ) VALUES ('old-policy', 1, 1, '2026-07-23', 'CHF', 'old-hash', 'old-audit', 'now');
        c                     h | ]
}|d          S namer4   r   s     r   	<setcomp>zTtest_migration_adds_request_idempotency_columns_to_sprint4_schema.<locals>.<setcomp>P  )       F  r   z%PRAGMA table_info(portfolio_policies)rK   payload_hashzSELECT policy_id, request_fingerprint, confirmation_id, payload_hash
           FROM portfolio_policies WHERE policy_id='old-policy'z
old-policyzold-hash)r   request_fingerprintrK   r   c                     h | ]
}|d          S r   r4   r   s     r   r   zTtest_migration_adds_request_idempotency_columns_to_sprint4_schema.<locals>.<setcomp>^  r   r   z%PRAGMA index_list(portfolio_policies)&idx_portfolio_policies_confirmation_id)r
   executescriptr   rT   rU   dict)r   columnsoldindexess       r   Atest_migration_adds_request_idempotency_columns_to_sprint4_schemar   9  s   D	  ( T #||,STT  G ~.'9999
,,	C  hjj  99!)	      #||,STT  G 4w>>>>>>r   c            	         t                      } t          |           }t          |t          d                                                    }t          ddddddd	ddddg
          }t          ||                                          }| j        }d |                     d                                          D             }d |                     d                                          D             }d |                     d                                          D             }|                    d|d                    }	|                    d|d                    }
|                    d|d                    }|	j	        |
j	        cxk    r|j	        cxk    rdk    sn J |	                                d         dk    sJ |	                                d         du sJ |	                                d         dk    sJ d |	                                d         D             ddhk    sJ |
                                d         dk    sJ |
                                d         du sJ |
                                d         |d         k    sJ d  |
                                d         D             d!d"hk    sJ | j        |k    sJ d# |                     d                                          D             |k    sJ d$ |                     d                                          D             |k    sJ d% |                     d                                          D             |k    sJ d S )&N
Historischr_   Aktivr   r   r!   r   r   r    )r&   r*   c                 ,    g | ]}t          |          S r4   tupler   s     r   r   zatest_policy_detail_returns_requested_historical_and_active_versions_read_only.<locals>.<listcomp>{  s.        	c

  r   z1SELECT * FROM portfolio_policies ORDER BY versionc                 ,    g | ]}t          |          S r4   r   r   s     r   r   zatest_policy_detail_returns_requested_historical_and_active_versions_read_only.<locals>.<listcomp>  s.        	c

  r   zJSELECT * FROM portfolio_policy_allocations ORDER BY policy_id, asset_classc                 ,    g | ]}t          |          S r4   r   r   s     r   r   zatest_policy_detail_returns_requested_historical_and_active_versions_read_only.<locals>.<listcomp>  s.        	c

  r   z)SELECT * FROM audit_log ORDER BY audit_id/api/portfolio/policy/r   r>   r   rQ   r   Fr&   c                 .    h | ]}|d          |d         fS r   r   r4   r   s     r   r   z`test_policy_detail_returns_requested_historical_and_active_versions_read_only.<locals>.<setcomp>  7        
]	S./  r   r*   )r   r   )r    r!   r   Tr   c                 .    h | ]}|d          |d         fS r   r4   r   s     r   r   z`test_policy_detail_returns_requested_historical_and_active_versions_read_only.<locals>.<setcomp>  r   r   )r   r   )r    r   c                 ,    g | ]}t          |          S r4   r   r   s     r   r   zatest_policy_detail_returns_requested_historical_and_active_versions_read_only.<locals>.<listcomp>  .        	c

  r   c                 ,    g | ]}t          |          S r4   r   r   s     r   r   zatest_policy_detail_returns_requested_historical_and_active_versions_read_only.<locals>.<listcomp>  r   r   c                 ,    g | ]}t          |          S r4   r   r   s     r   r   zatest_policy_detail_returns_requested_historical_and_active_versions_read_only.<locals>.<listcomp>  s.        	c

  r   )
r   r9   rM   r.   r=   rR   rT   r   getrA   )r   rB   rW   second_datarX   before_changesbefore_policiesbefore_allocationsbefore_audits
historicalactiverepeateds               r   Mtest_policy_detail_returns_requested_historical_and_active_versions_read_onlyr   d  sS   44DFFGl;;;<<AACCE  &"!!	   ("!!	 
  K" V[))..00F'N <<?
 

(**	  O <<X
 

(**	   << KLLUUWW  M
 IU;5GIIJJJZZF1DFFGGFzzG53EGGHHH!V%7VVVV8;OVVVVSVVVVVVV??Y'1,,,,??[)U2222??[)\9999 ??$$]3   *	+, , , , ;;==#q((((;;==%----;;==-.%2DDDDD ;;==/   *	+, , , , //// <<?
 

(**	  
 
    <<X
 

(**	  
 
    << KLLUUWW   
     r   c                  h   t                      } t          |           }d}|                    d|           }|j        dk    sJ |                                ddik    sJ ||j        vsJ d}t          d                                          d	         |         }t          |          d
hk    sJ d S )Nzpolicy-secret-do-not-echor   i  r]   zPolicy-Version nicht gefunden!/api/portfolio/policy/{policy_id}r0   r1   pathsr   )	r   r9   r   rA   r=   textr   openapir   )r   rB   
missing_idrD   path
operationss         r   Ctest_policy_detail_404_has_no_identifier_and_exposes_no_write_router     s    44DF,Jzz?:??@@H3&&&&==??x)HIIIIIX]****.Dv...6688A$GJz??ug%%%%%%r   c                  J   t                      t          d          } fd| j        t          <   t	          | d          }|                    di           j        dk    sJ |                                 }|d	         d
         d         }|d	         d
         d         }ddht          |d                   k    sJ h dt          |d                   k    sJ |d         d         d         }|d         d         d         d         d         ddik    sJ d|d         v sJ d S )Ndisabledr1   c                       S r   r4   r   s   r   r5   zYtest_policy_openapi_and_write_security_cover_preview_confirm_and_detail.<locals>.<lambda>  r6   r   )z100.64.0.10iP  )rB   rI   r<   i  
componentsschemasPolicyPreviewResponsePolicyConfirmRequestrJ   rK   required>   rM   rJ   r*   r#   r$   rK   r   r   r   	responses200contentzapplication/jsonschemaz$refz,#/components/schemas/PortfolioPolicyResponse404)	r   r   r7   r   r   r@   rA   r   r   )r8   remoter  preview_schemaconfirm_schema
detail_getr   s         @r   Gtest_policy_openapi_and_write_security_cover_preview_confirm_and_detailr    si   44D


+
+
+C'3|||CV$$:;;;F;;6R;@@LPSSSSS[[]]FL))45LMNL))45KLN+,N:4N0O0OOOOO   
^J'	(	() ) ) ) !DEeLJk"5))45GHR>W     J{+++++++r   c                     t                      } t          |           }t                      }t          ||          }dD ]C}i ||ddi}|                    |           |                    d|          j        dk    sJ D|                    di ||ddi          }|j        dk    sJ |                                d	d
ik    sJ d S )N)rJ   rK   r*   rM   TrI   r<   i  Fr[   r]   u'   Explizite Bestätigung ist erforderlich)r   r9   r.   rE   popr@   rA   r=   )r   rB   rC   rG   omittedr   rejecteds          r   @test_confirm_requires_complete_request_and_explicit_confirmationr    s   44DF99D&$''KC ] ]:T:[:)T::G{{:{IIUY\\\\\\{{'666Y66   H 3&&&&==??x)RSSSSSSSr   )&r=   sqlite3r   typingr   r   fastapi.testclientr   jarvis_finance.api.dependenciesr   jarvis_finance.api.mainr   (jarvis_finance.services.portfolio_policyr   r	   jarvis_finance.storage.databaser
   !jarvis_finance.storage.migrationsr   r   r.   r9   rE   rM   rY   rf   rh   rp   r   r   r   r   r   r   r   r   r  r  r4   r   r   <module>r     s4                 ) ) ) ) ) ) 2 2 2 2 2 2 . . . . . . S S S S S S S S : : : : : : > > > > > >    8     *. 
 
 
 
 
V V V$V V V<       
$ $ $ $ $ $ $ $.M M M:A A A.
 
 
D'G 'G 'GT@ @ @D(? (? (?VK K K\& & &, , ,,T T T T Tr   