
    Pj"                       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mZ ddlZ ee      j!                         j"                  d   Z ee      ej(                  vr"ej(                  j+                  d ee             ddlmZmZmZmZmZmZmZmZm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.y)u  anu_v2.tests.test_owner_trigger_pat_phase1_2553 — Phase 1 decision schema 단위 테스트.

회장 §명시 Phase 1 (task-2553):
  - `OwnerTriggerDecision` frozen dataclass schema 필드 검증
  - `serialize_decision` JSON 직렬화 가능 + token 필드 0
  - `write_decision_json` atomic write (`.tmp` → rename)
  - dedupe key = `{pr_number}#{head_sha}` — head 변경 시 자동 stale
  - `is_duplicate_trigger` jsonl audit lookup

본 회귀는 anu_v2/* 모듈만 import 한다 (one-way isolation).
    )annotationsN)Path   )	DECISION_PASSDECISION_REJECT!EVIDENCE_MISSING_FOR_CURRENT_HEAD
OUTCOME_OKOwnerTriggerDecisionis_duplicate_triggermake_dedupe_keyserialize_decisionwrite_decision_jsonc                    d} t         | k(  }|st        j                  d|fdt         | f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       dz  }dd|iz  }t        t        j                  |            d x}} d} t        | k(  }|st        j                  d|fdt        | f      d	t        j                         v st        j
                  t              rt        j                  t              nd	t        j                  |       dz  }dd|iz  }t        t        j                  |            d x}} y )
NPASS==z%(py0)s == %(py3)sr   py0py3assert %(py5)spy5REJECTr   )
r   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationr   )@py_assert2@py_assert1@py_format4@py_format6s       F/home/jay/workspace/anu_v2/tests/test_owner_trigger_pat_phase1_2553.pytest_phase1_decision_constantsr'   )   s    ""=F""""=F""""""="""="""F"""""""&&?h&&&&?h&&&&&&?&&&?&&&h&&&&&&&    c            
         t        ddt        dt        ddd      } t        j                  t
        j                        5  t        | _        ddd       y# 1 sw Y   yxY w)	uQ   frozen dataclass — 인스턴스 mutation 차단 (audit trail 박제 무결성).Q   deadbeefokr   81#deadbeef2026-05-11T00:00:00+00:00	pr_numberhead_shadecisionreasongemini_evidence_statequeue_position
dedupe_keytsN)	r
   r   r   pytestraisesdataclassesFrozenInstanceErrorr   r2   )r2   s    r&   test_phase1_decision_is_frozenr<   .   sU    #? &	H 
{66	7 ,+, , ,s   AAc                    t        j                  t              D  ch c]  } | j                   }} h 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}}y c c} w )N>   r7   r3   r2   r1   r0   r6   r5   r4   r   r   fieldsr   r   r   )r:   r>   r
   namer   r   r   r   r   r   r    r!   )fr>   r"   r#   r$   r%   s         r&   (test_phase1_decision_has_required_fieldsrA   >   s    )001EFGaffGFG	 	6 	 	 	 	6 	 	 	 	 	   	 	   	 	 	 	 	 	 	 	 	 Hs   Cc            
        t        ddt        dt        ddd      } t        |       }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 }|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}}|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}}|d   }|t        k(  }|st        j                  d|fd|t        f      t        j                  |      dt        j                         v st        j                  t              rt        j                  t              nddz  }d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}}y )Nr*   abc123all_gates_passr   z	81#abc123z2026-05-11T01:23:45+00:00r/   z5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstanceoutdict)r   py1py2py4r0   r   z%(py1)s == %(py4)srH   rJ   assert %(py6)spy6r1   r2   z%(py1)s == %(py3)sr   rH   r   r   r   r6   )r
   r   r   r   rE   rG   r   r   r   r   r   r    r!   r   )	r2   rF   @py_assert3@py_format5@py_assert0r"   @py_format7r$   r%   s	            r&   ;test_phase1_serialize_decision_returns_dict_with_all_fieldsrU   M   s   #?&	H X
&Cc4        :   :      c   c      4   4          {!r!r!!!!r!!!!!!r!!!!!!!z?&h&?h&&&&?h&&&?&&&h&&&&&&&z?+?m++++?m+++?++++++m+++m+++++++|+++++++++++++++++++r(   c            
        t        ddt        dt        ddd      } t        |       }t	        j
                  |d	      }t	        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}}y )Nc   cafefeedduplicate_triggerr   z99#cafefeedz2026-05-11T01:00:00+00:00r/   T)	sort_keysr0   r   rK   rL   rM   rN   )r
   r   r   r   jsondumpsloadsr   r   r   r    r!   )	r2   payloadtextparsedrS   rQ   r"   rR   rT   s	            r&   3test_phase1_serialize_decision_is_json_serializablera   `   s    # "? &	H !*G::g.DZZF+$"$"$$$$"$$$$$$"$$$$$$$r(   c            
     F  	 t        ddt        dt        ddd      } t        |       }|j	                         D ch c]  }t        |      j                          }}h d}|D ]  		fd	|D        }t        |      }| }|sd
dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}x}} yc c}w )ua   decision schema 에는 토큰 필드가 구조적으로 없음 — 박제 시 token raw 노출 0.   xrr   z1#xtr/   >   patghp_ghs_tokensecretauthorizationc              3  &   K   | ]  }|v  
 y w)N ).0khints     r&   	<genexpr>zAtest_phase1_serialize_decision_no_token_fields.<locals>.<genexpr>   s     5Qtqy5s   z0assert not %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}anyr   rI   rJ   N)r
   r   r   r   keysstrlowerrs   r   r   r   r   r   r    r!   )
r2   r^   rp   
keys_lowerforbidden_hintsr#   rQ   @py_assert5r%   rq   s
            @r&   .test_phase1_serialize_decision_no_token_fieldsr{   r   s    #?	H !*G*1,,.9Q#a&,,.9J9QO 65*55355555555555535553555555555555556 :s    Dc                6   | dz  dz  }ddt         dt        dddd	}t        ||       |j                  } |       }|sd
dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            d x}}|j                  }|j                  }d}||z   } ||      }	|	j                  }
 |
       }| }|sd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t        j                  |      t        j                  |      t        j                  |	      t        j                  |
      t        j                  |      dz  }t        t        j                  |            d x}x}x}x}x}	x}
x}}t        j                  |j                  d            }|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}}|d   }|t         k(  }|st        j                   d|fd|t         f      t        j                  |      dt	        j
                         v st        j                  t               rt        j                  t               nddz  }dd|iz  }t        t        j                  |            d x}}y )Nsubdirzowner_trigger_decision.jsonr*   r+   r,   r   r-   r.   r/   zAassert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}targetrt   z.tmpzassert not %(py14)s
{%(py14)s = %(py12)s
{%(py12)s = %(py10)s
{%(py10)s = %(py2)s
{%(py2)s = %(py0)s.with_suffix
}((%(py5)s
{%(py5)s = %(py3)s.suffix
} + %(py7)s))
}.exists
}()
})r   rI   r   r   py7py10py12py14utf-8encodingr0   r   rK   rL   rM   rN   r2   rO   r   rP   r   r   )r   r   r   existsr   r   r   r   r   r    r!   with_suffixsuffixr[   r]   	read_textr   )tmp_pathr~   r^   r#   rQ   rR   @py_assert4@py_assert6@py_assert8@py_assert9@py_assert11@py_assert13@py_assert15@py_format16r`   rS   r"   rT   r$   r%   s                       r&   ,test_phase1_write_decision_json_atomic_writer      sJ    #@@F!!B#)	G (===??66=?!!B&--B&B-&"8B!"89B9@@B@BBBBBBBBBBBvBBBvBBB!BBBBBB&BBB&BBB-BBB&BBB9BBB@BBBBBBBBBBBZZ(('(:;F+$"$"$$$$"$$$$$$"$$$$$$$*........................r(   c                   | dz  }|j                  dd       t        dd}t        ||       t        j                  |j                  d            }|d   }|t        k(  }|st        j                  d|fd	|t        f      t        j                  |      d
t        j                         v st        j                  t              rt        j                  t              nd
dz  }dd|iz  }t        t        j                  |            dx}}y)uL   이미 파일이 있어도 새 PASS/REJECT 결정으로 덮어쓰기 가능.zdecision.jsonzOLD CONTENTr   r   evidence_not_missing)r2   r3   r2   r   rO   r   rP   r   r   N)
write_textr   r   r[   r]   r   r   r   r   r   r   r   r    r!   )r   r~   r^   r`   rS   r"   r$   r%   s           r&   2test_phase1_write_decision_json_overwrite_existingr      s    'F
mg6*6LMG(ZZ(('(:;F*000000000000000000000000r(   c                    d} d}t        | |      }d}||k(  }|st        j                  d|fd||f      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}}d} d
}t        | |      }d}d}t        ||      }	||	k7  }|sNt        j                  d|fd||	f      dt        j                         v st        j
                  t               rt        j                  t               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                  |      t        j                  |	      dz  }
dd|
iz  }t        t        j                  |            d x} x}x}x}x}x}}	y )Nr*   r+   r-   r   )z9%(py6)s
{%(py6)s = %(py0)s(%(py2)s, %(py4)s)
} == %(py9)sr   )r   rI   rJ   rN   py9zassert %(py11)spy11newshax)!=)zd%(py6)s
{%(py6)s = %(py0)s(%(py2)s, %(py4)s)
} != %(py14)s
{%(py14)s = %(py8)s(%(py10)s, %(py12)s)
})r   rI   rJ   rN   py8r   r   r   zassert %(py16)spy16)	r   r   r   r   r   r   r   r    r!   )r#   rQ   rz   r   @py_assert7@py_format10@py_format12r   r   r   @py_format15@py_format17s               r&   "test_phase1_make_dedupe_key_formatr      st   ;z;?2z*;m;*m;;;;*m;;;;;;?;;;?;;;2;;;z;;;*;;;m;;;;;;;LyL?2y)LRLL_R-LL)-LLLLL)-LLLLLLL?LLL?LLL2LLLyLLL)LLLLLL_LLL_LLLRLLLLLL-LLLLLLLLLr(   c                `   | dz  }d}t        ||      }d}||u }|s
t        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                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            d x}x}x}}y )Nowner_trigger_audit.jsonlr-   Fisz9%(py5)s
{%(py5)s = %(py0)s(%(py1)s, %(py3)s)
} is %(py8)sr   
audit_pathr   rH   r   r   r   assert %(py10)sr   )	r   r   r   r   r   r   r   r    r!   r   r   r"   r   r   r   @py_format9@py_format11s           r&   Atest_phase1_is_duplicate_trigger_returns_false_when_no_audit_filer      s    77J,9C
M:CeC:eCCCC:eCCCCCCCCCCCCCCC
CCC
CCCMCCC:CCCeCCCCCCCr(   c                   | dz  }|j                  t        j                  dt        dd      dz   d       d}t	        ||      }d}||u }|s
t        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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}x}}y )Nr   r-   r.   )r6   outcomer7   
r   r   Tr   r   r   r   r   r   r   r   r[   r\   r	   r   r   r   r   r   r   r   r    r!   r   s           r&   Ctest_phase1_is_duplicate_trigger_returns_true_for_matching_ok_entryr      s    77J

'!-
 	 		
    -:B
M:BdB:dBBBB:dBBBBBBBBBBBBBBB
BBB
BBBMBBB:BBBdBBBBBBBr(   c                   | dz  }|j                  t        j                  dt        d      dz   d       d}t	        ||      }d}||u }|s
t        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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}x}}y )Nr   z	81#oldshar6   r   r   r   r   z	81#newshaFr   r   r   r   r   r   r   r   r   s           r&   Gtest_phase1_is_duplicate_trigger_returns_false_for_different_dedupe_keyr      s    77J

+*EFM  
 -8A
K8AEA8EAAAA8EAAAAAAAAAAAAAAA
AAA
AAAKAAA8AAAEAAAAAAAr(   c                   | dz  }|j                  t        j                  ddd      dz   t        j                  ddd      z   dz   d       d}t        ||      }d	}||u }|s
t	        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                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}x}}y)uO   REJECT/failed outcome 은 dedupe 로 카운트되지 않음 (재시도 가능).r   r-   rejectedr   r   failedr   r   Fr   r   r   r   r   r   r   N)r   r[   r\   r   r   r   r   r   r   r   r    r!   r   s           r&   9test_phase1_is_duplicate_trigger_ignores_rejected_entriesr      s    77J

-JGH4O
**MhG
H	IKO	P  
 -:C
M:CeC:eCCCC:eCCCCCCCCCCCCCCC
CCC
CCCMCCC:CCCeCCCCCCCr(   c                   | dz  }|j                  dt        j                  dt        d      z   dz   dz   d       d}t	        ||      }d	}||u }|s
t        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                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}}y)u0   깨진 jsonl 라인은 skip — 안전 fallback.r   zthis is not json
r-   r   r   z{broken
r   r   Tr   r   r   r   r   r   r   Nr   r   s           r&   6test_phase1_is_duplicate_trigger_skips_malformed_linesr      s    77J
**MjI
J	KMQ	R
	 	   -:B
M:BdB:dBBBB:dBBBBBBBBBBBBBBB
BBB
BBBMBBB:BBBdBBBBBBBr(   c                   t        ddt        dt        dt        dd      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}}y)uH   decision dataclass 의 dedupe_key 가 make_dedupe_key 와 동일 포맷.*   cafebaber,   r   z2026-05-11T02:00:00+00:00r/   z42#cafebaber   )z2%(py2)s
{%(py2)s = %(py0)s.dedupe_key
} == %(py5)sr2   )r   rI   r   zassert %(py7)sr   N)r
   r   r   r   r6   r   r   r   r   r   r   r    r!   )r   r2   r#   r   rQ   r%   @py_format8s          r&   "test_phase1_dedupe_key_in_decisionr      s    #?"2z2&	H /-/-////-//////8///8//////-///////r(   )returnNone)r   r   r   r   )/__doc__
__future__r   builtinsr   _pytest.assertion.rewrite	assertionrewriter   r:   r[   syspathlibr   r8   __file__resolveparentsWORKSPACE_ROOTrv   pathinsertanu_v2.owner_trigger_patr   r   r   r	   r
   r   r   r   r   r'   r<   rA   rU   ra   r{   r   r   r   r   r   r   r   r   r   rn   r(   r&   <module>r      s   
 #      
   h'')11!4~chh&HHOOAs>*+
 
 
'
, ,&%$6(/,1MD

CBD	C0r(   