
    A jq                    V   d Z ddlmZ ddlZddlmZ ddlmZm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mZmZmZ dZddd	ed
d	 	 	 	 	 	 	 d/dZd0dZd0dZ d0dZ!d0dZ"d0dZ#d0dZ$d0dZ%d0dZ&d0dZ'd0dZ(d0dZ)d0dZ*d0dZ+d0dZ,d0dZ-d0dZ.d0dZ/d0dZ0d0dZ1d0d Z2d0d!Z3d0d"Z4d0d#Z5d0d$Z6d0d%Z7d0d&Z8d0d'Z9d0d(Z:d0d)Z;d0d*Z<d0d+Z=d0d,Z>d- Z?d. Z@y)1u  anu_v2.tests.test_auto_gemini_triage_2538 — 9 회귀 (회장 §명시 1:1 박제).

회귀 케이스 (회장 §명시):
  1. false_positive dismiss              — 회귀 fixture 매칭 시 dismiss
  2. style_only dismiss                  — 코드 동작 무관 style 권고 dismiss
  3. minor_fix_in_scope auto_apply       — expected_files 내부 minor fix 자동 적용
  4. scope_expansion escalate            — expected_files 밖 수정 요구 → Critical
  5. Security-High in scope              — Security 권고는 minor 무관 적용 (단 scope 내부)
  6. Security-High out of scope          — escalate
  7. interface contract                  — applied/dismissed/escalated 3 키 제공
  8. token raw 0                         — finding 처리 후 raw 토큰 노출 0
  9. chat=6937032012 격리                 — 다른 chat record 노출 0

본 회귀는 anu_v2/* 모듈만 import 한다 (one-way isolation).
    )annotationsN)Path)AnyMapping   )ACTION_AUTO_APPLY_MINOR_FIXACTION_DISMISS_FALSE_POSITIVEACTION_DISMISS_STYLE_ONLYACTION_ESCALATE_SCOPE_EXPANSIONACTIONSCRITICAL_GEMINI_SCOPE_EXPANSIONDEFAULT_CHAT_IDTOKEN_KEY_HINTSAutoGeminiTriageFalsePositiveFixtureTriageResult_redact_tokens)anu_v2/auto_gemini_triage.pyz,anu_v2/tests/test_auto_gemini_triage_2538.py z	task-2538)audit_callsfix_applierfixtureschat_idtask_idc                6      g   fd}t        |||||      S )Nc                :    j                  t        |              y Nappenddictrecr   s    @/home/jay/workspace/anu_v2/tests/test_auto_gemini_triage_2538.pyaudit_writerz"_make_triage.<locals>.audit_writer>       49%    )r$   r   false_positive_fixturesr   r   )r   )r   r   r   r   r   r$   s   `     r#   _make_triager(   3   s3     & ! ( r&   c                     g } t        dd      f}t        | |      }dddddd	d
}|j                  |t              \  }}|t        k(  sJ |d   dk(  sJ |d   dk(  sJ y )Nzunused-importzvendored stubrule_idreason)r   r   Lowstyler   zF401 unused importzmodule-level import not usedr+   severitycategorypathtitlebodyr+   fixture_reasonr   r(   classify_evidenceEXPECTED_FILES_DEFAULTr	   )auditr   triagefindingactiondetailss         r#   )test_1_false_positive_dismiss_via_fixturer>   K   s    %'E__MH eh?F #.%.G ..w8NOOFG22229000#$777r&   c                     t        ddd      f} t        |       }ddd}ddd}|j                  |t              \  }}|j                  |t              \  }}|t        k(  sJ |t        k7  sJ y )	Nzredundant-castzint\(int_value\)zPython 3 idiomr+   	signaturer,   r   zint(int_value) is redundant)r+   r4   zdifferent body contentr6   )r   r:   matchingnon_matchinga1_a2s          r#   +test_1_false_positive_signature_regex_matchrH   `   s    $)#	
H 8,F+5RSH/9QRL$$X/EFEB$$\3IJEB........r&   c                     t               } ddddddd}| j                  |t              \  }}|t        k(  sJ |d   dk(  sJ y )	Nzindent-not-multiple-of-fourr-   r.   r   indentationzuse 4-space indentr/   r1   r(   r7   r8   r
   r:   r;   r<   r=   s       r#   test_2_style_only_dismissrM   u   s]    ^F0.$G ..w8NOOFG....:')))r&   c                 p    t               } dddddd}| j                  |t              \  }}|t        k(  sJ y)uK   style 권고는 path 가 scope 밖이어도 dismiss (코드 동작 무관).ztrailing-whitespacer-   r.   zutils/some_other.pyztrailing whitespacer+   r0   r1   r2   r3   NrK   )r:   r;   r<   rF   s       r#   0test_2_style_only_dismiss_even_when_out_of_scoperP      sG    ^F(%&G ((2HIIFA....r&   c                    g } t        |       }dddddd}|j                  |t              \  }}|t        k(  sJ |d   dk(  sJ |j	                  |d      }|d	   d
u sJ |d   du sJ t        |       dk(  sJ | d   d   dk(  sJ y )Nr   zmissing-type-annotationMediumbugr   zadd type hintrO   r2   appliedT	escalatedF   r   kindauto_apply_minor_fix)r(   r7   r8   r   apply_minor_fixlenr9   r:   r;   r<   r=   r"   s         r#   $test_3_minor_fix_in_scope_auto_applyr]      s    %'Ee,F,. G ..w8NOOFG00006?<<<<

 
 *H
ICy>T!!!{u$$$u:??8F5555r&   c                     g } dd}t        | |      }ddddd}|j                  |d      }|d   d	u sJ |d
   du sJ | d   d   dk(  sJ y)uP   fix_applier 가 False 반환 시 record 가 escalate 로 전환되어야 한다.c                    | |f}y)NFr   )r;   target_filerF   s      r#   failing_applierzAtest_3_minor_fix_apply_failure_escalates.<locals>.failing_applier   s    k"r&   )r   r   xrS   rT   r   r+   r0   r1   r2   rU   FrV   Tr   rX   auto_apply_failed_escalatedN)r;   zMapping[str, Any]r`   strreturnbool)r(   rZ   )r9   ra   r:   r;   r"   s        r#   (test_3_minor_fix_apply_failure_escalatesrh      s    %'E eIF.	G 
 
 *H
ICy>U"""{t###8F<<<<r&   c                     g } t        |       }dddddd}|j                  |t              \  }}|t        k(  sJ |d   d	k(  sJ |j	                  |      }|d
   t
        k(  sJ |d   dk(  sJ | d   d
   t
        k(  sJ y )NrR   zmissing-validationrS   rT   zutils/external_module.pyzadd input validationrO   r,   scope_expansion_requiredcritical_coderX   scope_expansion_criticalr   r(   r7   r8   r   escalate_scope_expansionr   r\   s         r#   (test_4_scope_expansion_escalate_criticalro      s    %'Ee,F'*'G ..w8NOOFG44448 ::::

)
)'
2C#BBBBv;44448O$(GGGGr&   c                     t               } dddddd}| j                  |t              \  }}|t        k(  sJ |d   dk(  sJ y	)
uX   Security-High 권고는 style/minor 분류와 무관하게 우선 적용 (scope 내부).zowasp-a02-injectionHighsecurityr   zSQL-like string concatenationrO   r,   security_high_in_scopeN)r(   r7   r8   r   rL   s       r#   *test_5_security_high_in_scope_auto_appliesrt      s[    ^F(.0G ..w8NOOFG00008 8888r&   c                     g } t        |       }dddddd}|j                  |t              \  }}|t        k(  sJ |d   d	k(  sJ |j	                  |      }|d
   t
        k(  sJ y)uN   Security-High 라도 scope 밖이면 절대 자동 적용 X — 회장 보고.rR   zowasp-a01-broken-accessrq   rr   zutils/auth.pyzauth bypassrO   r,   security_high_out_of_scoperk   Nrm   r\   s         r#   +test_6_security_high_out_of_scope_escalatesrw      s    %'Ee,F,G ..w8NOOFG44448 <<<<

)
)'
2C#BBBBr&   c                 p   t        t        dd      f      } dddddd	d
ddddddddddddddg}| j                  |t              }t	        |j                               h dk(  sJ t        |d         dk(  sJ t        |d         dk(  sJ t        |d         dk(  sJ |d   d   d   t        k(  sJ y )Nzknown-fpfixturer*   rB   r-   r.   r   rb   r+   r0   r1   r2   r4   indentrc   z	type-hintrS   rT   zperf-xperformancezutils/perf.py>   rU   	dismissedrV   r}   r   rU   rW   rV   r   rk   )r(   r   triage_batchr8   setkeysr[   r   )r:   findingsouts      r#   +test_7_triage_batch_interface_contract_keysr      s    &z)LNF
 Ew/	> %W/	1  X5/	1 ( 	"H 

h(>
?C sxxz?CCCCs; A%%%s9~!###s; A%%%{A/3RRRRr&   c                 Z    t               } | j                  g t              }|g g g dk(  sJ y )N)rU   r}   rV   )r(   r~   r8   )r:   r   s     r#   6test_7_triage_batch_empty_findings_returns_empty_listsr     s1    ^F


b"8
9CbrCCCCr&   c                     t               } | j                  du sJ | j                  j                  dt        i       | j                  du sJ y)uI   has_escalation 은 Critical 보고 대상 존재 여부를 즉시 반환.Frk   TN)r   has_escalationrV   r   r   )rs    r#   (test_7_triage_result_has_escalation_flagr     sI    Au$$$KK)HIJt###r&   c            	         g } t        |       }dddddddd	d
id}|j                  |gt              }t        |      t        |       z   }d|vsJ d|vsJ d|vsJ d|v sJ y)uc   finding 안에 BOT_GITHUB_TOKEN raw 가 박혀 들어와도 audit/결과에 노출되지 않는다.rR   zleak-xrS   rT   r   zToken leak in logz#found GH_TOKEN=ghs_LEAKED1234567890ghs_should_be_redacted_xxxxxgithub_token ghp_owner_pat_should_be_redacted)r+   r0   r1   r2   r3   r4   BOT_GITHUB_TOKENevidenceghs_should_be_redactedghp_owner_patghs_LEAKED1234567890***REDACTED***N)r(   r~   r8   repr)r9   r:   leaked_findingr   blobs        r#   )test_8_token_raw_zero_in_audit_and_resultr   #  s    %'Ee,F.$5:#%GH	N 

~.0F
GC 9tE{"D#4///$&&&!---t###r&   c                 ~    ddddiddgd} t        |       }t        |      }d|vsJ d|vsJ d|vsJ |d	   d
k(  sJ y )NTghs_RAW_TOKEN_xxxxz	x-api-keyghp_xxxxxxxxxxxxxxxxxxxxfineghs_inside_string_value)okGITHUB_TOKENnested
list_fieldghs_RAW_TOKENr   r   r   r   )rawredactedss      r#   5test_8_redact_tokens_helper_handles_nested_structuresr   <  st    , :;89	C c"HXA!###%Q...$A---N#'7777r&   c                     t        d      } ddddddddddd	ddd
dg}| j                  |      }t        |      dk(  sJ |D ch c]  }|d   	 c}dd	hk(  sJ t        |      }d|vsJ d|vsJ yc c}w )uH   list_chat_audit 가 본 인스턴스 chat_id 외 record 를 0건 노출.
6937032012)r   rY   a)r   rX   r+   
9999999999brl   cd)rX   r+   r   r+   z"b"z'b'N)r(   list_chat_auditr[   r   )r:   mixed_recordsvisibler   r   s        r#   0test_9_chat_isolation_filters_other_chat_recordsr   L  s    ,/F *@SQ *@SQ *DQTU'C8M $$]3Gw<1")*QAiL*sCj888=D	 +s   A5c                     g } t        | d      }ddddd}|j                  |gt               t        d | D              sJ y	)
u]   audit_writer 호출 시점에 chat_id 가 박혀 있어야 list_chat_audit 가 격리 가능.r   )r   r   zrS   rT   r   rc   c              3  D   K   | ]  }|j                  d       dk(    yw)r   r   N)get).0r   s     r#   	<genexpr>zItest_9_audit_records_tagged_with_chat_id_at_write_time.<locals>.<genexpr>i  s     ?AquuY</?s    N)r(   r~   r8   all)r9   r:   r;   s      r#   6test_9_audit_records_tagged_with_chat_id_at_write_timer   _  sM    %'Ee\BFH%.G 	#9:?????r&   c                     t         dk(  sJ y)u)   회장 §명시: default chat=6937032012.r   N)r   r   r&   r#   'test_9_default_chat_id_is_chairman_chatr   l  s    l***r&   c                 |    t        t              dk(  sJ t        t        t        t        t
        t        h      k(  sJ y)uE   회장 §명시 4 분류 외 action 일체 금지 → 정확히 4개.   N)r[   r   	frozensetr	   r
   r   r   r   r&   r#    test_actions_set_is_exactly_fourr   r  s>    w<1i%!#'	!    r&   c                     t               } ddh}| j                  d|      du sJ | j                  d|      du sJ | j                  d|      du sJ | j                  d|      du sJ | j                  d	|      du sJ y
)uH   expected_files 가 glob 패턴인 경우도 매칭 정확 (`anu_v2/**`).z	anu_v2/**ztests/**/*.pyr   Tzanu_v2/sub/x.pyztests/regression/test_x.pyzutils/forbidden.pyF Nr(   is_in_scope)r:   expecteds     r#   &test_is_in_scope_handles_glob_patternsr   }  s    ^F_-H<hG4OOO/:dBBB:HEMMM2H=FFFb(+u444r&   c                     t               } ddddddddd	dd
ddd	ddddddg}|D ]%  }| j                  |t              \  }}|t        v r%J  y)uJ   모든 분류 결과가 ACTIONS 집합 내부 값만 반환 (typo 방지).rb   r-   r.   za/b.pyrc   yrS   rT   r   r   rq   rr   wr|   zexternal/x.pyN)r(   r7   r8   r   )r:   r   fr<   rF   s        r#   1test_classify_evidence_returns_only_known_actionsr     s    ^FURX5/	1V/	1X= 	"H  !,,Q0FG	   !r&   c                     t               } | j                  g t              }d|v sJ d|v sJ d|v sJ t        |      dk(  sJ |j	                         D ]  }t        |t              rJ  y)uC   task-2531 executor 가 기대하는 dict 형식 (3 키) 1:1 박제.rU   r}   rV      N)r(   r~   r8   r[   values
isinstancelist)r:   r   vs      r#   7test_executor_contract_dict_signature_matches_task_2531r     s{    ^F


b"8
9C##s8q==ZZ\ #!T"""#r&   c                 J    dt         v sJ ddi} t        |       }|d   dk(  sJ y)u   Gemini medium #1: TOKEN_KEY_HINTS 에 `github_pat_` 포함 박제.

    회귀 박제: dict 키 기반 마스킹과 문자열 값 기반 마스킹의 일관성.
    Gemini 6차 Security-High 이후로 key 자체에도 redact 적용됨.
    github_pat_my_github_token_fieldgithub_pat_LEAKED_xxxxxxxxxxxxr   N)r   r   )r   r   s     r#   2test_gemini_medium_token_hints_includes_github_patr     s<     O+++"$D
ECc"H+,0@@@@r&   c                 4    t               } t        | d      rJ y)u   Gemini medium #2: audit_dir 인자 제거 박제 — 미사용 속성 0건.

    회귀: AutoGeminiTriage 인스턴스에 _audit_dir 속성이 존재하지 않음을 확인.
    
_audit_dirN)r(   hasattr)r:   s    r#   0test_gemini_medium_no_unused_audit_dir_attributer     s    
 ^Fv|,,,,r&   c                     t               } dh}| j                  d|      du sJ | j                  d|      du sJ | j                  dd      du sJ y)u   Gemini medium #3: is_in_scope 가 이미 set 인 expected_files 재정규화 생략.

    회귀: 동일 set 인스턴스를 반복 전달해도 복사가 일어나지 않음 (id 비교).
    `triage_batch` 핫패스 최적화의 박제.
    r   Tz
utils/x.pyF)z   anu_v2/auto_gemini_triage.py  Nr   )r:   expected_sets     r#   7test_gemini_medium_is_in_scope_skips_renormalize_on_setr     st     ^F<=L <lKtSSSlL9UBBB&- 
  r&   c                     t               } dddddd}| j                  |t              \  }}|t        k(  sJ i |ddi}| j                  |t              \  }}|t        k(  sJ y	)
u   Gemini 2차 medium #1: STYLE_ONLY_CATEGORIES 에 `docs` 포함 박제.

    docs 는 코드 동작 무관 → low/medium severity 일 때 dismiss.
    Security-High 우선 분기가 high docs 케이스를 먼저 차단하므로 안전.
    zmissing-docstringr-   docsr   zmodule-level docstring missingrO   r0   rS   NrK   )r:   docs_findingr<   rF   docs_meds        r#   8test_gemini_medium_docs_category_dismissed_as_style_onlyr     s     ^F '.1L ((7MNIFA.... 6,5
H5H((3IJIFA....r&   c                     t               } dh}ddddd}| j                  ||      \  }}|t        k(  sJ | j                  ||      \  }}| j                  ||      \  }}||cxk(  r
t        k(  sJ  J |dhk(  sJ y)u   Gemini 2차 medium #2: classify_evidence 도 set 입력 재정규화 생략 박제.

    `triage_batch` 가 한 번 정규화한 set 을 매 finding 처리에 그대로 전달하므로
    재정규화 비용 0 — 핫패스 최적화.
    r   r   rS   rT   rc   N)r(   r7   r   )r:   pre_normalizedr;   r<   rF   action2action3s          r#   =test_gemini_medium_classify_evidence_skips_renormalize_on_setr     s     ^F >?NH%.G
 ((.AIFA0000 ))'>BJGQ))'>BJGQg<!<<<<<<<====r&   c                     t        d      dk(  sJ t        d      dk(  sJ t        d      dk(  sJ t        d      dk(  sJ t        d      dk(  sJ y)u   Gemini 3차 medium #1: _redact_tokens 가 value.lower() 를 루프당 1회만 호출.

    회귀 fixture: prefix 검출 결과는 동일하되, 동일 결과를 보장.
    ghp_TEST_abc123r   GHP_UPPERCASE_xxgithub_pat_TESTnormal textghs_BOT_session_Nr   r   r&   r#   +test_gemini_medium_redact_lowers_value_oncer     sk     +,0@@@@,-1AAAA+,0@@@@-(M999,-1AAAAr&   c                 \    ddi} t        |       }d|vsJ d|v sJ t        |      }d|vsJ y)u  Gemini 6차 Security-High #1: dict key 자체에 박힌 raw token 도 redact.

    예전 동작: 끼='ghp_TOKEN' 인 경우 끼는 raw 그대로 노출 (token leak).
    수정 후: key 자체에 _redact_tokens 재귀 → '***REDACTED***' 로 마스킹.
    ghp_LEAKED_KEY_NAMEzok-valuer   Nr   )r   r   r   s      r#   5test_gemini_security_high_dict_key_itself_is_redactedr     sK     !*
-Cc"H 000x'''>D ,,,r&   c                 p    ddl m}   |        }d|d<   d|d<   t        |      }|d   dk(  sJ |d   dk(  sJ y)	uS   Gemini 6차 Security-High #2: dict 외 Mapping (OrderedDict 등) 도 동일 처리.r   )OrderedDictghs_LEAKED_xxxr   r   
safe_fieldr   N)collectionsr   r   )r   r   r   s      r#   6test_gemini_security_high_supports_ordereddict_mappingr   !  sQ    '
-C*CCc"HN#'7777L!T)))r&   c                 
   ddl m}  | j                  d      J | j                  d      J | j                  d      J | j                  d      J | j                  d      J d	d
z  dz   dd
z  z   }t        |      dk(  sJ y)u   Gemini 5차 medium: _TOKEN_VALUE_RE 컴파일 IGNORECASE 정규식 박제.

    re.IGNORECASE + compiled regex 로 lower() 복사본 생성 + substring 루프 제거.
    동작 회귀: case-insensitive 매칭 결과는 동일.
    r   )_TOKEN_VALUE_REghp_xxxNGHP_UPPER_xxxGhs_Mixed_Casegithub_PAT_xxxr   zPREFIX i  GHP_LEAKED_xxxz SUFFIX r   )anu_v2.auto_gemini_triager   searchr   )r   big_bodys     r#   8test_gemini_medium_redact_uses_compiled_regex_ignorecaser  .  s     :!!),888!!/2>>>!!"23???!!"23???!!-08884"22Z$5FFH(#'7777r&   c                     t        dd      t        dd      f} t        |       }dddd	d
}|j                  |t              \  }}|t        k(  sJ y)u   Gemini 4차 medium: signature=None fixture 만 있을 때 body 문자열 생성 생략 박제.

    body 가 lazy init 라도 분류 결과는 정확해야 한다.
    zrule-only-1u   signature 없음r*   zrule-only-2u   signature 없음 2rB   rS   rT   r   rc   Nr6   )r   r:   r;   r<   rF   s        r#   Etest_gemini_medium_match_fp_lazy_body_init_with_no_signature_fixturesr  @  sh     	];MN];OPH 8,F .	G ((2HIIFA2222r&   c                    t        ddd      t        ddd      f} t        |       }dd	d
ddd}|j                  |t              \  }}|t        k(  sJ dd	d
ddd}|j                  |t              \  }}|t
        k(  sJ y)u   Gemini 3차 medium #2: fp.signature == "" 가 모든 finding 매칭하지 않게 가드.

    빈 문자열 signature 는 re.search 가 항상 매칭하는 함정 회귀 박제.
    zempty-sig-ruler   emptyr@   zreal-fpNz	rule-onlyrB   rS   rT   r   zany random body contentrz   anything)r   r(   r7   r8   r   r	   )r   r:   	candidater<   rF   
candidate2r   s          r#   5test_gemini_medium_empty_signature_does_not_match_allr  U  s     	%5GTY${SH 8,F $.)I ((4JKIFA0000 .J ))*6LMJGQ3333r&   c                     ddl m}   | dddhi      }|d   }t        |t              sJ d|v sJ d|vsJ  | t	        ddh            }t        |t              sJ d|v sJ d|vsJ y	)
uW   Gemini 7차 medium #1 박제: set/frozenset 안의 token 도 마스킹되어야 한다.r   r   r   ghp_LEAKsafer   	ghs_TOKENr   N)r   r   r   r   r   )r   resultredacted_listfset_results       r#   @test_gemini_7_medium_redact_set_and_frozenset_normalizes_to_listr  y  s    8Vj&%9:;F6NMmT***},,,]*** K+>!?@Kk4((({***k)))r&   c                     ddl m} m} g fd} | |d      } |dd      f|_        dd	d
dd}|j	                  d|      }|J D cg c]  }|j                  d       }}d|v sJ yc c}w )uP   Gemini 7차 medium #2 박제: 잘못된 regex fixture 는 audit 로깅 후 skip.r   )r   r   c                :    j                  t        |              y r   r   r!   s    r#   r$   zQtest_gemini_7_medium_fixture_regex_error_emits_audit_record.<locals>.audit_writer  r%   r&   l   L5: )r$   r   zbad-rulez	(unclosed)r+   rA   zany bodyz	any titler   )r+   r4   r3   r2   NrX   fixture_regex_error)r   r   r   	_fixtures_match_false_positiver   )	r   r   r$   r:   r;   matchedr"   kindsr   s	           @r#   ;test_gemini_7_medium_fixture_regex_error_emits_audit_recordr    s    P K& !F 	Z;GF
 .	G **:w?G?? )44SWWV_4E4 E))) 5s   A-)r   zlist[Mapping[str, Any]] | Noner   re   r   re   rf   r   )rf   None)A__doc__
__future__r   syspathlibr   typingr   r   __file__resolveparentsWORKSPACE_ROOTre   r2   insertr   r   r	   r
   r   r   r   r   r   r   r   r   r   r8   r(   r>   rH   rM   rP   r]   rh   ro   rt   rw   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r  r  r   r&   r#   <module>r'     si    # 
   h'')11!4~chh&HHOOAs>*+   "  37"/
   08*/**/6,=,H*9 C(S8D$$28 &
@+	5!"# A-(/0>0
B-
*8$3* 4H*"*r&   